首页 > 其他分享 >一次分页慢查询导致的事故处理过程 转载

一次分页慢查询导致的事故处理过程 转载

时间:2023-08-21 11:44:49浏览次数:39  
标签:sku code 处理过程 分页 查询 索引 SQL id

原文:https://juejin.cn/post/7126356005192990750

事故背景

这次事故也是我们组里遇到的一次关于分页慢查询的典型例子,通过这篇文章,你可以很清晰的跟随我们还原事故现场,以及每一步遇到问题做出的调整和改动。

事故问题现场

  • 16:00 收到同事反馈,融合系统分⻚查询可⽤率降低
  • 16:05 查询接⼝UMP监控,发现接⼝TP99异常彪⾼

打开机器监控,发现⼏乎所有机器的TP999都异常的⾼,观察机器CPU监控,发现CPU使⽤率并不⾼

图片

  • 16:10 查看数据库监控,发现数据库CPU异常彪⾼,定位到是数据库问题,同时收到了⼤量的慢SQL邮件。

图片

定位到这里,我们基本确定这个不是几分钟能解决的问题,于是我们分成两步去处理。第一步:打开限流,防止更多的慢sql请求进行 第二步:分析慢sql,进行改造上线 查看慢SQL,⼤部分都是融合系统分⻚查询接⼝涉及到的SQL,同时由于上游系统在15:35左右对于该接⼝调⽤流量激增,和数据库CPU暴涨,接⼝TP999暴涨的时间吻合,推测是由于库存对于该接⼝的调⽤对于数据库造成了压⼒,导致接⼝耗时增加。但是该接⼝的调⽤量并不⾼,再次查看慢SQL,发现有⼤量已经遍历到⼏百⻚的慢SQL。推测是深分⻚的问题。

  • 16:15 排查⽇志发现,⼤部分SQL都指向商家xxxx,查询发现其下有10W条数据(占⽤总数量的⼗分之⼀),MQ发现有⼤量重试,分⻚查询接⼝超时时间发现配置的是2S。推测是慢查询导致的⾼频次重试将数据库的性能拖垮。
  • 16:25 观察代码后,确定了是深分⻚问题,确定下来了优化⽅案。为了避免库存修改接⼝,⾸先我们优化SQL将其优化为⼦查询的形式。即先通过pageNo和pageSize查询出ID,然后取出当中的最⼩值和最⼤值,然后使⽤范围查询去查询出来全表数据。由于线上持续对数据库造成压⼒,先让上游把MQ的消费暂停消费。
  • 17:40 优化代码上线,上游重新打开MQ消费,但是由于消费积累的消息⽐较多,直接打开后,还是对融合数据库造成了压⼒。接⼝的TP99再次飙升,数据库CPU再次飙到100%。
  • 18:00 复盘了下,决定不再优化旧接⼝,⽽是开发新接⼝,基于滚动ID进⾏分⻚查询。需要推动上游⼀起参与开发和联调。
  • 22:20 新接⼝上线,重新放开MQ消费,上游积压了⼤量消息的情况下,新接⼝表现平稳,“问题解决”

图片

问题原因和解决⽅法

深分⻚出现原因

问题SQL:

select * from table where org_code = xxxx limit 1000,100

以上⾯的SQL为例,MySQL的limit⼯作原理就是先读取前⾯1000条记录,然后抛弃前1000条,读后⾯100条想要的,所以⻚码越⼤,偏移量越⼤,性能就越差。

深分⻚的⼏种解决⽅法

查询ID+基于ID查询

即先使⽤查询条件查询出来id,再通过id进⾏范围查询,也就是说我第⼀次优化的时候使⽤的⽅法 ⾸先查询出来ID,以上⾯的SQL为例

select id from table where org_code = xxxx limit 1000,5

然后查询出来id后,使⽤id进⾏in查询,由于是直接基于主键的in查询,所以效率较⾼

select * from table where id in (1,2,3,4,5);

基于ID查询优化

由于在第⼀次查询已经查询出来了所有符合条件的ID了,可以使⽤范围查询来替代in查询,效率更⾼(in 查询需要和集合⾥⾯的元素进⾏⽐对,但是范围查询只需要⽐较最⼤和最⼩即可)

select * from table where org_code = xxxx and id >= 1 and id <= 5;

使⽤⼦查询

select a.id,a.dj_sku_id,a.jd_sku_id from table a join (select id from

jd_spu_sku where org_code = xxxx limit 1000,5) b

