写点什么

哪些因素会导致 MySQL 慢查询

  • 2020-05-07
  • 本文字数:5512 字

    阅读完需:约 18 分钟

哪些因素会导致 MySQL 慢查询

前言

不管是开发同学还是 DBA,想必大家都遇到慢查询(select,update,insert,delete 语句慢),影响业务稳定性。这里说的 ,有两个含义一是 比正常的慢 ,有可能正常执行时间是 10ms,异常的是 100ms 。二是 sql 执行时间超过设置的慢查询标准 比如 500ms。


本文从 IT 架构以及数据库纬度来分析导致 sql 执行慢的原因/场景,抛砖引玉,有不足之处还请大家多多提建议。

二、基础知识

分析慢查询之前,我们先看看 sql 执行的路径,理清楚可能会影响 sql 执行速度的相关因素。

执行路径

app —[proxy]—db


app — db


目前大部分的数据库架构基本都是上面的路径,sql 从 app 的应用服务器发起经过 proxy 然后到 db,db 执行 sql 进过 proxy 或者直接返回给 app 应用服务器。分析这个过程我们可以得到几个会影响 sql 执行速度的因素


1 网络,各个节点之间的网络2 OS系统 ,即数据库服务器3 MySQL数据库本身
复制代码

三、基础系统层面

3.1 网络层面

网络丢包,重传

其实这个比较容易理解。当 sql 从 app 端发送到数据库,执行完毕,数据库将结果返回给 app 端,这个将数据返回给 app 端的过程本质是网络包传输。因为链路的不稳定性,如果在传输过程中发送丢包会导致数据包重传,进而增加数据传输时间。从 app 端来看,就会觉得 sql 执行慢。



(图来自)

网卡满 比如大字段

这个场景可能不容易遇到,如果公司业务体量很大,比如平时每天 300w 订单的电商平台,平台大促(双十一,618)的时候极有可能出现网卡被打满。网卡带宽被占满类似各种节假日高速公路收费站(网卡)拥堵导致车流(数据包传输的速度)行动缓慢。



网络链路变长


该场景会影响应用纬度的一个事务比如交易下单整体耗时。


我们知道每个节点之间的数据传输是需要时间的,比如同城跨机房(15KM)之间的访问一般网络耗时 1.5ms 左右。


链路 1 [app1]–调用–[app2]—[proxy]—[db] 相比 链路 2[app1] – [proxy] --[db]


执行一条 sql 请求会增加 [app1]–[app2]之间的网络传输耗时大约 3ms。如果一个业务事件包含 30 个 sql ,那么链路 1 要比链路 2 多花至少 90ms 的时间成本。导致业务整体变慢。

3.2 受到影响 IO 的场景

磁盘 io 被其他任务占用

有些备份策略为了减少备份空间的使用,基于 xtrabckup 备份的时候 使用了 compress 选项将备份集压缩。当我们需要在数据库服务器上恢复一个比较大的实例,而解压缩的过程需要耗费 cpu 和占用大量 io 导致数据库实例所在的磁盘 io 使用率 100%,会影响 MySQL 从磁盘获取数据的速度,导致大量慢查询。

raid 卡 充放电,raid 卡重置

RAID 卡都有写 cache(Battery Backed Write Cache),写 cache 对 IO 性能的提升非常明显,因为掉电会丢失数据,所以必须由电池提供支持。电池会定期充放电,一般为 90 天左右,当发现电量低于某个阀值时,会将写 cache 策略从 writeback 置为 writethrough,相当于写 cache 会失效,这时如果系统有大量的 IO 操作,可能会明显感觉到 IO 响应速度变慢,cpu 队列堆积系统 load 飙高。下面是一个 raid 充放电导致 sql 慢查的案例。


root@rac1#megacli -FwTermLog dsply -aALL11/08/143:36:58: prCallback: PR completed for pd=0a11/08/143:36:58: PR cycle complete11/08/143:36:58: EVT#14842-11/03/12 3:36:58: 35=Patrol Read complete11/08/143:36:58: Next PR scheduled to start at 11/10/123:01:5911/08/140:48:04: EVT#14843-11/04/12 0:48:04: 44=Time established as 11/04/12 0:48:04; (25714971 seconds since power on)11/08/1415:30:13: EVT#14844-11/05/12 15:30:13: 195=BBU disabled; changing WB virtual disks to WT ---问题的原因11/08/1415:30:13: Changein current cache property detected for LD : 0!11/08/1415:30:13: EVT#14845-11/05/12 15:30:13: 54=Policy change on VD 00/0 to [ID=00,dcp=0d,ccp=0c,ap=0,dc=0,dbgi=0,S=0|0] from [ID=00,dcp=0d,ccp=0d,ap=0,dc=0,dbgi=0,S=0|0]
复制代码


