最稳方案是三步嵌套SUBSTRING_INDEX():先去@前内容,再剥离//后半段,最后取/前部分,并用COALESCE兜底空值;单次或两次调用无法覆盖ftp://、file:///、mailto:等真实脏数据。

直接说结论:用嵌套 SUBSTRING_INDEX() 是最稳的方案,但必须分三步处理——先剥离协议和用户信息,再切路径,最后兜底空值。单靠一次或两次调用大概率漏掉 ftp://user:pass@host:8080/path 这类真实脏数据。
为什么不能只用 SUBSTRING_INDEX(url, '/', 3)
这个写法在 https://www.example.com/path 上看似能出 https://www.example.com,但实际会卡在几个关键点上:
- 遇到
http://和https://协议长度不同,'/'第3次出现的位置不固定; -
file:///var/log里有三个/,但第3个前面是空段,结果变成file:; - 如果 URL 没有协议(如
www.example.com/path),'//'根本不存在,整个嵌套表达式可能返回空或错位; - 更隐蔽的是
mailto:user@example.com,它不含/,SUBSTRING_INDEX(url, '/', 3)直接返回原串,后续再切就全崩了。
正确提取域名的三步清洗逻辑
核心思路是:不假设结构,而是按语义逐层剥离。每一步都用 SUBSTRING_INDEX() 做最小化切割,避免正则开销:
- 第一步:去掉
@前所有内容(用户认证部分)→SUBSTRING_INDEX(url, '@', -1); - 第二步:从结果中剥离协议头 → 先取
'//'后半段:SUBSTRING_INDEX(SUBSTRING_INDEX(url, '@', -1), '//', -1); - 第三步:再切掉路径部分 → 对上一步结果取
'/'前半段:SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(url, '@', -1), '//', -1), '/', 1)。
最终完整表达式:COALESCE(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(url, '@', -1), '//', -1), '/', 1), url)
外层 COALESCE(..., url) 是兜底——当所有切割都失效时,至少保留原始字符串,避免字段变 NULL。
端口、中文域名、空值这些细节怎么处理
端口(如 :8080)默认保留在结果里,这是合理行为。如果你明确要剔除,得额外加 LOCATE() 判断:
-
LOCATE(':', domain_part) > 0才触发截断,用LEFT(domain_part, LOCATE(':', domain_part) - 1); - 中文域名(如
https://例子.中国/path)完全不影响SUBSTRING_INDEX(),前提是字段字符集是utf8mb4; -
NULL输入会导致整条表达式返回NULL,所以生产 SQL 必须包COALESCE(url, '')在最外层; - 性能上,这个嵌套最多 3 层
SUBSTRING_INDEX(),比REGEXP_SUBSTR()快 5–8 倍,但千万别把它放进WHERE条件里做实时提取——该建冗余字段就建。
真正容易被忽略的,不是函数怎么嵌套,而是你手里的 URL 字段到底混了多少种协议、多少个 @、多少个没协议的“伪 URL”。上线前务必拿 ftp://、git@github.com:path/repo.git、//cdn.example.com/asset.js 这几类边界样例跑一遍,别只测 https 开头的。


















