SQL 在更大数据上为每个用户选择 first/last 行时的性能 table

SQL Performance on selecting first/last row for each user on bigger data table

我已经阅读了很多关于 greatest-n-per-group 的帖子,但似乎仍然没有在性能方面找到好的解决方案。我是 运行 10.1.43-MariaDB。

我正在尝试获取给定时间范围内数据值的变化,因此我需要从该时间段获取最早和最新的行。现在需要计算的时间范围内的最大行数约为 700k,并且只会增加。现在我刚刚做了两个查询,一个是最近的,一个是最早的,但即使这样,目前的性能也很慢。 table 看起来像这样:

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

这给了我正确的日期和 user_id 在大约 ~0.05 秒内非常快。但是就像每组最大 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 分组的数据进行 (MAX)-(MIN),但它的性能也很慢。

要获得与日期相同的数据值,甚至每个用户的最新和最早数据的差异,什么是更有效的方法?

假设您使用的是 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 子查询的执行。