【JAVA】【慢SQL/应用假死】性能优化案例 /why not Merge join/ Bitmap heap scan

215 阅读8分钟

背景

  • 准生产发版,功能假死,导入奇慢
  • 在上个版本还能用,但推广反馈性能慢,于是开发加了个async,反而更慢了。
  • 反复问测试确认,他才讲清楚,是上个版本就慢,这次慢的离谱

定位

SQL执行计划

explain
select a.user_id
    from test_man_part_time_job a
             left join test_man_base_m_202410 b
                       on (a.user_id = b.user_id and b.region2_id = '9110000085' and
                           b.last_entry_date_m <= '202411' and b.end_date_m >= '202411')
    where a.region2_id = '9110000085'
      and a.biz_line = '2'
      and b.biz_line != '2'
      and b.job_status in ('10'
        , '40'
        , '50'
        , '60'
        , '70');

脱敏执行计划 可以看到

image.png

1、Subquery的left join联表,走索引Index scan,但因为野蛮设计依旧在filter索引外的条件时产生了14907的cost

2、主表part_time为Seq scan普通扫描无任何优化

奇怪的index scan: 按理说m_idx这个是联合索引region1开始,region2的条件不该命中。我们留到添加后一起说。

对于1

  • 第1点中,比较扎眼的是对日期的正则操作。而且还在第四行和倒数第三行,走了两次。

仔细一看是开发给的sql有问题,查的202410表,sql里却还筛202411的,此点过。

至于为什么有这个正则补丁-不可接受的设计:

(1)早期设计变更,甲方无担当好大喜功,摇摆。

这里的事太复杂了,我前面《准生产应用宕机-项目流程问题》专门喷这个的,不详细说了。 但他们上面领导一要汇报,就摇摆,一句话就改。层层向上负责。

原设计基于:说1期推广3-5省的,结合时间紧任务重,设计上从简考虑那时人员信息的数据量级不需要拆分月表/市表。

草包不可言性别领导【审核魅力时刻GHG】舔上级(这是项目组被坑的一致意见),什么都不决策,临时改推广全国,量级10倍翻,又强压时间,最简单的方案,做视图啦,于是才有的这个subquery

(2)本人前期整体系统设计考虑不全面,没留足冗余,但我也要狡辩的,tmd一说就任务重大,一要人没有,就几个外包远程,我能做什么设计,他们那能力加点复杂度就要出不知道多少bug,尤其是需求还来回变,测试走过场。

(3)评审的缺失:老问题了,一个个都刚愎自用,需求定不下来还直接找开发,部门经理自嗨和开发聊客户需求,结果最终的设计他们是一点不评审,聊爽了就走了。项目经理中年人一点业务不懂吃老本。

但其实你真要我擦屁股评审我也没时间,源头还是甲方搞得。

  • 那么就第1点来说,我们要快速解决问题的角度来说,是无计可施的,结论上是没有优化点。
    • 可以加上联合索引吗? 不是很可以,in走不走索引看树的心情,即使走索引那b.job_status枚举值个位数,这里in的范围太广了,性能提升几乎可忽略。
    • biz_line <> 无法索引。

对于2

虽然显示的cost只有8000多,但对应实际操作,是这个sql会直接卡死,根据trace定位当时生产上执行了60秒。 两张表加一起符合条件的总数据量根据排查,只有几千条。

根据执行计划推测:2在执行时顺序上在1后,1得到结果再进行的一条条join filter去查2的表,实际代价相当于千乘千=约为百万级扫描查询

  • 如果查询的主表是正常表不会出现join filter应为hash join filter,数据库自己分析会选择内存join就好了。 性能差也不会暴雷阻塞,一般数据库内存瓶颈也到不了,凑合就用了。

  • 可这里毕竟视图改造赶工,最终算是一个个雷点堆积起来的大雷。

故唯一的希望:添加2表索引

添加后

仅从执行计划来看:只是减少了5000cost。

但是实际db工具执行,已修复成功,仅耗时1.3秒。

数据库分析得到的执行计划整体大变样,join的表由index变为bitmap index,主表part_time顺利走index scan。

