2017-09-15 59 views
1

我試圖檢索分頁列表和總數「關於屬於特定用戶的」案例「的」通知「。在JOERE中使用OR緩慢JOIN查詢 - 缺少可能的索引?

通知有幾個條件爲「未鎖定」,「不是私人」,「尚未見過」,應該返回#找到,然後按照創建的日期降序排列。

最後一個條件是,該通知不是由用戶本身產生,或該通知的類型是「行爲」(枚舉)和user_id是在通知中涉及「REF_ID」

此查詢正在接近5秒鐘,以對最近的變化中的200k行和在cases和50個用戶中少於4k行進行運行。

+-----+ 
| cnt | 
+-----+ 
| 13 | 
+-----+ 
1 row in set (4.67 sec) 

該查詢是否可以自行優化,還是需要重構?

SELECT count(*) as cnt 
FROM recent_changes rc 
LEFT JOIN `case` c on c.id = rc.case_id 
LEFT JOIN `user` u on u.id = rc.user_id 
WHERE 
(
    rc.user_id != c.user_id AND c.user_id = '25' 
    OR 
    (rc.type = 'conduct' AND rc.ref_id = '25') 
) 
AND c.locked = 'N' AND rc.private != 'Y' 
AND seen = 'false' 
ORDER BY rc.datecreated DESC; 

Explain輸出上recent_changes

+----+-------------+-------+--------+--------------------------+-------------------------+---------+--------------------------+------+------------------------------+ 
| id | select_type | table | type | possible_keys   | key      | key_len | ref      | rows | Extra      | 
+----+-------------+-------+--------+--------------------------+-------------------------+---------+--------------------------+------+------------------------------+ 
| 1 | SIMPLE  | c  | ALL | PRIMARY,user_user_id_idx | NULL     | NULL | NULL      | 3699 | Using where; Using temporary | 
| 1 | SIMPLE  | rc | ref | idx_recent_changes_case | idx_recent_changes_case | 5  | xxxxxxxxxxxxx.c.id  | 25 | Using where     | 
| 1 | SIMPLE  | u  | eq_ref | PRIMARY     | PRIMARY     | 4  | xxxxxxxxxxxxx.rc.user_id | 1 | Using index     | 
+----+-------------+-------+--------+--------------------------+-------------------------+---------+--------------------------+------+------------------------------+ 

指數:上case

+----------------+------------+------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| Table   | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | 
+----------------+------------+------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| recent_changes |   0 | PRIMARY      |   1 | id   | A   |  182807 |  NULL | NULL |  | BTREE  |   | 
| recent_changes |   1 | recent_changes_user_id_idx |   1 | user_id  | A   |   96 |  NULL | NULL | YES | BTREE  |   | 
| recent_changes |   1 | idx_recent_changes_user_case |   1 | user_id  | A   |   92 |  NULL | NULL | YES | BTREE  |   | 
| recent_changes |   1 | idx_recent_changes_user_case |   2 | case_id  | A   |  18280 |  NULL | NULL | YES | BTREE  |   | 
| recent_changes |   1 | idx_recent_changes_case  |   1 | case_id  | A   |  7312 |  NULL | NULL | YES | BTREE  |   | 
+----------------+------------+------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 

指標:

+-------+------------+------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+ 
| Table | Non_unique | Key_name   | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | 
+-------+------------+------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+ 
| case |   0 | PRIMARY   |   1 | id     | A   |  3753 |  NULL | NULL |  | BTREE  |   | 
| case |   1 | id_idx   |   1 | member_id   | A   |  3753 |  NULL | NULL | YES | BTREE  |   | 
| case |   1 | user_user_id_idx |   1 | user_id    | A   |   2 |  NULL | NULL | YES | BTREE  |   | 
| case |   1 | case_ha_id  |   1 | health_authority_id | A   |   28 |  NULL | NULL | YES | BTREE  |   | 
+-------+------------+------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+ 

它做以下概念:

在recent_changes查找最近的行,其中:

i)本recent_changes行通過由該電流的user_id II擁有CASE_ID加入到case表)和recent_changes行不是由當前USER_ID創建

OR

i)所述recent_changes行是 「行爲」 類型的,並且當前的user_id是在recent_changes.ref_id柱

如果刪除了「OR( rc.type ='conduct'AND rc.ref_id ='25')「condition,那麼我得到1秒鐘的響應時間。

如果我刪除「rc.user_id!= c.user_id AND c.user_id ='25'或」條件,它仍然需要大約5秒才能完成。


編輯

改變連接順序剃掉1/2秒,雖然我不能在rc .case_id加入case直到我加入rc第一:未知列「rc.user_id '在'where子句'中。

新建查詢:

