【发布时间】:2018-03-07 09:49:24
【问题描述】:
有一个典型的Users 表,其中包含字段:id (primary)、application_id、login、phone 等(application_id - 选择性字段)
索引很少:
index_users_on_application_id,
unique_index_users_on_application_id_and_login
unique_index_users_on_application_id_and_phone
查询本身很简单:
SELECT `users`.*
FROM `users`
WHERE `users`.`application_id` = 1234
LIMIT 10 OFFSET 0;
棘手的部分是此查询使用两个唯一索引之一(例如unique_index_users_on_application_id_and_login),然后返回按login 排序的用户列表。但我需要它们按id 排序。
为此,我更新了查询:
SELECT `users`.*
FROM `users`
WHERE `users`.`application_id` = 1234
ORDER BY id
LIMIT 10 OFFSET 0;
好吧,现在解释表明 MySQL 开始使用 PRIMARY 键而不是任何索引。但这是怎么发生的?如果index_users_on_application_id 实际上应该包含两个字段:[application_id, id] (InnoDB),那么该索引非常适合查询,但 MySQL 决定选择另一个。
如果我说IGNORE INDEX(PRIMARY),MySQL 开始使用unique_index_users_on_application_id_and_login,仍然忽略正确的索引。 ORDER BY id+0 时的结果相同。
我还尝试ORDER BY application_id, id 以确保索引最适合,MySQL 仍然选择错误的索引。
任何想法,为什么会发生以及如何确保 MySQL 使用正确的索引而不明确说 USE INDEX(index_users_on_application_id)?
Users 表的完整索引列表:
mysql> show indexes from users;
+-------+------------+-----------------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-----------------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| users | 0 | PRIMARY | 1 | id | A | 21893 | NULL | NULL | | BTREE | | |
| users | 0 | index_users_on_confirmation_token | 1 | confirmation_token | A | 28 | NULL | NULL | YES | BTREE | | |
| users | 0 | index_users_on_reset_password_token | 1 | reset_password_token | A | 50 | NULL | NULL | YES | BTREE | | |
| users | 0 | index_users_on_application_id_and_external_user_id | 1 | application_id | A | 32 | NULL | NULL | YES | BTREE | | |
| users | 0 | index_users_on_application_id_and_external_user_id | 2 | external_user_id | A | 995 | NULL | NULL | YES | BTREE | | |
| users | 0 | index_users_on_application_id_and_login | 1 | application_id | A | 30 | NULL | NULL | YES | BTREE | | |
| users | 0 | index_users_on_application_id_and_login | 2 | login | A | 21893 | NULL | NULL | YES | BTREE | | |
| users | 1 | users_account_id_fk | 1 | account_id | A | 44 | NULL | NULL | | BTREE | | |
| users | 1 | users_blob_id_fk | 1 | blob_id | A | 118 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_remember_token | 1 | remember_token | A | 8 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_application_id | 1 | application_id | A | 32 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_application_id_and_facebook_id | 1 | application_id | A | 32 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_application_id_and_facebook_id | 2 | facebook_id | A | 3127 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_application_id_and_twitter_digits_id | 1 | application_id | A | 32 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_application_id_and_twitter_digits_id | 2 | twitter_digits_id | A | 138 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_application_id_and_email | 1 | application_id | A | 32 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_application_id_and_email | 2 | email | A | 2189 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_application_id_and_full_name | 1 | application_id | A | 32 | NULL | NULL | YES | BTREE | | |
| users | 1 | index_users_on_application_id_and_full_name | 2 | full_name | A | 5473 | NULL | NULL | YES | BTREE | | |
+-------+------------+-----------------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
19 rows in set (0.01 sec)
解释示例:
mysql> EXPLAIN SELECT `users`.* FROM `users` WHERE `users`.`application_id` = 56374 ORDER BY id asc LIMIT 1 OFFSET 0;
+----+-------------+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | users | ref | index_users_on_application_id_and_external_user_id,index_users_on_application_id_and_login,index_users_on_application_id,index_users_on_application_id_and_facebook_id,index_users_on_application_id_and_twitter_digits_id,index_users_on_application_id_and_email,index_users_on_application_id_and_full_name | index_users_on_application_id_and_external_user_id | 5 | const | 1 | Using where; Using filesort |
+----+-------------+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)
问题本身是使用错误的索引会导致这样的查询(限制为 100 次而不是 1 次)执行 MINUTES 分钟,而使用正确的索引则只是一瞬间的问题。
分析:
SET PROFILING = 1; SELECT `users`.* FROM `users` WHERE `users`.`application_id` = 56374 ORDER BY id asc LIMIT 1 OFFSET 0; SHOW PROFILE FOR QUERY 1; SET PROFILING = 0;
Query OK, 0 rows affected (0.00 sec)
+----------+----------+-----------------+-------+-------+----------------------------------------------------------------------------------------------------------------------------------+----------------------+-------+---------+---------------------+---------------------+---------------------+------------------+-------------+------------+---------+----------------------+------------------------+---------------------+--------------------+---------------------+----------------------+-------------------+------------+----------------+-------------+---------------------------------------------------------------------------------------+-------------------+-------------------------+--------------------------------------------------+----------------+
-- fields list --
+----------+----------+-----------------+-------+-------+----------------------------------------------------------------------------------------------------------------------------------+----------------------+-------+---------+---------------------+---------------------+---------------------+------------------+-------------+------------+---------+----------------------+------------------------+---------------------+--------------------+---------------------+----------------------+-------------------+------------+----------------+-------------+---------------------------------------------------------------------------------------+-------------------+-------------------------+--------------------------------------------------+----------------+
| 27265241 | NULL | Some Username | NULL | 9777 | SomeHash | AnotherHash | NULL | NULL | 2017-04-12 15:53:32 | 2017-09-21 13:39:51 | 2017-09-24 19:19:06 | 1234 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 2017-07-05 10:59:59 | NULL | NULL | 12345 | NULL | NULL | something_else | NULL | 1 | another_hash | 54321 |
+----------+----------+-----------------+-------+-------+----------------------------------------------------------------------------------------------------------------------------------+----------------------+-------+---------+---------------------+---------------------+---------------------+------------------+-------------+------------+---------+----------------------+------------------------+---------------------+--------------------+---------------------+----------------------+-------------------+------------+----------------+-------------+---------------------------------------------------------------------------------------+-------------------+-------------------------+--------------------------------------------------+----------------+
1 row in set (1 min 14.43 sec)
+--------------------------------+-----------+
| Status | Duration |
+--------------------------------+-----------+
| starting | 0.000068 |
| Waiting for query cache lock | 0.000025 |
| init | 0.000025 |
| checking query cache for query | 0.000047 |
| checking permissions | 0.000026 |
| Opening tables | 0.000031 |
| After opening tables | 0.000025 |
| System lock | 0.000025 |
| Table lock | 0.000026 |
| Waiting for query cache lock | 0.000037 |
| init | 0.000046 |
| optimizing | 0.000032 |
| statistics | 0.000225 |
| preparing | 0.000042 |
| executing | 0.000025 |
| Sorting result | 0.000057 |
| Sending data | 42.952100 |
| end | 0.000070 |
| query end | 0.000027 |
| closing tables | 0.000025 |
| Unlocking tables | 0.000028 |
| freeing items | 0.000028 |
| updating status | 0.000039 |
| cleaning up | 0.000025 |
+--------------------------------+-----------+
24 rows in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
【问题讨论】:
-
很难回答您的问题 - 您能否发布此表的完整 DDL,包括索引,并显示查询计划?
-
添加了索引列表和解释示例
-
@Mjh 我想这里的问题是这个查询(带有选定的 PRIMARY 索引)需要大约 30 秒。但是当它与 USE INDEX(index_users_on_application_id) 一起使用时,它需要 0 秒。问题是为什么?为什么 MySQL 在这里选择那个慢索引?
-
@Mjh 这是一个现实世界的问题,“正确”和“不正确”索引之间的区别就像查询执行时间的几分钟。
-
注意到
Profile是多么无用?它通常说最多的时间是在Sending data。 (事实证明,这不一定是它的目的。)