DB2 TEMPSPACE1膨胀引发LATCH等待的慢SQL排查

发布时间:2026/11/4 9:38:16
DB2 TEMPSPACE1膨胀引发LATCH等待的慢SQL排查

简介:围绕某银行 DB2 系统真实故障整理的单篇技术文档,面向数据库运维 DBA、性能分析人员和 DB2 应用开发人员,聚焦 SQL 执行时间变长、ACTIVE SESSION 异常升高时如何排查系统临时表空间过大问题。文档从常规 CPU、内存、I/O、锁等待与缓冲池命中率检查切入,逐步记录 TEMPSPACE1 增至 10GB 的发现过程,并展开临时表空间引发资源消耗、内存压力、LATCH 竞争与磁盘排序开销的机理分析。压缩包仅含 1 个 doc 文件,约 607KB,内容紧凑,便于离线查阅和团队传阅。已有 5903 人学习,价值在于提供一套可复用的排错路径:利用 db2top 观察活动会话,结合 db2pd -latch、STACK 收集和 db2trc 暂停实例等手段定位问题 SQL,再从 SQL 优化、排序参数调整和临时对象清理等方向落地解决。适合需要积累 DB2 性能诊断思路与工具实操参考的读者。

1. DB2 数据库突然变慢:一条隐藏着 10G 的 TEMPSPACE1 线索

一套运行多年的 OLTP 系统,某天开始业务反馈“点一下要等好几秒”,监控上 DB2 数据库的 ACTIVE SESSION 从常年个位数冲到 50 以上,登录主机手工执行一条平时毫秒级的语句也要三四秒才返回。第一反应都是去查 CPU、内存、I/O、锁等待和缓冲池命中率,一圈查下来全部正常,问题反而更难处理——常规指标都正常,故障表现却真实存在。最后定位到的对象,是几乎没人会主动查看的 DB2 系统临时表空间 TEMPSPACE1:它从接近 0 涨到 10G,里面塞满了残留的 TDA 文件,而监控代理对表空间做快照时要抢占同一把 pool_table_latch,把大批查询堵在原地。下面把这套排查链路完整展开:怎么用 db2top 和监控视图抓慢 SQL,怎么用 db2pd 盯住 LATCH,怎么收集 STACK 并读懂 Holder 与 Waiter 的函数栈,最后怎么用 deactivate/activate 把空间真正释放掉。正在写数据库性能类毕设、课程设计,或者要交一份运维排障方案的人,可以照着这条线索走一遍。

2. 现象确认与快速排除:ACTIVE SESSION、CPU、I/O、锁四路并查

性能问题最怕的不是难查,而是一上来就扎进某个方向,查了三天发现方向错了。这一章做的事很简单:先用一两个小时把所有“可能性大、验证成本低”的方向全排掉,剩下那个方向哪怕再冷门,也值得深挖。

2.1 ACTIVE SESSION 数量与状态怎么读

业务人员说“系统慢”只是主观感受,可能来自应用层、网络、也可能来自数据库。判断依据要落到可量化的指标上,ACTIVE SESSION 数量就是其中之一。这个指标指数据库当前正在执行的会话数,也就是正在处理 SQL 请求的 application 数量。正常的 OLTP 系统通常在 10 个以内,较繁忙的系统可能到二三十个,再高就该考虑扩容了。如果一套系统平时只有几个,突然频繁冲到几十个,基本可以判定两种情况之一:业务访问量真的涨了,或者 SQL 执行时间变长了导致会话堆积。

观察工具首选 db2top,连上去之后进入会话页面看状态分布,命令很简单:

# 每 2 秒刷新一次,观察活动会话数量变化 db2top -d <dbname> # 进入交互界面后按 d 查看整体指标,按 l(小写 L)进入 SESSIONS 页面看状态

关键在于不要只看总数,要看状态构成。SQL 语句从编译到执行完成会经历 Compiling、UOW Executing、Lock Waiting、Commit Active、Queued 等状态,这些状态都计入 ActSess;而应用执行完一条语句、下一条还没开始的 UOW Waiting 状态不计入。这次问题里,活动会话全部停在 UOW Executing,说明它们确实在跑,不是卡在锁或者排队上。这个细节很重要——如果是 Lock Waiting 扎堆,排查方向立刻转向锁和事务;如果 Queued 扎堆,那要看 WLM 并发限制。状态分布相当于给后续排查划定了范围,能省掉大量无用检查。

