You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure SQL出现PAGEIOLATCH_SH阻塞,Spring+Hibernate连接池耗尽求助

问题分析与排查方向

结合你提供的信息(连接全卡在同一条JOIN语句、等待类型PAGEIOLATCH_SH、高逻辑读、大索引、弹性池资源监控表面正常),核心矛盾是大量并发的IO等待导致连接池耗尽,以下是具体原因推测和排查方向:

可能原因及对应排查点

1. 语句执行计划低效,触发大量物理IO

PAGEIOLATCH_SH本质是等待磁盘数据加载到内存,你的语句逻辑读达到798万+,如果缓存命中率低,会直接引发持续的IO等待。

  • 排查操作:
    • 执行SET STATISTICS IO ON后运行目标JOIN语句,查看physical_reads(物理读)、read_ahead_pages(预读页)的具体数值,确认是否存在大量物理IO。
    • 细致分析执行计划:检查是否存在全表扫描/大范围索引扫描(尤其是16GB的大索引),是否有可优化的JOIN顺序或索引覆盖方案。
    • 检查涉及表的数据分布:如果存在大量长期未被访问的冷数据,这类数据不会在缓冲池中留存,每次查询都需要从磁盘重新加载。

2. 数据库缓冲池内存不足,缓存页频繁被驱逐

即使弹性池的CPU/IO监控正常,数据库的缓冲池可能被其他查询或系统进程占用,导致目标语句依赖的数据无法被稳定缓存,反复触发磁盘读取。

  • 排查操作:
    • 查询缓冲池缓存情况:
      SELECT 
          OBJECT_NAME(p.object_id) AS table_name,
          i.name AS index_name,
          COUNT(*) AS cached_pages,
          COUNT(*) * 8 / 1024 AS cached_size_mb
      FROM sys.dm_os_buffer_descriptors bd
      JOIN sys.allocation_units au ON bd.allocation_unit_id = au.allocation_unit_id
      JOIN sys.partitions p ON au.container_id = p.hobt_id
      JOIN sys.indexes i ON p.object_id = i.object_id AND p.index_id = i.index_id
      WHERE bd.database_id = DB_ID()
          AND OBJECT_NAME(p.object_id) IN ('表1', '表2', '表3') -- 替换为涉及的表名
      GROUP BY OBJECT_NAME(p.object_id), i.name
      
      查看三个索引在缓冲池中的缓存占比,判断是否大部分数据未被缓存。
    • 计算语句的缓存命中率:
      SELECT 
          (1 - (SUM(CAST(reads AS BIGINT)) / (SUM(CAST(logical_reads AS BIGINT)) + 1))) * 100 AS cache_hit_ratio
      FROM sys.dm_exec_query_stats 
      WHERE sql_handle = (SELECT sql_handle FROM sys.dm_exec_requests WHERE session_id = <任意阻塞会话ID>)
      
      若命中率低于90%,说明缓冲池内存不足。

3. 大索引存在严重碎片

16GB的大索引如果碎片率过高,会导致读取时需要访问更多物理页,且预读效率低下,进一步加剧IO等待。

  • 排查操作:
    • 查询索引碎片率:
      SELECT 
          OBJECT_NAME(object_id) AS table_name,
          name AS index_name,
          avg_fragmentation_in_percent
      FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED')
      WHERE OBJECT_NAME(object_id) IN ('表1', '表2', '表3') -- 替换为涉及的表名
      
      若外部碎片率(avg_fragmentation_in_percent)超过30%,需重建索引;10%-30%可考虑重组索引。

4. 应用层出现请求风暴

连接池30个连接全部执行同一条语句,说明短时间内有大量重复请求涌入,超过了数据库的IO处理能力,并发叠加导致所有连接卡在IO等待上。

  • 排查操作:
    • 检查应用日志:查看阻塞发生时是否有批量任务触发、缓存失效事件,或前端请求突增。
    • 检查Hibernate配置:确认是否开启二级缓存,是否存在批量查询未做限流,或查询参数未绑定导致无法复用查询计划。

5. 弹性池存储IO达到隐性限制

Azure弹性池的存储IOPS/吞吐量有上限(24vCore Gen5的弹性池,最大IOPS约为12000,吞吐量约为192MB/s),如果语句的IO需求瞬间达到上限,会触发IO等待,但粗粒度监控可能未捕捉到。

  • 排查操作:
    • 在Azure Portal查看数据库的存储IOPS/吞吐量细粒度监控(1分钟粒度),确认阻塞发生时是否有突增接近阈值的情况。
    • 查询文件级IO统计:
      SELECT 
          DB_NAME(vfs.database_id) AS database_name,
          mf.name AS file_name,
          vfs.io_stall_read_ms / (vfs.num_of_reads + 1) AS avg_read_latency_ms,
          vfs.io_stall_write_ms / (vfs.num_of_writes + 1) AS avg_write_latency_ms,
          vfs.num_of_reads,
          vfs.num_of_writes
      FROM sys.dm_io_virtual_file_stats(DB_ID(), NULL) vfs
      JOIN sys.master_files mf ON vfs.database_id = mf.database_id AND vfs.file_id = mf.file_id
      
      若平均读延迟超过20ms,说明存储IO存在瓶颈。

6. 统计信息过期导致执行计划退化

过期的统计信息会让查询优化器做出错误的执行计划选择(比如用扫描代替查找),引发大量IO。

  • 排查操作:
    • 检查统计信息更新时间:
      SELECT 
          OBJECT_NAME(object_id) AS table_name,
          name AS stats_name,
          last_updated
      FROM sys.dm_db_stats_properties(OBJECT_ID('表1'), 1) -- 替换为涉及的表名和统计信息ID
      
      如果统计信息更新时间远早于最近的数据变更,执行UPDATE STATISTICS <表名> WITH FULLSCAN,然后重新执行语句观察执行计划是否优化。

内容的提问来源于stack exchange,提问作者Peter Clause

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 18:25:23