【问题标题】:MySQL query takes too long -- what should be the index?MySQL 查询耗时太长——索引应该是什么?
【发布时间】:2013-04-08 17:53:47
【问题描述】:

这是我的查询:

CREATE TEMPORARY TABLE temptbl (
  pibn INT UNSIGNED NOT NULL, page SMALLINT UNSIGNED NOT NULL)
  ENGINE=MEMORY;
INSERT INTO temptbl (
  SELECT pibn,page FROM mytable
  WHERE word1=429907 AND word2=0);
ALTER TABLE temptbl ADD INDEX (pibn,page);
SELECT word1,COUNT(*) AS aaa
  FROM mytable a
  INNER JOIN temptbl b
  ON a.pibn=b.pibn AND a.page=b.page
  WHERE word2=0
  GROUP BY word1 ORDER BY aaa DESC LIMIT 10;
DROP TABLE temptbl;

问题在于SELECT word1,COUNT(*) AS aaa,特别是计数。该 select 语句需要 16 秒。

解释说:

+----+-------------+-------+------+---------------------------------+-------------+---------+-------------------------------------------------------------+-------+---------------------------------+
| id | select_type | table | type | possible_keys                   | key         | key_len | ref                                                         | rows  | Extra                           |
+----+-------------+-------+------+---------------------------------+-------------+---------+-------------------------------------------------------------+-------+---------------------------------+
|  1 | SIMPLE      | b     | ALL  | pibn                            | NULL        | NULL    | NULL                                                        | 26778 | Using temporary; Using filesort |
|  1 | SIMPLE      | a     | ref  | w2pibnpage1,word21pibn,pibnpage | w2pibnpage1 | 9       | const,db.b.pibn,db.b.page                                   |     4 | Using index                     |
+----+-------------+-------+------+---------------------------------+-------------+---------+-------------------------------------------------------------+-------+---------------------------------+

使用的索引 (w2pibnpage1) 开启:

word2,pibn,page,word1,id

我已经为此苦苦挣扎了好几天,为索引尝试了不同的列组合(这很烦人,因为重建需要一个小时 - 数百万行)。

我的索引应该是什么,或者我应该怎么做才能让这个查询在几分之一秒内运行(应该如此)?

【问题讨论】:

  • 表格包含多少行(大约)?
  • 目前为 1.5 亿。但这很快就会达到数十亿。
  • 对于您的测试,我建议您创建 2 个表的空副本。更改这些空表上的索引。使用复制的表的名称重写您的查询。您可以使用此修改后的查询运行EXPLAIN。由于表是空的,因此更改索引或运行EXPLAIN 会非常快。

标签: mysql sql indexing


【解决方案1】:

这是一个建议。

大概临时表很小。您可以删除该表上的索引,因为那里可以进行全表扫描。事实上,这就是你想要的。

然后您希望在大表上使用索引。首先索引需要匹配连接条件,然后匹配where 条件,最后是group by 条件。所以,建议是:

mytable(pibn, page, word2, word1, aaa)

我在order by 列中添加,因此它不必从原始数据中获取值。

【讨论】:

  • 好的。但实际上我更希望我的陈述首先根据 where 条件排除。这不是我的查询所做的吗?
  • 另外临时表可能包含10000个左右。
  • 另外 aaa 不是一个真正的列,它只是 COUNT 的标签,这就是导致问题的原因。
  • 解释计划显示它正在做全表扫描?
  • 我尝试了您的索引建议,但没有任何速度差异。
【解决方案2】:

查询需要很长时间,但代价高昂的部分似乎是访问“mytable”(您尚未提供此结构的结构),但优化器似乎认为它只需要使用索引 - 这应该非常快。即数据似乎非常倾斜 - 最后一个查询检查了多少行(计数)?

如果不了解数据的确切分布,就很难确定 - 当然,您可能需要提示查询以使其高效工作。设计索引的问题在于它们应该使所有查询更快——或者至少给出一个合理的折衷。

查看您提供的查询中的谓词...

WHERE word1=429907 AND word2=0

最好由 word1,word2,.... 或 word2,word1,..... 上的索引提供服务

ON a.pibn=b.pibn AND a.page=b.page
WHERE a.word2=0

最好由 mytable 上的索引提供服务,其中 word2+pibn+page 在前导列中。

mytable.word1 和 mytable.word2 有多少不同的值?如果 word2 的不同值数量较少(少于 20 个左右),那么它不会为索引增加太多选择性,可以省略。

word2,pibn,page,word1 上的索引为您提供第二个查询的覆盖索引。