另一条获取途径是db2 LIST APPLICATIONS SHOW DETAIL,输出比 db2top 更完整,适合需要留存证据、写进排障报告的场合。把两种方式的结果交叉对一遍,数量能对上,就可以确认监控没有误报。

2.2 四项常规指标的判定标准

确认是数据库侧的问题之后,接下来走一遍标准排除法。这套动作每个 DBA 都熟,但熟不等于做得对,关键是要有明确的判定标准,不能凭“看着还行”就跳过。

检查项观察手段正常判定本次结果
CPU 利用率操作系统监控稳态低于 70%,无明显尖峰正常
内存使用操作系统监控无持续 swap,可用内存稳定正常
缓冲池命中率db2top HitRatio数据缓冲池 95% 以上正常
I/O 响应AvgPRdTime / AvgDRdTime / AvgPWrTime / AvgDWrTime无数量级跳变正常
锁等待db2top 锁页面、db2pd -wlocks无持续 Lock Waiting 会话无等待
排序溢出db2top SortOvf相对执行次数占比低有溢出但可解释

db2top 主界面右下角那四个平均读写时间参数,很多人扫一眼就过去了,其实它们是判断 I/O 是否正常的快照。物理读、直接读、物理写、直接写四类操作的平均耗时如果在同一数量级上平稳波动,I/O 这条路基本可以排除。反过来,如果 AvgDRdTime 突然飙到几十毫秒,问题多半在存储侧,跟本次这条线索完全不同。

表格里还有两个容易被误判的项。一个是 Deadlocks,本次显示 218,看着吓人,但这是实例启动以来的累计值,不是当前值,只要不持续增长就不影响性能,把它当成告警去追是典型的浪费工时。另一个是 SortOvf 显示 10,确实偏高,但应用 SQL 里本来就有大量排序,有溢出属于可解释范围,不能立刻扣上“性能元凶”的帽子。判断一个指标要不要追,标准不是它大不大,而是它会不会持续变大、能不能解释当前的现象。

2.3 业务量侧的交叉确认

技术指标排完之后,还有一条必须确认的外部变量:业务访问量到底有没有涨。这条如果不去问,后面所有分析都建立在一个未验证的假设上。

做法是找业务系统负责人核对最近的访问量曲线、交易笔数、批量任务排期,确认有没有大促、有无新上线的批量对账、有没有定时任务时间调整。本次核对结论是访问量没有增加,也没有应用变更和数据库变更。这就把“业务变忙”和“代码改坏了”两条路同时排除,问题范围收缩到“同一批 SQL,为什么突然变慢了”。

这一步的意义在于,它为后面“及时调整排查方向”提供了依据。当你发现某条复杂 SQL 执行要五六秒、正准备动手优化时,会先问自己一句:这条 SQL 是不是最近才变慢的?如果不是,那优化它只是顺手改善,不是解决问题。很多排障过程就是在这里偏离的——改了一堆 SQL,建了几个索引,系统该慢还是慢,最后连原来查到哪一步都记不清了。

排除法做完,剩下的嫌疑对象只有两个:SQL 执行计划出了问题,或者数据库内部存在某种等待。下一章从慢 SQL 入手,把执行时间拆开来看到底花在哪儿。

3. 慢 SQL 抓取:用三个入口把执行时间拆到等待项上

抓到“慢 SQL”不等于找到原因,一条语句慢可能是读数据慢、等锁慢、排序落盘慢,也可能是等一把内部闩锁。这一章用三个入口,逐层把执行时间的构成拆出来。

3.1 db2top 缓存视图与 MON_CURRENT_SQL 的取舍

db2top 的动态 SQL 页面适合快速扫一眼 package cache 里谁最慢,按平均执行时间排序,几秒钟就能看出大概。但它有两个局限:一是只反映缓存中的统计,二是排序依据是平均值,执行次数极少的语句容易被淹没。

要看“此刻正在跑什么”,得用管理视图 MON_CURRENT_SQL。它同时覆盖动态 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 显示当前处于哪个阶段,和执行计划对照着看能判断卡在哪一步;DIRECT_READS 和 DIRECT_WRITES 如果数值异常大,说明语句在做大量直接 I/O,通常是排序或临时表落盘。这条查询需要多执行几次才抓得到现场,因为 ACTIVE SESSION 高峰不是每时每刻都有,抓一次就下结论容易漏。

