[英]Optimizing MySQL query where the number of returned rows is very large
背景:我们有一个网站,用户(商家)可以将他们的应用程序/网站添加到系统中,并通过 API 向用户付款。 现在,当我们必须在他们的仪表板上向商家显示这些交易的列表时,问题就来了。 每个商家每秒产生数百笔交易,平均每天商家有大约 200 万笔交易,在仪表板上,我们必须向商家显示今天的统计数据。
主要问题:我们必须向商家显示今天的交易,单个商家大约有 200 万条记录。 所以像这样的查询,
SELECT * FROM transactions WHERE user_id = 123 LIMIT 0,15
在我们的示例中,检查的行数为 200 万,并且不能以任何方式减少。 我认为这个限制在这里没有帮助,因为 MySQL 仍然会检查所有行,然后从结果集中选择前 15 行。
我们如何优化这样的查询,我们必须向用户显示数百万条记录(当然还有分页)?
编辑:
解释输出:
询问:
explain select a.id, a.user_app_id, a.created_at, a.type, a.amount, a.currency_id, b.name, b.url from transactions as a left join user_apps as b on a.user_app_id = b.id where a.sender_user_id = ? and a.created_at BETWEEN '2020-03-20' AND '2020-03-21' order by a.created_at desc limit 15 offset 0
细节:
索引sender_user_id_2
是sender_user_id(int)
和created_at(timestamp)
列的复合索引。
此查询需要 5 到 15 秒才能返回 15 行。
如果我对表中只有 24 个事务的 sender_user_id 运行相同的查询,则立即响应。
首先,让我们修复可能存在的错误:您在那个“一天”中包含了两个午夜。 BETWEEN
是“包容性”。
AND a.created_at BETWEEN '2020-03-20' AND '2020-03-21'
-->
AND a.created_at >= '2020-03-20'
AND a.created_at < '2020-03-20' + INTERVAL 1 DAY
(没有性能变化,只是消除了明天的午夜。)
在您的简单查询中,由于LIMIT
,只会触及 15 行。 然而,对于更复杂的查询,它可能需要收集所有行,对它们进行排序,然后才剥离 15 行。 防止这种低效率的技术是这样的:如果可能,设计一个处理所有WHERE
和ORDER BY
的INDEX
。
where a.sender_user_id = ?
AND a.created_at >= '2020-03-20'
AND a.created_at < '2020-03-20' + INTERVAL 1 DAY
order by a.created_at desc
需要INDEX(sender_user_id, created_at)
-- 按照这个顺序。 (而且,在您的查询中,没有其他内容会侵犯它。)
通过OFFSET
分页引入了另一个性能问题——它必须在获得您想要的行之前遍历所有OFFSET
行。 这可以通过记住你离开的地方来解决。
那么,为什么EXPLAIN
认为它会达到一百万行呢? 因为 Explain 在处理LIMIT
时是愚蠢的。 有一种更好的方法来估计工作量。 如果一切正常,那将显示 15,而不是 100 万。 对于LIMIT 150, 15
,它将显示 165。
您说“索引 sender_user_id_2 是 sender_user_id(int) 和 created_at(timestamp) 列的复合索引。” 你能提供SHOW CREATE TABLE
以便我们可以检查其他微妙的事情吗?
嗯...我想知道是否
order by a.created_at desc
应更改以匹配索引:
order by a.sender_user_id DESC, a.created_at desc
(你使用的是什么版本的 MySQL?我做了一些实验,发现没有区别,因为在 ORDER BY 中有(或没有) sender_user_id
。)
(麻烦 - 似乎JOIN
阻止了LIMIT
的有效使用。仍在挖掘......)
新建议:
select a.id, a.user_app_id, a.created_at, a.type, a.amount, a.currency_id,
b.name, b.url
from
(
SELECT a1.id
FROM transactions as a1
where a1.sender_user_id = ?
AND a.created_at >= '2020-03-20'
AND a.created_at < '2020-03-20' + INTERVAL 1 DAY
order by a1.created_at desc
limit 15 offset 0
) AS x
JOIN transactions AS a USING(id)
left join user_apps as b ON x.user_app_id = b.id
这使用通用的“技巧”将LIMIT
移动到派生表中,其他东西最少。 然后,只有 15 个 id,到其他表的JOINs
变得“快速”。
在我的实验中(使用一对不同的表),它只触及 5*15 行。 我检查了多个版本; 似乎所有人都需要这种技术。 我习惯用Handler_reads
来验证结果。
当我尝试使用JOIN
而不是派生表时,它触及 2*N 行,其中 N 是没有LIMIT
的行数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.