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;
我认为内部连接是一种矫枉过正,可以用更优化/更高效的查询来代替。我用where
、group by
和having
编写了以下查询,但它不起作用。有人可以帮忙吗?
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)
)
所以,没有主键。但是id
和updated_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)
上建立一个综合索引。 id
和 updated_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 查询?