不能直接用INSTR或LOCATE,因其仅返回首次位置;需用(LENGTH(str)-LENGTH(REPLACE(str,substr,'')))/NULLIF(LENGTH(substr),0)计算次数,并加NULLIF防护除零和空串。

为什么不能直接用 INSTR 或 LOCATE?
INSTR 和 LOCATE 只返回子串首次出现的位置,无法统计总次数。想算“出现几次”,得靠长度差:把所有目标子串删掉,看少了多少字符,再除以子串本身长度。
核心公式怎么写才安全?
基本思路是:(LENGTH(str) - LENGTH(REPLACE(str, substr, ''))) / LENGTH(substr)。但必须加两层防护:
- 用
NULLIF(LENGTH(substr), 0)防止除零错误(空字符串会导致LENGTH返回 0) - 用
NULLIF(str, '')避免对NULL或空串做REPLACE时逻辑错乱
最终稳妥写法:
SELECT (LENGTH(str) - LENGTH(REPLACE(str, substr, ''))) / NULLIF(LENGTH(substr), 0) AS cnt FROM (SELECT 'ababab' AS str, 'ab' AS substr) t;
遇到大小写或字符集问题怎么办?
REPLACE 默认区分大小写,且受字段/连接的 collation 影响。比如 utf8mb4_0900_as_cs 会严格区分 a 和 A,而 utf8mb4_general_ci 不区分。
- 要忽略大小写,先统一转成大写:
REPLACE(UPPER(str), UPPER(substr), '') - 若字段是
TEXT且含 emoji 或四字节字符,确保连接和字段都用utf8mb4,否则LENGTH可能误算字节数而非字符数
性能差在哪?能不能缓存或优化?
每次执行都要全表扫描 + 逐行计算 LENGTH 和 REPLACE,没有索引可用。在大表上直接 WHERE 套这个表达式等于全表函数计算。
- 如果只是偶尔查,没问题;但用于
ORDER BY或JOIN条件,响应会明显变慢 - 高频使用建议建生成列:
cnt_col INT AS ((LENGTH(content) - LENGTH(REPLACE(content, 'key', ''))) / NULLIF(LENGTH('key'), 0)) STORED,再在该列上建索引 - 注意:生成列不支持子查询或变量,
'key'必须是常量
真正麻烦的是动态子串 —— 比如从另一张表取关键词来匹配,这时只能硬扛计算开销,没捷径。


















