news 2026/7/27 6:10:57

【金仓数据库征文】一次慢 SQL 诊断:从等待事件到执行计划一次慢 SQL 诊断:从等待事件到执行计划

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【金仓数据库征文】一次慢 SQL 诊断:从等待事件到执行计划一次慢 SQL 诊断:从等待事件到执行计划

文章目录

    • 每日一句正能量
    • 1. 背景与问题
    • 2. 环境与数据
    • 3. 复现过程
      • 3.1 第一步:从活动会话锁定 SQL
      • 3.2 第二步:用 SQL 聚合统计确认影响面
      • 3.3 第三步:读懂优化前执行计划
      • 3.4 第四步:验证参数是否是主因
    • 4. 方案实施
      • 4.1 SQL 改写:让时间条件可索引
      • 4.2 索引设计:匹配等值过滤、范围与排序
      • 4.3 刷新统计信息并检查估算偏差
      • 4.4 建立可重复的回归脚本
    • 5. 结果对比
    • 6. 风险与复盘
      • 6.1 灰度与回退
      • 6.2 需要重点防范的风险
      • 6.3 本次诊断的可复用方法

每日一句正能量

“原本的我就很好,我只需要做减法,卸载不必要的负担,成为真实的自己。”
真正的成长不是不断添加技能、标签、成就,而是减去外界强加的期待、无谓的比较、内耗的执念。就像雕刻——去掉多余的石料,里面早已有完整的形象。

1. 背景与问题

某订单中心在月初促销结束后出现间歇性查询超时。客服工作台根据租户、订单状态和时间区间查询最近 50 条订单,平时响应在 100 ms 左右,故障时段平均耗时升至 4~6 s,个别请求超过 10 s。应用日志只记录了“数据库查询超时”,没有说明数据库是在等待锁、等待磁盘,还是单纯执行了低效计划。

最初团队提出三个猜测:一是促销期间写入量大,订单表膨胀导致磁盘读取增加;二是存在长事务阻塞查询;三是连接池参数异常。若直接根据猜测扩容或调整参数,既可能掩盖根因,也可能引入新的抖动。因此本次处理坚持一条原则:先建立“业务现象—活动会话—等待事件—SQL 画像—执行计划—数据对象”的证据链,再实施改动。

故障 SQL 的业务形态如下:

SELECTo.order_id,o.create_time,o.total_amount,i.sku_id,i.quantityFROMorders oJOINorder_item iONi.order_id=o.order_idWHEREo.tenant_id=:tenant_idANDo.status=:statusANDTO_CHAR(o.create_time,'YYYY-MM-DD')BETWEEN:begin_dateAND:end_dateORDERBYo.create_timeDESCFETCHFIRST50ROWSONLY;

这段 SQL 在功能上没有错误,但把create_time包在TO_CHAR中,过滤条件难以直接利用以时间列为尾列的普通 B-tree 复合索引;同时,订单明细表在连接前没有受到“前 50 条订单”的有效约束,可能放大扫描和连接代价。

2. 环境与数据

复现实验环境使用 KingbaseES 测试实例,业务模型与生产保持同构:

项目示例值
orders行数约 1280 万
order_item行数约 3560 万
单租户月订单约 4.2 万
高峰并发180~260 会话
原索引orders(tenant_id)orders(create_time)order_item(order_id)
目标 SLA平均 < 100 ms,P95 < 200 ms
SQL 超时8 s

在诊断前先冻结变量:不同时调整内存参数、并行度、索引和 SQL;不在生产直接运行不可控的EXPLAIN ANALYZE;所有采样记录保留时间戳、会话、应用名和 SQL 文本,确保能够回溯。

KingbaseES 文档指出,sys_stat_activity中的等待事件是瞬时状态,不累计等待时长,因此一次查询结果不足以量化问题,应通过连续采样判断等待是否反复出现。查看当前 SQL 与等待事件还依赖track_activities,开启该参数存在一定监控开销。正式实施前应确认版本、权限与参数状态。

3. 复现过程

3.1 第一步:从活动会话锁定 SQL

