简体   繁体   English

为什么 MySQL 不使用索引来处理 99% 的其他相同形式的查询?

[英]Why isn't MySQL using an index for this query when it uses one for 99% of other queries of an identical form?

I have a database which I make a lot of queries to like this:我有一个数据库,我做了很多这样的查询:

mysql> explain     SELECT  time, user, x, y, z, type, action
        FROM  co_block
        WHERE  time >= 1642497664
          AND  wid = 4
          AND  x <= -7650
          AND  x >= -7651;

+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
| id | select_type | table    | partitions | type  | possible_keys | key  | key_len | ref  | rows    | filtered | Extra                 |
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
|  1 | SIMPLE      | co_block | NULL       | range | wid           | wid  | 10      | NULL | 3940334 |    33.33 | Using index condition |
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+

This query returns 101 rows in 0.7 seconds.此查询在 0.7 秒内返回 101 行。

Almost all of them return very quickly, but occasionally I run into one that takes an eternity, like this almost-identical query which differs only in the X range that is searched.几乎所有这些都很快返回,但偶尔我会遇到一个需要永恒的查询,例如这个几乎相同的查询,它仅在搜索的 X 范围内有所不同。

mysql> explain SELECT time, user, x, y, z, type, action FROM co_block WHERE time >= 1642497664 AND wid = 4 AND x <= -7651 AND x >= -7652;
+----+-------------+----------+------------+------+---------------+------+---------+------+-----------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows      | filtered | Extra       |
+----+-------------+----------+------------+------+---------------+------+---------+------+-----------+----------+-------------+
|  1 | SIMPLE      | co_block | NULL       | ALL  | wid           | NULL | NULL    | NULL | 116322801 |     3.59 | Using where |
+----+-------------+----------+------------+------+---------------+------+---------+------+-----------+----------+-------------+

This query returns 85 rows after a painful 68 seconds.此查询在痛苦的 68 秒后返回 85 行。

If I use FORCE INDEX to force the use of the index, the query returns in only 1.2 seconds, so it obviously can be used and to great benefit, and indeed MySQL seems to actually use it in 99.99% of the queries of this sort that I make.如果我使用 FORCE INDEX 来强制使用索引,则查询仅在 1.2 秒内返回,因此显然可以使用它并带来很大的好处,实际上 MySQL 似乎在 99.99% 的此类查询中实际使用它我做。 So why is it choosing not to use the index in this case?那么为什么在这种情况下选择不使用索引呢?

The "explain" of the FORCE INDEX queries, in case they're insightful: FORCE INDEX 查询的“解释”,以防它们有见地:

mysql> explain SELECT time, user, x, y, z, type, action FROM co_block FORCE INDEX (`wid`) WHERE time >= 1642497664 AND wid = 4 AND x <= -7650 AND x >= -7651;
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
| id | select_type | table    | partitions | type  | possible_keys | key  | key_len | ref  | rows    | filtered | Extra                 |
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
|  1 | SIMPLE      | co_block | NULL       | range | wid           | wid  | 10      | NULL | 3940334 |    33.33 | Using index condition |
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain SELECT time, user, x, y, z, type, action FROM co_block FORCE INDEX (`wid`) WHERE time >= 1642497664 AND wid = 4 AND x <= -7651 AND x >= -7652;
+----+-------------+----------+------------+-------+---------------+------+---------+------+----------+----------+-----------------------+
| id | select_type | table    | partitions | type  | possible_keys | key  | key_len | ref  | rows     | filtered | Extra                 |
+----+-------------+----------+------------+-------+---------------+------+---------+------+----------+----------+-----------------------+
|  1 | SIMPLE      | co_block | NULL       | range | wid           | wid  | 10      | NULL | 12543806 |    33.33 | Using index condition |
+----+-------------+----------+------------+-------+---------------+------+---------+------+----------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

Table definition:表定义:

