为什么SQL查询即使单索引下也慢,关联字段选择性低是关键因素吗?
- 内容介绍
- 相关推荐
本文共计1035个文字,预计阅读时间需要5分钟。
关联查询走索引可能比较慢,常见原因是关联字段分区度过低——例如使用status(只有0/1/2三个值)或gender(男/女)做ON条件。MySQL虽然可能走索引,但type可能是ref或range,实际需要扫描几万行才能得到结果,和全表扫描没有本质区别。
选择性 = 唯一值数量 / 总行数。低于 0.01(即 1%)就属于低选择性。这种字段单独建索引意义不大,优化器很可能直接放弃使用。
- 用
SELECT COUNT(DISTINCT status) / COUNT(*) FROM orders;快速估算 - 如果结果是
0.003,说明每 300 行才有一个新值,索引基本等于摆设 - 别急着加索引,先确认这个字段是不是真该出现在
ON或WHERE里——有时是业务逻辑写错了,把过滤条件误当关联条件
EXPLAIN里rows远大于预期?说明索引没拦住多少数据
EXPLAIN输出里的rows列,代表 MySQL 预估需要扫描的行数。如果关联后rows高达几十万,哪怕type显示ref,也说明索引只缩小了范围,没真正“切掉”大部分数据。
- 例如:
EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE o.status = 1;,若o.status低选择性,rows可能接近orders总行数 - 这时
key虽显示用了idx_status,但filtered列可能只有5.0(表示仅 5% 行满足条件),证明索引效率极差 - 优先考虑把低选择性字段挪到
WHERE最后、或配合高选择性字段组成联合索引,比如(status, created_at)不如(created_at, status)——因为时间戳选择性通常远高于状态码
联合索引顺序错了,低选择性字段放前面会直接废掉索引
复合索引不是“把所有条件字段堆一起就行”。如果低选择性字段放在联合索引最左,后续字段基本失效——因为 B+ 树按最左前缀排序,第一个字段就分不出几组,后面再精细也没用。
- 错误示例:
CREATE INDEX idx_status_user ON orders(status, user_id);——status只有 3 个值,user_id再好也白搭 - 正确方向:
CREATE INDEX idx_user_status ON orders(user_id, status);,前提是user_id是高选择性(如主键或唯一业务ID) - 验证方法:用
EXPLAIN对比两个索引下rows和key_len,key_len明显变短,大概率只用了索引第一列
隐式类型转换让索引失效,比选择性低更隐蔽
关联字段类型不一致时,MySQL 会自动做隐式转换,导致索引无法使用。比如users.id是BIGINT,orders.user_id是VARCHAR,即使两者值完全匹配,ON u.id = o.user_id也会触发全表扫描。
- 检查方式:
SHOW CREATE TABLE users;和SHOW CREATE TABLE orders;对比字段类型 - 典型报错不会出现,但
EXPLAIN中key为NULL、type是ALL就是信号 - 修复必须改字段类型,不能靠
CAST()或CONVERT()——这些函数同样会让索引失效
低选择性字段本身不“坏”,但把它当核心关联条件或放索引最左,就像拿筛沙子的网去捞鱼。真正关键的是搞清数据分布、验证EXPLAIN里的rows和filtered是否可信,以及类型是否严格一致——这三处漏掉任何一点,索引都只是假象。
本文共计1035个文字,预计阅读时间需要5分钟。
关联查询走索引可能比较慢,常见原因是关联字段分区度过低——例如使用status(只有0/1/2三个值)或gender(男/女)做ON条件。MySQL虽然可能走索引,但type可能是ref或range,实际需要扫描几万行才能得到结果,和全表扫描没有本质区别。
选择性 = 唯一值数量 / 总行数。低于 0.01(即 1%)就属于低选择性。这种字段单独建索引意义不大,优化器很可能直接放弃使用。
- 用
SELECT COUNT(DISTINCT status) / COUNT(*) FROM orders;快速估算 - 如果结果是
0.003,说明每 300 行才有一个新值,索引基本等于摆设 - 别急着加索引,先确认这个字段是不是真该出现在
ON或WHERE里——有时是业务逻辑写错了,把过滤条件误当关联条件
EXPLAIN里rows远大于预期?说明索引没拦住多少数据
EXPLAIN输出里的rows列,代表 MySQL 预估需要扫描的行数。如果关联后rows高达几十万,哪怕type显示ref,也说明索引只缩小了范围,没真正“切掉”大部分数据。
- 例如:
EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE o.status = 1;,若o.status低选择性,rows可能接近orders总行数 - 这时
key虽显示用了idx_status,但filtered列可能只有5.0(表示仅 5% 行满足条件),证明索引效率极差 - 优先考虑把低选择性字段挪到
WHERE最后、或配合高选择性字段组成联合索引,比如(status, created_at)不如(created_at, status)——因为时间戳选择性通常远高于状态码
联合索引顺序错了,低选择性字段放前面会直接废掉索引
复合索引不是“把所有条件字段堆一起就行”。如果低选择性字段放在联合索引最左,后续字段基本失效——因为 B+ 树按最左前缀排序,第一个字段就分不出几组,后面再精细也没用。
- 错误示例:
CREATE INDEX idx_status_user ON orders(status, user_id);——status只有 3 个值,user_id再好也白搭 - 正确方向:
CREATE INDEX idx_user_status ON orders(user_id, status);,前提是user_id是高选择性(如主键或唯一业务ID) - 验证方法:用
EXPLAIN对比两个索引下rows和key_len,key_len明显变短,大概率只用了索引第一列
隐式类型转换让索引失效,比选择性低更隐蔽
关联字段类型不一致时,MySQL 会自动做隐式转换,导致索引无法使用。比如users.id是BIGINT,orders.user_id是VARCHAR,即使两者值完全匹配,ON u.id = o.user_id也会触发全表扫描。
- 检查方式:
SHOW CREATE TABLE users;和SHOW CREATE TABLE orders;对比字段类型 - 典型报错不会出现,但
EXPLAIN中key为NULL、type是ALL就是信号 - 修复必须改字段类型,不能靠
CAST()或CONVERT()——这些函数同样会让索引失效
低选择性字段本身不“坏”,但把它当核心关联条件或放索引最左,就像拿筛沙子的网去捞鱼。真正关键的是搞清数据分布、验证EXPLAIN里的rows和filtered是否可信,以及类型是否严格一致——这三处漏掉任何一点,索引都只是假象。

