我在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 |
+------+------+
此表中包含3000多万个条目。我正在按查询运行一个组,运行该查询需要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脚本来读取文件中的所有内容并解析它并从中创建一个哈希值。通过查询(< 3min)与Group相比花费的时间非常少。最后,hash有1400万个键(单词)。它占用了大量的内存。那么有没有办法提高Group BY查询的性能,这样我就不需要经历上述所有步骤了?
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/
谢谢,
维诺德
答案 0 :(得分:2)
我认为您想要的索引是:
create index words_word_pid on words(word, pid)
这有两件事。首先,group by
可以通过索引扫描来处理,而不是加载原始表并对结果进行排序。
其次,该索引还消除了加载原始数据的需要。
我的猜测是原始数据不适合内存。因此,处理通过索引(有效地),找到单词,然后需要加载带有单词的页面。好吧,最终内存填满了,带有单词的页面不在内存中。页面从磁盘加载。并且下一页可能不在内存中,并且该页面是从磁盘加载的。等等。
您可以通过增加内存大小来解决此问题。您还可以通过索引覆盖查询中使用的所有列来解决问题。
答案 1 :(得分:1)
问题在于,数据库不是经常使用整个30M行表输出到文件中。使用Perl脚本的方法的优点是您不需要随机磁盘IO。要在MySQL中模拟bahaviour,你需要将everythin加载到索引(p_id,word)(整个单词,而不是前缀)中,这可能会导致数据库过度使用。
您只能将p_id放入索引,这样可以加快分组速度,但需要大量随机磁盘IO才能获取每行的字数。
顺便说一下,覆盖索引需要〜(4 + 4 + 3 * 256)* 30M字节,即超过23Gb的内存。似乎使用Perl脚本的解决方案是您能做的最好的。
您应该注意的另一件事是,您需要通过MySQL连接获得超过20Gb的结果,并将这20 Gb的结果收集到临时表中(如果不这样,则按p_id排序)添加ORDER BY NULL)。如果您打算通过MySQL绑定下载到编程语言,则需要强制绑定使用流式传输(默认情况下绑定通常会获得整个结果集)
答案 2 :(得分:0)
在word
列上为表格编制索引。这将大大加速分组,因为SQL引擎可以通过表格进行最少的搜索来定位记录以进行分组。
CREATE INDEX word_idx ON Words(word);