有一个典型的Users
表,包含字段:id (primary)
, application_id
, login
, phone
, etc. (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
table:
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)执行几分钟,而使用正确的索引则只需一瞬间的事情。
分析:
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)