【问题标题】:Index IN and a range索引 IN 和范围
【发布时间】:2011-03-09 03:17:12
【问题描述】:

我需要为此查询找到最佳索引:

SELECT c.id id, type
FROM Content c USE INDEX (type_proc_last_cat)
LEFT JOIN Battles b ON c.id = b.id
WHERE type = 1
    AND processing_status = 1
    AND category IN (13, 19)
    AND status = 4
ORDER BY last_change DESC
LIMIT 100";

表格如下所示:

mysql> describe Content;
+-------------------+---------------------+------+-----+---------+-------+
| Field             | Type                | Null | Key | Default | Extra |
+-------------------+---------------------+------+-----+---------+-------+
| id                | bigint(20) unsigned | NO   | PRI | NULL    |       |
| type              | tinyint(3) unsigned | NO   | MUL | NULL    |       |
| category          | bigint(20) unsigned | NO   |     | NULL    |       |
| processing_status | tinyint(3) unsigned | NO   |     | NULL    |       |
| last_change       | int(10) unsigned    | NO   |     | NULL    |       |
+-------------------+---------------------+------+-----+---------+-------+

mysql> show indexes from Content;
+---------+------------+---------------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table   | Non_unique | Key_name            | Seq_in_index | Column_name       | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+---------------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
| Content |          0 | PRIMARY             |            1 | id                | A         |        4115 |     NULL | NULL   |      | BTREE      |         |
| Content |          1 | type_proc_last_cat  |            1 | type              | A         |           4 |     NULL | NULL   |      | BTREE      |         |
| Content |          1 | type_proc_last_cat  |            2 | processing_status | A         |          20 |     NULL | NULL   |      | BTREE      |         |
| Content |          1 | type_proc_last_cat  |            3 | last_change       | A         |        4115 |     NULL | NULL   |      | BTREE      |         |
| Content |          1 | type_proc_last_cat  |            4 | category          | A         |        4115 |     NULL | NULL   |      | BTREE      |         |
+---------+------------+---------------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+


mysql> describe Battles;
+---------------------+---------------------+------+-----+---------+-------+
| Field               | Type                | Null | Key | Default | Extra |
+---------------------+---------------------+------+-----+---------+-------+
| id                  | bigint(20) unsigned | NO   | PRI | NULL    |       |
| status              | tinyint(4) unsigned | NO   |     | NULL    |       |
| status_last_changed | int(11) unsigned    | NO   |     | NULL    |       |
+---------------------+---------------------+------+-----+---------+-------+

mysql> show indexes from Battles;
+---------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table   | Non_unique | Key_name  | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Battles |          0 | PRIMARY   |            1 | id          | A         |        1215 |     NULL | NULL   |      | BTREE      |         |
| Battles |          0 | id_status |            1 | id          | A         |        1215 |     NULL | NULL   |      | BTREE      |         |
| Battles |          0 | id_status |            2 | status      | A         |        1215 |     NULL | NULL   |      | BTREE      |         |
+---------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

我得到这样的输出:

mysql> explain
    -> SELECT c.id id, type
    -> FROM Content c USE INDEX (type_proc_last_cat)
    -> LEFT JOIN Battles b USE INDEX (id_status) ON c.id = b.id
    -> WHERE type = 1
    ->     AND processing_status = 1
    ->     AND category IN (13, 19)
    ->     AND status = 4
    -> ORDER BY last_change DESC
    -> LIMIT 100;
+----+-------------+-------+--------+--------------------+--------------------+---------+-----------------------+------+--------------------------+
| id | select_type | table | type   | possible_keys      | key                | key_len | ref                   | rows | Extra                    |
+----+-------------+-------+--------+--------------------+--------------------+---------+-----------------------+------+--------------------------+
|  1 | SIMPLE      | c     | ref    | type_proc_last_cat | type_proc_last_cat | 2       | const,const           | 1352 | Using where; Using index |
|  1 | SIMPLE      | b     | eq_ref | id_status          | id_status          | 9       | wtm_master.c.id,const |    1 | Using where; Using index |
+----+-------------+-------+--------+--------------------+--------------------+---------+-----------------------+------+--------------------------+

