mysql 的 like '%xxx%' 性能差是因为前导通配符使 b+ 树索引失效,只能全表扫描;fulltext 和内置函数同样有局限;高阶模糊需求应引入 elasticsearch,通过 trigram、pinyin 分析器等策略实现高效文本检索。

为什么 MySQL 的 LIKE '%xxx%' 查不出性能?
因为前导通配符(% 在开头)会让 MySQL 完全无法使用 B+ 树索引,只能全表扫描。哪怕加了普通索引,WHERE name LIKE '%abc%' 也基本等于放弃索引——这是底层存储结构决定的,不是配置能绕过的。
常见误区是以为“加了索引就快”,但索引只对 LIKE 'abc%' 有效;对 LIKE '%abc' 或 LIKE '%abc%',索引形同虚设。
- 百万级数据下,
LIKE '%keyword%'查询可能从毫秒级升到秒级甚至超时 -
FULLTEXT索引虽可缓解,但仅支持自然语言模式或布尔模式,不支持任意子串、大小写敏感控制、拼音模糊等场景 - 内置函数如
LOCATE()、INSTR()同样无法走索引,纯 CPU 匹配,数据量大时更慢
什么时候该切到 Elasticsearch?
当你的模糊查询需求开始出现这些信号,就该考虑异构:多字段联合检索(比如“部门名+人名+标签”一起搜)、支持拼音/分词/同义词、需要高亮、要求亚秒级响应、或者已有分库分表导致跨库 JOIN 成本极高。
以通讯录系统为例:人员信息分散在 12 个分库,每个库有 user、department、tag_rel 三张表,想查“北京研发部带‘AI’标签的张*”,用 MySQL 拼 SQL 几乎不可维护,而 ES 只需一条 multi_match 查询。
- ES 不是替代 MySQL,而是补足它不擅长的“文本即服务”能力
- 同步延迟可接受(秒级)的业务,用 binlog + canal 或 maxwell 做增量同步足够稳定
- 冷热分离明确的场景,比如历史归档数据只读,更适合放 ES 而非长期压 MySQL
MySQL 到 ES 的数据同步关键点
别直接用 SELECT * FROM user 全量灌 ES——字段映射错、类型不匹配、中文分词没配,查出来全是空结果。必须控制源头结构。
典型坑:user.status 是 tinyint(1),ES 自动识别成 long,但业务代码传的是字符串 "active",查不到;又或者 name 字段没配 ik_smart 分词器,搜“张三”能出,“张”单独搜就失败。
- 建索引前先定义
mapping:显式声明text字段用什么 analyzer,数值字段用keyword还是long - 同步脚本里做轻量清洗:把 MySQL 的
NULL转成""或false,避免 ES 字段类型被污染 - 增量同步务必带上时间戳字段(如
updated_at),并用它驱动游标,别依赖自增 ID——分库后 ID 不连续
ES 查询怎么避免“查得到但不对”?
很多人把 MySQL 的 LIKE 思维直接搬过去,写 match_phrase: { query: "张三" },结果发现搜“张”不出来。ES 默认是分词检索,而 match_phrase 要求完整短语+顺序+邻近,不适合子串场景。
真正贴近 LIKE '%xxx%' 的是 wildcard 或 regexp,但它们性能差、易 OOM;更稳的做法是组合策略:
- 前缀搜索(类似
LIKE 'xxx%'):用prefix查询,快且准 - 中缀/后缀(
LIKE '%xxx'或LIKE '%xxx%'):提前在索引时生成 trigram(3-gram)字段,查时用match打 trigram 字段 - 拼音容错:加
pinyinanalyzer,搜“zhangsan”也能命中“张三” - 必要时 fallback:ES 查不到再走 MySQL 主键回查,但得控制 fallback 比例,否则失去异构意义
trigram 不是银弹——它让存储翻 3 倍、写入变慢,但换来的是中缀搜索的可控延迟。要不要上,得看你的“模糊”到底有多模糊。











