为什么 MySQL 选择范围扫描(慢)与索引扫描(快)
Why MySQL chooses range scan (slow) vs index scan (faster)
我 运行 遇到一个问题 table,描述如下:
mysql> desc myTable;
+---------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| score | int(11) | YES | MUL | 0 | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
| previous_score | int(11) | NO | MUL | 0 | |
+---------------------+--------------+------+-----+---------+----------------+
具有以下指标:
mysql> show indexes from myTable;
+-------+------------+-------------------------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------------------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| myTable | 0 | PRIMARY | 1 | id | A | 48 | NULL | NULL | | BTREE | | |
| myTable | 1 | index_my_table_on_previous_score | 1 | previous_score | A | 48 | NULL | NULL | | BTREE | | |
| myTable | 1 | index_my_table_on_score | 1 | score | A | 48 | NULL | NULL | YES | BTREE | | |
+-------+------------+-------------------------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
问题如下:
mysql> select count(*) from myTable where score > 10;
+----------+
| count(*) |
+----------+
| 48 |
+----------+
1 row in set (0.07 sec)
mysql> select count(*) from myTable ignore index(index_my_table_on_score) where score > 10;
+----------+
| count(*) |
+----------+
| 48 |
+----------+
1 row in set (0.00 sec)
如您所见,使用索引产生的结果比完整table扫描慢。由于我在 previous_score
列上有另一个索引,我决定使用 explain
来尝试进一步了解问题:
mysql> select count(*) from myTable where previous_score > 10;
+----------+
| count(*) |
+----------+
| 48 |
+----------+
1 row in set (0.00 sec)
因此,如您所见,既好又快。让我们进行 explain
比较:
mysql> explain select count(*) from myTable where score > 10;
+----+-------------+-------+-------+----------------------+----------------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------------+----------------------+---------+------+------+--------------------------+
| 1 | SIMPLE | myTable | range | index_my_table_on_score | index_my_table_on_score | 5 | NULL | 24 | Using where; Using index |
+----+-------------+-------+-------+----------------------+----------------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> explain select count(*) from myTable where previous_score > 10;
+----+-------------+-------+-------+-------------------------------+-------------------------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+-------------------------------+-------------------------------+---------+------+------+--------------------------+
| 1 | SIMPLE | myTable | index | index_my_table_on_previous_score | index_my_table_on_previous_score | 4 | NULL | 48 | Using where; Using index |
+----+-------------+-------+-------+-------------------------------+-------------------------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
作为附加信息,我正在执行的查询遍历了我 table (48) 的所有结果。
- 为什么,如果列定义完全相同,索引完全相同,MySQL选择对
score
进行范围扫描,并进行全previous_score
? 的索引扫描
- 其次,为什么
range scan
比 full index
扫描慢?范围扫描应该优于索引扫描,索引扫描优于完整 table 扫描。这显然不是我的特定用例。
- 有没有办法建议 MySQL 进行索引扫描而不是范围扫描?
非常感谢任何解释或建议。
对于像这样的微小 tables,这里有一个比计时更好的方法:
FLUSH STATUS;
SELECT SQL_NO_CACHE ...;
SHOW SESSION STATUS LIKE 'Handler%';
汇总数字列是比较 SELECT
的一个变体与另一个变体的一个很好的指标。
两个 EXPLAINs
都说它们是 Using index
-- 实际上是在构成索引的 BTree 中执行查询。没有 table 扫描。
请提供SHOW CREATE TABLE
,它比DESCRIBE
更具描述性。
如果 'cache' 是冷的,那可以解释 0.07 sec
。
我 运行 遇到一个问题 table,描述如下:
mysql> desc myTable;
+---------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| score | int(11) | YES | MUL | 0 | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
| previous_score | int(11) | NO | MUL | 0 | |
+---------------------+--------------+------+-----+---------+----------------+
具有以下指标:
mysql> show indexes from myTable;
+-------+------------+-------------------------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------------------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| myTable | 0 | PRIMARY | 1 | id | A | 48 | NULL | NULL | | BTREE | | |
| myTable | 1 | index_my_table_on_previous_score | 1 | previous_score | A | 48 | NULL | NULL | | BTREE | | |
| myTable | 1 | index_my_table_on_score | 1 | score | A | 48 | NULL | NULL | YES | BTREE | | |
+-------+------------+-------------------------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
问题如下:
mysql> select count(*) from myTable where score > 10;
+----------+
| count(*) |
+----------+
| 48 |
+----------+
1 row in set (0.07 sec)
mysql> select count(*) from myTable ignore index(index_my_table_on_score) where score > 10;
+----------+
| count(*) |
+----------+
| 48 |
+----------+
1 row in set (0.00 sec)
如您所见,使用索引产生的结果比完整table扫描慢。由于我在 previous_score
列上有另一个索引,我决定使用 explain
来尝试进一步了解问题:
mysql> select count(*) from myTable where previous_score > 10;
+----------+
| count(*) |
+----------+
| 48 |
+----------+
1 row in set (0.00 sec)
因此,如您所见,既好又快。让我们进行 explain
比较:
mysql> explain select count(*) from myTable where score > 10;
+----+-------------+-------+-------+----------------------+----------------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------------+----------------------+---------+------+------+--------------------------+
| 1 | SIMPLE | myTable | range | index_my_table_on_score | index_my_table_on_score | 5 | NULL | 24 | Using where; Using index |
+----+-------------+-------+-------+----------------------+----------------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> explain select count(*) from myTable where previous_score > 10;
+----+-------------+-------+-------+-------------------------------+-------------------------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+-------------------------------+-------------------------------+---------+------+------+--------------------------+
| 1 | SIMPLE | myTable | index | index_my_table_on_previous_score | index_my_table_on_previous_score | 4 | NULL | 48 | Using where; Using index |
+----+-------------+-------+-------+-------------------------------+-------------------------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
作为附加信息,我正在执行的查询遍历了我 table (48) 的所有结果。
- 为什么,如果列定义完全相同,索引完全相同,MySQL选择对
score
进行范围扫描,并进行全previous_score
? 的索引扫描
- 其次,为什么
range scan
比full index
扫描慢?范围扫描应该优于索引扫描,索引扫描优于完整 table 扫描。这显然不是我的特定用例。 - 有没有办法建议 MySQL 进行索引扫描而不是范围扫描?
非常感谢任何解释或建议。
对于像这样的微小 tables,这里有一个比计时更好的方法:
FLUSH STATUS;
SELECT SQL_NO_CACHE ...;
SHOW SESSION STATUS LIKE 'Handler%';
汇总数字列是比较 SELECT
的一个变体与另一个变体的一个很好的指标。
两个 EXPLAINs
都说它们是 Using index
-- 实际上是在构成索引的 BTree 中执行查询。没有 table 扫描。
请提供SHOW CREATE TABLE
,它比DESCRIBE
更具描述性。
如果 'cache' 是冷的,那可以解释 0.07 sec
。