Mysql

使用 direct_join 進行慢速查詢以獲得小結果

  • December 9, 2012

我的應用程序中的查詢存在性能問題,我不了解 mysql 的行為。

查詢由不同的連接組成,尤其是與提及記憶體表的連接。如果與mentioncache-table 的連接是簡單連接,則查詢大約需要三秒鐘(對於在mentioncache-table 中有大約80.000 條記錄的配置文件)。如果這個連接是一個直接連接,查詢只需要大約 0.001 秒。

查詢是:

SELECT SQL_NO_CACHE `Mention`.`id`, `Mention`.`title`, `Mention`.`title_text`, `Mention`.`content_text`, `Mention`.`url`, `Mention`.`root_url`, `Mention`.`sub_type`, `Mention`.`indexed`, `Mention`.`plain_host_url`, `Favoureditem`.`foreign_id`, `Visiteditem`.`foreign_id`, `Visiteditem`.`created`, `Mentionfeedscore`.`score`, Image.id, Image.model, Image.foreign_key, Image.dirname, Image.basename 
FROM `mentions` AS `Mention` 

LEFT JOIN attachments AS `Image` ON (`Image`.`foreign_key` = `Mention`.`id` AND `Image`.`model` = 'Mention') 

LEFT JOIN favoureditems AS `Favoureditem` ON (`Favoureditem`.`model` = "Mention" AND `Favoureditem`.`foreign_id` = `Mention`.`id` AND `Favoureditem`.`owner_id` = 803) 

LEFT JOIN visiteditems AS `Visiteditem` ON (`Visiteditem`.`model` = "Mention" AND `Visiteditem`.`foreign_id` = `Mention`.`id` AND `Visiteditem`.`owner_id` = 803) 

LEFT JOIN mentionfeedscores AS `Mentionfeedscore` ON (`Mentionfeedscore`.`mention_id` = `Mention`.`id` AND `Mentionfeedscore`.`feed_id` = 'iparkmedia') 

STRAIGHT_JOIN mentioncache AS `Mentioncache` ON (`Mentioncache`.`mention_id` = `Mention`.`id` AND `Mentioncache`.`profile_id` = 803) 

WHERE `Mention`.`language` = ('de') AND 

DATE(`Mention`.`indexed`) BETWEEN "2012-11-04" AND "2012-12-04" AND 

`Mention`.`sub_type` IN ('NEWSSITE_TVRADIO', 'NEWSSITE_AGENCY') 

ORDER BY `Mention`.`indexed` DESC 
LIMIT 0, 10

這個查詢的解釋是:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  Mention index   PRIMARY,mention_id,id_indexed,id_sub_type,id_sub_type_indexed,id_sentimentlevel,id_sentimentlevel_indexed   indexed 8   NULL    10  Using where
1   SIMPLE  Image   ref foreign_key,model_foreign_key   foreign_key 66  clippingcroc.Mention.id,const   1    
1   SIMPLE  Favoureditem    ref model_foreign_id_owner_id   model_foreign_id_owner_id   163 const,clippingcroc.Mention.id,const 1   Using index
1   SIMPLE  Visiteditem ref model_foreign_id_owner_id   model_foreign_id_owner_id   163 const,clippingcroc.Mention.id,const 1    
1   SIMPLE  Mentionfeedscore    ref mention_id,feed_id  mention_id  4   clippingcroc.Mention.id 1    
1   SIMPLE  Mentioncache    eq_ref  mention_id_profile_id,mention_id,profile_id mention_id_profile_id   8   clippingcroc.Mention.id,const   1   Using index

