简介:这份文档面向DB2数据库运维DBA与性能调优学习者,取材于某银行DB2系统的一次真实故障:SQL执行时间变长、活动会话数异常攀升,常规CPU、内存、I/O与锁等待排查均无收获,最终锁定系统临时表空间TEMPSPACE1膨胀至10GB这一根源。资源包仅含1个doc文件,约607KB,完整记录了从现象确认、指标监控到根因定位的全过程,并给出LATCH分析方法、STACK收集技巧以及用db2trc暂停实例抓取诊断信息等实操手段,同时附有SQL优化与参数调整建议。已有5903人学习下载,适合希望积累真实生产案例排错思路、提升DB2性能诊断能力的中高级读者参考。
1. 一次“查不出原因”的变慢:从 ACTIVE SESSION 异常说起
业务侧反馈系统变慢,多数人的第一反应是查 CPU、内存、I/O、锁等待、缓冲池命中率,这套动作本身没错,但这次它失效了。某银行 OLTP 系统平时 ACTIVE SESSION 长期在 10 以内,某天起频繁冲到 50 以上,db2top 里活动会话清一色 UOW Executing,CPU 正常、内存正常、无锁等待、缓冲池命中率正常、I/O 时间也正常。常规指标全部“健康”,SQL 却实实在在慢了 3 到 6 秒。这类问题最考验 DBA 的地方在于:监控大盘没红,但用户已经在骂。这篇方案把整个排查链路拆开——从 db2top 定位活动会话,到 MON_GET_PKG_CACHE_STMT 拆解等待时间,再到 db2pd 抓 LATCH 与 STACK、analyzestack 汇总、db2trc 做验证实验,最后落到 TEMPSPACE1 暴涨 10G 的根因与 deactivate/activate 的处置。适合正在做 DB2 性能诊断毕设、课设,或需要交一份数据库运维方案的人直接照着走。
2. 问题定位链路设计:从监控大盘到单条 SQL 的四层收敛
排查性能问题最怕“东一榔头西一棒子”,今天看锁、明天看排序,最后什么都没抓住。我一般会把定位过程设计成四层收敛:系统层 → 会话层 → 语句层 → 内部机制层,每一层都有明确的“通过条件”和“退出条件”,不满足就往下走,满足了就停手。这套分层不是为了好看,而是为了在时间压力下不重复劳动。
2.1 第一层:系统层快速排除,先证明“不是资源问题”
系统层要回答的问题只有一个:CPU、内存、I/O、锁、缓冲池这五项里,有没有一项明显异常。做法是 db2top 挂上去看实时指标,同时用db2 LIST APPLICATIONS SHOW DETAIL统计活动会话状态分布。这一步的价值在于排除法——如果五项都正常,就可以把“扩容”“加内存”“换存储”这类建议先压下去,避免在错误方向上花钱。
# 连接数据库并进入 db2top 交互界面 db2top -d <dbname> # 交互指令说明: # d -> 查看 ACTIVE SESSION 数量与整体指标 # l -> 进入 SESSIONS 页面,看每个会话的状态 # D -> 查看 PACKAGE CACHE 中的动态 SQL参数上要注意,db2top 默认 2 秒刷新一次,观察 ACTIVE SESSION 不能只看一眼,至少连续看 5 分钟,因为这类问题往往是“间歇性冲高”。会话状态里,UOW Executing 表示正在执行语句,UOW Waiting 表示事务中两条语句之间的空档,后者不计入 ACTIVE SESSION。如果冲高的会话全是 UOW Executing,基本可以判定是语句执行变慢,而不是连接堆积。
2.2 第二层:会话层抓现行,用 MON_CURRENT_SQL 锁定慢语句
系统层确认“资源没问题、语句确实慢”之后,就要抓现行。MON_CURRENT_SQL管理视图能同时看到动态 SQL 和静态 SQL,按已运行时间倒序排,一眼就能看出谁在拖后腿。
SELECT COORD_MEMBER AS MEMBER, VARCHAR(SESSION_AUTH_ID, 10) AS USER, VARCHAR(CLIENT_APPLNAME, 10) AS APPNAME, ELAPSED_TIME_SEC AS ELAPSEDTIME, VARCHAR(ACTIVITY_STATE, 10) AS STATE, TOTAL_CPU_TIME, ROWS_READ, ROWS_RETURNED, QUERY_COST_ESTIMATE, DIRECT_READS, DIRECT_WRITES, VARCHAR(STMT_TEXT, 2000) AS STMT_TEXT FROM SYSIBMADM.MON_CURRENT_SQL ORDER BY ELAPSED_TIME_SEC DESC;这条语句的关键在ELAPSED_TIME_SEC排序和ACTIVITY_STATE字段。前者帮你把“已经跑了很久还没结束”的语句顶到最上面,后者告诉你它卡在编译、执行还是锁等待。需要提醒的是,抓取时语句往往还没执行完,所以看到的 ELAPSED TIME 比完整执行时间短,通常要连续抓多次才能拼出全貌。这次抓下来接近 30 条语句同时在跑,和 ACTIVE SESSION 数量对得上,说明问题不是个别语句,而是有一批语句集体变慢。
2.3 第三层:语句层拆时间分布,判断“慢在算还是慢在等”
抓到慢语句只是第一步,真正有价值的是知道时间花在哪。MON_GET_PKG_CACHE_STMT表函数能返回每条语句的等待时间、读时间、锁等待、日志等待、排序时间,把它们换算成占总执行时间的百分比,慢的“成分”就一目了然。
SELECT MEMBER, NUM_EXECUTIONS, STMT_EXEC_TIME, DECIMAL((TOTAL_ACT_WAIT_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_wait, DECIMAL((POOL_READ_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_read, DECIMAL((LOCK_WAIT_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_lock, DECIMAL((LOG_DISK_WAIT_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_log, DECIMAL((TOTAL_SECTION_SORT_TIME / DOUBLE(STMT_EXEC_TIME)) * 100, 5, 2) AS pct_sort, CAST(FLOAT(STMT_EXEC_TIME) / FLOAT(NUM_EXECUTIONS) AS DECIMAL(10, 3)) AS AVG_EXEC_TIME, VARCHAR(STMT_TEXT, 500) AS STMT_TEXT FROM TABLE(MON_GET_PKG_CACHE_STMT(NULL, NULL, NULL, -2)) WHERE NUM_EXECUTIONS > 1 AND STMT_EXEC_TIME > 0;这里-2表示取所有成员上的语句,NUM_EXECUTIONS > 1过滤掉只跑过一次的语句,避免平均值失真。结果里发现 5 条 with 开头的语句pct_wait超过 95%,但pct_read、pct_lock、pct_log、pct_sort都很低——等待的不是 I/O、不是锁、不是日志、不是排序。这个“四不像”的等待分布就是关键线索,顺着它加查TOTAL_EXTENDED_LATCH_WAIT_TIME,才把矛头指向 LATCH。
2.4 第四层:内部机制层,LATCH 与 STACK 联合取证
到了 LATCH 这一层,常规监控已经帮不上忙了。DB2 文档里对 LATCH 的介绍极少,只能把它理解成“保护内部共享数据结构的轻量锁”。做法是先用db2pd -latches -rep 2持续观察,看有没有某个 EDU 长时间持有 LATCH 而一堆 EDU 在等。这次观察到pool_table_latch被一个 EDU 持有、23 个 EDU 排队,和 ACTIVE SESSION 冲高完全吻合。
# 每 2 秒采样一次 LATCH 状态,持续输出 db2pd -latches -rep 2 # 收集 STACK,每 3 秒一次,共 10 次,输出到指定目录 mkdir -p /tmp/stack0531 db2pd -stack all dumpdir=/tmp/stack0531 -rep 3 10 # 在结果中检索目标 LATCH grep pool_table_latch /tmp/stack0531/*.stack.txt-rep 3 10的含义是间隔 3 秒、共采 10 轮,因为 LATCH 等待往往几秒就消失,单次采样大概率扑空。STACK 文件里同时记录了每个 EDU 当时持有的 LATCH 和等待的 LATCH,Holder 的调用栈能看到sqlbSnapshotTablespace → sqlbMonSMSGetContainerPages,说明它在对 SMS 表空间做快照、读容器数据页;Waiter 的调用栈能看到sqldTableCreateTemp → sqldInternalCreateTable → sqlbGetSysTempDatapool,说明它在创建内部临时表、要用系统临时表空间。两条栈一对照,因果链就出来了:监控代理定期对表空间做快照,快照在 SMS 表空间上耗时过长、占着 pool_table_latch,把正在用临时表空间的查询全堵住了。
3. 根因确认与处置方案:TEMPSPACE1 从 10G 回到正常
定位到 LATCH 只是找到了“堵点”,还得回答“为什么快照会慢”。答案在表空间本身:db2 LIST TABLESPACES SHOW DETAIL要跑好几秒,查下来 TEMPSPACE1 竟然有 10G,而正常情况下系统临时表空间应该很小。这一章讲怎么确认根因、怎么处置、怎么防止复发。
3.1 用表空间详情和文件列表坐实“异常膨胀”
确认 TEMPSPACE1 状态,重点看总页数、页大小、容器数,再配合操作系统层面看容器目录里的文件。
-- 查看所有表空间详情,重点看 TEMPSPACE1 LIST TABLESPACES SHOW DETAIL;# 进入 TEMPSPACE1 的容器目录,观察 TDA 文件 ls -al /db2data/<instance>/NODE0000/<dbname>/T0000001/结果里Total pages = 354661、Page size = 32768,乘起来约 10G,和监控历史曲线对得上——某个时间点从 0 直接涨到 10G。容器目录里能看到大量SQL0xxxx.MEMBER0000.TDA文件,每个 1MB 左右。TDA 是临时数据文件,正常用完应该被回收,这里却大量遗留,说明曾经有一波极端并发把临时表空间撑爆后没有正常释放。翻监控历史,果然在那个时间点 ACTIVE SESSION 瞬间冲到 800 多,大量 SQL 同时用临时表空间,语句都执行成功了,但留下了一地 TDA 文件,成了后来踩到的雷。
3.2 处置动作:deactivate/activate 而不是重启实例
确认根因后,处置反而简单,但有个关键细节容易翻车:必须 deactivate/activate 数据库,只重启实例释放不掉这些临时表空间。
# 先断开所有应用连接,再执行 db2 deactivate db <dbname> db2 activate db <dbname> # 处置后复查表空间 db2 LIST TABLESPACES SHOW DETAILdeactivate 会把数据库从活动状态彻底拉下来,释放所有临时段和 TDA 文件;activate 再拉起来。执行前要安排维护窗口、确认没有长事务在跑,执行后立刻复查 TEMPSPACE1 的 Total pages 是否回到正常量级,并持续监控一段时间确认 ACTIVE SESSION 回落到个位数。这次处置后LIST TABLESPACES SHOW DETAIL秒回,性能问题完全消失。
3.3 防复发:把临时表空间纳入日常巡检
处置完不等于结束,这类问题的复发成本很高。我一般会在巡检脚本里加三条硬指标:TEMPSPACE1 使用页数超过阈值告警、容器目录 TDA 文件数量异常告警、ACTIVE SESSION 突增告警。同时把“监控代理对表空间的快照频率”纳入评估——快照本身是必要的,但在 SMS 表空间上高频快照会放大 LATCH 竞争,频率和时段要结合业务低峰来定。另外,应用侧尽量用变量绑定代替拼 SQL,减少硬解析带来的临时对象创建,这条对 OLTP 系统是长期收益。
4. 避坑与排查:这类问题最容易翻车的五个点
4.1 只盯 CPU 内存 I/O,忽略“等待型”慢查询
现象是常规资源指标全绿,但用户持续报慢。原因是慢在 LATCH 这类内部等待上,不体现在 CPU 和 I/O 上。解决办法是把MON_GET_PKG_CACHE_STMT的等待时间占比作为固定检查项,看到“等待高但读、锁、日志、排序都低”的组合,直接往 LATCH 方向查。
4.2 单次采样 LATCH 扑空就放弃
现象是db2pd -latches跑一次没发现异常,就判定“没有 LATCH 问题”。原因是 LATCH 等待往往只持续几秒,单次采样命中率很低。解决办法是用-rep参数持续采样,至少覆盖一个业务高峰周期,同时配合 STACK 收集,两者时间点对齐才能拿到 Holder 和 Waiter 的完整证据。
4.3 生产环境无节制收集 STACK
现象是收集 STACK 期间数据库更慢了。原因是 STACK 收集会挂起 EDU、消耗资源,繁忙系统上影响明显。解决办法是控制采集轮次和间隔,选业务低峰执行,采集前评估影响面,采集后及时清理 dump 文件。这条是血泪经验,别在高峰期图省事。
4.4 以为重启实例就能释放临时表空间
现象是重启实例后 TEMPSPACE1 还是很大。原因是 TDA 文件的释放依赖数据库 deactivate,实例重启不触发这个动作。解决办法是走db2 deactivate db+db2 activate db,并在维护窗口内执行、执行后复查页数。
4.5 看到慢 SQL 就急着加索引
现象是加了一堆索引,慢查询没改善。原因是这次慢的根因不在访问路径,而在 LATCH 竞争,加索引解决不了等待。解决办法是先做时间分布分析,确认慢在“算”还是“等”,再决定优化方向。方向错了,努力全白费。
5. 验收与答辩:用 db2trc 做一次可复现的验证实验
方案交上去,评审最常问的一句是:“你怎么证明 pool_table_latch 就是元凶,而不是巧合?”光靠现象吻合不够,得有一个可复现的实验把因果链钉死。我一般会用db2trc做一次受控实验:让 DB2 在特定函数入口处暂停,人为制造“快照占着 LATCH”的场景,然后观察其他会话是否被堵住。
# 终端一:连接数据库 db2 CONNECT TO <dbname> # 终端一:开启 trace,在 sqlbSMSGetContainerPages 入口处挂起 db2trc on -crash DB2.SQLB.sqlbSMSGetContainerPages.entry -suspend # 终端二:此时发起一条会用到临时表空间的查询,观察其是否被阻塞 db2 "SELECT ... FROM ... ORDER BY ..." # 终端一:恢复执行,关闭 trace db2trc off-suspend的作用是在命中指定函数入口时把进程挂起,相当于人为把“快照读容器页”这一步拉长,复现出 LATCH 被长期持有的状态。如果此时终端二的查询被卡住,而恢复执行后立刻返回,因果链就闭环了。这个实验的价值在于:它把“事后推断”变成了“事前可演示”,答辩时现场跑一遍,比讲十页 PPT 都有说服力。
验收清单可以按这个顺序过一遍:系统层五项指标是否都有记录、慢语句是否有 MON_CURRENT_SQL 抓取结果、等待时间分布是否有量化数据、LATCH 是否有 Holder/Waiter 对照、STACK 是否定位到具体函数、TEMPSPACE1 处置前后页数是否有对比、处置后 ACTIVE SESSION 是否回落到基线。每一项都对应一个可展示的证据文件或截图,评审问哪一项都能翻出来。
从那以后我每次处理“指标全绿但就是慢”的问题,都强制先跑一遍等待时间分布,再决定往哪个方向深挖,绝不凭直觉加索引。希望这套链路能帮你在自己的项目里少走几个弯路。
本文还有配套的精品资源,点击获取