MySQL复合主键表执行IN查询未用索引,全表扫描原因咨询
嘿,我来帮你排查下这个问题——你用复合主键(tag_id, created_at)的表执行EXPLAIN SELECT * FROM tag_value_copy WHERE (tag_id,created_at) IN ((1,1518136666), (2,1518154836))时走了全表扫描,大概率是以下几个原因之一:
MySQL版本对多列IN的优化支持不足
较老的MySQL版本(比如5.6及更早)对多列IN表达式的索引兼容性很差,优化器无法识别这种写法和复合主键索引的匹配关系,会直接选择全表扫描。如果你的版本偏低,升级到5.7+或者8.0版本,这个问题通常会得到解决——新版本优化器能更好地解析多列IN,并利用复合索引的最左前缀规则。数据分布让优化器觉得全表扫描更高效
MySQL优化器会根据数据的选择性来判断走索引还是全表:- 如果
tag_id=1和tag_id=2的行占了表中绝大多数数据,优化器会认为走索引再回表(哪怕是聚簇主键索引)的成本比直接全表扫描更高; - 如果表本身数据量极小(比如只有几十行),全表扫描的开销远低于索引查找,优化器也会主动选择全表扫描。
- 如果
复合主键的顺序不符合查询的最左前缀规则
确认你的复合主键定义是PRIMARY KEY (tag_id, created_at),而不是(created_at, tag_id)!复合索引的最左前缀原则要求查询条件必须先匹配索引的第一列,要是主键顺序反过来,你的tag_id作为第二列,完全无法触发索引使用,只能全表扫描。表的统计信息过时
优化器依赖表的统计信息来做决策,如果统计数据很久没更新(比如表经历了大量插入/删除操作),优化器可能会误判数据分布,选择错误的执行计划。你可以执行ANALYZE TABLE tag_value_copy;更新统计信息,然后再跑EXPLAIN看看是否会触发索引。查询写法的兼容性问题
有些情况下,哪怕版本支持多列IN,优化器也可能“卡壳”。你可以试试把查询改写成OR条件的形式:SELECT * FROM tag_value_copy WHERE (tag_id=1 AND created_at=1518136666) OR (tag_id=2 AND created_at=1518154836)这种写法更符合优化器对复合索引匹配的常规判断逻辑,大概率能触发主键索引的使用。
你可以按照上面的顺序逐一排查,先确认主键顺序和统计信息,再尝试修改查询写法或者升级版本,应该能解决问题。
内容的提问来源于stack exchange,提问作者Ярослав Гойса

