在某次业务逻辑中写到如下逻辑:查询学校级别的未拦截弹窗记录。而弹窗设备表(t_block_blacklist_device)有四种类型(1:学校级黑名单 2:设备级别黑名单 3:略 4:学校级白名单 注:从来没有被加入黑名单或白名单不存在这张表中)。弹窗弹出次数表为t_block_count。于是写下以下sql:查询弹窗弹出次数表的次数,排除掉出现在弹窗设备表中学校级的弹窗id(c_block_id中排除掉c_blacklist_id)
SELECT
SUM( bc.c_count ) AS blockNum
-- ....
FROM
t_block_count AS bc
WHERE
bc.c_school_code = '****'
AND bc.c_block_id NOT IN (
SELECT
bbd2.c_blacklist_id
FROM
t_block_blacklist_device AS bbd2
WHERE
bbd2.c_school_code = '****'
AND bbd2.c_enabled = 1
AND bbd2.c_type IN ( 1, 4 )
)
GROUP BY
bc.c_block_id
ORDER BY
blockNum DESC;
explain结果
得到sql后发现not in加子查询在数据量很高的时候性能很差,于是参考网上和《高性能mysql》优化方案,写出not exits和left join的方案,并对比其性能。
not exits方案:
SELECT
SUM( bc.c_count ) AS blockNum
-- ...
FROM
t_block_count AS bc
LEFT JOIN t_block_blacklist AS bb ON bc.c_block_id = bb.c_id
WHERE
bc.c_school_code = '****
AND NOT EXISTS (
SELECT
bbd2.c_blacklist_id
FROM
t_block_blacklist_device AS bbd2
WHERE
bbd2.c_school_code = '****
AND bbd2.c_enabled = 1
AND bbd2.c_type IN ( 1, 4 )
AND bc.c_block_id = bbd2.c_blacklist_id
)
GROUP BY
bc.c_block_id
ORDER BY
blockNum DESC;
explain结果
left join方案
简单的将其子查询部分以表的形式进行左外连接得到如下sql
SELECT
SUM( bc.c_count ) AS blockNum
-- ...
FROM
t_block_count AS bc
LEFT JOIN (
SELECT
bbd2.c_blacklist_id
FROM
t_block_blacklist_device AS bbd2
WHERE
bbd2.c_school_code = '****'
AND bbd2.c_enabled = 1
AND bbd2.c_type IN ( 1, 4 )
) AS t ON t.c_blacklist_id = bc.c_block_id
WHERE
bc.c_school_code = '****'
AND t.c_blacklist_id IS NULL
GROUP BY
bc.c_block_id
ORDER BY
blockNum DESC;
执行SHOW WARNINGS的结果;查看一下系统的优化
EXPLAIN EXTENDED
-- 你的sql
SHOW WARNINGS;
注意:有两个结果,一个是explain的结果一个是show warnings的结果
根据show warings的结果再进行优化得到下面的sql
SELECT
SUM( bc.c_count ) AS blockNum
-- ...
FROM
t_block_count AS bc
LEFT JOIN t_block_blacklist_device AS bbd2 ON bbd2.c_blacklist_id = bc.c_block_id
AND bbd2.c_enabled = 1
AND bbd2.c_type IN ( 1, 4 )
AND bbd2.c_school_code = '****'
WHERE
bc.c_school_code = '****'
AND bbd2.c_blacklist_id IS NULL
GROUP BY
bc.c_block_id
ORDER BY
blockNum DESC
瞬间sql就变得简洁多了
explain的结果:
分析:
当数据量多的时候,我们发现left join 和not exits可以用到idx_blacklist_id和idx_school_code_enabled,可以看到优先用的是idx_blacklist_id这个索引,因为这个索引比idx_school_code_enabled的辨识度更大,性能也相对好一些,数据量大的时候更加明显。而not in中只能使用到idx_school_code_enabled。因此单从索引的使用上not in的性能就比这两者差。
我们可以看到left join 和not exits这两者explain的结果差不多,区别在t_block_blacklist_device表的select_type和extra不太,我们把它放一起会更加清楚
left join
not exits
left join
not exits
想要对比他们谁的性能更优,于是我去网上查找这两者的区别,众说纷纭,有人说这个好,有人说那个好。当然网上的观点不一定是正确的,都是一些个人的理解与经验,这个时候查找书籍是最为可靠的,于是又翻了一下《高性能mysql》,书中也又一个类似的例子。以下是书中的截图
可以看到文字的尾部,书中建议是结合实际的一些测试结果来判断那种写法更好。到此,我的优化过程也结束来,我选取的是left join的方式来作为我的sql,在现实生活中我们往往不需要对性能要求得那么吹毛求疵,因此我采用来更加简介的方式来写这个sql(或许你觉得not exits更加易读,也可以用这种方式)。
结论:以上的过程是我结合具体业务写的sql,并不代表not exits, left join的性能就一定比not in好,not exits和left join性能就差不多。据网上查阅的资料说:mysql官方文档中又提到在一些场景下,这两者mysql自己的优化结果是相同的(当然,我还没查找到相关资料)。因此建议遇见sql优化时,不要用绝对的态度去优化,而应该explain分析一下。也可以用show warings查看mysql优化后执行的真实sql。还有重要的是网上的观点不一定是可靠的(包括我),因此更提倡大家去查阅官方文档和书籍