3.2 MON_GET_PKG_CACHE_STMT 拆解等待时间构成

真正把时间构成拆开的是表函数 MON_GET_PKG_CACHE_STMT。它返回 package cache 中每条语句的详细统计,包括总执行时间、各类等待时间、读写时间、排序时间等,是这一轮分析里信息密度最高的工具。

直接看原始字段比较费劲,我一般会换算成占比,把每条语句的时间去向做成一张“体检表”:

-- 计算每条语句各项等待占总执行时间的百分比,便于横向比较 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;

参数说明:表函数第一个参数传 NULL 表示不过滤特定语句,最后一个参数-2表示返回所有成员上的统计信息,不区分当前成员;NUM_EXECUTIONS > 1是过滤掉只跑过一次的语句,避免偶发值干扰;STMT_EXEC_TIME > 0防止除零。这个查询还可以继续加字段,最值得加的是 TOTAL_EXTENDED_LATCH_WAIT_TIME,也就是闩锁等待时间——正是这个字段把排查方向定了下来。

结果里有一批以 with 开头的语句,平均执行时间 2 到 6 秒不等,其中 5 条的 pct_wait 在 95% 以上。也就是说,它们的时间几乎全花在等待上,而 pct_read、pct_lock、pct_log、pct_sort 都不高。等锁可以排除,等日志可以排除,等读也可以排除,剩下的等待类型就很有指向性了。另有一批语句是存储过程调用,从这个视图看不透内部耗时分布,需要结合应用侧日志判断。

对 OLTP 系统来说,单条语句 3 到 6 秒已经很慢,并发稍微上来一点,ACTIVE SESSION 必然堆高。到这里又出现一个诱惑:既然抓到了慢语句,直接优化不就完了?

3.3 从“改 SQL”里及时调整方向:判断改动是否对得上时间线

动手优化的冲动很强,也确实有产出空间,比如针对全表扫描的语句补索引,通常几分钟就能见效。作者当时就是这么做的,先快速解决了几个全表扫描的语句,然后去梳理那些又长又复杂的 SQL,试图重写逻辑。

但很快有人停下来问了一句:这些复杂 SQL 并不是最近才上线的,代码没改、库也没改,它们以前是不是也这么慢?如果以前也慢,那优化它们只是让系统“更健康”,并不能解释“最近为什么突然慢”。这个反问是整条排查链路的关键转折。方向不对的时候,努力的方法越多,离答案越远,最后往往连最初的线索都丢了。

一个可以照做的判断习惯:把“最近发生变化的东西”列出来。应用变更、数据库参数变更、数据量突增、批量任务时间调整、监控策略变更,这些都算。如果全都对不上,那就说明问题出在“一直存在但达到临界点才暴露”的东西上,比如某个空间涨到一定规模之后,原本几十毫秒的操作变成了几秒。临时表空间正好符合这个特征——它平时几乎没人管,涨到 10G 之后,所有跟它打交道的人都开始付代价。

顺着 LATCH 等待这个线索往下,下一章解决一个很实际的问题:LATCH 属于数据库内部机制,没有对外文档可查,怎么才能坐实它。

4. LATCH 与 STACK 坐实:pool_table_latch 的 Holder 与 Waiter 对照法

LATCH 可以理解成 DB2 内部的一种轻量锁,用来保证多个线程访问内部共享结构时的一致性。以预取队列为例,协调线程往里加任务,预取线程从里读任务,双方访问前都得先拿到这把闩锁,拿不到就得等。它和用户级锁不是一回事,不出现在锁等待视图里,也没有公开文档解释每个 LATCH 名字的含义,所以这一章的重点不是“背概念”,而是“怎么用工具把现场按住”。

4.1 LATCH 等待的持续观察:db2pd -latches -rep 2

第一次用db2pd -latches看的时候很可能什么异常都没有,因为这类等待往往只持续几秒钟,扫一眼的时机不对就错过了。解决办法是加上重复采样参数,让它持续输出。

# 每 2 秒采集一次闩锁信息,持续输出到终端 db2pd -latches -rep 2