raid 卡充电 将磁盘的写策略有 write back 修改为 write through ,io 性能急剧下降导致 sql 慢查,进而影响应用层的逻辑处理。


raid 卡重置 当 raid 卡遇到异常时,会进行重置,相当于程序重启,导致系统 io hang。此时也会导致 sql 慢。下图是生产中遇到的 RAID 卡重置案例。



io 调度算法

noop(电梯式调度策略):


NOOP 实现了一个 FIFO 队列,它像电梯的工作方式一样对 I/O 请求进行组织,当有一个新的请求到来时,它将请求合并到最近的请求之后,以此来保证请求同一个介质。NOOP 倾向于饿死读而利于写,因此 NOOP 对于闪存设备,RAM 以及嵌入式是最好的选择。


deadline(介质时间调度策略):


Deadline 确保了在一个截至时间内服务请求,这个截至时间是可调整的,而默认读期限短于写期限。这样就防止了写操作因为不能被读取而饿死的现象。Deadline 对数据库类应用是最好的选择。


anticipatory(预料 I/O 调度策略):


本质上与 Deadline 一样,但在最后一次读操作后,要等待 6ms,才能继续进行对其他 I/O 请求进行调度。它会在每个 6ms 中插入新的 I/O 操作,而会将一些小写入流合并成一个大写入流,用写入延时换取最大的写入吞吐量。AS 适合于写入较多的环境,比如文件服务器,AS 对数据库环境表现很差。

3.3 cpu 类型

cpu 电源策略是控制 cpu 运行在哪种模式下的耗电策略的,对于数据库服务器推荐 最大性能模式 以下内容摘自 《Red Hat Enterprise Linux7 电源管理指南》:



 https://access.redhat.com/documentation/zh-cn/red_hat_enterprise_linux/7/pdf/power_management_guide/Red_Hat_Enterprise_Linux-7-Power_Management_Guide-zh-CN.pdf
复制代码


指令集 最近遇到的一个性能案例是 hw 的机器,因为指令集合默认关闭导致性能下降 15%。



https://support.huawei.com/enterprise/zh/doc/EDOC1000039566/c2662e35
复制代码


自己对 CPU 并不精通,所以这里的 2 个点并非 CPU 优化配置的全部,自建机房的运维朋友依赖官方技术支持的建议或者技术资料的指导来设置 cpu 相关参数。

四、数据库层面

4.1 没有索引,或者索引不正确

这个场景其实比较容易理解。相信每个 DBA 工作过程中都会或多或少遇到性能案例都和索引设计有关:创建表,没有索引,sql 随着数据量增大全表扫描而变慢。这个就不额外举例子了。

4.2 隐式转换

发生隐式转换时,MySQL 选择执行计划并不能利用到合适的索引而是选择全表扫描导致慢查询。常见的引发隐式转换的场景如下:


in 参数包含多个类型, 简单说,就是在 IN 的入口有一个判断, 如果 in 中的字段类型不兼容, 则认为不可使用索引. 例如 --图

判断符号左边是字符串,右边是数字 ,比如 where a=1;其中 a 是字符串

多表 join 时,where 左右两边的字段的字符集类型不一致。


推荐阅读《聊聊隐式转换

4.3 执行计划错误

由于 MySQL 优化器本身的不足,选择执行计划时会导致错误的执行计划使 sql 走了错误的索引或者没有做索引。比如


在检查某业务数据库的 slowlog 时发现一个慢查询,查询时间 1.57s ,检查表结构 where 条件字段存在正确的组合索引,正确的情况下优化器应该选择组合索引,而非为啥会导致慢查询呢?


  root@rac1 10:48:11>explain select id,gmt_create, gmt_modified,order_id,service_id, seller_id,seller_nick, sale_type from lol where seller_id= 1501204and service_id= 1and sale_type in(3, 4) and use_status in(3, 4, 5, 6) and process_node_id= 6 order by id desc limit 0,20 \G*************************** 1. row ***************************           id: 1  select_type: SIMPLE        table: lol         type: indexpossible_keys:idx_sellerid,idx_usestatus_saletype,idx_sellerid_saletype,idx_sidustsvidtype          key: PRIMARY  --- 应该选择 idx_sidustsvidtype      key_len: 8ref: NULL         rows: 3076Extra: Usingwhere1 row inset (0.00 sec)
复制代码


推荐阅读《order by 主键id导致全表扫描的问题

4.4 数据巨大

比如 select count(*) from t1 where a=‘xxxx’; 尽管字段 a 有索引,但是如果 符合条件的记录数超高 10w ,查询速度还是会比较慢。


select  count(*) from t1 where app = 'marketing';+----------+| count(*) |+----------+|  2671690 |+----------+1 row inset (0.92 sec)
复制代码

4.5 MetaData Lock 锁等待 ****