SELECT count(*) as cnt 
FROM `user` u 
LEFT JOIN `recent_changes` rc on u.id = rc.user_id 
LEFT JOIN `case` c on c.id = rc.case_id 
WHERE 
(
    rc.user_id != c.user_id AND c.user_id = '25' 
    OR 
    (rc.type = 'conduct' AND rc.ref_id = '25') 
) 
AND c.locked = 'N' AND rc.private != 'Y' 
AND seen = 'false' 
ORDER BY rc.datecreated DESC; 

取出「ORDER BY」條款似乎並沒有增加新的連接順序查詢,雖然我現在是更好地瞭解它的性能影響。

使用UNION沒有任何速度快,但運行的每個單獨選擇已指出,首隻選擇需要.3s其中第二選擇是在4S:

select count(*) as cnt 
FROM (
SELECT count(*) FROM `user` u 
LEFT JOIN `recent_changes` rc on u.id = rc.user_id 
LEFT JOIN `case` c on c.id = rc.case_id 
WHERE rc.user_id != c.user_id AND c.user_id = '25' 
AND c.locked = 'N' AND rc.private != 'Y' 
AND seen = 'false' 
UNION ALL 
SELECT count(*) as cnt 
FROM `user` u 
LEFT JOIN `recent_changes` rc on u.id = rc.user_id 
LEFT JOIN `case` c on c.id = rc.case_id 
WHERE rc.type = 'conduct' AND rc.ref_id = '25' 
AND c.locked = 'N' AND rc.private != 'Y' 
AND seen = 'false') x 

我相信recent_changes rc表沒有按「T有必要的索引作爲每說明:

EXPLAIN SELECT count(*) FROM `user` u LEFT JOIN `recent_changes` rc on u.id = rc.user_id LEFT JOIN `case` c on c.id = rc.case_id WHERE rc.user_id != c.user_id AND c.user_id = '25' AND c.locked = 'N' AND rc.private != 'Y' AND seen = 'false'; 

在奔跑,5S <

+----+-------------+-------+--------+---------------------------------------------------------------------------------+-------------------------+---------+--------------------------+------+-------------+ 
| id | select_type | table | type | possible_keys                 | key      | key_len | ref      | rows | Extra  | 
+----+-------------+-------+--------+---------------------------------------------------------------------------------+-------------------------+---------+--------------------------+------+-------------+ 
| 1 | SIMPLE  | c  | ref | PRIMARY,user_user_id_idx              | user_user_id_idx  | 5  | const     | 383 | Using where | 
| 1 | SIMPLE  | rc | ref | recent_changes_user_id_idx,idx_recent_changes_user_case,idx_recent_changes_case | idx_recent_changes_case | 5  | hsaedmp_jason.c.id  | 20 | Using where | 
| 1 | SIMPLE  | u  | eq_ref | PRIMARY                   | PRIMARY     | 4  | hsaedmp_jason.rc.user_id | 1 | Using index | 
+----+-------------+-------+--------+---------------------------------------------------------------------------------+-------------------------+---------+--------------------------+------+-------------+ 

在奔跑> 4S

EXPLAIN SELECT count(*) as cnt FROM `user` u LEFT JOIN `recent_changes` rc on u.id = rc.user_id LEFT JOIN `case` c on c.id = rc.case_id WHERE rc.type = 'conduct' AND rc.ref_id = '25' AND c.locked = 'N' AND rc.private != 'Y' AND seen = 'false'; 

密鑰= NULL這是不好的。

+----+-------------+-------+--------+---------------------------------------------------------------------------------+-------------------------+---------+--------------------------+------+-------------+ 
| id | select_type | table | type | possible_keys                 | key      | key_len | ref      | rows | Extra  | 
+----+-------------+-------+--------+---------------------------------------------------------------------------------+-------------------------+---------+--------------------------+------+-------------+ 
| 1 | SIMPLE  | c  | ALL | PRIMARY                   | NULL     | NULL | NULL      | 3797 | Using where | 
| 1 | SIMPLE  | rc | ref | recent_changes_user_id_idx,idx_recent_changes_user_case,idx_recent_changes_case | idx_recent_changes_case | 5  | hsaedmp_jason.c.id  | 20 | Using where | 
| 1 | SIMPLE  | u  | eq_ref | PRIMARY                   | PRIMARY     | 4  | hsaedmp_jason.rc.user_id | 1 | Using index | 
+----+-------------+-------+--------+---------------------------------------------------------------------------------+-------------------------+---------+--------------------------+------+-------------+ 

我感到困惑的是,解釋說,case表不使用鑰匙節目,但現在看來,該recent_changes表是需要對ref_id列的索引的一個?

下面是該索引的解釋,在這裏看起來好多了,但我還沒有能夠在產品上測試它。

