区分度高的字段应放在联合索引最左侧,因其筛选能力强,可快速过滤大量无关行;等值查询字段优先于范围查询字段,因范围查询会截断后续列的索引使用;排序字段宜置于末尾以避免filesort。

区分度高的字段必须放在联合索引最左侧
MySQL联合索引底层是B+树,按定义顺序逐列排序:先排第一列,相同时再排第二列,依此类推。这意味着查询能否走索引、能用上几列,完全取决于是否满足最左前缀原则。而区分度(cardinality)高,代表该列值重复少、筛选能力强——把它放最左,能让索引从第一步就快速过滤掉大量无关行。
判断字段区分度可以用这条语句:SHOW INDEX FROM your_table,看结果中 cardinality 列的数值。它越接近表总行数,说明该列越适合作为索引首列。
- 错误做法:把
status(如只有 0/1/2 三个值)放在最左,即使后面跟了高区分度的user_id,WHERE status = 1 AND user_id = 123也大概率只能靠status筛出全表 1/3 数据,再逐行比对user_id - 正确做法:把
user_id放最左,WHERE user_id = 123 AND status = 1能直接定位到极小数据段,status只需在该段内做二次过滤 - 注意:如果查询中压根不出现最左列(比如只查
WHERE status = 1),整个联合索引就完全失效,哪怕其他列区分度再高也没用
等值查询字段优先于范围查询字段
联合索引中,一旦遇到范围查询(>、、BETWEEN、LIKE 'abc%'),后续字段就无法用于索引查找——这是B+树结构决定的硬限制。
例如索引 (a, b, c):
-
WHERE a = 1 AND b = 2 AND c > 3→ 只能用上a和b,c仅用于过滤,不参与定位 -
WHERE a = 1 AND c > 3→c在b右侧,且b缺失,所以c完全用不上 - 因此,要把常用于等值匹配的字段(如
user_id、tenant_id)尽量往前放,把可能做范围查询的字段(如create_time、amount)往后挪
排序和分组字段适合放在联合索引末尾
如果查询带 ORDER BY 或 GROUP BY,且字段顺序与索引末尾一致,MySQL可直接利用索引有序性,避免额外的 filesort 或 temporary table。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
比如常见订单列表查询:SELECT * FROM orders WHERE user_id = 123 AND status = 1 ORDER BY create_time DESC
- 索引
(user_id, status, create_time)就能覆盖 WHERE + ORDER BY,执行计划中Extra字段不会出现Using filesort - 但如果建成了
(user_id, create_time, status),虽然也能用上前两列做定位,但status在最后,ORDER BY create_time无法复用索引顺序,仍要排序 - 注意:
DESC和ASC混用时(如ORDER BY a ASC, b DESC),MySQL 8.0+ 才支持在联合索引中混合指定方向;老版本统一按ASC建索引更稳妥
别忽略查询条件的实际分布和频率
区分度不是唯一标准。有些字段虽然 cardinality 不高(比如 is_deleted 只有 0/1),但如果 99% 查询都带 WHERE is_deleted = 0,那它就是高频过滤条件,值得前置——前提是它和高区分度字段组合后,整体选择性依然足够好。
实操建议:
- 用
EXPLAIN验证:加索引后key_len是否变长、rows是否显著减少 - 避免盲目堆字段:三列以上联合索引会增大写入开销和内存占用,优先保障核心查询路径
- 线上建索引前,先用
SELECT COUNT(*)和COUNT(DISTINCT col)粗算区分度,比凭经验靠谱得多
真正难的不是记住“高区分度放左边”,而是结合具体查询模式、数据分布、写入压力,动态权衡字段顺序。一个索引建错,可能让慢查询从 50ms 拉长到 2s,而修复往往要锁表或走在线DDL——这点在千万级订单表上尤其明显。










