当表很大时找到每个组的最大记录时如何优化sql?
Posted
技术标签:
【中文标题】当表很大时找到每个组的最大记录时如何优化sql?【英文标题】:how to optimize sql when find the max record of each group while table is large? 【发布时间】:2019-10-02 05:15:56 【问题描述】:我有一个包含近 100 万条记录的表。我想找到每个组的最大记录。 这是我的sql:
SELECT *
FROM t
WHERE id IN (SELECT max(id) AS id
FROM t
WHERE a = 'some' AND b = 0
GROUP BY c, d);
表声明如下。
CREATE TABLE `t` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`a` varchar(32) NOT NULL COMMENT 'a',
`b` tinyint(3) unsigned NOT NULL COMMENT 'b',
`c` bigint(20) unsigned NOT NULL COMMENT 'c',
`d` varchar(32) NOT NULL COMMENT 'd',
PRIMARY KEY (`id`),
KEY `idx_c_d` (`c`,`d`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='test table';
我在 c 和 d 上有一个联合索引。所以第二条语句(SELECT max(id) AS id FROM t WHERE a = 'some' AND b = 0 GROUP BY c, d
)在 200 毫秒内执行。但总语句花费近 6 秒(结果包含 5000 行)。
这是explain
的节目(省略了一些列)。
+-------------+-------+-------+---------------+--------+---------+----------+--------------------------+
| select_type | table | type | possible_keys | key | rows | filtered | Extra |
+-------------+-------+-------+---------------+--------+---------+----------+--------------------------+
| PRIMARY | t | ALL | NULL | NULL | 9926024 | 100.00 | Using where |
| SUBQUERY | t | index | idx_1 | idex_1 | 9926024 | 1.00 | Using where; Using index |
+-------------+-------+-------+---------------+--------+---------+----------+--------------------------+
【问题讨论】:
什么是 1000W?.. 瓦特。 1000W - 这是一个巨大的吉他放大器! 虽然结果只包含 5000 行 查询所花费的时间,通常不取决于它获得的结果量,而是取决于您必须得到的数据量看。如果您有 1000000 本书没有任何顺序(索引)并且您想找到一本您没有的书,那么您将不得不查看这 1000000 本书。所以你需要很长时间才能得到 0 个结果 @nacho 是的,我知道。我只想提供有关此问题的更多信息。还是谢谢你。 在检查 id 是否为 5000 条记录之一时,使用 IN 的子查询实际上不使用索引。因此可能是速度缓慢的原因。 【参考方案1】:您可以尝试使用相关子查询并在column c and d
中创建索引
SELECT t1.* FROM table_name t1
WHERE id = (SELECT max(id) AS id FROM table_name t2 where
t1.c=t2.c and t1.d=t2.d
) and t1.a = 'some' AND t1.b = 0
【讨论】:
子查询中缺少 Group by c,d。 @mkRabbani vai 这里不需要分组:) @Strawberry yap,你是对的,谢谢 由于 OP 想要每个组的 MAX 记录,所以我觉得 GROUP BY 是必需的,而且条件应该是 WHERE ID IN (.....)。您的查询将从所有行中返回一行。 @mkRabbani nope vai 它会为每个组返回最大 1,而不是所有单个。你可以尝试使用小提琴 :)【参考方案2】:避免对子查询的需要
SELECT t1.*
FROM t t1
LEFT OUTER JOIN t t2
ON t1.c = t2.c
AND t1.d = t2.d
AND t1.id < t2.id
AND t2.id IS NULL
AND t2.a = 'some'
AND t2.b = 0
【讨论】:
sql有一些错误。而且还是不能满足我的要求。它也很慢。 @weaver ,放上表声明,我可以测试它。但是如果你有有用的索引应该会更快(按顺序排列 a、b、c、d 和 id 的索引) 我刚刚添加了表声明。我想也许in
是解决这个问题的有效方法。
@weaver - 意识到我在 a 和 b 列上犯了一个错误。但是,带有子查询的 IN 可能对此效率不高,因为我将在子查询中的 5000 条记录和表中的 1000 万条记录之间强制进行非键连接。不使用子查询时,添加合适的索引将有很大帮助。【参考方案3】:
我建议使用相关子查询:
SELECT t.*
FROM t
WHERE t.id IN (SELECT MAX(t2.id)
FROM t t2
WHERE t2.c = t.c AND t2.d = t.d AND
t2.a = 'some' AND t2.b = 0
);
这假定id
在表中是唯一的。
为了提高性能,您需要在(c, d, a, b, id)
上建立索引。
【讨论】:
我想找到每个组的最大id,我怕你的sql不能满足需求。 @weaver 。 . .你如何定义“群体”?这遵循您在问题中暗示的定义。【参考方案4】:“skin-a-cat”的所有不同方式,但这里略有不同......由于您正在寻找 IN,我会将那个查询移到前面的位置。此外,它可能有助于使用 mysql 的语言特定关键字“STRAIGHT_JOIN”告诉 MySQL 按照您列出的顺序执行。再次它可能会有所帮助
SELECT
T.*
FROM
(SELECT max(id) AS id
FROM t
WHERE b = 0
AND a = 'some'
GROUP BY c, d) PQ
JOIN T
on PQ.ID = T.ID
我也会有专门按顺序排列的索引
(b, a, c, d, id )
显然保留主ID键,如果使用STRAIGHT_JOIN,会是
SELECT STRAIGHT_JOIN
T.* ( ... rest of query)
【讨论】:
MySQL 很可能先做子查询,不管顺序如何,也不需要说STRAIGHT_JOIN
。而且,是的,这个 5 列索引对派生表是有益的。除非这不能得到“正确”的答案,否则我预测它是“最快的”。以上是关于当表很大时找到每个组的最大记录时如何优化sql?的主要内容,如果未能解决你的问题,请参考以下文章