输出的关键信息是 Holder 和 Waiter 的对应关系:哪个 EDU 拿着某种 latch,哪些 EDU 在等同一个 latch。本次采样发现 EDU ID 11665 拿着类型为 pool_table_latch 的闩锁,而 2877、2981 等 23 个 EDU 在等它。这意味着一个操作把 23 个操作堵住了,光这一把闩锁就足以让 ACTIVE SESSION 至少到 24。而且这个现象每隔一段时间出现一次,间隔不固定——这解释了为什么业务感受到的是“时不时卡一下”而不是“一直慢”。

采样时机的把握有个经验:不要在工作负载低谷采样,也不要在刚重启后采样。挑业务高峰,或者至少挑监控里 ActSess 抬头的时间窗,采样频率压到 2 到 3 秒,连续采几分钟。这一轮采样成本很低,先做它,再决定要不要上更重的工具。

4.2 STACK 收集:一次抓准 Holder 和 Waiter 的现场

LATCH 等待几秒就释放,等你想去看 Holder 在干什么,它早就干完了。这时候要用 db2pd 的 stack 收集功能,它在抓取线程调用栈的同时,也把每个 EDU 当时的 LATCH 信息和锁信息一起带出来。

# 进入临时目录,建立专门的存放目录,避免和既有诊断文件混在一起 cd /tmp mkdir stack0531 # 每隔 3 秒抓一次,连续抓 10 次,覆盖偶发的等待窗口 db2pd -stack all dumpdir=/tmp/stack0531 -rep 3 10

参数的含义要清楚:all表示抓取所有 EDU 的栈,dumpdir指定输出目录,否则默认落到 DIAGPATH 里,容易和别的诊断文件混;-rep 3 10是三秒一轮、共十轮,正好对应这次“等待不常驻、需要连续采样”的特点。抓完之后先别急着逐个文件读,先做一次筛选:

# 在所有栈文件里筛出与 pool_table_latch 相关的内容 cd /tmp/stack0531 grep pool_table_latch *stack.txt

输出会直接标出哪些文件是 Holding、哪些是 Waiting,以及对应的地址、代码行号和源文件名。如果一条都没筛到,说明这轮没抓住现场,换个时间窗继续采,不要拿没命中的样本硬分析。

提示:生产库上收集栈信息会明显消耗系统资源,对繁忙实例的冲击比一般查询大得多。安排在可控窗口做,采完立刻停,别让它长时间跑着。

4.3 STACK 怎么读:从函数名反推操作类型

拿到 Holder 和 Waiter 的栈文件之后,很多人第一反应是“全是内部函数,看不懂”。其实栈里给出的信息已经足够多,函数名本身就是线索,配合调用关系,能判断出 EDU 正在做什么类型的操作、访问什么类型的对象。

Holder 侧的栈里能认出两个关键函数:sqlbSnapshotTablespace 和 sqlbMonSMSGetContainerPages。前者在给表空间做快照,后者在获取 SMS 表空间容器的数据页信息。合起来就是:这个 EDU 正在为表空间快照逐个容器扫页,扫描期间一直拿着 pool_table_latch。表空间越大、容器越多,这个操作持续的时间就越长,拿锁的时间也越长。

Waiter 侧的栈更能说明业务影响:sqldTableCreateTemp 调用 sqldInternalCreateTable,再往下到 sqlbGetSysTempDatapool,这是一条创建内部临时表、申请系统临时数据池的路径;再结合同一栈里的 sqldEvalDataPred,可以确认它正在执行带谓词求值的查询语句,语句需要临时表。也就是说,被堵住的不是后台任务,而是实时业务查询。

到这里逻辑链条就闭合了:表空间快照操作执行时间长,长期持有 pool_table_latch,把需要临时表的查询全部挡在门外;查询等闩锁,执行时间从毫秒级拉到秒级;会话堆积,ACTIVE SESSION 冲高。而“为什么表空间快照会频繁执行且执行很久”,指向两个问题——监控代理在定期采集表空间信息,以及被采集的表空间本身异常庞大。

4.4 analyzestack:把几十个栈文件归并成一份结论

十轮采样会产生几十个栈文件,逐个 grep 再逐个读,效率低还容易漏。DB2 安装目录下有个 Perl 脚本专门干这件事,放在sqllib/pd目录,名叫 analyzestack,主要功能是对目录内所有栈文件做相同调用栈的汇总统计,并单独分析 LATCH 的 Holder 与 Waiter。