【讨论】:

  • word1 和 word2 有 600,000 个不同的值。有数亿行。 pibn 有数十万个不同的值。填充临时表的第一个选择平均需要 10,000 行,可能多达 100,000 行。我已经跟踪到 COUNT(*) 的减速。
  • 您的意思是 word1+word2 有 600,000 个值,还是 word1 有 600,000 个值,word2 有 600,000 个值?如果是后者,那么您的索引需要重建。
【解决方案3】:

如果您的 temptbl 很小,您希望首先限制较大的表 (mytable),然后将其(最终通过索引)加入您的 temptbl。

目前,MySQL 认为最好使用更大表的索引来连接。

你可以通过直接加入来解决这个问题:

  SELECT word1,COUNT(*) AS aaa
    FROM mytable a
    STRAIGHT_JOIN temptbl b
      ON a.pibn=b.pibn AND a.page=b.page
  WHERE word2=0
  GROUP BY word1 
  ORDER BY aaa DESC LIMIT 10;

这应该使用 mytable 中的索引作为 where 子句,并通过 temptbl 中的索引将 mytable 连接到 temptbl。

如果 MySQL 还想不一样,可以使用 FORCE INDEX 让它使用索引。

【讨论】:

  • 我试过你的查询。同样的结果需要 1 分半钟,所以效率较低。
  • 您能解释一下我的查询吗?这可能表明问题。
【解决方案4】:

无论您做什么,如果不更改架构,您的数据量都不会快速运行。

如果我的理解正确,您正在寻找与429907 一起出现在同一页面上的热门词。

您现在的模型需要在每次运行查询时重新计算所有这些单词。

为了加快速度,您需要创建一个额外的统计表:

CREATE TABLE word_pairs
        (
        word1_1 INT NOT NULL,
        word1_2 INT NOT NULL,
        cnt BIGINT NOT NULL,
        PRIMARY KEY (word1_1, word1_2),
        INDEX (word1_1, cnt),
        INDEX (word1_2, cnt)
        )

并在每次将记录插入大表时更新它(为新插入的单词及其在同一页面上的所有单词增加 cnt)。

这对于单个服务器来说可能太慢了,因为这样的更新需要一些时间,所以您还需要在多个服务器上分片该表。

如果你有这样的表,你可以运行:

SELECT  *
FROM    word_pairs
WHERE   word1_1 = 429907
ORDER BY
        cnt DESC
LIMIT   10

这将是即时的。

【讨论】:

  • 是的,我明白了。实际上,这个查询的结果将被放入另一个 word_pairs(通常与这个词相关联的词)表中。我确实认为可以将查询优化得很快,但我已经为这个巨型表上的其他查询完成了它,并设法让它们超快。只有这一个我仍在努力,但它必须是可能的!
  • @Alasdair:它涉及在 MySQL 中不可索引的计数。祝你好运,如果你成功请告诉我! :)
  • @Alasdair:顺便说一句,当查询完成时,它返回的最高计数是多少?
  • 429907 26778, 657171 15886, 657271 14193, 657272 11459 等。似乎有些重复,这一定意味着该组无法正常工作。
  • 我的错误......没有重复,数字只是巧合地非常接近。可能是同一个词或拼写的变体。
【解决方案5】:

我想出了这个:

CREATE TEMPORARY TABLE temp1 (
  pibn INT UNSIGNED NOT NULL, page SMALLINT UNSIGNED NOT NULL)
  ENGINE=MEMORY;
INSERT INTO temp1 (
  SELECT pibn,page FROM mytable
  WHERE word1=429907 AND word2=0);
CREATE TEMPORARY TABLE temp2 (
  word1 MEDIUMINT UNSIGNED NOT NULL)
  ENGINE=MEMORY;
INSERT INTO temp2 (
SELECT a.word1
  FROM mytable a, temp1 b
  WHERE a.word2=0 AND a.pibn=b.pibn AND a.page=b.page);
DROP TABLE temp1;
CREATE INDEX index1 ON temp2 (word1) USING BTREE;
CREATE TEMPORARY TABLE temp3 (
  word1 MEDIUMINT UNSIGNED NOT NULL, num INT UNSIGNED NOT NULL)
  ENGINE=MEMORY;
INSERT INTO temp3 (SELECT word1,COUNT(*) AS aaa FROM temp2 USE INDEX (index1) GROUP BY word1);
DROP TABLE temp2;
CREATE INDEX index1 ON temp3 (num) USING BTREE;
SELECT word1,num FROM temp3 USE INDEX (index1) ORDER BY num DESC LIMIT 10;
DROP TABLE temp3;

需要 5 秒。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-07
    • 2013-04-08
    • 2015-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多