InnoDB 会自动将每个辅助索引通过在其中追加主键列来进行扩展。考虑以下表定义
CREATE TABLE t1 (
i1 INT NOT NULL DEFAULT 0,
i2 INT NOT NULL DEFAULT 0,
d DATE DEFAULT NULL,
PRIMARY KEY (i1, i2),
INDEX k_d (d)
) ENGINE = InnoDB;此表在 (i1, i2) 列上定义了主键。它还在 (d) 列上定义了辅助索引 k_d,但内部而言,InnoDB 会扩展此索引并将其视为 (d, i1, i2) 列。
优化器在确定如何以及是否使用该索引时会考虑扩展辅助索引的主键列。这可能会导致更有效的查询执行计划和更好的性能。
优化器可以使用扩展辅助索引进行 ref、range 和 index_merge 索引访问、松散索引扫描访问、连接和排序优化,以及 MIN()/MAX() 优化。
以下示例展示了优化器是否使用扩展辅助索引会如何影响执行计划。假设 t1 中填充了以下行
INSERT INTO t1 VALUES
(1, 1, '1998-01-01'), (1, 2, '1999-01-01'),
(1, 3, '2000-01-01'), (1, 4, '2001-01-01'),
(1, 5, '2002-01-01'), (2, 1, '1998-01-01'),
(2, 2, '1999-01-01'), (2, 3, '2000-01-01'),
(2, 4, '2001-01-01'), (2, 5, '2002-01-01'),
(3, 1, '1998-01-01'), (3, 2, '1999-01-01'),
(3, 3, '2000-01-01'), (3, 4, '2001-01-01'),
(3, 5, '2002-01-01'), (4, 1, '1998-01-01'),
(4, 2, '1999-01-01'), (4, 3, '2000-01-01'),
(4, 4, '2001-01-01'), (4, 5, '2002-01-01'),
(5, 1, '1998-01-01'), (5, 2, '1999-01-01'),
(5, 3, '2000-01-01'), (5, 4, '2001-01-01'),
(5, 5, '2002-01-01');现在考虑以下查询
EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'执行计划取决于优化器是否使用扩展索引。
当优化器不考虑索引扩展时,它会将索引 k_d 仅视为 (d)。针对此查询的 EXPLAIN 会产生以下结果
mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: ref
possible_keys: PRIMARY,k_d
key: k_d
key_len: 4
ref: const
rows: 5
Extra: Using where; Using index当优化器考虑索引扩展时,它会将 k_d 视为 (d, i1, i2)。在这种情况下,它可以使用最左侧索引前缀 (d, i1) 来产生更好的执行计划
mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: ref
possible_keys: PRIMARY,k_d
key: k_d
key_len: 8
ref: const,const
rows: 1
Extra: Using index在这两种情况下,key 都表示优化器使用了辅助索引 k_d,但 EXPLAIN 输出展示了使用扩展索引带来的以下改进
key_len从 4 字节变为 8 字节,这表示键查找使用了d和i1列,而不仅仅是d。ref值从const变为const,const,因为键查找使用了两个键部分,而不是一个。rows计数从 5 降至 1,这表示InnoDB应该只需要检查更少的行即可产生结果。Extra值从Using where; Using index变为Using index。这意味着可以仅使用索引来读取行,而无需查阅数据行中的列。
使用扩展索引时优化器行为的差异也可以在 SHOW STATUS 中看到
FLUSH TABLE t1;
FLUSH STATUS;
SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01';
SHOW STATUS LIKE 'handler_read%'前面的语句包括 FLUSH TABLES 和 FLUSH STATUS 来刷新表缓存并清除状态计数器。
如果没有索引扩展,SHOW STATUS 会产生以下结果
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 5 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+如果有索引扩展,SHOW STATUS 会产生以下结果。Handler_read_next 值从 5 降至 1,这表示更有效地使用了索引
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 1 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+use_index_extensions 是 optimizer_switch 系统变量的标志,允许控制优化器在确定如何使用 InnoDB 表的辅助索引时是否要考虑主键列。默认情况下,use_index_extensions 是启用的。要检查禁用使用索引扩展是否可以提高性能,请使用以下语句
SET optimizer_switch = 'use_index_extensions=off';优化器使用索引扩展的具体行为会受到索引中的键部分数量(16)和最大键长度(3072 字节)的通常限制。