zoukankan      html  css  js  c++  java
  • 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次,效率提高不少。

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

  • 相关阅读:
    记录一次对接XX支付SDK过程中报错问题
    接口回调(重点是理解)
    jQuery form插件的使用--ajaxForm()和ajaxSubmit()的可选参数项对象
    如何通过submit提交form表单获取后台传来的返回值
    Unsupported major.minor version 52.0
    js获取input file路径改变图像地址
    html 横线的代码
    FastJson对于JSON格式字符串、JSON对象及JavaBean之间的相互转换
    UUID不失精度,长度改进
    java Date时间的各种转换方式和Mysql存时间类型字段的分析
  • 原文地址:https://www.cnblogs.com/goloving/p/15193637.html
Copyright © 2011-2022 走看看