数据库并发能力受限往往不是计算资源不够,而是存储卷的队列深度先把请求堵在了门外。对多数中低端存储卷来说,队列深度不足导致的IO排队延迟,比磁盘本身速度不够更隐蔽且更致命。
存储卷队列深度不足怎么排查
当数据库出现并发提升后性能骤降的情况,很多人第一反应是加CPU或扩内存,但有一种更典型的场景:数据库的连接数从100提升到300,CPU利用率只到40%,内存余量充足,应用却开始大面积超时,这个状态下,判断瓶颈方向比盲目扩容更关键。
并发压测时最先暴露的三个信号
- 平均延迟与峰值延迟严重脱节,比如IO平均延迟不到5ms,p99却飙到200ms以上
- 数据库大量会话处于IO等待事件,在Oracle中表现为
db file sequential read,在MySQL中表现为waiting for handler commit - 存储卷的利用率看起来不高,吞吐量远低于标称值,但请求响应时间持续恶化
这三个信号同时出现,队列深度的嫌疑最大,存储卷队列深度决定了它同时能处理多少个IO请求,并发一旦超出这个数值,多出来的请求就进入排队状态。
用系统工具定位队列瓶颈
第一步,在数据库服务器上观测实际IO等待,Linux系统下,iostat -x 1输出中的avgqu-sz字段就是活动队列长度,如果该值长期接近甚至大于存储卷的设定深度,说明请求一直在排队。
第二步,用fio做一个简单的只读测试,命令参考:
fio -name=qd_test -rw=randread -bs=4k -iodepth=32 -numjobs=1
-direct=1 -group_reporting -time_based -runtime=60
对比iodepth=32和iodepth=1的IOPS差异,如果深度从1提高到32的收益很小,问题在存储卷后端;如果收益放大明显,说明现有工作负载需要更高队列深度才能跑满性能。
数据库侧等待事件与存储指标的对照
行业共识认为,数据库侧的IO等待事件与存储队列指标存在直接对应关系,观察两者的关联变化,就能确认瓶颈。
- 数据库侧IO平均等待时间持续上升,存储侧
接近上限avgqu-sz
- 数据库并发线程数增加,存储侧
util不增长但await明显变大 - 存储侧IOPS和带宽都未达上限,但安全组的超时重试开始增多
这个时候可以基本确定,问题的根源是存储卷队列深度限制了并发处理能力,而不是磁盘老化或网络带宽不足。
队列深度与数据库并发之间的作用机制
存储卷队列深度的本质,是存储设备驱动层和存储控制器端能挂起的未完成IO请求数量,数据库每一条SQL在执行过程中,会产生多次物理读、日志写和检查点刷盘操作,这些操作在数据库内是独立的异步请求,发到存储层之后却要按队列深度排队处理。
从应用提交到存储落盘的完整链路
一条更新语句从进入到提交,要经过这样一个链路:
- InnoDB把数据页修改写入redo log buffer
- 事务提交时触发redo日志写盘操作
- 后台线程把脏页刷入数据文件
- 每个写操作都生成一个块设备层IO请求
块设备层把这些请求交给存储驱动的队列,如果队列里已经挂满了待处理请求,新到的请求就只能等待空闲槽位,表现出来就是延迟瞬间拉高。
队列深度和并发数之间到底什么关系
数据库并发数描述的是同时执行的会话数量,队列深度描述的是存储设备同时处理的IO数量,两者不是一一对应的关系,一个会话的一条SQL可能发起多个IO请求,多个会话的IO请求也可能被合并成更少的队列条目。
但总体趋势是一致的:并发越高,生成的总IO请求就越多,对队列深度的需求也就越大,当数据库活跃会话数远大于存储队列深度时,绝大部分IO请求都在排队,而不是真正被磁盘执行。
低队列深度场景下的实际表现
想象一个只能同时处理两个请求的存储卷,来了四个并发写入请求,两个先处理,两个排队,排队的两个不仅等待时间翻倍,还会占用数据库的会话线程,数据库连接池里的线程被IO等待占满后,新的SQL无法获得连接,整个应用的吞吐量直接断崖下跌。
这也是为什么很多数据库性能下降排查最后发现,加了连接池数量反而更慢,因为更多连接意味着更多并发IO请求涌向同一个受限队列。
数据库高并发场景下队列深度设置多少合适
没有一个严格通用的数值,但存储介质和工作负载类型给出了清晰的参考范围,SSD队列深度怎么调,取决于设备使用的是SATA协议还是NVMe协议,以及存储阵列的架构。
不同存储介质的最佳深度参考
| 存储介质 | 推荐队列深度范围 | 说明 |
|---|---|---|
| 机械硬盘HDD | 16-32 | 过高的深度只会增加排队,物理寻道时间无法通过并发消除 |
| SATA SSD | 32-64 | SATA协议本身的命令队列上限是32,超出无意义 |
| NVMe SSD | 64-128 | 标准NVMe设备支持极高的深度,但数据库场景不需要无限拉高 |
| 全闪存储阵列 | 128-256 | 阵列前端有缓存聚合,较高的深度有助于吸收并发峰值 |
NVMe设备的命令队列最高可以支持到65535,但数据库场景下不是越大越好,深度过高会导致单个请求的延迟变大,因为设备调度器可能倾向于让队列保持忙碌而不是优先处理每个请求。
常见数据库场景的推荐配置
- OLTP高并发小IO场景,核心是降低单次请求延迟,队列深度设置在64以内比较合理,配合
none或noop调度器效果更好 - OLAP批量扫描场景,单请求数据量大,队列深度可以放到128以上,让存储设备有足够的并发来流水线处理
- 数据库日志文件所在的存储卷,建议独立设置,深度不低于数据文件的1.5倍,因为日志写入对延迟极敏感
一套可以复用的调整步骤
第一步,确认当前设备使用的IO调度器,在高性能SSD和NVMe设备上,推荐使用none调度器,减少不必要的排队和合并操作。
echo none > /sys/block/nvme0n1/queue/scheduler
第二步,调整存储卷的系统级队列参数。
nr_requests值可以在块设备层控制排队上限,默认值一般是128,如果确认是队列深度不足导致的问题,可以适当提高到256或512。
echo 512 > /sys/block/sda/queue/nr_requests
第三步,修改完成后用fio重新做压测验证,对比调整前后的IOPS和p99延迟,单次调整后观察数据库侧等待事件的变化,确认有效后再灰度应用到全部节点。
调大队列深度不是万能的
业内专家指出,增大队列深度只解决队列瓶颈,不能弥补后端存储能力的不足,多数情况下,调大队列深度后延迟不降反升,正好说明问题出在物理层而不是队列容量。
物理盘饱和:最大队列也救不了
存储介质的物理处理能力是硬上限,一块SATA SSD的随机写能力大约就是几千IOPS,当物理盘已经满负荷运转时,把队列深度从64调到128只意味着更多请求在排队,每个请求的等待时间更长,最终表现为更严重的性能下降。
判断是否物理饱和的简单方法:调大队列深度后,存储卷的吞吐量没有明显提升,延迟反而明显增加,这种情况说明存储后端的能力已经见顶,需要换更高规格的存储卷,而不是调参数。
存储阵列和Hypervisor层的人工限制
部分存储阵列在管理端对单卷的IOPS和队列深度做了QoS限制,这种限制不体现在服务器侧的设备参数上,服务器把队列调整为很大,但存储阵列端仍然按限制值执行。
这种情况下,需要登录存储管理界面,检查卷的QoS策略配置,如果发现存在限流,调整策略或升级存储服务配额才能真正解决问题。
应用层连接池和事务粒度的影响
另一种常见误判是:存储层参数已经调到最优,数据库并发还是上不去,此时需要回头审视应用层的连接池配置和事务粒度,一个连接池最大连接数为10的微服务实例,即使底层存储可以支撑上千并发IO,数据库收到的并发请求也就只有10个。
排查存储问题时,把应用层的连接池数量、事务内执行耗时也纳入分析范围,避免在存储层花费大量精力却解决不了根因。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/638995.html





