MySQL使用了不正确的索引。为什么?
有一个典型的MySQL使用了不正确的索引。为什么?,mysql,sql,indexing,Mysql,Sql,Indexing,有一个典型的Users表,其中包含以下字段:id(primary),application\u id,login,phone,等等(application\u id-选择性字段) 索引很少: index_users_on_application_id, unique_index_users_on_application_id_and_login unique_index_users_on_application_id_and_phone 查询本身非常简单: SELECT `users`.*
Users
表,其中包含以下字段:id(primary)
,application\u id
,login
,phone
,等等(application\u 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\u index\u users\u on\u application\u id\u和\u login
),然后返回按login
排序的用户列表。但是我需要它们按id
排序
为此,我更新了查询:
SELECT `users`.*
FROM `users`
WHERE `users`.`application_id` = 1234
ORDER BY id
LIMIT 10 OFFSET 0;
好的,现在解释显示MySQL开始使用PRIMARY
键而不是任何索引。但这是怎么发生的?如果index\u users\u on\u application\u id
实际上应该包含两个字段:[application\u id,id](InnoDB),那么该索引非常适合查询,但MySQL决定选择另一个
如果我说ignoreindex(PRIMARY)
,MySQL开始使用unique\u INDEX\u users\u on\u application\u id\u和\u login
,仍然忽略正确的索引。按id+0排序时的结果相同
我还试着按应用程序的id,id排序,以确保索引最合适,MySQL仍然选择了错误的索引
任何想法,为什么会发生这种情况,以及如何确保MySQL使用正确的索引,而不显式地说使用索引(index\u users\u on\u application\u id)
用户索引的完整列表
表:
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)
您应该能够使用索引提示和优化器提示来建议正确的索引使用:
您可以直接提示该表:
tbl_name [[AS] alias] [index_hint_list]
index_hint_list:
index_hint [index_hint] ...
index_hint:
USE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
| IGNORE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
| FORCE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
index_list:
index_name [, index_name] ...
在您的情况下,我认为最好的解决方案是按名称命名属性,而不是使用star*
和使用忽略索引(应用程序id上的唯一索引用户和登录,应用程序id上的唯一索引用户和电话)直接在查询中进行订购:
SELECT `users`.*
FROM `users`
WHERE `users`.`application_id` = 1234
ORDER BY id
LIMIT 10 OFFSET 0;
基于您的代码的示例:
SELECT u.id,
u.application_id,
u.login,
u.phone,
# ... here to continue
FROM users as u
IGNORE INDEX (unique_index_users_on_application_id_and_login, unique_index_users_on_application_id_and_phone ) FOR ORDER BY
WHERE u.application_id = 1234
ORDER BY u.id
LIMIT 10 OFFSET 0;
第一次编辑
由于下面的评论,我添加了一个使主键无效的技巧
您还可以通过以下技巧使主键无效:
SELECT u.id,
u.application_id,
u.login,
u.phone,
#...
FROM users as u
WHERE u.application_id = 1234
ORDER BY u.id+0
很难确定,但在很多情况下,MySQL擅长选择正确的索引
可能有必要为查询分析器提供一些有用的信息—有时候查询分析器没有关于数据的好信息,这可能会导致奇怪的行为
但是…您的应用程序id上的索引用户\u仅具有应用程序id
,而不具有id
我猜application\u id
具有相当少的不同值。列ID
是唯一的,其值与表中的行数相同。没有包含where子句和ORDERBY子句的索引,因此MySQL猜测最昂贵的部分将是按ID
排序,而不是按application\u ID
筛选
所以,我认为MySQL在这里做的是正确的。在幕后,它将返回按ID排序的所有行,并遍历该列表,并为您提供具有指定应用程序ID的前10行
显然,要做的事情是创建一个带有应用程序id和id的索引,看看MySQL是否会选择它。当您使用orderbycolumn\u name
MySQL扫描ORDERBY子句中给定列中的所有数据时,在您的例子中,id
column将得到扫描。对于此数据库,请使用表指针。在InnoDB中,它是主键的值;在MyISAM中,它是.MYD文件中的偏移量
这就是为什么当您使用orderbyid时,您的查询开始只使用主键作为索引
要使用您创建的索引,请添加id
列作为索引的第一列。然后它将有效地使用索引。因此,应用程序id和用户登录表上的索引unique\u index\u users\u应按相同顺序1-id和2-application\u id包含以下列
有关ORDER BY/LIMIT的MySQL性能的更多详细信息,在这种情况下,不应该使用索引,甚至强制索引(correct_INDEX)更有效,因为您已经指定了索引?@milangelebit Yes USE&FORCE会更有效。我坚持希望索引,但没有明确说明使用索引(index\u users\u on\u application\u id)
。看到你回答了这个问题,你能告诉我实际的问题是什么,你的答案为OP解决了什么吗?@Mjh因为在我回答之前没有编辑,我不得不猜测nattfold想要对优化器使用索引(在编辑之前回答)。我的回答使他能够这样做,即使我现在看到的成本更高(以前也没有编辑)。是的,但我想问的是,你是否理解他的问题到底是什么?我不。我不明白如果他强制使用一个索引或使用另一个索引会发生什么。解决了什么问题?到目前为止,我还没有遇到过这样的情况,MySQL必须被明确告知不要使用主键而使用其他索引。很难理解你的问题-你能发布这个表的完整DDL吗,包括索引,并显示查询计划?添加了索引列表和解释example@Mjh我想这里的问题是这个查询(使用选定的主索引)需要大约30秒。但当与use INDEX(INDEX\u users\u on\u application\u id)一起使用时,需要0秒。问题是为什么?为什么MySQL会选择这个慢索引?@Mjh这是一个现实世界的问题,“正确”和“不正确”索引之间的区别就像查询执行时间的分钟数。请注意,Profile
是多么无用?它通常说最多的时间是在发送数据时。(事实证明,这不是n