mysql 8.0 的 regexp_substr 仅支持提取首个匹配项,不支持全局标志“g”或批量提取函数,需借助递归 cte 模拟循环逐次截取剩余字符串来提取全部 url。

MySQL 8.0 的 REGEXP_SUBSTR 只能提取第一个匹配,不是“全部”
直接用 REGEXP_SUBSTR 配合全局标志 'g' 是无效的——MySQL 8.0 的正则引擎不支持 g 标志,也不提供类似 PostgreSQL 的 regexp_matches() 或 SQLite 的 regexp 表值函数。它每次只返回第一个匹配项,反复调用也拿不到后续结果。
这意味着你不能靠单条 SQL + 一个正则就“一键提取所有 URL”。必须借助递归 CTE 模拟循环,逐次截取剩余字符串中下一个匹配。
用递归 CTE + REGEXP_SUBSTR 实现多轮提取
核心思路是:从原始文本出发,每次用 REGEXP_SUBSTR 找到第一个 URL,记录位置和内容;然后把该 URL 后面的子串作为新输入,继续找下一个,直到没匹配为止。
-
REGEXP_SUBSTR(text, 'https?://[^[:space:]"]+', 1, 1)是较稳妥的 URL 基础模式(匹配 http:// 或 https:// 开头、直到空格/引号/尖括号前为止) - 必须用
LOCATE()+CHAR_LENGTH()计算下一次搜索的起始偏移,因为REGEXP_SUBSTR的第四个参数是“第几个匹配”,不是“从哪开始搜” - 递归 CTE 的终止条件是
REGEXP_SUBSTR返回NULL,即无更多匹配
WITH RECURSIVE url_extract AS (
SELECT
id,
content,
REGEXP_SUBSTR(content, 'https?://[^[:space:]"]+', 1, 1) AS url,
LOCATE(REGEXP_SUBSTR(content, 'https?://[^[:space:]"]+', 1, 1), content)
+ CHAR_LENGTH(REGEXP_SUBSTR(content, 'https?://[^[:space:]"]+', 1, 1)) AS next_start
FROM your_table
WHERE REGEXP_SUBSTR(content, 'https?://[^[:space:]"]+', 1, 1) IS NOT NULL
<p>UNION ALL</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill2334" title="MySQL"><img
src="https://img.php.cn/upload/skill/000/000/081/178900927846657.jpg" alt="MySQL" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill2334" title="MySQL" class="overflowclass">MySQL</a>
<p class="overflowclass">编写正确的MySQL查询,避免字符集、索引和锁方面的常见陷阱。</p>
</div>
<a rel="nofollow" href="/xiazai/skill2334" title="MySQL" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>SELECT
id,
content,
REGEXP_SUBSTR(SUBSTRING(content, next_start), 'https?://[^[:space:]"]+', 1, 1),
next_start + LOCATE(
REGEXP_SUBSTR(SUBSTRING(content, next_start), 'https?://[^[:space:]"]+', 1, 1),
SUBSTRING(content, next_start)
) + CHAR_LENGTH(
REGEXP_SUBSTR(SUBSTRING(content, next_start), 'https?://[^[:space:]"]+', 1, 1)
)
FROM url_extract
WHERE REGEXP_SUBSTR(SUBSTRING(content, next_start), 'https?://[^[:space:]"]+', 1, 1) IS NOT NULL
)
SELECT id, url FROM url_extract;</p>
正则模式要兼顾实用性与安全性
简单写 'https?://\S+' 看似省事,但会截断 URL(比如遇到末尾标点)或误吞后续字符(如 https://example.com). 中的右括号)。更稳妥的做法是明确界定 URL 的合法结尾边界:
- 用
[^[:space:]"]+排除空格、、<code>>、双引号——这是 HTML 和纯文本中最常见的 URL 截断符 - 避免使用
.*?或贪婪匹配,防止跨字段污染(例如把整段 HTML 标签当 URL) - 如果数据含 Markdown 链接(如
[text](url)),需先剥离括号结构,否则正则会失败 - MySQL 8.0 不支持 Unicode 属性类(如
p{L}),中文域名需额外处理,通常建议先转 Punycode 或跳过
性能和边界情况必须提前验证
递归 CTE 在大数据量下容易超限:cte_max_recursion_depth 默认是 1000,而一条长文本里可能含几十个 URL,加上多行数据,很快触顶。实际使用前务必确认:
- 执行前设
SET SESSION cte_max_recursion_depth = 5000;(注意:不能超过全局max_recursive_iterations) - 对
content字段加前缀索引无意义,因为正则无法走索引,大表慎用 - 空字符串、NULL、不含 URL 的行会被自动过滤,但若某行有 200 个 URL,CTE 就会生成 200 行结果——务必在外部加
LIMIT或分页控制 - URL 中含特殊字符(如
@、:、%)时,MySQL 正则默认不转义,需确保模式已覆盖编码格式(例如%20)
真正落地时,多数人最终会把提取逻辑移到应用层——用 Python 的 re.findall() 或 JavaScript 的 match() 更可控、更易调试。MySQL 做这事,适合一次性清洗或小批量运维任务,别当成通用方案依赖。










