MySQL 教程:联合索引与最左前缀匹配详解

· Mysql笔记 MySQL 索引 SQL优化 · 3 阅读 · 约 5 分钟

前言

联合索引是最容易被误用的东西:明明建了 KEY (a, b, c),某些查询却偏偏不走。原因几乎都出在"最左前缀"这条规则上,这篇用一张表把它讲透。

CREATE TABLE article (
  id        BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  status    TINYINT NOT NULL DEFAULT 0,
  category  INT     NOT NULL DEFAULT 0,
  views     INT     NOT NULL DEFAULT 0,
  title     VARCHAR(200) NOT NULL DEFAULT '',
  PRIMARY KEY (id),
  KEY idx_s_c_v (status, category, views)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

什么是最左前缀

idx_s_c_v 里的数据先按 status 有序,status 相同的再按 category 有序,category 再相同的按 views 有序。后面的列只在前面列相等的前提下才有序,所以能用上的条件必须从最左列开始、连续不断:

查询条件 能否用到 idx_s_c_v 说明
status = 1 能,用到 1 列 最左列
status = 1 AND category = 5 能,用到 2 列 连续
category = 5 AND status = 1 能,用到 2 列 书写顺序不影响
status = 1 AND views = 100 只能用 1 列 中间断了 category
category = 5 用不上 缺最左列
views > 10 用不上 同上

MySQL 5.7 没有跳跃扫描(skip scan 是 8.0.13 才加的),缺最左列时这个索引基本帮不上忙。

范围条件之后就不再匹配

EXPLAIN SELECT id FROM article WHERE status = 1 AND category > 3 AND views = 100;

status 是等值,到 category 变成范围,索引里 category 之后的 views 就不再全局有序,所以 views = 100 不能用来定位。从 5.6 起有索引条件下推(ICP),存储引擎可以在索引层先把 views = 100 过滤掉再回表,Extra 里显示 Using index condition,省下无效回表,但扫描区间并不会变小。

ORDER BY 同样吃最左前缀

SELECT id FROM article WHERE status = 1 ORDER BY category, views LIMIT 10;

WHERE 用掉 status,ORDER BY 正好接上后面两列,索引顺序就是结果顺序,执行计划里不会有 Using filesort。排序写成 ORDER BY views, category 就只能额外排序;写成 ORDER BY category ASC, views DESC 也一样不行,5.7 的索引只能单向遍历,降序索引要到 8.0 才支持。

列顺序怎么排

  • 等值条件放前面,范围条件放最后,避免范围列把后面的列废掉
  • 全是等值条件时,把区分度高的列往前放,过滤得更早
  • 最常被单独拿来做条件的列放最左,同一个索引能被更多查询复用
  • 尾部列只有在凑成覆盖索引(查询要的列都在索引里,不用回表)时才有意义

几种把索引写废的写法

-- 在列上套函数,索引顺序被打乱
WHERE YEAR(create_time) = 2017
-- 隐式类型转换:code 是 varchar 却传了数字
WHERE code = 12345
-- 前置通配符无法确定起点
WHERE title LIKE '%mysql%'

第一条改成范围条件 create_time >= '2017-01-01' AND create_time < '2018-01-01';第二条把参数补上引号写成 '12345';LIKE 'mysql%' 这种右模糊仍然可以走索引。

用 key_len 验证用了几列

改完 SQL 不要凭感觉,EXPLAIN 的 key_len 直接给出命中的字节数,对照列类型就知道前缀吃到第几列:status 是 TINYINT 非空算 1,加上 category(INT 非空)是 5,再加 views 是 9;列允许 NULL 时每列还要多算一个字节的空值标记。这条判断在联合索引有四五个列时特别有用,光看 WHERE 里写了几个条件很容易误判。

小结

  • 联合索引的本质是多层排序,只有从最左列开始连续使用才有顺序可利用
  • 遇到范围条件这条链路就断了,后面的列只能靠 ICP 做过滤
  • 排序和查询共用同一套最左前缀规则,要把 WHERE 和 ORDER BY 连起来看
  • 建索引前先想清楚最常出现的条件组合,而不是把列随便堆在一起