【问题标题】:`MySQL GROUP BY is slower when using index`MySQL GROUP BY 使用索引时比较慢
【发布时间】:2016-04-29 01:03:02
【问题描述】:

我在 AWS m4.large(2 个 vCPU,8 GB 内存)上运行,我看到关于 MySQL 和 GROUPBY 的行为有点令人惊讶。我有这个测试数据库:

CREATE TABLE demo (
  time INT,
  word VARCHAR(30),
  count INT
);
CREATE INDEX timeword_idx ON demo(time, word);

我插入了 4,000,000 条记录,其中包含(统一)随机词 "t%s" % random.randint(0, 30000) 和次数 random.randint(0, 86400)

SELECT word, time, sum(count) FROM demo GROUP BY time, word;
3996922 rows in set (1 min 28.29 sec)

EXPLAIN SELECT word, time, sum(count) FROM demo GROUP BY time, word;
+----+-------------+-------+-------+---------------+--------------+---------+------+---------+-------+
| id | select_type | table | type  | possible_keys | key          | key_len | ref  | rows    | Extra |
+----+-------------+-------+-------+---------------+--------------+---------+------+---------+-------+
|  1 | SIMPLE      | demo  | index | NULL          | timeword_idx | 38      | NULL | 4002267 |       |
+----+-------------+-------+-------+---------------+--------------+---------+------+---------+-------+

然后我不使用索引:

SELECT word, time, sum(count) FROM demo IGNORE INDEX (timeword_idx) GROUP BY time, word;
3996922 rows in set (34.75 sec)

EXPLAIN SELECT word, time, sum(count) FROM demo IGNORE INDEX (timeword_idx) GROUP BY time, word;
+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra                           |
+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+
|  1 | SIMPLE      | demo  | ALL  | NULL          | NULL | NULL    | NULL | 4002267 | Using temporary; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+

如您所见,使用索引查询需要多 3 倍的时间。我并不感到惊讶,因为通过使用索引,查询可能不得不避免读取timeword 列,但不幸的是,由于索引如此稀疏,它不会获得太多收益。相反,在检索count 时,它会将直接扫描转换为随机访问模式。

我只是想确认这就是原因,并想知道是否有一个“紧凑规则”关于何时和索引在用于 GROUP BY 时最终会带来更差的性能。

编辑:

我遵循 Gordon Linoff 的回答并使用:

CREATE INDEX timeword_idx ON demo(time, word, count);

“覆盖索引”计算结果的速度比全扫描快 10 倍:

SELECT word, time, sum(count) FROM demo GROUP BY time, word;
3996922 rows in set (3.36 sec)

EXPLAIN SELECT word, time, sum(count) FROM demo GROUP BY time, word;
+----+-------------+-------+-------+---------------+--------------+---------+------+---------+-------------+
| id | select_type | table | type  | possible_keys | key          | key_len | ref  | rows    | Extra       |
+----+-------------+-------+-------+---------------+--------------+---------+------+---------+-------------+
|  1 | SIMPLE      | demo  | index | NULL          | timeword_idx | 43      | NULL | 4002267 | Using index |
+----+-------------+-------+-------+---------------+--------------+---------+------+---------+-------------+

非常令人印象深刻!

【问题讨论】:

    标签: mysql database group-by database-indexes


    【解决方案1】:

    您有一个大小合理的表,因此问题可能是数据的顺序访问或抖动。使用索引需要遍历索引,然后在数据页中查找数据得到count

    这实际上可能比仅仅读取页面并进行排序更糟糕,因为页面不是按顺序读取的。顺序读取比随机读取更优化。在最坏的情况下,页面缓存已满,随机读取需要刷新页面。如果发生这种情况,可能需要多次读取单个页面。只有 400 万行相对较小,除非内存严重受限,否则不太可能发生抖动。

    如果这种解释是正确的,那么在索引中包含count 应该会加快查询速度:

    CREATE INDEX timeword_idx ON demo(time, word, count);
    

    【讨论】:

    • 关于使用索引的“紧凑规则”的另一部分是关于限制需要访问的行数的谓词(条件),以及MySQL是否可以有效地使用索引范围扫描操作。如果必须访问表中的每一行,并且查询不使用“覆盖”索引,则需要查找基础表中的页面。这就像访问索引中的 every 块一次,并多次访问表中的 every 块。如果这是 InnoDB 表,没有主键或唯一索引,则集群键是内部 rowID。 +10
    • “覆盖索引”给出了惊人的结果。更新了问题以显示它们。
    【解决方案2】:

    来自手册页How MySQL Uses Indexes

    索引对于小表或大表的查询不太重要 报表查询处理大部分或所有行的位置。当一个查询 需要访问大部分行,顺序读取比 通过索引工作。顺序读取最小化磁盘寻道,甚至 如果查询不需要所有行。

    至于添加更多列以创建覆盖索引(其中数据页未被访问但索引中的所有数据都可用的索引),请小心。他们是有代价的。就您而言,无论如何,您的索引都在变宽。但始终需要谨慎的平衡。

    正如斯宾塞所暗示的,基数总是与范围有关。有关基数信息,请使用show index from tblName 命令。这不是您查询的驱动问题,但在其他设置中很有用。我应该改写一下:您的桌子的基数非常高。因此,您的索引在该查询中被视为它的障碍。

    【讨论】:

    • 给定 30,000 个单词值和 86,400 个时间值……这就是 2,592,000,000 个可能的元组。使用均匀分布,只有 4,000,000 行,任何重复的可能性很小。我们希望对数据页进行全面扫描,并且排序操作会快得多。使用查询的覆盖索引(以单词和时间作为前导列),将通过使用索引来优化 GROUP BY 操作以避免排序。将 INT 列添加到索引只会为每个索引条目添加 4 个字节。 +10
    • 我对此没有异议。已经很宽了。应注意不要将每个索引都变成初级开发人员的覆盖索引。
    • “覆盖索引”给出了惊人的结果。更新了问题以显示它们。
    • 他们总是这样。他们避开数据页!
    猜你喜欢
    • 2013-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-21
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多