为什么为 SELECT COUNT(*) ... 选择二级索引而不是聚集索引?

Mat*_*ick 5 mysql performance index-tuning query-performance

在这个查询中:

select count(*) from largetable;
Run Code Online (Sandbox Code Playgroud)

选择二级索引:

mysql> explain select count(*) from largetable;
+----+-------------+------------+-------+---------------+------+---------+------+----------+-------------+
| id | select_type | table      | type  | possible_keys | key  | key_len | ref  | rows     | Extra       |
+----+-------------+------------+-------+---------------+------+---------+------+----------+-------------+
|  1 | SIMPLE      | largetable | index | NULL          | iif  | 5       | NULL | 50000169 | Using index |
+----+-------------+------------+-------+---------------+------+---------+------+----------+-------------+
1 row in set (0.00 sec)

mysql> select count(*) from largetable;
+----------+
| count(*) |
+----------+
| 50000000 |
+----------+
1 row in set (5 min 52.02 sec)
Run Code Online (Sandbox Code Playgroud)

而强制使用聚集索引:

select count(*) from largetable force index (primary);
Run Code Online (Sandbox Code Playgroud)

提供更好的性能:

mysql> explain select count(*) from largetable force index (primary);
+----+-------------+------------+-------+---------------+---------+---------+------+----------+-------------+
| id | select_type | table      | type  | possible_keys | key     | key_len | ref  | rows     | Extra       |
+----+-------------+------------+-------+---------------+---------+---------+------+----------+-------------+
|  1 | SIMPLE      | largetable | index | NULL          | PRIMARY | 4       | NULL | 50000169 | Using index |
+----+-------------+------------+-------+---------------+---------+---------+------+----------+-------------+
1 row in set (0.00 sec)

mysql> select count(*) from largetable force index (primary);
+----------+
| count(*) |
+----------+
| 50000000 |
+----------+
1 row in set (2 min 23.07 sec)
Run Code Online (Sandbox Code Playgroud)

所以这是 5 分 52 秒对 2 分 23 秒。

我想了解为什么 MySQL 的查询优化器选择二级索引。

表中有 5000 万行,ID 为 1 到 5000 万(无间隙),按顺序插入。

这是在 MySQL 5.5.11 上。


这是桌子的设计:

create table largetable (
  id     int   primary key   auto_increment,
  field1 int,
    index iif (field1),
  ... some more columns, some with indexes ... each row is about 115 bytes ...
);
Run Code Online (Sandbox Code Playgroud)

Rol*_*DBA 4

该问题可能源于 MySQL 查询优化器做出选择的方式以及索引在 InnoDB 内部表示的方式。

首先看一下索引的基数。主键的基数必须始终是 InnoDB 表的实际行数。现在,看看字段1 的基数。如果索引iif小于主键的iif,MySQL查询优化器将选择辅助索引。要验证 field1 的基数是否较低,请运行以下查询:

SELECT COUNT(DISTINCT field1) FROM largetable;
SELECT field1,COUNT(1) fieldcount FROM largetable
GROUP BY field1 WITH ROLLUP;
Run Code Online (Sandbox Code Playgroud)

现在,看看索引的内部表示。二级索引将包含两项:1) 被索引的列值,2) 来自聚集索引的 rowid(也称为 gen_clust_index 。每次在二级索引中引用一列时,也会查找实际行。想象一下:两个键查找 InnoDB 中的每一行。

将这两个问题放在一起,您会发现基数低于主键的二级索引仍将使用主键查找实际行。这解释了为什么选择二级索引而不是主键,并且查询时间需要两倍甚至更长的时间。

有些人可能不同意这种推理,因为我在 StackOverflow(2011 年 11 月 15 日)中回答了类似的问题。虽然我的答案被接受了,但它有赞成票和反对票,因为有些人不以同样的方式看待 MySQL 查询优化器和 InnoDB 索引结构

如果 Percona 的任何人看到这个问题和我的答案,并发现我的推理有任何缺陷,请纠正我,以便所有人都能学习。

更新 2012-04-23 12:56 美国东部时间

InnoDB 存储引擎深入研究 BTREE 索引,对基数进行有根据的猜测。尝试将innodb_stats_on_metadata设置为关闭

[mysqld]
innodb_stats_on_metadata = 0
Run Code Online (Sandbox Code Playgroud)

根据文档,禁用后,InnoDB 在这些操作期间不会更新统计信息。禁用此变量可以提高具有大量表或索引的模式的访问速度。它还可以提高涉及 InnoDB 表的查询的执行计划的稳定性。