2016-09-28 57 views
2

我無法找到爲什麼我的查詢速度非常慢;在雙至強L5630與48GB DDR3運行Ubuntu 16.04與PHP7.0-FPM和MariaDB的60秒10.0.27極慢的特定表的左加入

SELECT v.video_id, v.user_id, v.title, v.slug, v.rating, v.rated_by, 
          v.duration, v.thumb, v.total_views, v.total_comments, v.add_time, 
          v.view_time, v.status, v.source_id, v.orientation, v.thumbs, 
          v.featured, v.flagged, 
          u.username, 
          s.name, 
          f.reason, 
          GROUP_CONCAT(c.name) AS categories 
        FROM video AS v 
        LEFT JOIN video_flags AS f ON (f.video_id = v.video_id) 
        LEFT JOIN video_sources AS s ON (s.source_id = v.source_id) 
        LEFT JOIN user AS u ON (u.user_id = v.user_id) 
        LEFT JOIN video_category AS vc ON (vc.video_id = v.video_id) 
        LEFT JOIN video_categories AS c ON (c.category_id = vc.category_id) GROUP BY v.video_id ORDER BY v.video_id DESC LIMIT 10 

我已經精確定位問題是在video_flags表,因爲當我的評論f.reason字段和video_flags上的左連接,查詢只需要152ms。該video_flags表對VIDEO_ID索引和字段類型是在兩個表INT相同(11)

當我運行解釋選擇,我得到以下回:

+------+-------------+-------+--------+---------------+----------+---------+----------------------------+---------+-------------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref      | rows | Extra           | 
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+---------+-------------------------------------------------+ 
| 1 | SIMPLE  | v  | ALL | NULL   | NULL  | NULL | NULL      | 1219933 | Using temporary; Using filesort     | 
| 1 | SIMPLE  | f  | ALL | video_id  | NULL  | NULL | NULL      |  1 | Using where; Using join buffer (flat, BNL join) | 
| 1 | SIMPLE  | s  | eq_ref | PRIMARY  | PRIMARY | 4  | adb_network.v.source_id |  1 |             | 
| 1 | SIMPLE  | u  | eq_ref | PRIMARY  | PRIMARY | 4  | adb_network.v.user_id  |  1 |             | 
| 1 | SIMPLE  | vc | ref | video_id  | video_id | 4  | adb_network.v.video_id  |  2 | Using index          | 
| 1 | SIMPLE  | c  | eq_ref | PRIMARY  | PRIMARY | 4  | adb_network.vc.category_id |  1 | Using where          | 
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+---------+-------------------------------------------------+ 

我不知道是什麼我在這裏失蹤,首先我認爲它必須有一些video_flags表爲空,然後我添加了一條記錄,並且查詢很快(200毫秒),但現在問題又回來了,並且查詢正在永久完成。

任何幫助,非常感謝。


更新:加入而不f.reason列@somnium的解釋中進行選擇:

+------+-------------+-------+--------+---------------+----------+---------+----------------------------+------+-------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref      | rows | Extra  | 
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+------+-------------+ 
| 1 | SIMPLE  | v  | index | NULL   | PRIMARY | 4  | NULL      | 5 |    | 
| 1 | SIMPLE  | f  | ref | video_id  | video_id | 4  | adb_network.v.video_id  | 1 | Using index | 
| 1 | SIMPLE  | s  | eq_ref | PRIMARY  | PRIMARY | 4  | adb_network.v.source_id | 1 |    | 
| 1 | SIMPLE  | u  | eq_ref | PRIMARY  | PRIMARY | 4  | adb_network.v.user_id  | 1 |    | 
| 1 | SIMPLE  | vc | ref | video_id  | video_id | 4  | adb_network.v.video_id  | 2 | Using index | 
| 1 | SIMPLE  | c  | eq_ref | PRIMARY  | PRIMARY | 4  | adb_network.vc.category_id | 1 | Using where | 
+------+-------------+-------+--------+---------------+----------+---------+----------------------------+------+-------------+ 

:由於@somnium建議我嘗試添加上的FORCE INDEXvideo_id列,並且將查詢時間從60秒降至272ms - 仍然不確定爲什麼它會在連接期間丟失索引,但問題得到解決。謝謝

