MySQL:按查询优化分组

Posted

技术标签:

【中文标题】MySQL:按查询优化分组【英文标题】:MySQL: Group by query optimization 【发布时间】:2018-06-18 12:19:30 【问题描述】:

我有一个以下模式的表:

+----+--------+----------------------------+----------------------------+
| id | amount | created_timestamp          | updated_timestamp          |
+----+--------+----------------------------+----------------------------+
| 1  |   1.00 | 2018-01-09 12:42:38.973222 | 2018-01-09 12:42:38.973222 |
+----+--------+----------------------------+----------------------------+

这里,对于id = 1,可能有多个金额条目。我想提取最后添加的条目及其对应的数量,按 id 分组。

我在 self 表上编写了一个带有内部联接的工作查询,如下所示:

SELECT t1.id, 
       t1.amount, 
       t1.created_timestamp, 
       t1.updated_timestamp 
FROM   transactions AS t1 
       INNER JOIN (SELECT id, 
                          Max(updated_timestamp) AS last_transaction_time 
                   FROM   transactions 
                   GROUP  BY id) AS latest_transactions 
               ON latest_transactions.id = t1.id 
                  AND latest_transactions.last_transaction_time = 
                      t1.updated_timestamp; 

我认为内部连接是一种矫枉过正,可以用更优化/更高效的查询来代替。我用wheregroup byhaving 编写了以下查询,但它不起作用。有人可以帮忙吗?

select id, any_value(`updated_timestamp`), any_value(amount) from transactions group by `id` having max(`updated_timestamp`);

【问题讨论】:

这就是你在 mysql 中的做法: 您的意思是使用内部连接,这是最有效的方法? 是的,在 MySQL 中我认为没有更好的方法。 你有(id,updated_timestamp) 的索引吗?你的主键是什么(假设不是(id,updated_timestamp) 所以,没有主键。但是idupdated_timestamp 上有单独的索引 【参考方案1】:

在 MySQL 中执行这样的查询时有两个(好的)选项。您已经尝试过一种选择。这是另一个:

SELECT t1.id, 
       t1.amount, 
       t1.created_timestamp, 
       t1.updated_timestamp 
FROM   transactions AS t1 
LEFT OUTER JOIN transactions later_transactions
       ON later_transactions.id = t1.id 
       AND later_transactions.last_transaction_time > t1.updated_timestamp
WHERE  later_transactions.id IS NULL

这些方法是documentation中的方法,也是我工作中基本每天都会用到的方法。哪一种效率最高取决于多种因素,但通常情况下,如果一种速度较慢,另一种会很快。

此外,正如 Strawberry 在 cmets 中指出的那样,您需要在 (id,updated_timestamp) 上建立一个综合索引。 idupdated_timestamp 有单独的索引是不等价的。

为什么是复合索引?

请注意,索引只是表中数据的副本。在许多方面,它的工作原理与表格相同。因此,创建索引就是创建表数据的副本,RDBMS 可以使用该副本以更有效的方式查询表的信息。

updated_timestamp 上的索引将创建包含updated_timestamp 作为第一列的数据的副本,并且该数据将被排序。它还将在每个索引行中包含一个隐藏的行 ID 值(将用作主键),以便它可以使用它来查找实际表中的完整行。

这对这个查询有什么帮助(任一版本)?如果我们只想要最新的(或最早的)updated_timestamp,这会有所帮助,因为它可以检查索引中的第一条或最后一条记录。但是因为我们想要每个id 的最新信息,所以这个索引是没用的。

如果只是id 上的索引呢?这里我们有一个id 列的副本,按id 列排序,行ID 附加到索引中的每一行。

这对查询有何帮助?它没有,因为它甚至没有将 updated_timestamp 列作为索引的一部分,因此甚至不会考虑使用此索引。

现在,考虑一个复合索引:(id,updated_timestamp)

这会创建一个数据的副本,其中首先包含 id 列,已排序,然后还包括第二列 updated_timestamp,并且它也在每个 id 中排序。

这与电话簿(如果人们仍将这些东西用作镇纸以外的东西)的排序方式相同。按姓氏然后名字排序。

因为行以这种方式排序,对于每个id,MySQL 可以只查找给定id 的最后一条记录。由于索引的定义方式,它知道该记录包含最高的updated_timestamp 值。

因此,它只需要为每个存在的id 查找一行。那很快。进一步解释为什么会占用更多空间,但如果您愿意,您可以自己研究它,只需查看 B-Trees。可以说,找到第一条(或最后一条)记录很容易。

尝试以下方法:

ALTER TABLE transactions
ADD INDEX `LatestTransaction` (`id`,`updated_timestamp`)

然后看看您的原始查询或我的备用查询是否更快。可能两者都比没有索引要快。随着表的增长或选择语句的更改,它可能会影响这些查询中的哪一个更快,但无论您使用哪个版本的查询,索引都会提供最大的性能提升。

【讨论】:

我不明白为什么在(id,updated_timestamp) 上有一个复合索引比单个索引更好。请解释一下。 @PankajSinghal 我已经添加了关于为什么需要复合索引的解释。 这看起来像一个 Order(N*N) 算法。有 Order(N) 方法。更快:mysql.rjweb.org/doc.php/groupwise_max @RickJames 如果您有其他解决方案,请自己留下答案。这个问题的核心问题似乎是缺乏适当的索引。添加之后,他的原始查询可能会执行得非常快。

以上是关于MySQL:按查询优化分组的主要内容,如果未能解决你的问题,请参考以下文章

需要帮助优化一个有趣的 MySQL 查询

具有多个分组或排序的mysql查询优化

优化按联接表中的字段对结果进行分组的查询

您如何优化连接自身并执行“自定义”分组的 MySQL 查询?

用于计数和显示(列中的不同值)的 Sql 查询优化,按其他两列分组

MySQL 查询优化与 group by 和 order by rand