在更大的数据表上为每个用户选择第一行/最后一行的 SQL 性能
Posted
技术标签:
【中文标题】在更大的数据表上为每个用户选择第一行/最后一行的 SQL 性能【英文标题】:SQL Performance on selecting first/last row for each user on bigger data table 【发布时间】:2020-04-30 21:44:38 【问题描述】:我已经阅读了很多关于每组最大 n 的帖子,但在性能方面似乎仍然没有找到一个好的解决方案。我正在运行 10.1.43-MariaDB。
我正在尝试获取给定时间范围内数据值的变化,因此我需要获取此期间最早和最新的行。现在需要计算的时间范围内的最大行数约为 700k,并且只会增长。现在我刚刚做了两个查询,一个是最新的,一个是最早的日期,但即使这样,目前的性能也很慢。该表如下所示:
user_id data date
4567 109 28/06/2019 11:04:45
4252 309 18/06/2019 11:04:45
4567 77 18/02/2019 11:04:45
7893 1123 22/06/2019 11:04:45
4252 303 11/06/2019 11:04:45
4252 317 19/06/2019 11:04:45
日期和 user_id 列已编入索引。如果不进行排序,那么行在数据库中的任何特定顺序都不会产生影响。
我在这个问题上得到的最远的结果是当前年度期间的这样的查询(700k 数据点):
SELECT user_id,
MIN(date) as date, data
FROM datapoint_table
WHERE date >= '2019-01-14'
GROUP BY user_id
这让我在大约 0.05 秒内很快就能找到正确的日期和 user_id。但就像每组最大 n 的常见问题一样,该行的其余部分(在这种情况下为数据)与日期不同。我已经阅读了其他类似的问题,并尝试使用这样的子查询:
SELECT a.user_id, a.date, a.data
FROM datapoint_table a
INNER JOIN (
SELECT datapoint_table.user_id,
MIN(date) as date, data
FROM datapoint_table
WHERE date >= '2019-01-01'
GROUP BY user_id
) b ON a.user_id = b.user_id AND a.date = b.date
此查询大约需要 15 秒才能完成并获取正确的数据值。 15 秒太长了,当第一次查询如此之快时,我一定是做错了什么。我还尝试对 user_id 的 group by 数据执行 (MAX)-(MIN) ,但它的性能也很慢。
有什么更有效的方法可以获得与日期相同的数据值,甚至是每个用户的最新和最早数据的差异?
【问题讨论】:
问题是我在 user_id 和 date 列上都没有复合索引,只有单个索引。接受的答案解决了这个问题。 【参考方案1】:假设您使用的是相当新的 MariaDB 或 mysql 版本,那么ROW_NUMBER
可能是为每个用户查找最早记录的最有效方法:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date) rn
FROM datapoint_table
WHERE date > '2019-01-14'
)
SELECT user_id, data, date
FROM cte
WHERE rn = 1;
您还可以考虑添加以下索引:
CREATE INDEX ON datapoint_table (user_id, date);
您还可以尝试以下列颠倒的变体索引:
CREATE INDEX ON datapoint_table (date, user_id);
目前尚不清楚哪个版本的索引性能最好,这取决于您的数据和执行计划。理想情况下,上述两个索引之一将帮助数据库执行ROW_NUMBER
,以及WHERE
子句。
如果您的数据库版本不支持ROW_NUMBER
,那么您可以继续当前的方法:
SELECT d1.user_id, d1.data, d1.date
FROM datapoint_table d1
INNER JOIN
(
SELECT user_id, MIN(date) AS min_date
FROM datapoint_table
WHERE date > '2019-01-14'
GROUP BY user_id
) d2
ON d1.user_id = d2.user AND d1.date = d2.min_date
WHERE
d1.date > '2019-01-14';
同样,建议的索引至少应该加快GROUP BY
子查询的执行速度。
【讨论】:
我读到只有 MariaDB 10.2 或更高版本才支持窗口函数,并且我正在运行 10.1.43。如果有帮助,我将升级我的版本并尝试 row_number()。 @Jack477 我还为您提供了早期版本的选项。无论如何,这里最大的性能提升器可能是索引您的表。 我尝试了另一种方法,14-15 秒大致相同。如果您的意思是这样,日期和 user_id 列将被编入索引。 您需要一个同时覆盖两个列的索引。仅其中一种可能无效。 感谢您提供此信息我不知道您可以这样做,但不幸的是它对我目前使用的方法没有帮助。以上是关于在更大的数据表上为每个用户选择第一行/最后一行的 SQL 性能的主要内容,如果未能解决你的问题,请参考以下文章