先查询持续时间超过 3 秒的活动会话:

SELECTpid,usename,application_name,client_addr,wait_event_type,wait_event,now()-query_startASrunning_time,LEFT(query,1000)ASsql_textFROMsys_stat_activityWHEREstate<>'idle'ANDquery_startISNOTNULLANDnow()-query_start>INTERVAL'3 seconds'ORDERBYrunning_timeDESC;

故障时连续采样 10 分钟。结果显示,慢会话多数没有长期停留在锁等待;少量会话出现数据文件读取相关等待,但持续时间短、出现频率高。这说明“锁阻塞”不是主要矛盾,I/O 更像是低效扫描带来的结果,而不是根因本身。

这里要避免一个常见误区:看到DataFileRead一类事件就立即增加缓存。等待事件描述的是会话当时在等什么,不等于为什么读了这么多数据。若 SQL 本来只需要 50 行,却扫描数千万行,扩容只能暂时降低延迟,不能消除访问路径问题。

3.2 第二步:用 SQL 聚合统计确认影响面

在已启用sys_stat_statements的环境中,按累计执行时间和平均执行时间定位高消耗 SQL:

SELECTqueryid,calls,total_exec_time,mean_exec_time,rows,shared_blks_hit,shared_blks_read,temp_blks_written,LEFT(query,1200)ASsql_textFROMsys_stat_statementsORDERBYtotal_exec_timeDESCFETCHFIRST20ROWSONLY;

样本窗口内,该订单查询调用 1842 次,平均耗时 4.82 s,累计耗时占业务库前台 SQL 时间的 31%。每次只返回几十行,却伴随大量共享块读取和临时文件写入,符合“扫描多、返回少”的典型特征。

3.3 第三步:读懂优化前执行计划

生产先使用不实际执行语句的EXPLAIN;在隔离测试库还原相同统计信息和绑定值后,再使用:

EXPLAIN(ANALYZE,BUFFERS,VERBOSE)SELECT...

EXPLAIN ANALYZE会实际执行 SQL,并带来额外分析开销,因此不能把它当成完全无害的查看命令。对于更新、删除或不可控查询,应放在事务回滚、只读副本或测试环境中执行。

优化前计划暴露出四个问题:

  1. orders采用顺序扫描,1280 万行中只保留约 4.2 万行。
  2. TO_CHAR(create_time, ...)使时间过滤没有形成理想的索引条件。
  3. 排序发生在大结果集之后,并写出约 640 MB 临时文件。
  4. 估算行数与实际行数相差两个数量级,说明统计信息或数据相关性未被充分反映。

3.4 第四步:验证参数是否是主因

检查work_memshared_buffers、随机页成本、有效缓存估算等参数,只做记录,不立即修改。测试中把会话级work_mem提高后,临时文件下降,但总耗时仍在 3 s 以上,说明参数只能缓解排序落盘,不能解决全表扫描与连接放大。

这一步的价值在于排除“只调参数就能解决”的假设。参数调整应有清晰的内存预算:work_mem往往按执行节点和并发会话消耗,简单全局放大可能在高峰触发内存压力。

4. 方案实施

4.1 SQL 改写:让时间条件可索引

把字符串日期比较改成左闭右开的时间范围:

SELECTo.order_id,o.create_time,o.total_amount,i.sku_id,i.quantityFROM(SELECTorder_id,create_time,total_amountFROMordersWHEREtenant_id=:tenant_idANDstatus=:statusANDcreate_time>=:begin_timeANDcreate_time<:end_timeORDERBYcreate_timeDESCFETCHFIRST50ROWSONLY)oJOINorder_item iONi.order_id=o.order_idORDERBYo.create_timeDESC;

改写有两个目的:第一,消除分区键或索引列上的函数包装;第二,先在订单主表完成过滤、排序和 Top-N,再访问明细,避免把数万条候选订单全部连接后再截取 50 条。

应用层必须使用时间类型绑定参数,不再传入受格式影响的字符串。结束时间取下一日或下一月零点,以< end_time表达,避免“23:59:59.999999”边界遗漏。