CREATE TABLE `co_block` (
  `rowid` bigint NOT NULL AUTO_INCREMENT,
  `time` int DEFAULT NULL,
  `user` int DEFAULT NULL,
  `wid` int DEFAULT NULL,
  `x` int DEFAULT NULL,
  `y` int DEFAULT NULL,
  `z` int DEFAULT NULL,
  `type` int DEFAULT NULL,
  `data` int DEFAULT NULL,
  `meta` blob,
  `blockdata` blob,
  `action` int DEFAULT NULL,
  `rolled_back` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`rowid`),
  KEY `wid` (`wid`,`x`,`z`,`time`),
  KEY `user` (`user`,`time`),
  KEY `type` (`type`,`time`)
) ENGINE=InnoDB AUTO_INCREMENT=252511481 DEFAULT CHARSET=utf8mb3

Index info:索引信息:

mysql> show index from co_block;
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table    | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| co_block |          0 | PRIMARY  |            1 | rowid       | A         |   116288328 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| co_block |          1 | wid      |            1 | wid         | A         |         886 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| co_block |          1 | wid      |            2 | x           | A         |      135141 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| co_block |          1 | wid      |            3 | z           | A         |    22393332 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| co_block |          1 | wid      |            4 | time        | A         |    71268960 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| co_block |          1 | user     |            1 | user        | A         |       49756 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| co_block |          1 | user     |            2 | time        | A         |    13558248 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| co_block |          1 | type     |            1 | type        | A         |       33126 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| co_block |          1 | type     |            2 | time        | A         |    28455380 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+

Likely irrelevant notes:可能不相关的注释:

The actual queries I'm making are grabbing 16x16 chunks by specifying both an X range and a Z range, but in trying to debug the problem myself, the Z range specified doesn't seem to be relevant to the problem, so I removed it from the queries.我正在做的实际查询是通过指定 X 范围和 Z 范围来获取 16x16 块,但是在尝试自己调试问题时,指定的 Z 范围似乎与问题无关,所以我删除了它从查询中。

In one of the many "why isn't MySQL using my index" questions I read, the problem was that the condition specified by the WHERE clause included almost all of the records.在我阅读的众多“为什么 MySQL 不使用我的索引”问题之一中,问题是 WHERE 子句指定的条件包括几乎所有记录。 This made me wonder if the problem might be the order of the "x >= -7652" and "x <= -7651" conditions, as the first would include almost all of the records in the database, and only the second actually eliminates most of them.这让我想知道问题是否可能是“x >= -7652”和“x <= -7651”条件的顺序,因为第一个条件将包括数据库中几乎所有的记录,而只有第二个条件实际上消除了他们中的大多数。 So I reversed the order of these two conditions, but it made no difference.所以我把这两个条件的顺序颠倒了,但是并没有什么区别。 I also tried using BETWEEN to specify the range but that too had no effect.我也尝试使用 BETWEEN 来指定范围,但这也没有效果。

Though perhaps only due to insufficient effort to find an example case, it seems to be required that there is both an "x >=" and an "x <=" condition.尽管可能只是由于没有足够的努力来找到示例案例,但似乎需要同时存在“x >=”和“x <=”条件。

The time constrain is not required but I included it above because otherwise there is much more data returned by the queries.时间约束不是必需的,但我在上面包含了它,因为否则查询返回的数据要多得多。 Here are the "explain" queries with the time constraint removed:以下是删除了时间限制的“解释”查询:

mysql> explain SELECT time, user, x, y, z, type, action FROM co_block WHERE wid = 4 AND x <= -7651 AND x >= -7652;
+----+-------------+----------+------------+------+---------------+------+---------+------+-----------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows      | filtered | Extra       |
+----+-------------+----------+------------+------+---------------+------+---------+------+-----------+----------+-------------+
|  1 | SIMPLE      | co_block | NULL       | ALL  | wid           | NULL | NULL    | NULL | 116325588 |    10.78 | Using where |
+----+-------------+----------+------------+------+---------------+------+---------+------+-----------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain SELECT time, user, x, y, z, type, action FROM co_block WHERE wid = 4 AND x <= -7650 AND x >= -7651;
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
| id | select_type | table    | partitions | type  | possible_keys | key  | key_len | ref  | rows    | filtered | Extra                 |
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
|  1 | SIMPLE      | co_block | NULL       | range | wid           | wid  | 10      | NULL | 3940334 |   100.00 | Using index condition |
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

