2016-09-29 25 views
2

EDITED:按請求添加完整查詢。mysql - 在連接表列上優化ORDER BY COALESCE

實質上,我有一個帖子的表格,其中鏈接了一對多的轉貼表,類似於Twitter。我想加載在重新發布時(如果有的話)或原始帖子的時間排序的帖子。但是,使用單個查詢的排序過程非常緩慢(可能是因爲COALESCE(x,y)沒有充分利用MySQL索引)。兩個相關表格的時間列都被編入索引。

我的查詢看起來像這樣。

SELECT * FROM Post p LEFT JOIN p.reposts ON ... WHERE ... 
ORDER BY COALESCE(r.time, p.time) LIMIT 0, 10 

更精確地(僞ISH),因爲我使用DAL:

SELECT * FROM Post p LEFT JOIN p.reposts repost ON (p.id = repost.post_id AND  
repost.time = (
    SELECT MIN(r.time) FROM Repost r WHERE p.id = r.post_id 
    AND r.user_id IN (1, 2, 3...) AND r.user_id NOT IN (4, 5, 6...)) 
)) 
WHERE (repost IS NOT NULL OR p.author_id IN (1, 2, 3...)) 
AND p.author_id NOT IN (4, 5, 6...) 
ORDER BY COALESCE(repost.time, p.time) LIMIT 0, 10 

在上文中,ON子句確保至多一個轉貼(一個我想)接合。 COALESCE是必要的,因爲如果帖子未被轉貼,則r可能爲NULL。該查詢的行爲如預期 - 當ORDER BY子句被省略時,或者僅在像p.time這樣的索引列上使用時,速度很快。這是預料之中的,因爲郵政表是大型的100k +行。

查詢說明

編輯:應該做什麼查詢更好的解釋。值得注意的是這裏的邏輯起作用 - 我得到了我想要的數據。問題是,應用ORDER BY子句會導致查詢運行速度降低大約50倍,因爲MySQL無法在連接的表上使用具有COALESCE的索引。

  • 加載10個帖子的列表,這些帖子是由一組用戶創作的(後面)或由同一集合(後面)轉發的,由最近排序的。
  • 帖子應按帖子發佈時間或第一次轉發時間排序。
  • 忽略一組不同的(阻塞)職位和轉播用戶

  • 獲取帖子:從帖子

    選擇
  • 由跟隨集合中的用戶獲取最早轉貼:LEFT JOIN ON ... r.time =(SELECT MIN(r.time)...)
  • 過濾掉未被用戶創作或轉貼的文章,其中包括:WHERE(轉貼不是NULL ...)
  • 訂購是第一個轉載(如果存在)或發佈時間:ORDER BY COALESCE(repost.time,p.time)
  • 負載最多10個帖子:LIMIT 0,10

UPDATE

我發現:

...ORDER BY repost.time DESC 

主要生產見效慢以及除非我還補充:

...WHERE repost.id IS NOT NULL... 

在這種情況下,查詢速度很快。這使我相信真正的問題是對可空列索引進行排序。我也試過:

... ORDER BY CASE WHEN repost.id IS NULL p.time ELSE repost.time END DESC 

哪沒有幫助。

更新2

原因在於MySQL使用B樹爲它的索引的事實,現在看來,這將是不可能以充分利用我想要的方式索引。因此,我目前最好的想法是將每個原始帖子視爲其作者的「轉貼」,然後在轉貼表上執行我的選擇和訂購,例如,

SELECT * FROM Repost r LEFT JOIN r.post ON ... WHERE ... ORDER BY r.time DESC 
+0

「我不會發布我的整個查詢,因爲它非常複雜。」那麼這個練習是毫無意義的。一個不同的查詢會有不同的性能問題 – e4c5

+0

如果它有幫助,我可以發佈整個事情。但我不認爲所有的WHERE和ON都必須相關。我已經剝離並在我自己的測試中省略了各個部分 - 似乎肯定ORDER BY子句和相關的LEFT JOIN導致了痛點。 – CaptainStiggz

+0

基本問題是,按表達式排序需要它生成一個包含所有結果的中間表,以便它可以計算每行的表達式。它不能使用索引來優化它。 – Barmar

回答

0

這裏的問題與我在我的問題的更新2中描述的一樣。 MySQL使用索引來快速執行ORDER BY操作。更具體地說,MySQL使用B-trees來索引列(如時間戳 - p.time/r.time),這會佔用更多的空間,但允許更快的排序。

我的查詢的問題是,它是由兩個表中的時間列進行排序,使用來自轉發表的時間戳(如果可用),否則使用發佈表。由於MySQL無法合併兩個表中的B樹,因此無法對來自兩個不同表的列進行快速索引排序。

我用兩種方式修改了我的查詢和表結構來解決這個問題。

1)首先基於被阻止的用戶進行過濾,因此只需對當前用戶可訪問的帖子進行排序。這不是問題的根源,而是實際的優化。例如

SELECT * FROM (SELECT * FROM Post p WHERE p.author_id NOT IN (4, 5, 6...))... 

2)對待每一個崗位作爲其作者一個重新發布,使每一個崗位是保證有一個可連接轉貼和repost.time在其上的索引和排序。例如

SELECT * FROM (...) LEFT JOIN p.reposts repost ON (p.id = repost.post_id AND 
repost.time = (
    SELECT MIN(r.time) FROM Repost r WHERE p.id = r.post_id 
    AND r.user_id IN (1, 2, 3...) AND r.user_id NOT IN (4, 5, 6...)) 
)) 
WHERE (repost.id IS NOT NULL) ORDER BY repost.time DESC LIMIT 0, 10 

在一天結束時,問題歸結爲ORDER BY - 此方法將查詢時間從大約8秒縮短到20 ms。