MDL 锁这个场景其实蛮多案例的,比如 ddl 开始时,针对同一个表的长查询还没结束,后续的写操作都会被堵住导致 thread running 飙高。实例整体的 sql 执行慢。


案例一 长查询/mysqldump 阻塞 DDL



未提交事务阻塞 ddl 阻塞查询



推荐阅读《MetaData Lock 之三

4.6 并发更新同一行

常见的秒杀场景:数据库并发执行 update,更新同一行的动作会被其他已经持有锁的会话堵住,并且需要要进行判断会不会由于自己的加入导致死锁,这个时间复杂度 O(n),如果有 1000 个请求,每个线程都要检测自己和其他 999 个线程是否死锁。如果其他线程都没有持有其他锁,约比较 50w 次(计算方式 999+998+…+1)。这个种锁等待和检查死锁冲突带来巨大的时间成本。对于 OLTP 业务高并发大流量访问的情况下,锁等待会直接导致 thread running 飙高,所有的请求会被阻塞并等待 innodb 引擎层处理,于是 sql 会变慢。


推荐阅读《热点更新优化方案

4.7 数据分布不均

其实和数据分布相关,常见的比如 字段 a 是标记状态 0,1,总行数 1000w,a=0 的值大概几千条,a=1 的有 999w 多。显然执行


select count(*) from tab where a=1 ;


的查询效率肯定比查询 a=0 的要慢很多。


select count(*) from tab where a=0 ;

4.8 sql 姿势不合理

常见的分页查询 ,使用大分页深度查询。


SELECT * FROM table where kid=1342 and type=1 order id desc limit 149420 ,20;


该 SQL 是一个非常典型的排序+分页查询:order by col desc limit N,M MySQL 执行此类 SQL 时需要先扫描到 N 行,然后再去取 M 行。对于此类操作,取前面少数几行数据会很快,但是扫描的记录数越多,SQL 的性能就会越差,因为 N 越大,MySQL 需要扫描越多的数据来定位到具体的 N 行,这样耗费大量的 IO 成本和时间成本。


针对 limit 优化有很多种方式:


1 前端加缓存、搜索,减少落到库的查询操作。比如海量商品可以放到搜索里面,使用瀑布流的方式展现数据,很多电商网站采用了这种方式。

2 优化 SQL 访问数据的方式,直接快速定位到要访问的数据行。

3 使用书签方式 ,记录上次查询最新/大的 id 值,向后追溯 M 行记录。对于第二种方式 我们推荐使用"延迟关联"的方法来优化排序操作,何谓"延迟关联" :通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据。


推荐阅读《性能优化之分页查询

4.9 表结构设计

表结构设计是否合理也是影响 sql 性能的重要因素之一。以下表格展示了字段类型不同带来的 rt 性能差异。其中字段 c1 为 int 类型的字段,字段 c2 则是表名对应的字符串长度类型 varchar(200)到 varchar(5000) ,还有 text 字段。



对于读请求,单独查询 c1 int 类型的性能并无差异。查询字段 c2 时,随着字段占用的实际字节大小增大,耗费的时间增加,也即 rt 增大。带宽逐步增大,text 的带宽 147MB 对于千兆网卡已经满了。



对于写请求,因为 binlog 为 row 模式,字段长度越大,binlog 也越大,网络传输带宽增加。整体 rt 也增加。

4.10 innodb 刷脏页

对数据库运行机制有一定了解的朋友都会知道 InnoDB 引擎采用 Write Ahead Log(WAL)策略,即事务提交时,先写日志(redo log),再写磁盘。 为了提高 IO 效率,在写日志的时候会先写 buffer,然后集中 flush buffer pool 到磁盘。 这个过程 我们称之为 刷脏页 。官方文档中描述:


With heavy DML activity, flushing can fall behind if it is not aggressive enough, resulting in excessive memory use in the buffer pool; or, disk writes due to flushing can saturate your I/O capacity if that mechanism is too aggressive. 这个过程中就有可能导致平时执行很快的 SQL 突然变慢。


推荐阅读:


https://dev.mysql.com/doc/refman/5.7/en/innodb-lru-background-flushing.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-performance-adaptive_flushing.html
复制代码

4.11 undo 没有被 purge/回收

UNDO 日志是 MVCC 的重要组成部分,当一条数据被修改时,UNDO 日志里面保存了记录的历史版本。当事务需要查询记录的历史版本时,可以通过 UNDO 日志构建特定版本的数据。




每条行记录上面都有一个指针 DATA_ROLL_PTR,指向最近的 UNDO 记录。同时每条 UNDO 记录包含一个指向前一个 UNDO 记录的指针,这样就构成了一条记录的所有 UNDO 历史的链表。当 UNDO 的记录还存在,那么对应的记录的历史版本就能被构建出来。

