MySQL复合索引严格遵循最左匹配原则:等值条件必须从索引最左字段连续出现,范围查询会截断后续字段索引使用;ORDER BY需与索引顺序及方向一致才能免排序;避免对索引列使用函数或隐式转换;复合索引字段数建议不超过3个,优先覆盖高频查询组合。
WHERE 条件里字段顺序决定索引能不能用
MySQL 的复合索引不是“包含哪些字段”就行,而是严格按定义顺序匹配 WHERE 中的等值条件。一旦某个字段没出现在最左连续位置(比如跳过第一个字段直接查第二个),后面所有字段索引失效。
常见错误现象:
,但索引是
—— 这时
字段完全走不上索引,因为
没出现在 WHERE 中。
等值条件(
、
)必须从左到右连续出现,才能触发最左匹配
范围查询(
、
、
)会截断后续字段的索引使用,但自身仍可用
如果 WHERE 同时有
,索引
仍能用前两个字段做等值筛选,
仅用于过滤,不参与索引查找
ORDER BY 和 GROUP BY 要和索引顺序对齐才免排序
即使 WHERE 条件完美命中索引,如果
字段顺序或方向(ASC/DESC)和索引不一致,MySQL 仍要额外排序,性能陡降。
使用场景:分页查询
,若索引是
,则有效;但如果是
(MySQL 8.0+ 支持),且查询也写
,就能跳过 filesort。
MySQL 5.7 不支持混合 ASC/DESC 索引,
实际被当作
处理
字段必须是索引最左连续部分,不能跳字段,也不能反向(如索引
,却
)
覆盖索引 + 匹配的
是免排序的关键组合
避免在索引列上做函数或隐式转换
哪怕字段顺序完全匹配,只要在 WHERE 中对索引列用了函数或类型转换,整个索引就作废。
MySQL(Linux)
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
下载
常见错误现象:
或
(而
是 INT 类型)—— MySQL 无法用索引快速定位,只能全表扫描或索引全扫。
把函数移到右边:
确保参数类型和字段一致:INT 字段就传整数,不要加引号;VARCHAR 字段注意 COLLATION 是否导致隐式转换
中看到
或
就要警惕——可能正踩在这个坑里
复合索引字段数不是越多越好,3 个通常是临界点
每多一个字段,索引体积增大、更新开销上升、缓存效率下降。超过 3 个字段的复合索引,除非高频固定组合查询,否则大概率是设计过度。
性能影响:插入/更新变慢(要维护更多 B+ 树节点),Buffer Pool 更容易被大索引挤占,反而拖累整体查询响应。
优先保障高频 WHERE + ORDER BY 组合,舍弃低频字段
用
估算选择性,低选择性字段(如
只有 3 个值)放后面,甚至不该进复合索引
宁可建两个精简索引(如
和
),也不要强行塞成
去覆盖所有情况
最左匹配不是玄学,是 B+ 树结构决定的必然行为。真正难的是在业务迭代中持续识别哪些字段组合正在成为瓶颈,而不是一上来就堆字段。线上
和
输出里的
和
才是唯一靠谱的判断依据。
SELECT * FROM orders WHERE status = 'paid' AND user_id = 123INDEX(user_id, status, created_at)statususer_id=IN>BETWEENLIKE 'abc%'user_id = 123 AND status IN ('paid','shipped') AND created_at > '2024-01-01'(user_id, status, created_at)created_atORDER BYSELECT * FROM logs WHERE app = 'web' ORDER BY created_at DESC LIMIT 20INDEX(app, created_at)INDEX(app, created_at DESC)ORDER BY created_at DESCINDEX(a ASC, b DESC)INDEX(a, b)ORDER BY(a,b)ORDER BY b,aORDER BYWHERE DATE(created_at) = '2024-04-01'WHERE user_id = '123'user_idcreated_at >= '2024-04-01' AND created_at < '2024-04-02'EXPLAINtype: ALLkey: NULLSELECT COUNT(DISTINCT concat(a,b,c)) / COUNT(*)status(a,b)(a,c)(a,b,c)slow_logEXPLAINkey_lenrows