Query results requested by nnichols: nnichls 要求的查询结果:

mysql> SELECT COUNT(*) FROM co_block FORCE INDEX (wid) WHERE wid = 4 AND x <= -7650 AND x >= -7651;
+----------+
| COUNT(*) |
+----------+
|  2958623 |
+----------+
1 row in set (0.86 sec)

mysql> explain SELECT COUNT(*) FROM co_block FORCE INDEX (wid) WHERE wid = 4 AND x <= -7650 AND x >= -7651;
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+--------------------------+
| id | select_type | table    | partitions | type  | possible_keys | key  | key_len | ref  | rows    | filtered | Extra                    |
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+--------------------------+
|  1 | SIMPLE      | co_block | NULL       | range | wid           | wid  | 10      | NULL | 3940334 |   100.00 | Using where; Using index |
+----+-------------+----------+------------+-------+---------------+------+---------+------+---------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> SELECT COUNT(*) FROM co_block FORCE INDEX (wid) WHERE wid = 4 AND x <= -7651 AND x >= -7652;
+----------+
| COUNT(*) |
+----------+
|  8982424 |
+----------+
1 row in set (2.38 sec)

mysql> explain SELECT COUNT(*) FROM co_block FORCE INDEX (wid) WHERE wid = 4 AND x <= -7651 AND x >= -7652;
+----+-------------+----------+------------+-------+---------------+------+---------+------+----------+----------+--------------------------+
| id | select_type | table    | partitions | type  | possible_keys | key  | key_len | ref  | rows     | filtered | Extra                    |
+----+-------------+----------+------------+-------+---------------+------+---------+------+----------+----------+--------------------------+
|  1 | SIMPLE      | co_block | NULL       | range | wid           | wid  | 10      | NULL | 12543806 |   100.00 | Using where; Using index |
+----+-------------+----------+------------+-------+---------------+------+---------+------+----------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

Add these two indexes and see if things improve:添加这两个索引,看看情况是否有所改善:

INDEX(wid, x, time),
INDEX(wid, time, x)

Also, run this once:另外,运行一次:

ANALYZE TABLE co_block;

How big is the table compared to innodb_buffer_pool_size ?innodb_buffer_pool_size相比,该表有多大? If quite big, then think about whether some of the INTs (4 bytes each) can be shrunk to smaller datatypes.如果很大,请考虑是否可以将某些INTs (每个 4 字节)缩小为更小的数据类型。

This might help, too:这也可能有帮助:

ORDER BY x, time

As already discussed in the comments and mentioned by @RickJames, you really should update the datatypes to better fit the data.正如评论中已经讨论的和@RickJames 提到的那样,您确实应该更新数据类型以更好地适应数据。 I would suggest starting with -我建议从 -

ALTER TABLE `co_block` 
    CHANGE COLUMN `time` `time` INT UNSIGNED NOT NULL, /* 5 => 4 bytes per index entry */
    CHANGE COLUMN `wid` `wid` TINYINT NOT NULL,        /* 5 => 1 byte */
    CHANGE COLUMN `x` `x` SMALLINT NOT NULL,           /* 5 => 2 bytes */
    CHANGE COLUMN `z` `z` SMALLINT NOT NULL;           /* 5 => 2 bytes */

This will reduce your wid index from 20 bytes => 9 bytes per row;这会将您的wid索引从每行 20 个字节 => 9 个字节减少; less than half the size.不到一半的大小。 Assuming similar changes can be made to all the integer fields, your space saving on both the data and index will be considerable.假设可以对所有 integer 字段进行类似的更改,那么您在数据和索引上节省的空间将是相当可观的。 That reduction in size will also lead to improved performance.尺寸的减小也将导致性能的提高。

If you are concerned about possible impact on the plugin then do some digging online or speak to the developer.如果您担心对插件的可能影响,请在线进行一些挖掘或与开发人员交谈。 Do you need to retain all the data?您需要保留所有数据吗? Should you implement a purge routine to remove data older than x months?您是否应该实施清除例程来删除超过 x 个月的数据?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM