zoukankan      html  css  js  c++  java
  • 一场一波三折的SQL优化经历

    背景

    最近收到一个SQL调优任务,该SQL在开发环境统计一个月的数据将近执行5秒。原本以为是一场波澜不惊的调优,没想到为了得到最优结果,经历了一波三折。

    第一印象

    • 初见慢SQL
    select case when sum(n_xsajs) is null then 0 else sum(n_xsajs) end as value ,ay.c_aymc as name,ay.c_aydm as id, case when tbdata.tb is null then 0 else 
    		round( cast ( sum(n_xsajs) - tbdata.tb as numeric )/ cast( tbdata.tb as numeric),2)*100 end as tb, case when sum(n_xsajs) < tbdata.tb then -1 when sum(n_xsajs) 
    			> tbdata.tb then 1 else 0 end as tbTag, case when hbdata.hb is null then 0 else round( cast ( sum(n_xsajs) - hbdata.hb as numeric )/ cast( hbdata.hb as 
    			numeric),2)*100 end as hb, case when sum(n_xsajs) < hbdata.hb then -1 when sum(n_xsajs) > hbdata.hb then 1 else 0 end as hbTag 
    
    from db_zntsfx.t_xsaj_ay aj 
    right join ( select c_aydm,c_aymc from db_zntsfx.d_ay ay WHERE ay.c_aylb = '14' ) ay 
    		on aj.c_aydm = ay.c_aydm 
    left join ( select sum(n_xsajs) tb,ay.c_aydm 
    					from db_zntsfx.t_xsaj_ay aj 
    						right join ( select c_aydm,c_aymc from db_zntsfx.d_ay ay WHERE ay.c_aylb = '14' ) ay 
    						on aj.c_aydm = ay.c_aydm  and substr(aj.c_fydm,0,2)= '3' and aj.c_tjq >= '201608' and aj.c_tjq <= '201608' 
    					GROUP BY ay.c_aymc,ay.c_aydm ) tbdata 	
    		on aj.c_aydm = tbdata.c_aydm 
    left join ( select sum(n_xsajs) hb,ay.c_aydm 
    					from db_zntsfx.t_xsaj_ay aj 
    					right join ( select c_aydm,c_aymc from db_zntsfx.d_ay ay WHERE ay.c_aylb = '14' ) ay 
    					on aj.c_aydm = ay.c_aydm  and substr(aj.c_fydm,0,2) = '3' and aj.c_tjq >= '201707' and aj.c_tjq <= '201707' 
    							GROUP BY ay.c_aymc,ay.c_aydm ) hbdata 
    		on aj.c_aydm = hbdata.c_aydm  and substr(aj.c_fydm,0,2) = '3' and aj.c_tjq >= '201708' and aj.c_tjq <= '201708' 
    GROUP BY ay.c_aymc,ay.c_aydm,tbdata.tb,hbdata.hb limit 17 offset 1
    

    原来的慢SQL是经过初步格式化。
    但是我还要进行手动格式化,原因有三点:

    • 原来格式化的SQL层次依然不清晰
    • 手动格式化的过程是理解SQL业务含义的过程
    • 工具自动的格式化只是按照关键字进行缩进,不利于展现SQL层次

    SQL格式化后

    这里写图片描述

    格式化后可以清晰的看出SQL的业务是“统计8月份案由类型14的新收刑事案件数和同比环比数据”

    我认为SQL主要的问题:

    • 执行速度慢,开发环境4秒。
    • SQL体大量重复,重复的子查询和group by。
    • 索引列上使用函数, substr(aj.c_fydm,0,2) = '3'。
    • SQL统计结果是错误的,按照案由聚集后案由是重复的。
    select  t.id,t.name,count(*) from (
    --原SQL
    ) t group by t.id,t.name having count(*)>1
    
    id           |name                       |count |
    -------------|---------------------------|------|
    0E0401540000 |农村土地承包合同纠纷          |2     |
    0E1014000000 |第三人撤销之诉               |2     |
    0E0601010500 |追索劳动报酬纠纷             |2     |
    0E1003010000 |申请宣告公民无民事行为能力     |2     |
    0E0901000000 |侵权责任纠纷                 |2     |
    0E0401551500 |物业服务合同纠纷             |2     |
    0E0802080000 |股权转让纠纷                 |2     |
    0E0201030000 |离婚后财产纠纷               |2     |
    0E0201020000 |离婚纠纷                     |2     |
    0E1013010000 |案外人执行异议之诉            |2     |
    0E0401350900 |农村建房施工合同纠纷           |2     |
    

    而且我认为系统中出现这样的SQL是与“智能”两个字不匹配的!

    第一次调优

    • 查看数据库版本
    db_zntsfx=# select version();
                                                     version                                                  
    ---------------------------------------------------------------------------------------------------
     PostgreSQL 9.5.4 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16), 64-bit
    (1 row)
    
    • with语句改写SQL,因为采用的是abase3.5.3,所以想尝试使用with语法解决SQL结构体重复的问题。
      改写后的SQL

    这里写图片描述

    • 查看执行计划和速度

    这里写图片描述

    • 结果
      1.SQL语句结构依然不清晰,复杂度和原来差不多。
      2.SQL 运行速度比原来还慢,将近6秒。主要原因with 语句的结果集合不能按照月份过滤,后续进行运算结果集太大。

    第二次调优

    • 重新改写SQL

    这里写图片描述

    • 查看执行计划

    这里写图片描述

    • 结果
      1. SQL语句的层次清晰很多,执行速度和原来相同。
      2. 根据《数据库选择索引探索一》,组合或覆盖索引会加快SQL执行速度,添加覆盖索引。
    create index i_ay_zh01 on db_zntsfx.d_ay(c_aydm, c_aymc);
    create index i_xsay_zh01 on db_zntsfx.t_xsaj_ay(c_aydm, c_tjq, c_fydm, n_xsajs);
    
    • 再看执行计划

    这里写图片描述

    SQL执行时间100ms

    • 初步排查结果正确性
    select id,name,count(*) from (
       --第二次重写SQL
    ) t group by t.id,t.name having count(*) >1
    
    
    id |name |count |
    ---|-----|------|
    

    没有重复的案由统计

    第三次调优

    • 统计结果正确性怀疑

    正当我以为找到了SQL清晰性与效率兼顾的完美解决方案时,DBA团队刘国明对查询结果的正确性提出了质疑。
    国明兄认为应该先group by再left join。

    • 验证过程

    单独取一类案由的现在、同比、环比案件数据。

    1.用第二次改造后的SQL计算“宅基地使用权纠纷”的同比环比数据。

    这里写图片描述

    2.新SQL单独计算“宅基地使用权纠纷”的同比环比数据。

    这里写图片描述

    • 验证结果

    改写后"完美SQL"的统计结果是错误的

    • 反思

    为什么改写后的SQL统计结果是错的呢?
    答案:再看SQL执行计划发现,SQL改写统计错误的原因是,想当然认为同比、环比、和当前月数据是分别和主表d_ay 表进行左连接的,其实不是。其实是主表先和当前月结果集做笛卡尔积,这个结果集再和同比数据做笛卡尔积,新集合再和环比数据做笛卡尔积。导致数据重复。

    正确的SQL

    这里写图片描述

    正确的执行计划

    这里写图片描述

    SQL执行时间:10ms以内(开发环境)

    总结

    在SQL优化过程中,需要经过格式化SQL、理解业务、改写SQL甚至重新设计表结构、尝试索引等若干步骤,不要想当然的认为SQL就是按照编写的顺序执行的。同时有若干感慨,经过SQLFX平台发现很多项目的SQL存在大量join超过四张表的情况,随着数据量的增加这些SQL的笛卡尔积运算量必定成指数级增加,造成CPU和IO资源的紧张。希望由sybase切换到abase,数据库平台升级带来的性能提升不要被程序中慢SQL所抵消掉,希望“安迪比尔定理”不要发生在我们身边。

  • 相关阅读:
    今天看了几个小时的微信小程序说说心得体会
    关于wordpress中的contact form7和WP Mail SMTP的一些设置
    关于163发邮件报错535 Error:authentication failed解决方法
    Numpy 基本除法运算和模运算
    基本的图像操作和处理
    Python中flatten用法
    media
    TensorFlow模型保存和提取方法
    docker 默认用户和密码
    Windows安装TensorFlow
  • 原文地址:https://www.cnblogs.com/wangzhen3798/p/7630589.html
Copyright © 2011-2022 走看看