使用普通連接的查詢的解釋是:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  Mentioncache    ref mention_id_profile_id,mention_id,profile_id profile_id  4   const   133001  Using temporary; Using filesort
1   SIMPLE  Mention eq_ref  PRIMARY,mention_id,id_indexed,id_sub_type,id_sub_type_indexed,id_sentimentlevel,id_sentimentlevel_indexed   PRIMARY 4   clippingcroc.Mentioncache.mention_id    1   Using where
1   SIMPLE  Image   ref foreign_key,model_foreign_key   foreign_key 66  clippingcroc.Mentioncache.mention_id,const  1    
1   SIMPLE  Favoureditem    ref model_foreign_id_owner_id   model_foreign_id_owner_id   163 const,clippingcroc.Mentioncache.mention_id,const    1   Using index
1   SIMPLE  Visiteditem ref model_foreign_id_owner_id   model_foreign_id_owner_id   163 const,clippingcroc.Mentioncache.mention_id,const    1    
1   SIMPLE  Mentionfeedscore    ref mention_id,feed_id  mention_id  4   clippingcroc.Mentioncache.mention_id    1    

奇怪的是:對於在提及記憶體表中只有大約 400 條記錄的配置文件,性能正好相反。在這種情況下,使用普通連接的查詢大約需要 0.015 秒,而對於 straight_join 大約需要 1.5 秒。對於在提及記憶體中只有 5 條記錄(並且沒有查詢結果)的配置文件,straight_join 甚至需要大約 15 秒(正常連接需要 0.01 秒)。

那麼為什麼在直接連接情況下的結果較少時會有這麼糟糕的性能呢?我不明白這一點。

獲得此查詢的良好性能的最佳方法是什麼?

感謝您的幫助!

最好的祝福,

蒂莫

查詢優化器可以根據其對查詢成本的估計,自由地將查詢中表的連接順序重新排列為任何邏輯一致的序列……除非您使用STRAIGHT_JOIN,這會強制優化器在該特定連接中的右表。(在 MySQL 中,您還SELECT STRAIGHT_JOIN ...可以強制按子句中指定的順序處理所有表)。FROM

這樣做的原因是為了強制優化器選擇一個你知道比它自己選擇的更好的計劃。在您的情況下,有時這是一個更好的計劃,有時則不是。

您只發布了一個EXPLAIN,但我強烈懷疑您會發現EXPLAIN沒有 的查詢會有所不同STRAIGHT_JOIN,這可能會使性能差異更加明顯。幾乎無法想像計劃是相同的,因為性能是如此不同。

您的查詢設計還有另一個問題,當查詢計劃更改時,這可能會導致性能下降:

WHERE ...
DATE(`Mention`.`indexed`) BETWEEN "2012-11-04" AND "2012-12-04"

這在語法上是有效的,但是不好的做法,因為你告訴伺服器“對於我們沒有在 WHERE 子句或連接中使用其他屬性消除的每一行,評估Mention.indexed使用DATE()函式並消除結果答案不是的行在“2012-11-04”和“2012-12-04”之間。

改成這樣:

WHERE ...
`Mention`.`indexed` BETWEEN '2012-11-04' 
                       AND DATE_SUB(DATE_ADD('2012-12-04',INTERVAL 1 DAY),INTERVAL 1 SECOND)

優化器將只對這兩個表達式求值一次,第二個表達式求值為 ‘2012-12-04 23:59:59’。所以現在你有兩個常量,可用於匹配索引為 on 的行Mentionindexed如果優化器認為這是一個好主意,則使用範圍掃描。在編寫查詢時,該索引不能用於過濾行。

“但是等等,”有人說,“EXPLAIN說它正在使用那個索引。” 是的,它使用它來對結果進行排序,但它沒有使用它來消除不匹配的行,因為將公式放在 where 子句的左側幾乎總是消除了在被傳遞的列上使用索引的可能性函式中的參數。

當您Using whereExtra列中看到時,優化器會說“使用我選擇的查詢計劃,我將不得不向底層儲存引擎詢問該表中比我們實際想要的更多的行,並在MySQL 層使用WHERE子句中的某些內容來查找我們實際需要的內容。”

另一個問題與所使用的 ENGINE 有關。MyISAM 和 InnoDB 都收集作為查詢計劃基礎的“統計數據”。然而,他們以完全不同的方式做到這一點。

ANALYZE TABLE 將重新計算統計數據。有時(並非總是)這會改變查詢計劃。

引用自:https://dba.stackexchange.com/questions/29811