在更大的数据表上为每个用户选择第一行/最后一行的 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 性能的主要内容,如果未能解决你的问题,请参考以下文章

Bootstrap:两行而不是一行的文本

excel表格中如何从第一行选到最后一行

excel如何快速选择第一行到最后一行

Excel 如何快速从第一行选到最后一行

Python - 检查字符串是不是在更大的字符串中

我应该如何使用 strcmp() 检查是不是在更大的字符串中找到了一个字符串?