讲解

联合索引把多列放进一棵 B+ 树:索引 (name, city) 先按 name 排序,name 相同再按 city 排序。这个存储结构直接推导出「最左前缀原则」——索引 (a, b, c) 能服务的查询是 a、a+b、a+b+c 的等值或范围条件,但服务不了只按 b 或只按 c 过滤的查询,因为跳过 a 之后数据不再有序。范围条件会「截断」后续列的使用:WHERE a = 1 AND b > 5 AND c = 2 里,c 用不上索引的有序性。

覆盖索引是联合索引的最佳拍档:如果 SELECT 的列全部包含在索引里(别忘了二级索引叶子自带主键值),就不需要回表,EXPLAIN 的 Extra 列会出现 Using index,这是索引高效利用的标志。设计联合索引的经验法则:等值条件的列在前、范围列在后;过滤性强的列在前;把 SELECT 常取的列一并包进去凑成覆盖索引。

MySQL 8.0 还提供了「不可见索引」(invisible index):把索引标记为 INVISIBLE 后优化器不再使用它,但索引仍在维护。想删一个拿不准的索引时,先设为不可见观察几天——没有副作用再真删,出问题一条 ALTER 就能恢复,这是删索引的安全姿势。

示例

先重建一万行的用户表(每章都是独立数据库,重新造数):

CREATE TABLE users (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  city VARCHAR(20) NOT NULL,
  age INT NOT NULL
);

SET SESSION cte_max_recursion_depth = 100000;

INSERT INTO users (name, city, age)
WITH RECURSIVE seq AS (
  SELECT 1 AS n
  UNION ALL
  SELECT n + 1 FROM seq WHERE n < 10000
)
SELECT CONCAT('user', n), ELT(1 + n MOD 5, '北京', '上海', '广州', '深圳', '杭州'), 18 + n MOD 40
FROM seq;

建联合索引 (name, city),验证最左前缀:按 name 查用上索引(type=ref),跳过 name 只按 city 查则全表扫描(type=ALL):

CREATE INDEX idx_name_city ON users (name, city);

EXPLAIN SELECT * FROM users WHERE name = 'user7'\G

EXPLAIN SELECT * FROM users WHERE city = '北京'\G

覆盖索引:SELECT 的列都在索引里,Extra 出现 Using index,无需回表:

EXPLAIN SELECT name, city FROM users WHERE name = 'user42'\G

把索引设为不可见再验证(EXPLAIN 退回全表扫描),然后恢复——删索引前先 invisible 观察,是生产上的安全做法:

ALTER TABLE users ALTER INDEX idx_name_city INVISIBLE;

EXPLAIN SELECT * FROM users WHERE name = 'user7'\G

ALTER TABLE users ALTER INDEX idx_name_city VISIBLE;

SELECT DISTINCT INDEX_NAME, IS_VISIBLE FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users';

常见坑

  • 联合索引列顺序随意:(city, name) 对 WHERE name = ? 无能为力,顺序错了等于白建。记住「等值在前、范围在后、高选择度在前」。
  • 冗余索引重复建:已有 (name, city) 再建 (name),后者完全冗余——最左前缀决定了 (name, city) 本身就能当 name 的索引用。
  • 范围列放前面:(age, name) 配 WHERE age > 20 AND name = 'x',范围条件 age 在前会把 name 截断掉。范围列放最后。
  • invisible 之后忘记收尾:不可见索引仍在占用写入成本,确认查询无回退后要么恢复要么 DROP,别让它永远挂着。

小结

联合索引按列序排序,最左前缀决定能用哪些条件;覆盖索引免除回表;列顺序按「等值→范围、高选择度优先」安排;删索引先 invisible。下一章系统解读 EXPLAIN 的每一列。