4.2 索引设计:匹配等值过滤、范围与排序

CREATEINDEXCONCURRENTLY orders_idx_qryONorders(tenant_id,status,create_timeDESC)INCLUDE(order_id,total_amount);CREATEINDEXCONCURRENTLY order_item_idxONorder_item(order_id)INCLUDE(sku_id,quantity,sale_amount);

索引列顺序遵循本次查询模式:tenant_idstatus为等值条件,create_time同时承担范围过滤和倒序输出。包含列用于降低回表概率,但是否支持、语法是否一致以及索引大小,应按实际 KingbaseES 版本验证。

索引不是越宽越好。orders是高频写入表,新增索引会增加插入、更新、WAL 和备份成本。上线前分别测量索引体积、建索引时长、写入 TPS 降幅和锁影响。

4.3 刷新统计信息并检查估算偏差

ANALYZEorders;ANALYZEorder_item;

对倾斜明显的租户和状态字段,要重点比较优化器估算行数与实际行数。若某些大租户占据绝大多数数据,单列统计可能无法描述tenant_id + status + create_time的相关性,应结合版本能力评估扩展统计信息,而不是用固定 Hint 掩盖估算问题。

4.4 建立可重复的回归脚本

每组测试至少执行 10 次,区分冷缓存和热缓存,记录:

  • 总执行时间、规划时间;
  • 实际返回行数;
  • 共享块命中与读取;
  • 临时块读写;
  • 扫描方式和连接方式;
  • 估算行数与实际行数偏差;
  • CPU、I/O、锁等待采样;
  • 同时段订单写入 TPS。

功能校验不能只比较COUNT(*)。使用订单主键集合做双向差集,并校验金额、明细数量和排序稳定性。分页查询还要验证相同create_time下的确定性顺序,建议补充order_id DESC作为次排序键。

5. 结果对比

在相同绑定值、相同数据快照、热缓存条件下,复现实验得到如下结果:

指标优化前优化后变化
平均耗时4820 ms38 ms下降约 99.2%
P956110 ms62 ms下降约 99.0%
返回行数5050一致
共享块读取约 31 万约 1260大幅下降
临时文件约 640 MB0消除
主表访问顺序扫描复合索引扫描改善
明细访问大范围连接按 50 个订单精确访问改善
估算/实际偏差约 87 倍约 1.3 倍明显收敛

结果表明,真正产生收益的不是某个“神奇参数”,而是访问路径重构:把函数化过滤改成可索引范围,把 Top-N 前推,把复合索引顺序与查询条件对齐,并刷新统计信息。等待事件中反复出现的数据读取随之下降,验证了 I/O 等待是低效计划的结果。

上线后观察 24 小时,除了查询延迟,还应检查新增索引对订单写入、自动清理、备份窗口和复制延迟的影响。性能优化只有在系统整体成本可接受时才算完成。

6. 风险与复盘

6.1 灰度与回退

改造采用应用开关保留新旧 SQL 两条路径。先放量 5%,限定内部租户和单个应用节点,连续观察 30 分钟;门禁包括:

  • 新旧 SQL 结果主键集合一致;
  • 错误率无上升;
  • P95 小于 200 ms;
  • 锁等待、复制延迟和写入 TPS 无明显恶化;
  • 执行计划稳定使用目标索引。

不满足门禁时,立即把路由切回旧 SQL。新索引先保留用于复盘,不在故障窗口匆忙删除;若确认索引引起写入或空间风险,再在低峰期撤销。回退脚本、应用开关和责任人必须在发布前演练。

6.2 需要重点防范的风险

绑定值差异。小租户和超级租户的数据分布不同,同一个计划未必适合全部租户。回归样本必须覆盖高、中、低基数,而不能只测一个“漂亮参数”。

统计信息变化。大批量归档、导入或促销数据写入后,行数分布变化可能触发计划漂移。应保存基线计划特征,并持续监控平均读块和 P95,而不是只盯平均耗时。

索引写放大。覆盖索引减少读取,但增加写入和存储。对高频更新列谨慎使用包含列,定期评估索引使用率和膨胀。

