MySQL优化器是否使用联合索引取决于选择率(selectivity),而非是否存在索引;低区分度字段(如del_flag)在最左位置会导致索引失效,应按实际过滤能力排序字段,并用COUNT(DISTINCT CONCAT(...))/COUNT(*)准确计算联合索引选择率。

区分度低的联合索引根本不会被优化器选中
MySQL 优化器决定是否走索引,核心依据不是“有没有索引”,而是该索引能过滤掉多少行——也就是 selectivity(选择率)。如果联合索引最左字段区分度只有 0.005(比如 del_flag),那优化器估算 WHERE 条件命中后还要扫描约 99.5% 的数据,不如直接全表扫描。你建了索引,EXPLAIN 却显示 type: ALL,八成就是这个原因。
COUNT(DISTINCT col) / COUNT(*) 才是真实 selectivity
SHOW INDEX 里的 Cardinality 是采样估算值,大表上误差常超 3 倍。它不能直接除以总行数算 selectivity,必须自己算:
- 单列:直接跑
SELECT COUNT(DISTINCT user_id) / COUNT(*) FROM users - 联合索引(如
(status, unit_id, user_id)):必须按顺序拼接,否则漏组合唯一性 → 用SELECT COUNT(DISTINCT CONCAT(status, '-', unit_id, '-', user_id)) / COUNT(*) FROM your_table - 注意跳过
TEXT/BLOB字段,否则COUNT(DISTINCT)会报错
联合索引字段顺序本质是 selectivity 排序
字段放哪一位,取决于它在实际查询条件中的过滤能力,而不是业务逻辑先后。常见错误:
-
(del_flag, user_id):del_flag区分度 WHERE del_flag = 0 AND user_id = 123 很可能不走索引 -
(user_id, del_flag):user_id区分度 > 0.8,能快速定位到少量行,再用del_flag过滤,I/O 成本可控 - 复合条件中,哪怕只查
user_id = ?,高区分度字段在前也能触发最左匹配;反之,只查del_flag = ?就完全用不上
线上几十个字段,别手动算 selectivity
靠人写 SQL 查每个字段太慢,且容易漏掉联合索引组合。Python 脚本更可靠:
- 先用
SHOW INDEX FROM your_table拉出所有索引列名和顺序 - 对每列或每组拼接字段,用
try/except包住COUNT(DISTINCT ...)查询,避免某列异常中断流程 - 输出表格含三列:
column_combo、distinct_count、selectivity,一眼识别哪些该删、哪些该调序
真正卡住性能的,往往不是没建索引,而是建了但 selectivity 太低,还占着内存和写入开销。算之前,记得先 ANALYZE TABLE your_table,否则连采样基数都是错的。


















