2017-09-15 67 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?