慢SQL是如何拖垮数据库的?(一个慢sql,如何将其优化)

慢SQL是如何拖垮数据库的?(一个慢sql,如何将其优化)

解决方案goocz2025-02-01 11:39:3421A+A-

1000万云上开发者,全栈云产品0元试用:点击「链接」免费试用,即刻开启云上实践之旅!


本文结合一个实际故障案例出发,从小白的视角分析慢SQL是如何打垮数据库并引发故障的。

作者 | 全达领(达领)

来源 | 阿里开发者公众号


一、案发现场

上午9:49,应用报警:
4103.ERR_ATOM_CONNECTION_POOL_FULL,应用数据库连接池满。

上午9:49-10:08期间,陆续出现
4200.ERR_GROUP_NOT_AVALILABLE、4201.

ERR_GROUP_NO_ATOM_AVAILABLE、
4202.ERR_SQL_QUERY_TIMEOUT等数据库异常报警。

由于数据库承载了销售核心的用户组织权限功能,故障期间,期间销售工作台无法打开,大量小二反馈咨询。

上午10:08,定位到有应用基础缓存包升级发布,上午9点40刚完成最后一批发布,时间点相吻合,尝试通过打开缓存开关,系统恢复。


二、现场结论

对此次升级缓存包应用发布内容分析,发现升级的某个二方包中,删除了本地缓存逻辑,直接请求DB,而本次升级没有对请求的SQL进行优化,如下代码所示,该SQL从Oracle迁移到MySQL,由于数据库性能的差异,最终造成慢查询,平均一次执行2S多,大量慢SQL最终打挂数据库。

SELECT CRM_USER_ID AS LOGIN_ID, CRM_ROLE_ID AS ROLE_NAME, CRM_ORG_ID AS ORG_ID
    , CONCAT(CRM_USER_ID, '#', CRM_ROLE_ID, '#', CRM_ORG_ID) AS URO
    , CONCAT(CRM_ORG_ID, '#', CRM_ROLE_ID) AS ORG_ID_ROLE_NAME
FROM CRM_USER_ROLE_ORG
WHERE IS_DELETED = 'n'
    AND CONCAT(CRM_ORG_ID, '#', CRM_ROLE_ID) = '123#abc'
ORDER BY ID DESC;

经过讨论排查分析,得出以下结论:

  1. 之前逻辑走本地内存,本次升级中由于涉及到的某个二方库代码变更删除了本地逻辑改查DB
  2. 查询DB的SQL去O阶段没有进行优化,在MySQL下为慢SQL,大量慢SQL查询拖垮了数据库


三、进一步的疑问

面对上述结论,作为数据库方面的小白,有以下几个疑问,感觉需要深入挖掘:

  1. 这条SQL为何是慢SQL
  2. 发布的应用为非核心应用,只是与登录权限共用了一个数据库,当时发布应用的QPS只有0.几,为何可以把库打挂
  3. 之前已经申请过一波连接池扩容,从10扩到了15,发布的应用线上有流量的机器不过7台,为何可以把数据库压垮
  4. 事后复盘,发布前一天灰度时也有过慢SQL,为何当时没有压垮数据库


四、深入分析原理

带着以上疑问,结合以下相关知识,一层层剥开深层次的原因

4.1、慢SQL分析

CONCAT(CRM_ORG_ID, '#', CRM_ROLE_ID) = '123#abc'

该SQL由工具直接从Oracle翻译过来的

  • 虽然两个拼接的字段各自都有索引,但是使用函数后,MySQL是不会使用索引的,退化为了普通查询
  • 由于表数据量较大,全表40W+数据,导致扫描行数很多,平均扫描16W行、逻辑读38W行,执行2s左右

4.2、业务代码排查

  • 故障后第二天,有个别销售反馈页面打开较慢,有好几秒,怀疑是止血时的操作是切到了tair而不是回滚到本地缓存逻辑导致,不过此时还是有疑问,为何一个页面会慢好几秒呢,听起来就像是一次请求大量循环调用缓存导致。
  • 代理账号经定位,确实是如上假设,此处的业务代码逻辑为查找组织下的指定角色,会递归遍历所有子组织,最差情况下,一次页面请求,会有1000+次访问缓存/DB
  • 结合数据库当时慢SQL趋势,符合我们的猜测,虽然业务流量不大,但是每次请求会放大1000倍,最终导致问题SQL执行了1.5W+次,同时同下图可以看到,其他正常SQL由于系统忙被排队,响应也变慢,而这些基本都是基础用户组织权限相关,所以造成了业务系统不可用


4.3、druid连接池?数据库连接池?

  • 上文背景处有说道,应用连接池配置的为15,切应用流量本身很小,那么是什么原因导致整个库都被拖垮呢
  • 这里要从一次SQL请求的链路说起,如下图所示
    • 应用层通过tddl访问MySQL数据源,其中连接管理是在atom层,利用druid进行连接池的管理,我们平时所说的tddl线程池,指的就是druid连接池,这个配置维护在diamond中,一般有dba来设置。
    • 对于我们的应用来说,单个应用7台机器,maxPoolSize配置为15,数据库是单库单表,则单个应用的最大连接数为1*15,所有应用连接数为7*1*15=105
  • 注意以上只是应用维度的连接数推导,正常工作下连接池也不会达到max的,如果达到了,TDDL会抛出4103.ERR_ATOM_CONNECTION_POOL_FULL,应用数据库连接池满错误,与第一节现象吻合
  • 那么问题来了,这么几个连接,为何打垮整个数据库呢;看数据库的最大连接数可是有8000
  • 经与DBA同学咨询,了解到了数据库server端的内部处理线程池与druid没有任何关系,两者是两个层面的东西,所以需要研究下server端的处理逻辑
  • 同时这里提一点,由于我们很多应用都在连接数据库,所以需要评估下数据库的最大连接数是否可以满足这么多应用的机器的链接,即应用服务器总数 * 一个数据库实例上的分库数(atom) * maxPoolSize < max_user_connections


剩余60%,完整内容请点击下方链接查看:

慢SQL是如何拖垮数据库的?


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点击这里复制本文地址 以上内容由goocz整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

果子教程网 © All Rights Reserved.  蜀ICP备2024111239号-5