慢MySQL查询。我应该索引什么?

时间:2009-02-04 19:27:27

标签: sql mysql indexing

每次保存页面编辑时,PHPWiki都会有5秒的慢速查询。经常在“mysql-slow.log”中捕获的查询是:

INSERT INTO wikiscore 
SELECT w1.topage, COUNT(*) 
FROM wikilinks AS w1, wikilinks AS w2 
WHERE w2.topage=w1.frompage 
GROUP BY w1.topage;

目前的指数如下:

table "wikilinks" has a primary index on "frompage" and "topage" 
table "wikiscore" has a primary index on "pagename" and "score"

我如何重新构造SELECT查询以更快地返回相同的结果?我怎么能改变索引,这样这个查询会更快?我的想法是它可能被过度索引了?

我只计算了查询的SELECT部分​​的结果,单独需要1-2秒。 INSERT必须占用剩下的时间。

保存我想要消除的页面时存在延迟。由于已经进行了大量的修改,我没有选择升级到另一个wiki引擎(或PHPwiki的版本)。

有什么想法吗?

编辑---

查询的SELECT部分​​的“EXPLAIN”结果是:

SIMPLE
w2
index
PRIMARY
204
31871   
Using index; Using temporary; Using filesort

SIMPLE
w1
ref
PRIMARY
PRIMARY
102 
phpwiki.w2.topage   
14
Using index

5 个答案:

答案 0 :(得分:3)

  

表“wikilinks”在“frompage”和“topage”上有一个主要索引

WHERE w2.topage=w1.frompage

无法通过上述复合索引搜索此条件。

更改顺序(在topage, frompage上创建索引)或在topage上创建其他索引。

P上。 S.问题的根源在于系统中每个页面的排名都会随着每次编辑而更新。

这个排名系统对我来说似乎有点奇怪:它会链接到链接,而不是链接本身。

如果1000页链接到莫斯科且只有莫斯科链接到 Beket池,那么池塘将获得1000点并且莫斯科根本不会得到任何积分,虽然每个人都知道莫斯科而且没有池塘。

我认为这不是你的意思。最有可能它应该是这样的:

INSERT INTO
       wikiscore 
SELECT
       linked.topage, COUNT(*) AS cnt
FROM   wikilinks current, wikilinks linked
WHERE  current.frompage=@current_page
       AND linked.topage = current.topage
GROUP BY
       linked.topage
ON DUPLICATE KEY UPDATE
       score = cnt;

这将汇总当前页面引用的所有页面的所有链接,这似乎是您想要的。

在这种情况下,你需要在score PRIMARY KEY上摆脱wikiscore,但我认为无论如何都要把它放在那里。

如果您想加快排名查询,可以创建类似的索引:

ALTER TABLE wikilinks ADD CONSTRAINT pk_wikilinkes_fromto PRIMARY KEY (frompage, topage);

CREATE INDEX ix_wikilinks_topage ON wikilinks (topage);

ALTER TABLE wikiscore ADD CONSTRAINT pk_wikiscore_pagename PRIMARY KEY (pagename);

CREATE INDEX ix_wikiscore_score ON wikiscore (score);

答案 1 :(得分:2)

使用EXPLAIN语句来确定查询的哪些部分花费的时间最多,这应该会很有帮助。然后,您可以决定采取哪些措施来优化查询。

答案 2 :(得分:1)

我在理解查询的作用方面遇到了一些麻烦。我认为它找到了从一个页面到另一个页面的链接。所以w1.topage是指向该页面的链接,而w1.frompage是从该页面到其他页面的链接。因此,插入会添加页面和指向该页面的链接数。

我是否正常?

你的主要问题是这一行:

FROM wikilinks AS w1, wikilinks AS w2 

如果您认为该表有1000个条目,则查询引擎必须将1000个条目与其他条目匹配,因此它会抓取1000×1000行(不考虑WHERE或GROUP子句)。随着条目越来越多,查询时间呈指数级增长。 (KABOOM)

此外,您只编辑单个页面,因此您应该能够合理地假设指向此特定页面的链接不会更改,但可能会发生链接。因此,不要在每次更新时编写wikilinks表,而是删除此特定页面中的链接,然后将此页面中的所有链接重新插入其他链接。

答案 3 :(得分:1)

Quassnoi的答案会让你在SELECT上获得一些速度。如果INSERT再花费4秒钟,那么添加索引对任何事情都无济于事。可能通过添加AND COUNT(*)>可以从流程中删除大量数据。如果希望省略输入链接数为零的页面,则为0到SELECT。

通过从wikiscore中删除索引,您可以获得至少一些改进。您在pagename上的主键,得分并没有多大意义(您可以在同一页面上存储多个分数,但如果它们是相同的分数则不能存储?),并且可能只是一个主要分数页面名称上的键。如果有其他索引,您可能可以摆脱它们。

如果在发生这种情况时没有新创建wikiscore,那么抛出一个OPTIMIZE TABLE可能会带来一些好处。

但是,如果您更改了此查询背后的整个理论,以便每次保存页面时不再重建整个wikiscore表,那么您只需更新分数保存的页面和链接到的页面。

答案 4 :(得分:0)

以下是我在PHPWiki的源代码中修改PHP代码的方法

// update pagescore
//old way... 
/*     
mysql_query("DELETE FROM $WikiScoreStore", $dbi["dbc"]);
mysql_query("INSERT INTO $WikiScoreStore"
                 ." SELECT w1.topage, COUNT(*) FROM $WikiLinksStore AS w1, $WikiLinksStore AS w2"
                 ." WHERE w2.topage=w1.frompage GROUP BY w1.topage", $dbi["dbc"]);

*/

//delete this pagescore            
mysql_query("DELETE FROM $WikiScoreStore WHERE pagename='$frompage'", $dbi["dbc"]);
//insert just this pagescore
mysql_query("INSERT INTO $WikiScoreStore" 
                    ." SELECT w1.topage, COUNT(*) FROM $WikiLinksStore AS w1, $WikiLinksStore AS w2"
                ." WHERE w2.topage=w1.frompage AND w1.topage='$frompage' GROUP BY w1.topage", $dbi["dbc"]);

由于此代码更改和索引调整,我没有慢查询。谢谢S.O。!