# -d 指定比较调用栈时比对的函数层数,-i 指定栈文件目录,-l 开启闩锁分析 perl ~/sqllib/pd/analyzestack -d 50 -i /tmp/stack0531 -l

它会输出两份结果文件,默认叫 LatchAnalysis.out 和 StackAnalysis.out。前者按闩锁归并出 Holder 与 Waiter 的对应关系,后者按调用栈归并出各类操作的分布,能直接看出哪条调用路径出现频次最高。-d参数控制比对深度,值调大更严格,调小会合并更多相似栈,实际使用时可以先按 20 到 50 试,看归并结果是否合理。

需要说清楚的是,这个脚本是 as-is 提供的示例工具,没有官方支持承诺,用之前先用-help看一遍参数,别拿它当正式产品的功能去承诺什么。但它确实把“翻几十个文件”压缩成了“看两份报告”,属于排障时很省事的工具。

5. 避坑与排查:临时表空间这条链路上的五个常见失误

根因定位到 TEMPSPACE1 之后,很多人以为剩下的就是执行一条命令。实际情况是,从发现空间异常到真正释放、到确认性能恢复,中间有好几个点会让你以为做完了其实没做对。这一章按“现象 → 原因 → 解决”整理五条。

5.1 执行 LIST TABLESPACES 也要等好几秒

现象:本来是一条秒回的管理命令,突然要等若干秒才出结果,某些时候甚至怀疑是不是连不上库了。

原因:查表空间明细需要遍历表空间和容器信息,当系统临时表空间膨胀到 10G、容器里堆了大量 TDA 文件时,这个遍历本身就变慢,同时它还要争 pool_table_latch。也就是说,这条命令慢了,既是症状,也参与到阻塞循环里。

解决:把它当成一个诊断信号记录下来,而不是当性能瓶颈去优化。执行前后各记一次耗时,配合db2 LIST TABLESPACES SHOW DETAIL的输出看总页数、可用页数、页大小、容器数量。本次查到的结果是总页数 354661、页大小 32768 字节、四个容器,折算下来正好对得上 10G 量级,而正常情况下这个系统临时表空间应该很小。

5.2 重启实例之后,空间一点没少

现象:发现异常后先按习惯重启了实例,服务恢复了,但表空间大小没变,过一段时间性能问题又回来。

原因:SMS 系统临时表空间里的 TDA 文件在特定场景下会残留,属于按设计如此的行为,实例重启不会清理这些文件。多个现场经验都指向同一件事:只有把数据库 deactivate 再 activate,才会释放这批残留的临时空间。

解决:走标准的维护窗口流程——先确认没有长事务,再 deactivate database,然后 activate database,之后立刻查表空间大小和执行耗时。这两个动作用的是数据库级别,不是实例级别,别混。做完之后观察一段时间的 ActSess 曲线,确认回落到个位数,再宣布处理完毕。

注意:deactivate 会断开所有连接,必须提前和业务约定窗口,不要在高峰期操作。

5.3 一上来就优化 SQL,越改越乱

现象:抓到慢 SQL 就开始建索引、重写查询,改完一圈,ActSess 该高还是高,反而多了一批“改动引入”的新问题需要回滚。

原因:慢是结果,不是原因。这次语句慢是因为等闩锁,闩锁被表空间快照长期持有,快照慢是因为表空间异常大。优化 SQL 对这条因果链没有任何作用,只是把注意力从根因上挪开。

解决:先做时间线交叉验证。语句是不是最近才变慢?应用有没有变更?如果没有,优先怀疑“一直存在、最近才越线”的东西,比如某个空间、某个计数器、某张累积表。把“这次改动能解释现象吗”当成动手前的必答题,答不上来就先别改。

5.4 栈信息采了一堆,一条也没命中

现象:按命令采了十轮栈,grep 之后发现全是无关内容,或者只有 Waiter 没有 Holder。

原因:要么采样窗口不对,等待现象没出现;要么-rep的间隔太长,几秒一次的等待在采样间隙里溜走了;要么 dumpdir 没指定,文件落到了 DIAGPATH 里,在错误目录里 grep。

解决:采样前先确认现象可复现,用db2pd -latches -rep 2先观察一两分钟,看到 Holder 和 Waiter 成形再启动栈收集;把间隔压到 2 到 3 秒,轮次加到 10 轮以上;命令里显式写 dumpdir,收集完立刻在目录里ls确认文件数符合预期。如果还是采不到,缩短单次采集周期、增加总量,而不是换个工具从头猜。