问题在于 Content 表的行数。 MySQL 似乎无法有效地使用 type_proc_last_cat 索引中的 last_change 和 category。如果我切换 last_change 和 category 的顺序,选择的行会更少,但会导致 ORDER BY 的文件排序,这意味着它会从数据库中提取所有匹配的行。更糟糕的是,因为两个表中都有 100,000 多行。

表都是 InnoDB,所以请记住 PRIMARY 键附加到每个其他索引。因此,上面索引 type_proc_last_cat 的索引表现得就像它处于打开状态一样(类型、处理状态、last_change、类别、id)。我知道我可以将 Battles 的 PRIMARY 键更改为 (id, status) 并删除 id_status 索引(我可以这样做)。

编辑:类型、类别、处理状态和状态的任何值都小于总值的 20%。 last_change 和 status_last_change 是 unix 时间戳。

编辑:如果我以相反的顺序使用 categorylast_change 的不同索引,我会得到:

mysql> show indexes from Content;
+---------+------------+---------------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table   | Non_unique | Key_name            | Seq_in_index | Column_name       | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+---------------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
| Content |          0 | PRIMARY             |            1 | id                | A         |        4115 |     NULL | NULL   |      | BTREE      |         |
| Content |          1 | type_proc_cat_last  |            1 | type              | A         |           6 |     NULL | NULL   |      | BTREE      |         |
| Content |          1 | type_proc_cat_last  |            2 | processing_status | A         |          26 |     NULL | NULL   |      | BTREE      |         |
| Content |          1 | type_proc_cat_last  |            3 | category          | A         |         228 |     NULL | NULL   |      | BTREE      |         |
| Content |          1 | type_proc_cat_last  |            4 | last_change       | A         |        4115 |     NULL | NULL   |      | BTREE      |         |
+---------+------------+---------------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+


mysql> explain SELECT c.id id, type FROM Content c USE INDEX (type_proc_cat_last) LEFT JOIN Battles b 
USE INDEX (id_status) ON c.id = b.id WHERE type = 1     AND processing_status = 1     AND category IN (13, 19)     AND status = 4 ORDER BY last_change DESC LIMIT 100;
+----+-------------+-------+-------+--------------------+--------------------+---------+-----------------------+------+------------------------------------------+
| id | select_type | table | type  | possible_keys      | key                | key_len | ref                   | rows | Extra                                    |
+----+-------------+-------+-------+--------------------+--------------------+---------+-----------------------+------+------------------------------------------+
|  1 | SIMPLE      | c     | range | type_proc_cat_last | type_proc_cat_last | 10      | NULL                  |  165 | Using where; Using index; Using filesort |
|  1 | SIMPLE      | b     | ref   | id_status          | id_status          | 9       | wtm_master.c.id,const |    1 | Using where; Using index                 |
+----+-------------+-------+-------+--------------------+--------------------+---------+-----------------------+------+------------------------------------------+

文件排序让我担心,因为它告诉我 MySQL 在排序之前先提取所有匹配的行。当有 100,000+ 时,这将是一个大问题。

【问题讨论】:

    标签: mysql sql indexing innodb


    【解决方案1】:

    EXPLAIN 中的rows 字段不反映实际读取的行数。它反映了可能会受到影响的行数。而且它不依赖LIMIT,因为LIMIT是在计划计算后应用的。

    所以你不必担心。

    另外我建议你在type_proc_last_cat 中交换last_changecategory,这样mysql 可以尝试使用最后一个索引部分(last_change)进行排序。

    【讨论】:

    • 那么在这种情况下,MySQL 会继续按last_change DESC 顺序拉取行,直到找到与category IN (13, 19) 匹配的LIMIT 100
    • @Mark Rose:是的。 Mysql 计算预期的行数并停止执行。 dev.mysql.com/doc/refman/5.1/en/limit-optimization.html -- 看看那里的第二项。
    • 我尝试按照您的建议切换顺序,但最终得到了文件排序。我已经更新了问题。
    • @Mark Rose: 然后改回来:-S 在 mysql 中优化 order by 有点棘手
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多