mysql 8.0+ 最稳妥方法是用 json_table 拆分逗号字符串:先通过 replace 和 concat 将 "a,b,c" 转为 '["a","b","c"]',再用 json_table('$[*]' columns(val path '$')) 展开为多行;5.7 只能用数字序列配合 substring_index 模拟拆分。

MySQL 8.0+ 直接用 JSON_TABLE 拆分最稳妥
MySQL 原生不提供类似 STRING_SPLIT() 的函数,但 8.0+ 可借助 JSON_TABLE 把逗号分隔字符串转成行集。关键在于先把字符串转成 JSON 数组格式,再展开。
常见错误是直接对原始字符串调用 JSON_TABLE,会报错 Invalid JSON text。必须先用 REPLACE() 和 CONCAT() 包裹成合法 JSON 数组:
SELECT * FROM JSON_TABLE(CONCAT('["', REPLACE('a,b,c', ',', '","'), '" ]'), "$[*]" COLUMNS(val VARCHAR(100) PATH "$")) AS jt;- 注意:空值、含引号或逗号的字段需额外处理(如用
REPLACE(val, '"', '\"')转义) - 性能上,每拆一个字符串都会触发一次 JSON 解析,大数据量时比预建数字表慢
MySQL 5.7 或更低版本用递归 CTE + SUBSTRING_INDEX 模拟
5.7 不支持 JSON_TABLE,也不支持标准递归 CTE(8.0 才引入),只能靠自连接或变量生成序号,再配合 SUBSTRING_INDEX 截取第 N 个片段。
典型写法是构造一个“数字表”(比如从 information_schema.columns 临时借行),然后用 SUBSTRING_INDEX(SUBSTRING_INDEX(str, ',', n), ',', -1) 提取第 n 项:
- 需要预估最大分割数(比如最多 10 个元素),否则会漏项
-
SUBSTRING_INDEX对空项处理不友好:'a,,c'中间空字段会被跳过,变成两行而非三行 - 若原字符串含空格(如
'a, b, c'),得先TRIM()否则结果带空格
避免用 FIND_IN_SET() 做“伪拆分”
FIND_IN_SET('x', 'a,b,c') 只返回位置序号,不能返回所有元素,常被误当作拆分手段。它本质是查找函数,不是集合操作符。
真实需求如“查出每个 tag”,用 FIND_IN_SET() 只能写成 WHERE FIND_IN_SET('php', tags) > 0,无法展开成多行结果。
- 它不能替代
JOIN或子查询做横向展开 - 索引完全失效,字段越长越慢
- 遇到重复值(如
'php,php,go')只返回第一个匹配位置,掩盖数据问题
业务层拆分通常比 SQL 层更可控
真正在生产环境频繁拆分逗号字符串,大概率说明设计有隐患。MySQL 不是文本处理器,硬在 SQL 里做解析容易失控。
比如导入 CSV 数据时遇到逗号字段,与其在 INSERT 后用 SQL 拆,不如在应用代码里用 str.split(',')(Python)、String.split()(Java)等原生方法处理后再批量插入。
- 能精确控制空值、转义、编码(如 UTF-8 逗号 vs ASCII 逗号)
- 可提前过滤非法字符、去重、校验长度,SQL 层很难做这些
- 万一字段里混了换行或双引号(CSV 常见),SQL 解析几乎必然出错
真正绕不开 SQL 拆分的场景,基本只剩历史遗留表或报表临时分析——这时务必先 SELECT COUNT(*) 确认分割项数量分布,再决定用哪种方式,别一上来就套复杂 CTE。











