MySQL 5.7不支持INCLUDE语法,覆盖索引需将所有查询列(含SELECT、WHERE等)显式定义在索引中,如CREATE INDEX idx ON t(a,b,c,d);其叶子节点自动包含主键,可利用该特性减少冗余列,但长主键或跳过最左前缀会导致索引失效,务必通过EXPLAIN确认Extra为“Using index”。

MySQL 5.7不支持INCLUDE,所有列都参与B+树排序
MySQL 5.7没有INCLUDE语法,所谓“覆盖索引”只能靠把查询所需的所有列都塞进索引定义里实现。这意味着CREATE INDEX idx ON t(a,b) INCLUDE(c,d)会直接报错;正确写法是CREATE INDEX idx ON t(a,b,c,d)——但要注意,c和d不再是“附加存储”,而是真正参与索引键排序和比较的列。
这带来两个关键影响:
- 索引体积显著增大,尤其是对长文本、JSON或大VARCHAR字段
- 最左前缀规则严格作用于全部列:如果查询只用
WHERE a = ? AND c = ?,而跳过b,则整个索引无法命中 - 范围查询(如
a > 10)之后的列自动失效,哪怕你只是想SELECT它们
利用主键自动追加特性减少冗余列
InnoDB二级索引的叶子节点天然包含主键值,这是MySQL 5.7实现“隐式覆盖”的核心捷径。例如表t_user主键为id,建索引CREATE INDEX idx_name ON t_user(user_name)后,该索引实际结构是(user_name, id)。
所以这类查询能走覆盖索引:
SELECT user_name, id FROM t_user WHERE user_name = 'LiLei';
但下面这个不行:
SELECT user_name, age FROM t_user WHERE user_name = 'LiLei';
因为age不在索引中,必须回表。实操建议:
- 优先把主键列列入SELECT,只要WHERE条件能命中索引前缀,就能触发覆盖
- 若业务强依赖非主键字段覆盖,宁可建
(user_name, age)而非(user_name),避免为单个字段单独建索引 - 注意:如果主键是UUID或长字符串,追加它会大幅膨胀索引,此时需权衡
用生成列绕过长字段或表达式限制
MySQL 5.7支持STORED生成列,可用于压缩或标准化高频查询字段,再将其纳入索引。比如状态字段status VARCHAR(20)常被WHERE和SELECT同时使用,但长度大、基数低:
ALTER TABLE orders ADD COLUMN status_code TINYINT AS (CASE status WHEN 'pending' THEN 1 WHEN 'shipped' THEN 2 ELSE 0 END) STORED;
然后建索引:
CREATE INDEX idx_covering ON orders(customer_id, created_date, status_code);
这样既能覆盖SELECT customer_id, created_date, status_code,又比直接索引status节省空间。注意点:
- 生成列必须声明为
STORED(不能是VIRTUAL),否则无法建索引 - 表达式不能含非确定性函数(如
NOW()、RAND()) -
EXPLAIN中仍要检查Extra: Using index,确保优化器真用了该索引
覆盖索引生效的硬门槛:EXPLAIN必须显示Using index
很多开发者以为“索引包含了SELECT字段”就等于覆盖,其实不然。MySQL是否真正免去回表,只看EXPLAIN输出的Extra列是否为Using index。常见误判场景:
- SELECT中出现未被索引覆盖的列(哪怕只是
*),哪怕WHERE全命中,也会回表 - ORDER BY或GROUP BY引用了索引未包含的列,触发
Using filesort或Using temporary,覆盖失效 - WHERE中用了函数或类型转换(如
WHERE DATE(created_date) = '2024-01-01'),导致索引无法用于过滤,自然也谈不上覆盖
最稳妥的做法:每次改完索引,用真实查询跑一遍EXPLAIN,盯着Extra字段看——其他都是推测。


