测试误差。EXPLAIN ANALYZE自身有开销;首次执行可能包含物理 I/O;测试库硬件和缓存状态也会影响数字。文章中必须写清采样方法、执行次数和缓存条件,避免把一次结果包装成稳定结论。

6.3 本次诊断的可复用方法

这次慢 SQL 处理最有价值的不是最终那条索引,而是诊断顺序:

  1. 从业务时间窗口和请求标识定位会话;
  2. 连续采样等待事件,判断数据库当时在等待什么;
  3. 用 SQL 聚合统计确认调用频率和资源占比;
  4. 用执行计划解释“为何读取这么多数据”;
  5. 分离 SQL、索引、统计信息和参数变量逐项验证;
  6. 用结果一致性、资源消耗和写入影响共同验收;
  7. 通过灰度开关和可逆 DDL 保证能够回退。

慢 SQL 诊断不应止于“加索引”。只有证据链能够解释优化前为什么慢、优化后为什么快,并证明业务结果没有变化,方案才具备可复用和可审计的价值。


转载自:https://blog.csdn.net/u014727709/article/details/163194995
欢迎 👍点赞✍评论⭐收藏,欢迎指正

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/27 6:08:30

龙珠Z动画资源编码解析与数字修复技术指南

1. 项目背景与核心价值"dragonballz_e234-1"这个看似随机的字符串组合&#xff0c;实际上蕴含着丰富的文化基因和技术可能性。作为《龙珠Z》系列的深度爱好者&#xff0c;我在整理动画资源时发现&#xff0c;这类编码格式常出现在老牌动漫作品的原始制作素材、海外发…

作者头像 李华
网站建设 2026/7/27 6:07:19

知识驱动型AI与RAG技术在企业数字化转型中的应用

1. 知识驱动型AI智能体的核心价值与应用场景在当今企业数字化转型浪潮中&#xff0c;知识管理正面临前所未有的挑战。传统的关键词检索方式已经难以满足业务需求——当用户搜索"喵星人护理指南"时&#xff0c;系统可能完全忽略包含"幼猫喂养注意事项"的文档…

作者头像 李华
网站建设 2026/7/27 6:03:02

SAP ABAP CDS开发中的语义命名规范与实践

1. 项目概述&#xff1a;语义命名在SAP ABAP CDS开发中的核心价值在SAP ABAP开发领域&#xff0c;CDS&#xff08;Core Data Services&#xff09;视图已成为现代数据建模的标准工具。但许多开发团队在实际项目中常遇到一个看似简单却影响深远的问题&#xff1a;字段命名混乱导…

作者头像 李华
网站建设 2026/7/27 6:02:01

Nginx 添加访问状态模块

需求查看 Nginx 的运行状态&#xff0c;包括&#xff1a; 当前连接数 请求总数 每秒请求数&#xff08;QPS&#xff09; 各虚拟主机访问情况 HTTP状态码统计&#xff08;200、404、500等&#xff09;要求1. 查看当前 Nginx 版本 2. 下载对应版本源码 3. 下载 nginx-module-vts …

作者头像 李华
网站建设 2026/7/27 6:00:35

DSP/BIOS I/O管理:IOM、SIO/DEV与PIP模块实战解析

1. 项目概述&#xff1a;DSP/BIOS中的I/O管理基石 在嵌入式实时系统开发&#xff0c;尤其是基于德州仪器&#xff08;TI&#xff09;DSP平台的音频处理、通信基站或工业控制项目中&#xff0c;高效、可靠的输入输出&#xff08;I/O&#xff09;管理是决定系统成败的关键。想象一…

作者头像 李华
网站建设 2026/7/27 6:00:21

ReactAgent构建器设计模式与Spring AI Alibaba实践

1. ReactAgent构建器设计哲学剖析Spring AI Alibaba框架中的ReactAgent构建器采用了经典的Builder模式实现&#xff0c;这种设计选择背后蕴含着对复杂对象构造过程的深度思考。我们先看核心构建流程的伪代码表示&#xff1a;ReactAgent agent ReactAgent.builder().modelProvi…

作者头像 李华