5.5 把监控采集本身忽略了

现象:问题解决之后复盘,发现表空间快照是被监控代理定时触发的,采集频率不低,而每次采集都要遍历整个表空间。

原因:监控采集通常被当成“无副作用”的旁路操作,实际上它会实质性地访问数据库内部结构、持有闩锁。表空间正常时没什么影响,一旦某个表空间异常大,采集就从“无感”变成“压力源”。

解决:把采集频率和表空间规模一起评估,对体积异常的库表空间降低采集频率或错开业务高峰;把 TEMPSPACE1 的大小纳入监控项本身,设阈值告警,别等它涨到 10G 才被发现。这条经验对任何“定时扫全量结构”的监控任务都适用。

6. 验收与答辩:一次 deactivate/activate 加一个 db2trc 实验把结论钉死

处理动作本身只有两条命令,难的是证明“问题确实解决了”,而不是“这次刚好没复现”。验收我一般按三个层次走:空间层、命令耗时层、业务指标层,三层都对上才算过。

验收项处理前处理后期望判定方式
TEMPSPACE1 总页数35 万页量级回落至很小规模db2 LIST TABLESPACES SHOW DETAIL
表空间列表命令耗时数秒秒级以内返回手工计时,连续执行 3 次
ActSess 峰值频繁超过 30,最高 50 以上回到个位数db2top 连续观察 1 小时
慢 SQL 平均执行时间3 到 6 秒毫秒到百毫秒级MON_GET_PKG_CACHE_STMT 复查
LATCH 等待出现 20 个以上 Waiter采样中不再成规模出现db2pd -latches -rep 2

前两项当场就能验,后三项要留观察窗口。我习惯处理完先盯一小时,再在第二天业务高峰回看一次曲线,两次都正常才敢把问题单关掉。只看“当前不慢”是危险的,等待现象本来就是断断续续的,一小时的平静说明不了太多。

最后一层验证针对的是结论本身,用 db2trc 做个小实验,把“表空间快照会持有 pool_table_latch”这件事变成可观察的事实,而不是推理。db2trc 能跟踪函数调用,还能在命中指定函数时把实例挂起,正好用来按现场:

# 终端一:连上库,开启 trace,命中目标函数入口时挂起实例 db2 CONNECT TO mydb db2trc on -crash DB2.SQLB.sqlbSMSGetContainerPages.entry -suspend # 终端二:执行一条会触发该路径的命令,此命令会卡住不返回 db2 LIST TABLESPACES SHOW DETAIL # 回到终端一:确认 trace 已挂起后,关掉并落盘、格式化 db2trc off db2trc dump trc.dmp db2trc fmt trc.dmp trc.fmt db2trc flw trc.dmp trc.flw

-crash后面跟的是限定跟踪的函数入口,只在这些点上落记录,能大幅压缩 trace 体积;-suspend让实例在命中的瞬间停住,给你从容取证的时间。实验做完记得把实例恢复,别让它一直挂着。这个实验的价值在答辩场景里尤其明显:与其口头解释“快照操作持有闩锁”,不如把 trace 或栈文件摆出来,Holder 的函数栈、Waiter 的函数栈、闩锁名字三样一对,逻辑链条自己就成立了。

答辩或者方案汇报时,讲法建议按“排除 → 收窄 → 证据 → 动作 → 验收”走,别从结论开始讲。先讲四项常规指标全绿、业务量没涨,把范围压到内部等待;再讲慢 SQL 的时间构成里等待占 95%、但锁和 I/O 都不是;然后把 Holder 与 Waiter 的函数栈亮出来;最后才是空间异常和处理动作。听众跟得上这条线,才会相信你的解决方案不是碰运气。

提示:排障报告里保留原始输出截图或文本片段,比事后复述更有说服力,尤其是 grep 命中那几行和表空间明细。

从那以后我每次接手一个新库,第一件事就是把 TEMPSPACE1 的大小和表空间列表命令的耗时一起拉进监控,阈值设低一点,宁可多响几次。性能问题里最难的不是工具不会用,是根本没想到去看那个地方——这次的失误不在技术,在检查清单上少了临时表空间这一项。希望帮到你。

本文还有配套的精品资源,点击获取