SQL优化:慎用标量子查询,改用left join提升查询效率

一、项目实例问题

1、问题背景

  某个需求做了之后,注意到有个接口返回数据特别慢,特别是使用下面的 3 个字段排序时就直接卡死,肯定是 sql 性能写法问题,所以决定研究一下查看究竟。

  其实需求挺简单,有几个字段排序,前端需要展示那些字段,然后之前的后端写的 sql 如下,仅提取主要问题点,其实就是需要拿到 starCount、commentCount、totalReward 用来前端展示,而这三个字段呢,又需要从另外三个表里去分别计数,所以不考虑 sql 性能优化的话,就很容易想到了这种错误的写法。

k.tags,
v.views,
(select coalesce(count(rid),0) from table1 where aa = 'kl' and rid = k.id) starCount,
(select coalesce(count(id),0) from table2 where aa = 'kl' and rid = k.id::varchar) commentCount,
(select coalesce(count(id),0) from table3 where aa = 'kl' and rid = k.id::varchar) totalReward
from table4 k left join table5 v on k.id = v.kl_id

2、优化方案

  主要优化后的 sql 如下:使用 left join 替代标量子查询

k.tags,
v.views,
coalesce (s.count,0) starCount,
coalesce (m.count,0) commentCount,
coalesce (p.count,0)  totalReward
from table4 k left join table5 v on k.id = v.kl_id 
left join (select rid,count(rid) from table1 where aa = 'kl' group by rid) s on k.id = s.rid
left join (select rid,count(rid) from table2 where aa = 'kl' group by rid) m on m.rid = k.id::varchar 
left join (select rid,count(rid) from table3 where aa = 'kl' group by rid) p on p.rid = k.id::varchar 
order by totalReward desc

  优化前比如我有10万篇文章,那就要执行10万次(select coalesce(count(rid),0) from table1 where aa = 'kl' and rid = k.id) starCount。

  优化后,仅需一次两表之间的匹配,即使是全表也是1次匹配,分组后也是1次匹配,数据量少是会提高效率但是顶多0.00几的提高,关键是left join。提高了n倍之前order by直接执行失败time out,优化之后是0.4s左右。

3、分析原因 - 为什么会想到错误的写法

  以前我确实很少看到第一种那种标量子查询的写法,所以很纳闷为什么会这样写。一般不都是用 left join 吗?后来了解到可能情况不一样:

(1)平常我们使用多表关联都会想到 left join,因为我们会用到关联表的多个字段或某个字段,需要将其查出来,所以很容易想到 left join。

(2)而这种情况只需要使用其他表的一个计数的值,没有使用表里的任何字段,没学过 sql 优化的,很难想到用 left join。

  而很多人使用标量子查询而不自知执行效率差,往往是因为数据量比较小,并没有发现不妥,一旦数据量大了之后,就会越来越慢。只有经过大数据量的考验,才能写出来优质的 sql。

  墨天轮平台有个标量子查询的优化案例可以看下:Oracle 标量子查询优化案例  —  https://www.modb.pro/db/41963

二、标量子查询的问题

  标量子查询、聚合标量子查询、行转列标量子查询、带top的标量子查询如何转成left join。

  之所以要转换,主要是因为标量子查询虽然写法上比较直观,容易理解,不用想就知道怎么写,但是存在:代码重复、多次访问同一个表 问题,所以效率比较低。

1、标量子查询的模板

  按标量子查询方式,写出来的sql,都类似下面的代码:

select tb.col1,
       tb.col2,
       --下面的代码是重复的,表和连接条件都类似,只是最后显示的字段不同
       (select x1 from t where t.id = tb.id) as x1,
       (select x2 from t where t.id = tb.id) as x2,
       (select x3 from t where t.id = tb.id) as x3,
       (select x4 from t where t.id = tb.id) as x4,
       ...
from tb

  可以看到,其中x1、x2、x3、x4等列,大部分代码都是重复的。当然,代码重复本身并没有太大的问题,最多就是复制粘贴,拷贝多次,然后把字段名改改,就行了。

2、标量子查询的执行过程

  上面的sql经过sql server的优化,生成执行计划,执行过程类似如下的过程:

(1)从tb表中取一条数据,用其中的id值,第1次和t表中的id值进行比较,如果相等,就返回t表的x1字段的值。

(2)从tb表中取一条数据,用其中的id值,第2次和t表中的id值进行比较,如果相等,就返回t表的x2字段的值。

(3)从tb表中取一条数据,用其中的id值,第3次和t表中的id值进行比较,如果相等,就返回t表的x3字段的值。

(4)从tb表中取一条数据,用其中的id值,第4次和t表中的id值进行比较,如果相等,就返回t表的x4字段的值。

(5)按照上述过程遍历整个tb表的每一条数据。

  从上面的过程可以看出,一共访问了t表4次,做了很多无用功。

  如果改成left join的方式,只需要访问1次t表,少访问3次,效率提高不少。

  所以,要尽量少用标量子查询的写法。

原文地址:https://www.cnblogs.com/goloving/p/15193637.html