使用 direct_join 進行慢速查詢以獲得小結果
我的應用程序中的查詢存在性能問題,我不了解 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 的行
Mention
。indexed
如果優化器認為這是一個好主意,則使用範圍掃描。在編寫查詢時,該索引不能用於過濾行。“但是等等,”有人說,“
EXPLAIN
說它正在使用那個索引。” 是的,它使用它來對結果進行排序,但它沒有使用它來消除不匹配的行,因為將公式放在 where 子句的左側幾乎總是消除了在被傳遞的列上使用索引的可能性函式中的參數。當您
Using where
在Extra
列中看到時,優化器會說“使用我選擇的查詢計劃,我將不得不向底層儲存引擎詢問該表中比我們實際想要的更多的行,並在MySQL 層使用WHERE
子句中的某些內容來查找我們實際需要的內容。”
另一個問題與所使用的 ENGINE 有關。MyISAM 和 InnoDB 都收集作為查詢計劃基礎的“統計數據”。然而,他們以完全不同的方式做到這一點。
ANALYZE TABLE 將重新計算統計數據。有時(並非總是)這會改變查詢計劃。