开发者

Index IN and a range

开发者 https://www.devze.com 2023-02-16 03:22 出处:网络
I need to find the best index for this query: SELECT c.id id, type FROM Content c USE INDEX (type_proc_last_cat)

I need to find the best index for this query:

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";

The tables look like this:

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      | 开发者_如何学Go        |
| Battles |          0 | id_status |            2 | status      | A         |        1215 |     NULL | NULL   |      | BTREE      |         |
+---------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

And I get output like this:

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 |
+----+-------------+-------+--------+--------------------+--------------------+---------+-----------------------+------+--------------------------+

The trouble is the rows count for the Content table. It appears MySQL is unable to effectively use both last_change and category in the type_proc_last_cat index. If I switch the order of last_change and category, fewer rows are selected but it results in a filesort for the ORDER BY, meaning it pulls all the matching rows from the database. That is worse, since there are 100,000+ rows in both tables.

Tables are both InnoDB, so keep in mind that the PRIMARY key is appended to every other index. So the index the index type_proc_last_cat above behaves likes it's on (type, processing_status, last_change, category, id). I am aware I could change the PRIMARY key for Battles to (id, status) and drop the id_status index (and I may just do that).

Edit: Any value for type, category, processing_status, and status is less than 20% of the total values. last_change and status_last_change are unix timestamps.

Edit: If I use a different index with category and last_change in reverse order, I get this:

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                 |
+----+-------------+-------+-------+--------------------+--------------------+---------+-----------------------+------+------------------------------------------+

The filesort worries me as it tells me MySQL pulls all the matching rows first, before sorting. This will be a big problem when there are 100,000+.


rows field in EXPLAIN doesn't reflect the number of rows that actually were read. It reflects the number of rows that possible will be affected. Also it doesn't rely on LIMIT, because LIMIT is applied after the plan has been calculated.

So you don't need to worry about it.

Also I would suggest you to swap last_change and category in type_proc_last_cat so mysql can try to use the last index part (last_change) for sorting purposes.

0

精彩评论

暂无评论...
验证码 换一张
取 消