当记录对应的版本通过 DATA_TRX_ID 比对发现不可见时,通过系统列 DATAROLLPTR,找到对应的回滚段记录,继续通过上述判断记录可见的规则,进行判断,如果记录依旧不可见,继续通过回滚段查找之前的版本,直到找到对应可见的版本。


所以当有长事务/异常未提交的情况就会因为其他查询需要构建快照导致 undo 不能被及时回收。查询遍历的 undo 越多 sql 执行的越慢。


推荐阅读《一次大量删除导致 MySQL 慢查的分析

五、小结

这里总结了我工作经历中遇到的一部分可能会影响 SQL 执行效率的场景或者案例,经历有限,难免有遗漏的案例/场景,抛砖引玉,欢迎各位有兴趣的读者朋友留言说说你们遇到的场景。


本文转载自公众号有赞 coder(ID:youzan_coder)。


原文链接


https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA==&mid=2455760748&idx=1&sn=67115eba10829ca3fb92da2c40c07307&chksm=8c686949bb1fe05f21d2b6e39674d0b35dadfb070fa7641217efc771f5db759866ba3da5a022&scene=27#wechat_redirect


2020-05-07 14:055217

评论

发布
暂无评论
发现更多内容

会声会影剪辑视频教程讲解

懒得勤快

JAVA 那些事 - 聊聊那些易混淆的概念:JVM/JRE/JDK,openJDK/oracleJDK,JAVA SE/JAVA EE/Jakarta EE

明哥的IT随笔

jdk Openjdk Java EE

Web Components 系列(八)—— 自定义组件的样式设置

编程三昧

前端 组件化 2月月更 WebComponent

解构流存储 — Pravega,与 Flink 构建端到端的大数据流水处理线

Apache Flink

大数据 flink 开源 编程 实时计算

K8s Ingress Provider 为什么选择 MSE 云原生网关?

阿里巴巴云原生

阿里云 Kubernetes 容器 微服务网关 云原生网关

独家交付秘籍之招式拆解(第一回)

阿里巴巴云原生

阿里云 云原生 应用交付

平安科技从 Oracle 迁移到 UbiSQL 的实践

PingCAP

服务网格在联通的落地实践

百度开发者中心

技术人聊开源:这并不只是用爱发电

SOFAStack

开源

FinClip 的 2021 与 2022

FinClip

产品设计与思考

敏捷开发流程的8个步骤

爱吃小舅的鱼

跨平台应用开发进阶(一) :走近 uni-app

No Silver Bullet

uni-app 跨平台 实战 2月月更

Go 语言入门很简单:基准测试

宇宙之一粟

Go 语言 2月月更

网络安全之小程序抓包渗透测试流程

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 安全漏洞

Ti-Click:通过浏览器快速搭建 TiDB 在线实验室 | Ti-可立刻团队访谈

PingCAP

云效Flow如何实现阿里云ECS多环境发布

阿里云云效

阿里云 运维 云原生 软件开发 研发

[建造者模式实战]如何用JAVA实现一个基于POI的复杂表格导出工具类?

山河已无恙

Java 2月月更

《新程序员》杂志|李鹏辉谈开源云原生消息流系统

Apache Pulsar

开源 架构 云原生 中间件 Apache Pulsar

焱融科技加入中国通信工业协会人工智能专业委员会,共创数智新未来

焱融科技

人工智能 云计算 分布式 高性能 文件存储

边缘计算加速视频直播场景:更清晰、流畅、实时

火山引擎边缘云

边缘计算 视频直播 异构算力

数智时代,谁都做平台,谁都做生态!这行吗?

海比研究院

网络安全kali渗透学习 web渗透入门 DDOS的原理和防护

学神来啦

火山引擎 MARS-APMPlus X 美篇 | 形成应用性能全面监控,大幅提升APP稳定性

字节跳动终端技术

字节跳动 APM 性能监控 应用性能监控产品 火山引擎MARS

使用Perf和FlameGraph进行系统性能分析

wong

flamegraph perf

Spring Boot Serverless 实战系列 | 性能调优

阿里巴巴云原生

spring 阿里云 Serverless 云原生 框架

恒源云(GpuShare)_AIphaCode是否能取代程序员?

恒源云

深度学习 AI transformers

车联网平台搭建从入门到精通 01|车联网场景中的 MQTT 协议

EMQ映云科技

车联网 物联网 通信 mqtt 平台搭建

GPU在Kubernetes中的使用与管理 | 社区征文

大菠萝

新春征文

11种绕过CDN查找真实IP方法

喀拉峻

网络安全

EdgeDB 架构简析

CRMEB

数据分析实际案例之:pandas在泰坦尼特号乘客数据中的使用

程序那些事

Python 数据分析 pandas 程序那些事 2月月更

哪些因素会导致 MySQL 慢查询_数据库_杨一_InfoQ精选文章