SQL:在超过 1500 万行查询中结合 WHERE、ORDER 和 LIMIT
Posted
技术标签:
【中文标题】SQL:在超过 1500 万行查询中结合 WHERE、ORDER 和 LIMIT【英文标题】:SQL: Combining WHERE, ORDER and LIMIT on 15 million+ row query 【发布时间】:2021-12-09 06:48:40 【问题描述】:我有 2 张桌子,item
和 config
。
item
有大约 1500 万行,config
有大约 1000 行。
我想用WHERE
子句连接这两个表并对结果进行排序。
这可能看起来像这样:
SELECT
`t0`.`id`,
`t0`.`item_name`,
`t1`.`id`,
`t1`.`config_name`,
FROM
`item` t0
LEFT OUTER JOIN `config` `t1` ON `t0`.`config_id` = `t1`.`id`
WHERE (`t0`.`config_id` = 678)
ORDER BY
`t0`.`item_name` ASC;
这在约 800 毫秒内成功运行并返回约 50k 行。
我也想分页这个结果,所以我运行相同的查询并添加一个LIMIT
:
SELECT
`t0`.`id`,
`t0`.`item_name`,
`t1`.`id`,
`t1`.`config_name`,
FROM
`item` t0
LEFT OUTER JOIN `config` `t1` ON `t0`.`config_id` = `t1`.`id`
WHERE (`t0`.`config_id` = 678)
ORDER BY
`t0`.`item_name` ASC LIMIT 200;
此查询现在需要 5 分钟以上。
我试图了解造成这种差异的原因。
我可以简化查询,完全去掉JOIN
,只查询大表来尝试找出变慢的原因:
SELECT
`t0`.`id`,
`t0`.`item_name`,
FROM
`item` t0
WHERE (`t0`.`config_id` = 678)
ORDER BY
`t0`.`item_name` ASC;
此查询运行良好,但同样,添加 LIMIT
会大大增加查询时间。
我该如何解决这个问题或更好地诊断是什么原因造成的?
没有LIMIT
的简化查询的执行计划如下:
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+-------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+-------+----------+---------------------------------------+
| 1 | SIMPLE | t0 | NULL | ref | ITEM_FK_1 | ITEM_FK_1 | 8 | const | 98524 | 100.00 | Using index condition; Using filesort |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+-------+----------+---------------------------------------+
在查询中添加LIMIT 200
会生成这个执行计划:
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+-------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+-------+----------+--------------------------+
| 1 | SIMPLE | t0 | NULL | index | ITEM_FK_1 | ITEM_RULE_ITEM_UNQ | 775 | NULL | 31933 | 0.63 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+-------+----------+--------------------------+
【问题讨论】:
请为这两个查询添加执行计划(explain select ...
的输出)。还要添加现有索引(或创建表语句) - 我假设您在 config_id 和 item_name 上有索引?
感谢@Solarflare - 添加了执行计划
如果你提供SHOW CREATE TABLE
会有所帮助。
【参考方案1】:
要查找带有config_id=678
的行并按item_name
排序并只取前200 个,您有(以及其他)以下选项:
使用按item_name
排序的索引,并继续阅读,直到找到200 行也满足config_id=678
(无需排序)
使用config_id
(您的外键)上的索引获取所有带有config_id=678
的行,然后按名称对这些行进行排序,并取前200 行
哪些更快取决于您的数据。
首先,它将取决于带有config_id=678
的行的位置。如果例如前 200 行(按名称排序,例如以 A
开头)都有这个 id,这将非常快:您可以读取 200 行,然后停止,甚至不必订购任何东西。如果你运气不好,所有这些 id 都在这个列表的末尾(例如,只有以 Z
开头的名字才有这个 id),你必须在找到 200 个合适的行之前阅读所有行。
第二个选项取决于config_id=678
的行数。它将读取所有 50k 个(使用索引),对它们进行排序,并为您提供前 200 个。这将介于上述快速和慢速选项之间。
mysql 现在基本上必须猜测哪个版本更快。对于limit 200
的查询,它猜错了,显然它必须读取比预期更多的行。
让您了解 MySQL 的想法:
MySQL 假设您有 98.524 行(而不是 50k)和 config_id=678
(您的第一个执行计划中 rows
中的数字)。
您有 1500 万行,因此特定行具有该 ID 的概率为 98.524 / 15 Mill = 1/150。您需要其中的 200 行,因此您需要读取大约 200*150=30.000(或 31.933,您的第二个执行计划中的数字)行,直到您可能找到足够的行。
现在 MySQL 将读取 100k 行加上排序与 可能读取 30k 行进行比较,并选择了后者。并且在这种情况下是错误的(虽然 5 分钟似乎有点多,但还有其他因素,例如增加的索引大小或可能会减慢速度的覆盖范围缺失)。但可能适用于不同的 ID。
如果您增加限制(您必须在后面的页面中这样做),MySQL 将在某个时候切换执行计划(例如,找到具有该概率的前 1.000 行需要大约 1.000*150=150k > 100k 行)。
那么,你能做什么:
-
您可以force MySQL 使用您想要的索引,例如与
... from item t0 force index (ITEM_FK_1) left outer join ...
。这样做的缺点是,根据 id,不同的执行计划可能会更快。
可以添加一个最优索引:复合索引(config_id, item_name)
允许您仅读取具有正确 id 的行,并且由于它们是按名称排序的,因此您可以在前 200 行之后停止。无论您的数据分布如何,您总是读取 200 行(或更少)。并且假设 id
是主键,没有比这更快的解决方案了。
我会选择选项 2。
【讨论】:
【参考方案2】:添加这个
INDEX(config_id, item_name, id) -- in this order!
以及DROP
任何作为该索引“前缀”的索引。
【讨论】:
谢谢,你能解释一下在此处添加最后一个id
有什么好处吗?
@duncanhall - "covering" -- 当所有需要的列都在索引中时,你会得到额外的提升,因为它只需要查看INDEX's
BTree。以上是关于SQL:在超过 1500 万行查询中结合 WHERE、ORDER 和 LIMIT的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Oracle SQL Developer 中执行超过 100 万条插入查询?