on a.id = b.id;

使⽤⼦查询可以减少和数据库的IO交互,也是⼀种常⽤的解决深分⻚的⽅法。

使⽤滚动查询

每次接⼝都会返回查询出来的数据的最⼤的id(游标),下⼀次查询传⼊这个游标,服务端只需要根据这个游标,取出id⼤于这个游标的n个数据即可。n为每⻚展示条数。

select * from table where org_code = xxxx and id > 0 limit 10;

这种⽅式服务端实现起来⽐较简单且性能很好。缺点是需要客户端修改,且需要保证ID是⾃增有序且结果需要是按照ID排序的。最终定下的是使⽤滚动查询的⽅法。最终优化SQL上线后,表现平稳。第⼆周和库存⼀起重新优化了⾮多规格SKU的SQL。如下:

SELECT id,dj_org_code,dj_sku_id,jd_sku_id,yn FROM table where

org_code = xxxx and id > 0 order by id asc limit 500

测试了没问题后上线。观察线上监控稳定。本以为⾼枕⽆忧的时候,⼀周之后,数据库再次出现了⼤量的慢查询,数据库CPU报警,观察接⼝监控:

图片

可以看到在调⽤量并不⼤的前提下,接⼝的耗时达到了60S。联系运维同学帮忙排查,发现了⼤量的慢 SQL:

SELECT id,dj_org_code,dj_sku_id,jd_sku_id,yn FROM table where

org_code = xxxx and id > 0 order by id asc limit 500

可以看出来,这就是我们优化后的SQL。运维同学explain这条sql后发现,这条SQL⾛了主键索引,没有⾛我们以为应该要⾛的org_code的索引。

图片

和运维初步沟通后得出结论,在某些情况下,主键索引的优先级是会⾼于普通索引的。

最终解决方案

引用join

因为我们使⽤了主键索引进⾏排序,且查询了不在索引树只在叶⼦节点中的字段。因此mysql认为主键索引更优,因为既可以排序,⼜不⽤回表,所以就使⽤主键索引最终导致了全表扫描。

最终使⽤了先查询ID(不查询叶⼦节点字段保证使⽤索引),在通过join,使⽤查询出来的ID来查询对应的数据的SQL:

select a.id AS id,a.dj_org_code AS djOrgCode,a.dj_sku_id AS

djSkuId,a.jd_sku_id AS jdSkuId,a.yn AS yn from

table a join

(

SELECT id FROM table where org_code = xxxx and id > 0 order

by id asc limit 500

) t on a.id=t.id;

再次explain了下,可以发现⾛了我们既定的索引:

图片

于是上线,解决问题。上线稳定后,分析之前的问题SQL,执⾏下⾯两条语句,同样的SQL,不同的商家,MYSQL的执⾏结果也是不⼀样的

图片

查询资料找原因

查阅资料得知

  • MYSQL会将limit的数量和where条件⾥查询出的数量进⾏⽐对,如果limit数量占⽐较⼩ (例如某些商家的sku数⽬⽐较多),则会"优化"为主键索引,因为MYSQL此时认为⾛主键索引会减少 ⼀次索引树的查询,且可以在较短时间⾥⾯得到结果。(没有LIMIT不会⾛主键索引)
  • 因此在where 索引A order by 主键索引 limit N的这种SQL,需要考虑MYSQL优化主键索引的情况。
  • 除了上⾯最终上线后的优化SQL,也可以通过force index强制使⽤索引:
SELECT id,dj_org_code,dj_sku_id,jd_sku_id,yn FROM table force

index(idx_upc) where org_code = xxxx and id > 0 order by id asc limit

500

但是这种写死了索引名称的⽅式,如果以后修改了索引名,容易导致安全隐患。

问题总结

  • B端系统也需要考虑对⾃⼰系统的保护,接⼊限流等,防⽌异常流量或者异常调⽤把⾃⼰的系统调死。这次幸亏上游系统是通过MQ调⽤融合API的,可以暂停消费,如果是⽤API调⽤,且流量较⼤,持续让数据库处于⾼压状态,会影响到融合系统的整体稳定性。
  • 针对可能出现的⻛险点绝不姑息。这次这个分⻚查询sku的接⼝,之前就看到过,但是当时觉得这个接⼝在数据量较少的情况下性能也还好,⽽且也有了商家维度的索引,就放过了,考虑后续优化。结果现在就爆出了问题。
  • 针对SQL的优化,上线前要谨慎,⽽且需要同⼀条SQL,需要针对不同的边界情况(例如这次的多SKU的商家)进⾏反复测试,调整。

