应使用 LOCATE、POSITION 或 INSTR 判断字段是否包含子串,而非 FIND_IN_SET;FIND_IN_SET 仅适用于逗号分隔的 SET 字符串,对普通文本无效。

MySQL里想查字段是否含某个子串,别用 FIND_IN_SET
FIND_IN_SET 看名字像能查“包含”,实际只适用于逗号分隔的枚举字符串(比如 'apple,banana,orange'),且要求整个字段是这种格式。拿它去查普通文本字段(如 content 里有没有 'error'),结果永远是 0 或 NULL,不是你想要的“是否包含”。
常见错误现象:SELECT * FROM logs WHERE FIND_IN_SET('error', content) > 0 —— 这条语句不会报错,但逻辑完全失效,因为 content 不是合法的 SET 字符串。
- 真正该用的是
POSITION(或等价的LOCATE、INSTR)或LIKE -
FIND_IN_SET的第一个参数必须是字面量或单值,不能是表达式;第二个参数必须是逗号分隔、无空格的纯 SET 字符串 - 性能上,
FIND_IN_SET无法走索引,而LIKE 'xxx%'在前缀匹配时可能走索引(取决于版本和 collation)
POSITION 和 LOCATE 怎么选?看参数顺序和 NULL 处理
三者都返回子串首次出现的位置(从 1 开始),找不到返回 0。区别主要在参数顺序和对 NULL 的敏感度:
-
POSITION('sub' IN str):标准 SQL 语法,IN关键字不可省略;任一参数为NULL,结果就是NULL -
LOCATE('sub', str):MySQL 特有,更常用;支持第三个参数指定起始位置(LOCATE('sub', str, 5));同样,任一参数NULL→ 结果NULL -
INSTR(str, 'sub'):LOCATE的反序版,参数顺序和POSITION相反;行为一致
推荐统一用 LOCATE,写法直觉强、功能完整。判断是否包含,就写 LOCATE('error', content) > 0。
用 LIKE 还是 LOCATE?看你要不要模糊匹配
LIKE 适合带通配符的模式匹配(如开头、结尾、中间某处),LOCATE 适合精确子串查找。两者语义不同,别混用。
- 查“以 error 开头”:
content LIKE 'error%'(可走索引) - 查“以 error 结尾”:
content LIKE '%error'(无法走索引) - 查“包含 error”:
content LIKE '%error%'(全表扫描风险高) - 查“包含 error”(更明确):
LOCATE('error', content) > 0(语义清晰,性能与LIKE '%...%'几乎一样)
注意:LIKE 受 collation 影响(比如大小写敏感),而 LOCATE 默认二进制比较,更稳定。若需忽略大小写,用 LOCATE(LOWER('Error'), LOWER(content)) > 0。
字符集和长度陷阱:中文、emoji、多字节字符容易出错
POSITION/LOCATE 返回的是字符位置,不是字节位置。但如果你的字段是 utf8mb4,又没注意 collation(比如用了 utf8mb4_bin),某些 emoji 或生僻汉字可能被拆开或比对失败。
- 确保字段和查询字符串使用相同字符集和 collation,否则
LOCATE可能返回 0(即使肉眼可见) - 测试时用
LENGTH()和CHAR_LENGTH()对比:前者返回字节数,后者返回字符数;如果两者不等,说明含多字节字符,要特别检查 collation - 避免在
WHERE中对大字段反复调用LOCATE,尤其配合ORDER BY或LIMIT时,性能会明显下降
真正难的不是函数怎么写,而是你得先确认字段存的是什么编码、客户端传进来的是什么编码、collation 是不是真匹配——这三个地方错一个,LOCATE 就会安静地返回 0。










