mysql 8.0+ 支持 regexpsubstr 提取手机号(如 1[3-9]d{9})和邮箱(如 [a-za-z0-9.%+-]+@[a-za-z0-9.-]+.[a-za-z]{2,}),低版本不支持;postgresql 用substring 更稳妥;sqlite/mysql 5.7- 需应用层处理,正则提取需防边界、性能与准确性陷阱。

MySQL 8.0+ 的 REGEXP_SUBSTR 提取手机号和邮箱
MySQL 8.0 才真正支持正则提取,低版本(如 5.7)没有 REGEXP_SUBSTR,强行用会报错 FUNCTION xxx.REGEXP_SUBSTR does not exist。别试了,升级或换方案。
手机号常见格式是 11 位纯数字、开头为 1,邮箱则是含 @ 和域名结构。但注意:正则不能 100% 验证有效性,只能做初步筛选。
- 手机号推荐模式:
^1[3-9]\d{9}$(严格匹配整字段),提取时常用1[3-9]\d{9}(允许上下文存在其他字符) - 邮箱宽松匹配:
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,},注意 MySQL 中反斜杠要双写 - 提取第一个匹配项:
REGEXP_SUBSTR(content, '1[3-9][0-9]{9}');加1参数可指定从第几个字符开始搜,2参数指定返回第几个匹配(默认 1)
PostgreSQL 用 regexp_matches 或 substring
PostgreSQL 没有单值提取函数,regexp_matches 返回行集(多行结果),直接 SELECT 可能炸表——尤其字段里有多个手机号时,一行变多行。
更稳的做法是用 substring,它只返回第一个匹配:
SELECT substring('联系我:13812345678,邮箱 test@example.com' FROM '1[3-9]d{9}');
SELECT substring('test@example.com' FROM '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}');
注意:PostgreSQL 正则元字符不用双反斜杠,d 直接写,但需开启 standard_conforming_strings = on(默认已开);如果提示 invalid escape,检查是否误开了 escape 模式。
SQLite 和旧版 MySQL 只能靠 LIKE + 字符串函数硬凑
它们不支持正则提取,REGEXP 只能用于 WHERE 判断真假,无法抽子串。想“提取”,得组合 INSTR、SUBSTR、REPLACE 等函数,非常脆弱。
- 比如找手机号:先用
INSTR(content, '1')定位,再判断后 10 位是不是数字——但遇到 “123456789012” 这种 12 位数就误判 - 邮箱更难:@ 前后长度不定,点号位置不确定,
SUBSTR(content, INSTR(content,'@')-10, 30)这类估算极易截断或带入垃圾字符 - 结论:这种场景下,应该把清洗逻辑移到应用层(Python/Java),SQL 只负责过滤(
WHERE content REGEXP '1[3-9][0-9]{9}')
正则提取的三个隐形陷阱
很多人写了正则能跑通就交差,结果上线后漏数据或崩查询。关键不在语法对不对,而在边界怎么控。
-
1[3-9]\d{9}会匹配到 “abc13812345678def” 里的号码,但也会匹配 “138123456789”(12 位)的前 11 位——加单词边界\b1[3-9]\d{9}\b更安全(MySQL 8.0 支持\b) - 邮箱正则中
[a-zA-Z]{2,}排除了 .co.uk 这类二级域名,实际业务若含国际域名,得改成[a-zA-Z]{2,}(\.[a-zA-Z]{2,})* - 性能上,正则扫描是全表逐字符匹配,没索引加速。字段量大时,先用
WHERE content LIKE '%@%' AND content LIKE '%1%'快速缩小范围,再进正则
真正麻烦的不是写对正则,而是想清楚你要的是“最可能的候选值”,还是“必须 100% 准确的值”——后者在 SQL 层基本做不到,得靠后续校验。










