【问题标题】:Mysql Group by query is taking long timeMysql Group by 查询需要很长时间
【发布时间】:2013-04-23 17:20:44
【问题描述】:

我在 mysql 数据库中有一个表“Words”。此表包含 2 个字段。 word(VARCHAR(256)) 和 p_id(INTEGER)。 为表创建表语句:

CREATE TABLE `Words` (
  `word` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
  `p_id` int(11) NOT NULL DEFAULT '0',
  KEY `word_i` (`word`(255))
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

表中的示例条目是:

+------+------+
| word | p_id |
+------+------+
| a    |    1 |
| a    |    2 |
| b    |    1 |
| a    |    4 |
+------+------+

此表包含 30+ 百万个条目。我正在按查询运行一个组,运行该查询需要 90 多分钟。我正在运行的分组查询是:

SELECT word,group_concat(p_id) FROM Words group by word;

为了优化这个问题,我使用以下查询将表中的所有数据发送到一个文本文件中。

SELECT p_id,word FROM Words INTO OUTFILE "/tmp/word_map.txt";

之后,我编写了一个 Perl 脚本来读取文件中的所有内容并对其进行解析并从中生成哈希值。与 Group by query(

EDT:我在下面添加 my.cnf 文件条目。

[mysqld]
datadir=/media/data/.mysql_data/mysql
tmpdir=/media/data/.mysql_tmp_data
innodb_log_file_size=5M
socket=/var/lib/mysql/mysql.sock
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
group_concat_max_len=4M
max_allowed_packet=20M
[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
tmpdir=/media/data/.mysql_tmp_data/

谢谢,

维诺德

【问题讨论】:

  • 您是否设置了 mysql 以允许如此高的内存使用?
  • (word, p_id) 上的索引可能会有所帮助。
  • 你真的需要这么长 (256) 个 utf8 字符吗?
  • @ypercube,是的,有 500 万多个条目的长度在 230-250 的范围内。这里的“单词”列实际上存储了一个短语。
  • 500 万还是 3000 万?又有多少不同的词?

标签: mysql group-by group-concat


【解决方案1】:

我认为你想要的索引是:

create index words_word_pid on words(word, pid)

这有两件事。首先,group by 可以通过索引扫描来处理,而不是加载原始表并对结果进行排序。

其次,这个索引也消除了加载原始数据的需要。

我的猜测是原始数据不适合内存。因此,处理过程(有效地)通过索引,找到单词,然后需要加载带有单词的页面。好吧,最终内存会填满,并且带有单词的页面不在内存中。该页面是从磁盘加载的。下一页可能不在内存中,并且该页面是从磁盘加载的。以此类推。

您可以通过增加内存大小来解决此问题。您还可以通过使用涵盖查询中使用的所有列的索引来解决此问题。

【讨论】:

    【解决方案2】:

    问题在于,数据库将整个 30M 行的表输出到文件中几乎不是一个常见的用例。使用 Perl 脚本的方法的优点是不需要随机磁盘 IO。要模拟 MySQL 中的行为,您需要将所有内容加载到索引 (p_id, word)(整个单词,而不是前缀)中,这可能会导致数据库过度使用。

    您可以只将 p_id 放入索引中,这将加快分组速度,但需要大量随机磁盘 IO 来获取每行的单词。

    顺便说一句,覆盖索引将占用 ~(4+4+3*256)*30M 字节,即超过 23Gb 的内存。看来使用 Perl 脚本的解决方案是您能做的最好的解决方案。

    您应该注意的另一件事是,您需要通过 MySQL 连接获得超过 20Gb 的结果,并且应该将这 20Gb 的结果收集到一个临时表中(如果不这样做,则按 p_id 排序附加 ORDER BY NULL)。如果要通过 MySQL 绑定到编程语言来下载,则需要强制绑定使用流式传输(默认情况下绑定通常会获取整个结果集)

    【讨论】:

    • +1 用于流式传输。我没有关于那个功能。这有助于我完成我目前正在做的项目。在我添加索引和 ORDER BY NULL 之后,按查询分组花费的时间非常少。谢谢。
    【解决方案3】:

    索引word 列上的表。这将大大加快分组速度,因为 SQL 引擎可以通过对表的最少搜索来定位要分组的记录。

    CREATE INDEX word_idx ON Words(word);
    

    【讨论】:

      猜你喜欢
      • 2015-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-18
      • 1970-01-01
      • 1970-01-01
      • 2020-05-05
      相关资源
      最近更新 更多