SELECT v.video_id, v.user_id, v.title, v.slug, v.rating, v.rated_by, 
           v.duration, v.thumb, v.total_views, v.total_comments, v.add_time, 
           v.view_time, v.status, v.source_id, v.orientation, v.thumbs, 
           v.featured, v.flagged, 
           u.username, 
           s.name, 
           f.reason, 
           GROUP_CONCAT(c.name) AS categories 
         FROM video v 
         LEFT JOIN video_flags f FORCE INDEX FOR JOIN (video_id) ON (f.video_id = v.video_id) 
         LEFT JOIN video_sources s ON (s.source_id = v.source_id) 
         LEFT JOIN user u ON (u.user_id = v.user_id) 
         LEFT JOIN video_category vc ON (vc.video_id = v.video_id) 
         LEFT JOIN video_categories c ON (c.category_id = vc.category_id) GROUP BY v.video_id ORDER BY v.video_id DESC LIMIT 10 
+0

您的視頻表中有1219933,並且您將其加入到其他幾個表中。您在此表上沒有任何過濾器,因此所有這些行都用於這些多個聯接中。 60秒聽起來非常好。但是,如果您還沒有 – e4c5

+0

感謝您的評論,您可以嘗試在video_id上添加索引。奇怪的是我的查詢執行多個連接,問題只發生在包含f.reason列的時候,如果我發表評論,查詢只需120ms,所有其他連接仍然完好無損。 video_flags.video_id上有一個索引,因此不應該是 – Hugo

+2

這個問題,並且您意識到您應該聚合SELECT中未分組的所有列?否則MySQL允許,但這是不好的做法。 –

回答

2

您意外地在一張大桌子上造成全表掃描videos。潛在問題清單可以在at the MySQL documentation找到。

潛在的問題

丟失的鑰匙

看着不f.reason你的解釋,優化器會忽略video_flags表。這允許MySQL/MariaDB充分利用所有索引。

當添加f.reason時,MySQL現在需要匹配v.video_id = f.video_id。由於video_flags有一行,因此MySQL將嘗試爲video中的每個條目檢索v.video_id。看起來你在v.video_id上沒有索引。因此,MySQL必須從磁盤/內存中掃描完整的videos表以獲得video_id。這導致檢索1219933行(相比於explain select中的5而沒有video_flags)。

低基數

另一個潛在的問題是低基數,但我真的不知道到底是什麼導致了優化搞砸。

從MySQL文檔:

您正在使用低基數的關鍵(許多行匹配的鍵值),通過 另一列。在這種情況下,MySQL假定通過使用密鑰,它可能會執行許多密鑰查找,並且表掃描會更快。

我的理解是,由於video_flags非常低的基數(1-2值),則可能會導致MySQL來查找全表上videos由於左連接(你總是需要從左側的所有值側)。此時它決定全表掃描更好。在其他使用video_id的情況下不會發生這種情況,因爲基數較高。您可以使用FORCE INDEX語法強制使用索引。

潛在溶液

嘗試以加快查找上v.video_id添加索引。仔細檢查兩個explain selects以查找突然沒有使用的索引。 注意NULL對於possible_keysv在您的慢選擇。

嘗試使用FORCE INDEX

希望有所幫助。

+0

感謝您的回答,'video_id'是'video'表中的主鍵,所以'v.video_id'有一個索引。看看第一個'explain select',它在'video'和'video_flags'表的鍵列上表示NULL。似乎主鍵沒有被使用? – Hugo

+1

我正在通過https://dev.mysql.com/doc/refman/5.5/en/how-to-avoid-table-scan.html進行查看。也許'FORCE INDEX'可以幫助你。 – somnium

+0

謝謝@somnium加上'FORCE INDEX'似乎解決了這個問題。我將用最終查詢更新原始帖子。不知道爲什麼它失去了指數,但你的建議節省了我的一天。 – Hugo

0

計劃A:看看這是否更好。 (似乎沒有必要去通過所有的加入或分組,以得到你想要的10個video_ids。)

SELECT ... -- as before 
    FROM (
     SELECT video_id 
      FROM video 
      ORDER BY video_id DESC 
      LIMIT 10) AS v1 
    JOIN video AS v USING (video_id) 
    LEFT JOIN ... -- as before 
    ... 
    ORDER BY video_id DESC; -- no GROUP BY or LIMIT here 

計劃B:左轉拼接爲子查詢

s.name, 
LEFT JOIN video_sources AS s ON (s.source_id = v.source_id) 

- >

(SELECT name FROM video_sources WHERE source_id = v.source_id) AS name, 

對於任何其他單行值及其左連接,同上。