like '%keyword' 触发全表扫描,join 无法利用索引;需改用后缀匹配、全文索引或计算列优化,同时确保 join 字段类型一致且索引合理。

LIKE '%keyword' 触发全表扫描,JOIN 无法借力索引
当 LIKE 表达式以通配符 % 开头(如 name LIKE '%echola'),数据库无法使用常规 B+ 树索引的前缀匹配机制,只能对整张表逐行扫描。此时若再参与 JOIN,相当于先做一次全表扫描生成中间结果集,再拿这个庞大结果去和其他表关联——JOIN 的驱动表变大、连接键无索引支撑、内存/临时空间压力陡增。
常见错误现象:
- 执行计划中出现
type: ALL或Extra: Using where; Using join buffer -
EXPLAIN显示rows值远超实际匹配行数(比如百万级扫描只返回几十行)
解决方向不是“优化 JOIN 写法”,而是先让 LIKE 不拖后腿:
- 改用后缀匹配:
name LIKE 'echola%'(可走索引) - 对模糊前缀场景,考虑全文索引(
FULLTEXT)或倒排表/ES 同步 - 若必须前缀模糊,且字段长度固定,可加计算列 + 索引(如 MySQL 5.7+ 的
GENERATED COLUMN)
JOIN 条件字段类型不一致,隐式转换让索引失效
即使 LIKE 写得规范,JOIN 本身也可能因字段类型不匹配而丢掉索引。比如 A.user_id 是 INT,B.ref_id 是 VARCHAR,写 ON A.user_id = B.ref_id 时,MySQL 会把 A.user_id 隐式转成字符串再比对——这导致 A.user_id 上的索引完全无法使用。
典型表现:
-
EXPLAIN中key列为NULL,但你知道那个字段明明建了索引 - 连接字段在
WHERE单独查很快,一 JOIN 就慢几倍
检查和修复步骤:
- 用
SHOW CREATE TABLE对比两边字段定义,确认类型、字符集、是否允许 NULL 完全一致 - 显式转换(不推荐)不如改表结构;宁可
ALTER TABLE B MODIFY ref_id INT,也不要ON A.user_id = CAST(B.ref_id AS SIGNED) - JOIN 前先用子查询或 CTE 把类型不一致的表“规整”一遍(仅限临时救急)
LEFT JOIN + LIKE 在 ON 子句里,逻辑膨胀不可控
把 LIKE 放在 LEFT JOIN ... ON 里(如 LEFT JOIN logs l ON u.name LIKE CONCAT('%', l.keyword, '%')),是性能杀手。这种写法会让优化器放弃常规连接算法,转而尝试嵌套循环(Nested Loop),对左表每行都扫描右表全部数据——时间复杂度接近 O(n×m)。
更糟的是,这类语句往往无法被索引加速,因为 LIKE 的模式是动态拼接的,数据库连“该走哪个索引”都判断不了。
替代思路:
- 把模糊匹配下推到右表预处理:先
SELECT DISTINCT keyword FROM logs,再用IN或JOIN关联(如果关键词量不大) - 用
EXISTS替代LEFT JOIN+ 模糊条件,至少能提前终止匹配 - 彻底剥离模糊逻辑:把匹配结果存成宽表或打标签字段,JOIN 时只走等值条件
复合索引顺序错位,WHERE + JOIN 共同失效
当查询同时含 WHERE 过滤和 JOIN 条件,又用了 LIKE,索引设计稍有不慎就全盘失效。例如表 orders 有复合索引 (status, create_time, user_id),但查询是:
SELECT o.* FROM orders o JOIN users u ON o.user_id = u.id WHERE o.status = 'pending' AND u.name LIKE 'Alice%';
这里 u.name 上的索引没被用于 JOIN,o 表的复合索引也因 user_id 不在最左位而无法支持连接——两头落空。
关键原则:
- JOIN 字段必须单独建索引,或作为复合索引的最左列(如
(user_id, status)) - WHERE 中的
LIKE字段,索引要独立存在,不能指望靠“顺便”覆盖 - 如果
WHERE和JOIN共享字段(如都用user_id),优先保证它在索引最左;LIKE字段另建索引
真正容易被忽略的点是:很多团队只给单表建索引,却忘了 JOIN 涉及的**每个表**都要独立保障连接字段的索引可用性——哪怕另一张表只是小字典表,缺索引也会让整个 JOIN 退化成 Block Nested Loop。











