我有一个关于mysql的表和两个性能完全不同的查询。我已经提取了查询计划,但是我无法完全理解性能差异背后的原因。
表格:
+-------------+----------------------------------------------+------------------------------------+
| TableA | | |
+-------------+----------------------------------------------+------------------------------------+
| id | int(10) unsigned NOT NULL AUTO_INCREMENT | |
| userId | int(10) | unsigned DEFAULT NULL |
| created | timestamp | NOT NULL DEFAULT CURRENT_TIMESTAMP |
| PRIMARY KEY | id | |
| KEY userId | userId | |
| KEY created | created | |
+-------------+----------------------------------------------+------------------------------------+
键/索引:id
字段上的主键,userId
字段上的键ASC
,是created
字段ASC上的另一个键。
tableA
是一个很大的表,它包含数百万行。
我在此表上运行的查询是:
标识为1234
的用户在此表中有150万条记录。我想获取其最新的100行。为了实现这一点,我有2个不同的查询:
查询1:
SELECT * FROM tableA USE INDEX (userId)
WHERE userId=1234 ORDER BY created DESC LIMIT 100;
查询2:
SELECT * FROM tableA
WHERE userId=1234 ORDER BY id DESC LIMIT 100;
由于id
的{{1}}字段是自动递增的,因此保留了最新的条件。这两个查询返回相同的结果。但是,存在巨大的性能差异。
查询计划是
tableA
我了解到查询1上有一个排序操作。在每个查询中,使用的索引为+----------+-----------------------------------------------+-------------------------------+------+---------------------------------------+
| Query No | Operation | Params | Raws | Raw desc |
+----------+-----------------------------------------------+-------------------------------+------+---------------------------------------+
| Query 1 | Sort(using file sort) Unique index scan (ref) | table: tableA; index: userId; | 2.5M | Using index condition; Using filesort |
| Query 2 | Unique index scan (ref) | table: tableA; index: userId; | 2.5M | Using where |
+----------+-----------------------------------------------+-------------------------------+------+---------------------------------------+
+--------+-------------+
| | Performance |
+--------+-------------+
| Query1 | 7,5 s |
+--------+-------------+
| Query2 | 741 ms |
+--------+-------------+
。但是为什么在查询2中不使用排序?主索引如何影响?
MySQL 5.7
编辑:表上有更多列,我从上面的表定义中提取了它们。
答案 0 :(得分:1)
由于tableA的id字段是自动递增的,因此保留了最新的条件。
这通常是有效的声明。
WHERE userId=1234 ORDER BY created DESC LIMIT 100
需要此“复合”索引:(userId, created)
。这样,无论表大小或该用户的行数如何,它都只会命中100行。
同样的道理
WHERE userId=1234 ORDER BY id DESC LIMIT 100;
即它需要(userId, id)
。但是,在InnoDB中,当您说INDEX(x)
时,它会默默地附加在PRIMARY KEY
列上。这样您就可以有效地获得INDEX(x,id)
。这就是为什么您的普通INDEX(userId)
运作良好的原因。
EXPLAIN
很少(如果有的话)考虑到LIMIT
。这就是两个查询的“行”均为“ 2.5M”的原因。
如果您删除了INDEX(userId)
提示,则第一个查询可能(也可能没有)使用了USE INDEX
。选择取决于表中有userId = 1234
的百分比。如果小于约20%,则将使用索引。但是它会在二级索引和数据之间来回跳动-全部进行150万次。如果超过20%,则只需读取所有“百万”行,而忽略不适用的行,就可以避免跳动。
注意:Q1的内容仍将读取至少150万行,对其进行排序(“使用文件排序”),然后剥离所需的100行。但是使用INDEX(userId, created)
,它可以跳过排序并查看仅100行。
我无法解释“唯一索引扫描”,而没有看到SHOW CREATE TABLE
和未注释的EXPLAIN
。 (EXPLAIN FORMAT=JSON SELECT...
可能会提供更多见解。)
答案 1 :(得分:0)
在第一个查询中,您强制MySQL使用userId
索引,结果是第一个查询的性能比第二个查询差。
通常,强制数据库使用索引是不好的。在这里使用的最佳索引是:
CREATE INDEX idx ON tableA (userId, created, id); -- perhaps id can be omitted if on InnoDB
此索引将使WHERE
子句非常严格,也可以用于created
子句中的ORDER BY
进行排序。该索引还完全覆盖了SELECT *
,这意味着MySQL不需要寻找其他数据的聚集索引。