为什么数据库决定可以通过索引/bitmap扫?

检查bitmap走的索引,是租户信息索引最左顺序,region1、2、3,而查询条件是直接region2=?

可见八股文不可信,日新月异,数据库具备对于非最左前缀查询条件的兼容性。 数据库分析判断,【虽然不符合最左】,但先通过scan联合索引(无论是bitmap index scan还是index scan,都是基于这个联合索引的扫描),得到region2符合条件的物理位置(bitmap),再高效(顺序)定位数据位置IO的代价,低于普通SeqScan代价; 效率低于直接命中,但是对于垃圾代码的兼容性健壮很多。

继续疑问:为什么之前添加前是普通index scan,添加后才变成bitmap index scan?

从执行逻辑上无法解释,因为慢的原因是笛卡尔积的一端没有索引所以慢:先查的join表的结果,按其宽度再fetch主表。大提速是因为添加后,主表走索引速度提升100倍。

后发现是因为2次测试sql的region2=?的值不同,数据量不同。故数据库智能分析量大了改用bitmap:添加前的sql中对应region2的结果2000条,添加后的测试sql对应region2结果8000条,而对应cost仅从14914增加到18246。

数据库综合分析数据量和构建bitmap heap的内存代价决定策略。

image.png

!! 当然,最好的是你必须给我走联合索引,你凭什么不规范不传region1?要么就有苦衷咱们评审加个region2单索引
偷图bitmap pg

看起来类似于倒排的原理,堆中直接按查询条件定位符合条件的tuple-pointer all image.png image.png

发散,为什么不试试merge join?

引用【译】PostgreSQL 中的 JOIN 策略及其性能 | Japin

多表连接的三种方式详解 hash join、merge join、 nested loop - 小强斋太 - 博客园

首先数据量不大,一票否决了。

如果这次的sql对应场景,数据量很大,那确实适合排序然后merge join。

但这样你也要建user_id的索引,不然量大排序也不是闹着玩的,work_mem不够和bitmap heap一个原因死掉了,降级或seq scan。

具体情况具体分析,数据库自己有分析算法,真测试不行再拉会呗,给我提供支持。

背景中还有个问题,为什么多线程反而更慢了?

  • tmd一次就要60秒,原本外包写的代码就完蛋,遍历n=50,2n100次。现在多线程*5倍。你就饶了国产数据库吧,资源热点压死了。

  • 而且就事论事,外包人可能不差,但外包技术不差不太可能:甚至还要用@Async注解也不加线程池,哥哥啊,这1C2G的机器

总结

  • 1、任何反馈都给我讲原始现象/问题,不要在那加上自己的判断。
  • 2、必须评审必须评审必须评审,必须出设计必须出设计必须出设计,不出不做,给的人能力不够都给你留档邮件。责任全在甲方和你们扛不住压的尸位素餐的领导。
  • 3、加强洗脑研发尤其外包和菜正式,写sql时注意点量级和索引,培训其sql能力。但唉,这本质不是开发的问题,是排期是甲乙是制度是尸位素餐的上层。。。
  • 4、执行计划确实是不保准的,数据库执行分析算法也没那么智能,如果排期还允许人力还允许,一定要仿真测试下你的sql效率。
  • 5、就这公司还是tob的行业头头,行业远超想象的草台班子,以后尽管要价了。
  • 6、本次问题比起外包/开发的质量,实际上主要体现的是测试、需求人员的加戏/刚愎自用/形式主义敲钟主义

    做事并没有根据和章法,多少年经验了还能不进行评审就让外包直接做。(实际后期已经节奏放缓可以评审了)。。

    部门经理更是搞,和人聊天聊的挺爽,就是没什么落成文字的设计和评审,聊半天需求灌给开发,形式主义dmp工单,然后就是开发质量失控。

    不过虽然开发负责但我也不要去评审,这是项目背景的公司内部不同部门还互相推托的结果,本部门尽快逃离这个项目。都想着赶进结束了

    而且10几年测试也只是功能测试,就这样,没有性能没有集成没有贯穿。

    但这点钱确实留不到好人。