substring_index+数字序列最兼容(5.7/8.0),json_table更简洁但仅限8.0.24+且需严格json格式;二者均须过滤空值、去重、防注入,避免逗号连写与首尾空格污染数据。

SUBSTRING_INDEX 是最常用、兼容性最好的方案,但必须配合数字序列使用;JSON_TABLE 更简洁可靠,但仅限 MySQL 8.0.24+,且对输入格式敏感。别指望写个 WHILE 循环就完事——容易漏空值、越界、重复插入。
用 SUBSTRING_INDEX + 数字序列安全拆分(兼容 5.7/8.0)
核心是“用已知长度的数字序列驱动分割”,避免循环中动态判断边界带来的不确定性。
常见错误现象:SUBSTRING_INDEX('a,b', ',', 3) 返回 'a,b' 而不是 NULL,导致重复插入最后一项。
- 先构造一个足够长的数字辅助表(比如 1~10),可用
WITH RECURSIVE或临时表 - 对每个数字
n,取第n个子串:SUBSTRING_INDEX(SUBSTRING_INDEX(str, ',', n), ',', -1) - 必须加
WHERE n 控制上限,否则会多出脏行 - 再用
TRIM去首尾空格,HAVING tag != ''过滤空字符串(逗号连写如'a,,b'会产生空值)
用 JSON_TABLE 拆分并插入(仅 8.0.24+ 推荐)
这是目前最干净的方案,但失败率高往往是因为 JSON 格式没做对,不是函数本身问题。
典型错误:JSON_TABLE('a,b,c', '$[*]' COLUMNS(tag TEXT PATH '$')) 直接报 Invalid JSON text。
- 必须先转成合法 JSON 数组:
CONCAT('["', REPLACE(str, ',', '","'), '"]') - 原始字符串含双引号或反斜杠时,得提前
REPLACE(REPLACE(str, '\', '\\'), '"', '\"')转义,否则解析崩溃 - 插入语句示例:
INSERT INTO tags (name) SELECT jt.tag FROM JSON_TABLE(CONCAT('["', REPLACE('web,api,db', ',', '","'), '"]'), '$[*]' COLUMNS(tag TEXT PATH '$')) AS jt; - 注意:如果输入是字段(如
tags_col),不能直接拼进CONCAT,需在外部查出后拼接,或用派生表包裹
存储过程中用游标逐行处理(慎用!只适合复杂逻辑)
游标不是为“拆字符串”设计的,而是为“对每段内容执行不同 SQL 或调外部动作”准备的。性能差、锁住连接、易出错。
容易踩的坑:FETCH 后没设 NOT FOUND handler,导致最后一次读取失败后继续循环,变量值残留上一轮数据。
- 声明游标前必须先定义
DECLARE done INT DEFAULT FALSE;和DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; - 循环体里
FETCH必须紧跟IF done THEN LEAVE loop_label; END IF;,不能放在最后 - 拆字符串本身仍建议用
SUBSTRING_INDEX在游标内取值,而不是在游标外循环拼接——游标只负责“一行一处理”,不负责“怎么拆”
插入前必须做的三件事:去重、去空、防注入
用户传进来的 tagString 是不可信输入,哪怕只是内部系统调用,也得当外部参数对待。
- 用
INSERT IGNORE INTO ... SELECT DISTINCT TRIM(tag) FROM ...防重复和空格污染 - 如果业务要求忽略大小写去重,得统一
LOWER(TRIM(tag))再DISTINCT - 不要用
CONCAT拼接用户输入进 SQL 字符串——这等于给 SQL 注入开后门;所有参数一律用IN参数传入,由 MySQL 自动转义
实际部署时,JSON_TABLE 方案写起来短,但上线前务必在目标版本(比如 8.0.33)实测含特殊字符的字符串;SUBSTRING_INDEX 方案啰嗦点,但只要控制好数字上限和空值过滤,5.7 到 8.0 全通吃。最容易被忽略的是逗号连写和首尾空格——它们不会报错,但会让数据表里悄悄混进空标签。