标签:sku,code,处理过程,分页,查询,索引,SQL,id
From: https://www.cnblogs.com/testzcy/p/17645621.html

相关文章

  • Git分支查询
    要查看Git仓库中的分支信息,可以使用以下命令:查看本地分支:gitbranch这将列出所有本地分支,并在当前分支前添加一个星号。查看远程分支:gitbranch-r这将列出所有远程分支。查看所有本地和远程分支:gitbranch-a这将列出所有本地和远程分支,包括隐藏的分支。查看包括最后一次提交信......
  • ora.erp61.vip FAILED OVER 或者故障节点vip地址查询不到了
    [root@erp61~]#crsctlstatusres-t--------------------------------------------------------------------------------NAMETARGETSTATESERVERSTATE_DETAILS-------------------------------------------------------......
  • Oracle数据库经纬度坐标查询优化与结果错误原因分析、SQL中WKT超长文本字符串处理
    目录一、Oracle几何空间数据对象和其他数据库的差异二、Oracle查询一个经纬度坐标是否在边界内部2.1查询条件2.2查询结果错误,似乎是仅做了MBR匹配2.3错误原因2.4解决办法三、SQL中WKT超长文本在Oracle中如何编写3.1Oracle中执行含超长文本的SQL报错3.2使用CLOB无限拼接得到......
  • sqlite 实现分页排序
    版本号MacOSAppleM1|Jdk17|Maven3.8.5|SpringBoot2.6.9|内嵌式Sqlite3.42.0.0Pageable使用方式findAll()importorg.springframework.data.domain.Page;importorg.springframework.data.domain.PageRequest;importorg.springframework.data.domain.Pageabl......
  • MySQL中查询和事务的大小
    有时候了解事务的大小非常重要,尤其是当计划迁移到HA环境,为了保证集群的最佳性能,事务的大小是有限制的。这里来尝试分析一下了解事务大小的不同方法。首先要将事务分成两种类型:1.生成数据的事务(写操作,比如insert、delete、update等DML操作)2.只读的事务(查询操作)在HA环境,第一......
  • 一次Greenplum 查询性能优化
    最近接手一个实际查询调优的活儿,对方说Greenplum的性能太弱了,于是按网上教程调整了许多参数。当然,有些有点儿用,有些没什么用。于是几经周折。我们首先做了硬件设备的性能测试,就用Greenplum自带的工具进行:gpcheckperf-fall_segments-S512G-d/gpdata因为偷懒,就没测那么大。......
  • PHP查询MySQL 数据库后返回中文为问号
    面向对象1$conn=newmysqli($servername,$user,$password);3增加$conn->query("setnamesutf8");$pdo=newPDO("mysql:host=$servername",$username,$password);增加$pdo->query("setnamesutf8");  面向过程$conn=mysql......
  • mybatis设置命名格式转换 与 批量插入更新&select查询返回自定义实体类 的sql写法
    在mybatis的配置文件中设置了Java实体类驼峰命名与表属性下划线命名的自动转换。在mybatis中,从接口获取到大量数据之后,将数据集合分批量插入更新到表中。在mybatis中,select查询表数据,返回数据的存储类型为自定义的实体类。1.设置Java实体类驼峰命名与表属性下划线......
  • 8.14-8.20学习总结博客五:Hive进阶与复杂查询
    博客题目:学习总结五:Hive进阶与复杂查询实践内容概要:学习Hive进阶的使用方法,包括复杂查询、数据转换和性能优化等方面的知识。学习资源:推荐的Hive进阶教程、实践案例和性能优化技巧。实践内容:通过编写复杂的Hive查询语句,探索Hive的高级功能和性能优化方法,并分享实践中的挑战和解决......
  • 剖析MongoDB数据库:理解NoSQL设计模式、优化查询性能和数据安全性
    MongoDB是一个流行的NoSQL文档数据库,它使用JSON样式的文档存储数据。本文将对MongoDB进行剖析,包括NoSQL设计模式、优化查询性能和数据安全性。NoSQL设计模式文档数据库MongoDB采用文档数据库的设计模式,即将相关数据保存在单个文档中,而不是将其拆分成多个表。这种设计模式使得数......