当表很大时找到每个组的最大记录时如何优化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?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 hive sql 中获取每个组的最大 row_number()

SQL:查找每组的最大记录[重复]

SQL:查找每组的最大记录[重复]

如何优化 SQL 查询(Oracle 数据库)

如何在sql中找到具有多个最大值的组的最大值?

如何从SQL表中删除不是组的最大值的记录[重复]