+----+-------------+-------+------------+--------+---------------------------------------------------------------------------------------------------------------------------------- 
---+------------------------+---------+--------------------------+------+----------+-------------+ 
| id | select_type | table | partitions | type | possible_keys 
    | key     | key_len | ref      | rows | filtered | Extra  | 
+----+-------------+-------+------------+--------+---------------------------------------------------------------------------------------------------------------------------------- 
---+------------------------+---------+--------------------------+------+----------+-------------+ 
| 1 | SIMPLE  | rc | NULL  | ref | recent_changes_user_id_idx,idx_recent_changes_user_case,idx_recent_changes_case,idx_recent_changes_case_date,idx_recent_changes_r 
ef | idx_recent_changes_ref | 5  | const     | 2096 |  3.12 | Using where | 
| 1 | SIMPLE  | u  | NULL  | eq_ref | PRIMARY 
    | PRIMARY    | 4  | hsaedmp_jason.rc.user_id | 1 | 100.00 | Using index | 
| 1 | SIMPLE  | c  | NULL  | eq_ref | PRIMARY 
    | PRIMARY    | 4  | hsaedmp_jason.rc.case_id | 1 | 50.00 | Using where | 
+----+-------------+-------+------------+--------+---------------------------------------------------------------------------------------------------------------------------------- 
---+------------------------+---------+--------------------------+------+----------+-------------+ 

UPDATE

我已經返工使用UNION語句的查詢,改變連接順序和由上recent_changes表添加化合物索引一起帶來的查詢響應時間< 10ms的。

這是使用UNION語句的新查詢。

select count(*) as num 
FROM (
(
SELECT rc1.* 
FROM `user` u1 
LEFT JOIN `recent_changes` rc1 on u1.id = rc1.user_id 
LEFT JOIN `case` c1 on c1.id = rc1.case_id 
WHERE 
(rc1.user_id != c1.user_id AND c1.user_id = '1') 
AND c1.locked = 'Y' 
AND rc1.private != 'Y' 
AND seen = 'false' 
ORDER BY rc1.datecreated DESC 
) 
UNION 
(
SELECT rc.* 
FROM `user` u 
LEFT JOIN `recent_changes` rc on u.id = rc.user_id 
LEFT JOIN `case` c on c.id = rc.case_id 
WHERE 
(rc.type = 'conduct' AND rc.ref_id = '1') 
AND c.locked = 'Y' 
AND rc.private != 'Y' 
AND seen = 'false' 
ORDER BY rc.datecreated DESC 
) 
) x; 

而我根據最終查詢創建的索引是我需要的。

ALTER TABLE recent_changes ADD INDEX idx_recent_changes_notification (type, ref_id, private, seen, user_id); 

感謝大家的意見!

+0

'OR'是MySQL中的一個性能殺手。試着將它分成兩個查詢,你可以結合'UNION'。 – Barmar

+0

另外,運行EXPLAIN EXTENDED,然後顯示SHOW WARNINGS。它會揭示一些關於MySQL如何解釋你的JOIN的有用信息 – Strawberry

+0

嘗試在'recent_changes(type,ref_id,private,user_id)上創建一個複合索引'這是一個所謂的複合覆蓋索引,並且有助於加速UNION的第二部分。請[編輯]你的問題,讓我們知道它是否有幫助。嘗試修復性能問題時,在表中放置大量單列索引通常是有害的。 –

回答

0

小表應該放在連接子句的第一個。 這取決於表中有多少記錄。我認爲你的用戶表是最小的一個。所以先放置它。看起來'rc'表是最大的一個。你應該把它放在最後加入。

下面是一個例子。

SELECT count(*) as cnt 
FROM `user` u 
LEFT JOIN `case` c on c.id = rc.case_id 
LEFT JOIN `recent_changes` on u.id = rc.user_id 
WHERE 
(
    rc.user_id != c.user_id AND c.user_id = '25' 
    OR 
    (rc.type = 'conduct' AND rc.ref_id = '25') 
) 
AND c.locked = 'N' AND rc.private != 'Y' 
AND seen = 'false' 
ORDER BY rc.datecreated DESC; 

此外,請參閱下面的文章。這是MSSQL的事情,但幾乎所有的DBMS這裏

https://www.mssqltips.com/sqlservertutorial/3201/how-join-order-can-affect-the-query-plan/

具有相同點更新

我檢查了您的問題,發現另一名嫌疑人,這是關於ORDER BY子句。 從查詢返回的行數很多,'order by'的時間成本將顯着增加。這是我的經歷中經常遇到的問題。你有沒有嘗試刪除子句的順序?它快多快?

請參閱Why is this INNER JOIN/ORDER BY mysql query so slow?