
凌晨两点被监控短信吵醒通常不是什么好事情。那条短信内容是某订单库活动日志目录使用率91%预计不到40分钟就会把文件系统写满。走到终端前的时候心里其实很明白——DB2日志占用过高是生产事故里相当棘手的一种因为一旦活动日志无法复用整个数据库会直接挂起连只读会话都进不去恢复动作每一步都带着压力。这篇内容不是讲理论而是把我自己反复打磨的一套DB2日志占用过高问题分析脚本完整拆开。脚本做什么一句话在日志空间告急的时候用最快速度回答三个问题——日志到底被谁吃了、吃得多快、还剩多少时间可以动手。适合数据库运维工程师、DBA、以及那些被日志目录满了折腾过的应用负责人参考不管你是刚接触DB2还是已经带过几个生产库下面这些思路和命令片段都可以直接抄。1. DB2日志机制拆解为什么日志会把磁盘吃满1.1 日志文件究竟在记录什么DB2的事务日志transaction log本质上是数据库的黑匣子记录的是所有数据页面的变更痕迹每一笔INSERT、UPDATE、DELETE修改了哪些页面、事务何时开始、何时提交以及崩溃恢复时需要的回滚信息。DB2采用预写日志WAL机制数据页还没落盘之前对应的日志必须先写到日志文件里这样实例崩溃后可以通过日志做前滚和回滚。你可以把日志理解成记账凭证账本数据文件可以晚点誊清但流水日志必须逐笔记下。只要事务没提交这些日志记录就不能删只要日志文件没归档完成对应的在线日志空间就不能被复用。这就是日志目录会持续上涨的底层原因。DB2的日志分为两类活动日志active log和归档日志archive log。活动日志是循环使用的在线日志文件当前所有未提交事务持有的日志都在这里归档日志是活动日志写满后、在复用之前被复制到归档路径的旧日志文件用于前滚恢复和时间点恢复。1.2 活动日志、归档日志与空间公式活动日志空间的计算公式并不复杂活动日志总空间 LOGFILSIZ × (LOGPRIMARY LOGSECOND) × 4KB其中LOGFILSIZ的单位是4KB页面默认值通常是1000也就是单个日志文件4MB左右LOGPRIMARY是主日志文件数LOGSECOND是辅助日志文件数。数据库日常只使用LOGPRIMARY部分的日志当使用量达到主日志文件的70%时DB2会开始动态分配辅助日志文件。如果LOGSECOND也耗尽事务会直接报SQL0964C日志空间满。实际生产环境里日志占用过高通常包含两条线在线活动日志空间不断膨胀说明活动日志总量被撑大往往和长事务、异常连接有关归档目录持续增长说明在线日志在正常归档但归档链路堵塞、归档存储满了或者归档介质故障旧的日志文件无法从在线目录里释放磁盘空间被归档日志慢慢吃满。这两条线经常同时出现比如归档存储坏了在线日志无法释放加上一个还没提交的长事务几分钟内就能把一块几百GB的盘耗干净。分析脚本的第一要务就是先分清当前是哪种上涨再往下挖。2. 日志占用过高的四条典型触发链路2.1 长事务不提交最常见也最隐蔽长事务是日志空间杀手排行榜的榜首。一个事务从BEGIN到COMMIT之间所有修改的日志记录都必须保留日志文件不能复用。如果应用代码里有个循环每处理几千行才提交一次而这个循环里又包含锁等待、外部接口调用、甚至人工审核环节事务挂一小时是常事。典型现场db2pd -act里能看到一个应用句柄的事务开始时间停留在几十分钟前Txn Log Space数值持续上涨其他连接都被锁堵住日志空间缓慢而坚定地上升。这类问题隐蔽之处在于——数据库本身没有错误CPU不高、内存正常就是日志目录在涨如果不看最老事务这个指标很容易被当成归档问题去排查。2.2 归档链路堵塞日志不是不吃是倒不掉归档链路堵塞的典型表现是活动日志明明不多但归档目录里的日志文件数量几小时不增加同时在线日志目录的使用率持续上升。因为DB2只有在日志成功归档后才会复用在线日志文件归档卡住在线空间就只进不出。最常见的堵塞原因有三个归档目标存储空间满写不进去归档命令本身超时、路径不可达、权限错误使用第三方备份软件做归档软件进程僵死。检查手段也不复杂看归档目录最新文件的时间戳、数最近5分钟新增文件数、再翻一下db2diag.log里有没有ARCHIVE相关的错误或重试记录。这些检查必须固化到脚本里不能每次都人工去翻日志。2.3 配置失配峰值事务撞上小水管有些库的日志配置是按日常负载设计的主日志文件数只够撑住平时十分钟内的日志量结果月末跑批、促销活动、数据订正任务一来事务写入量瞬间翻几倍主日志文件用满后开始疯狂申请辅助日志。如果LOGSECOND配置得又很小数据库可能直接拒绝新事务。这属于配置容量规划问题单纯靠kill连接没用得从参数层面解决。值得一提的是LOGPRIMARY属于静态配置调整后需要重启实例才能生效LOGSECOND虽然可以动态调整但辅助日志本身是应急手段长期靠辅助日志扛峰值说明主日志配置已经不合适了。2.4 批量操作与工具场景的日志放大最后一条链路容易被忽略REORG、LOAD、大批量DELETE/UPDATE操作会产生远超日常的日志量。REORG在重组表数据时几乎所有被移动的页面都会写日志一张几亿行的表REORG下来日志量可能达到表体积的1.5倍以上。LOAD虽然默认可以绕开部分日志记录但如果你用了LOAD ... WITH COPY额外的复制镜像也会往日志机制里掺一脚。这类操作的特点是日志增长快但通常都有明确的操作主体。脚本里单独检查db2 list utilities的输出把正在跑的REORG、LOAD、REDIRECT等工具类任务标记出来能省掉不少排查时间。3. 分析脚本的设计思路与命令选型3.1 脚本的定位定位现场不做自动修复写这个脚本之前我给自己定了一条边界脚本只做诊断不做任何自动修复动作。原因很简单——日志空间告警的场景往往伴随锁等待、长事务和正在执行的批处理自动化kill连接或者删归档日志的风险太高误操作一次比日志满本身更灾难。脚本的职责是把现场拆解清楚谁在吃日志、吃的速率、归档是否健康、配置是否失配然后给出处置建议。3.2 核心采集命令怎么选DB2的命令和视图很多但真正适合告警现场快速采集的就这么几个我把选型理由放在表格里命令/视图采集内容选型理由db2 get db cfgLOGFILSIZ、LOGPRIMARY、LOGSECOND、归档方法等参数配置基线判断是否属于容量失配db2pd -db xxx -logs当前日志号、已写日志页数、待归档日志数、归档状态输出稳定、开销小秒级拿数据db2pd -db xxx -act应用句柄、事务开始时间、事务日志空间占用定位长事务和日志消耗大户的核心手段db2 get snapshot for database日志空间使用率、每秒日志写入字节数官方快照指标计算水位和速率方便db2 list utilities正在执行的LOAD、REORG等工具任务识别批量操作导致的日志放大db2diag.log归档错误、日志分配失败等异常记录捕获归档链路和底层错误为什么优先用db2pd而不是监控视图db2pd不需要开启额外的监控开关对运行中的数据库几乎无侵入输出格式稳定在数据库已经半挂的状态下依然能快速响应。这是告警现场最宝贵的特性。3.3 计算口径水位、速率与最老事务采集到的原始数据要变成可判断的指标核心换算逻辑是日志写入速率通过两次读取db2pd -logs里的Log pages written累计值间隔5秒做差再乘以4KB得到每秒写入字节数。比如两次相差500个页面速率就是500×4KB/5s400KB/s。日志空间使用率直接取快照里的日志空间使用率(%)字段这个百分比是DB2根据当前活动日志已用空间除以LOGPRIMARYLOGSECOND总空间算好的不需要自己重复计算。最老事务从db2pd -act输出里找事务开始时间最早、事务日志空间最大的应用句柄这两个指标指向的往往是同一个根因。这三个口径就是脚本输出的核心围绕它们做告警分级和建议生成。4. 脚本实现DB2日志分析脚本的完整拆解4.1 前置环境与入参约定脚本用bash编写以数据库实例所有者身份运行这是硬性要求——db2pd和db2 get snapshot都需要实例级权限普通账号拿不到完整信息。脚本入参设计为三个数据库名、归档日志目录、工作目录。#!/bin/bash # db2_log_analyzer.sh - DB2 日志占用过高问题分析脚本 # 用法: db2_log_analyzer.sh 数据库名 [归档日志目录] [工作目录] DB_NAME${1:-SAMPLE} ARCH_DIR${2:-} WORK_DIR${3:-/tmp/db2logdiag} TS$(date %Y%m%d%H%M%S) mkdir -p $WORK_DIR # 加载实例环境变量确保db2命令可用 if [ -f $HOME/sqllib/db2profile ]; then . $HOME/sqllib/db2profile fi db2 connect to $DB_NAME /dev/null 21 if [ $? -ne 0 ]; then echo 无法连接数据库 $DB_NAME请检查实例状态和连接权限 exit 1 fi工作目录用来存放历史趋势CSV文件配合定时任务跑能积累出日志增长的长期曲线。这个设计后面还会细讲。4.2 日志水位与写入速率采集采集部分分三步配置参数、空间使用率、写入速率。空间使用率用快照拿简单直接写入速率用两次数值差计算避免对数据库产生额外负担。echo 1. 日志配置参数 db2 get db cfg for $DB_NAME | grep -E Log file size|Number of primary log files|Number of secondary log files|First log archive method|Infinite log active | sed s/^[[:space:]]*// echo echo 2. 日志空间使用率 LOG_UTIL$(db2 get snapshot for database on $DB_NAME \ | grep -E Log space used|日志空间使用率 \ | awk -F[()] {print $2}) echo 当前日志空间使用率: ${LOG_UTIL}% echo echo 3. 日志写入速率(5秒采样) P1$(db2pd -db $DB_NAME -logs | grep Log pages written | awk {print $NF}) sleep 5 P2$(db2pd -db $DB_NAME -logs | grep Log pages written | awk {print $NF}) WRITE_RATE_KB$(( (P2 - P1) * 4 / 5 )) echo 日志写入速率: ${WRITE_RATE_KB} KB/s字段名在不同语言环境下有差异grep里同时匹配中英文关键字我在实际使用中验证过这个写法在中文和英文环境都能抓到数据。如果抓不到先手动跑一次命令确认输出格式再调整grep关键字这种细节比脚本本身更值得注意。4.3 活动事务画像提取活动事务画像就是找谁在吃日志。db2pd -act的输出里重点关注三个字段应用句柄、事务开始时间、事务日志空间。脚本用awk扫一遍同时找日志空间最大的连接和事务开始时间最早的连接。echo 4. 活动事务画像 db2pd -db $DB_NAME -act 2/dev/null | awk NR 8 $1 ~ /^[0-9]$/ { handle$1; txn_start$5; txn_space$6; if (txn_space max_space txn_start ! ) { max_spacetxn_space; top_handlehandle; top_starttxn_start } if (txn_start oldest_start || oldest_start ) { oldest_starttxn_start; oldest_handlehandle } } END { if (top_handle ! ) { print 日志空间占用最大连接: 应用句柄 top_handle , 事务开始 top_start , 占用 max_space KB } if (oldest_handle ! ) { print 最老事务连接: 应用句柄 oldest_handle , 事务开始 oldest_start } }不同DB2版本的db2pd -act列位置可能有细微差异跑脚本前先用db2pd -db xxx -act看一眼实际输出的列顺序再调整awk里的字段序号。这个提醒是我自己踩过坑的地方——曾经因为版本升级后列错位脚本抓出来的一直是空值排查半天才发现是字段索引变了。4.4 归档链路与诊断日志检查归档检查的逻辑很直观归档目录最近5分钟有没有新增文件以及db2diag.log里有没有归档错误。这两项结合起来基本能判断是归档存储故障还是根本没有新日志产生。echo 5. 归档链路检查 if [ -n $ARCH_DIR ]; then NEW_COUNT$(find $ARCH_DIR -type f -mmin -5 2/dev/null | wc -l) LATEST_FILE$(ls -lt $ARCH_DIR 2/dev/null | sed -n 2p) echo 归档目录最近5分钟新增文件数: ${NEW_COUNT} echo 最新归档文件信息: ${LATEST_FILE} else echo 未提供归档目录跳过归档文件检查 fi DIAG_LOG$(ls ~/sqllib/db2dump/db2diag.log 2/dev/null || ls /home/*/sqllib/db2dump/db2diag.log 2/dev/null | head -1) if [ -n $DIAG_LOG ]; then echo db2diag.log 最新归档相关记录: grep -iE archive|log full|SQL0964 $DIAG_LOG 2/dev/null | tail -10 fi归档目录路径在不同环境差异很大所以脚本里做成可选参数。如果DBA自己都说不清归档路径在哪那说明问题可能比日志空间告警更严重——归档配置本身就有隐患。4.5 历史趋势落盘与报告组装历史趋势是本脚本长期价值的关键。单次快照只能看到当前水位但告警场景真正需要回答的是这个上涨速度是突发还是持续了一小时。把每次采样的关键指标追加到CSV文件配合定时任务跑就能画出趋势线。echo $TS,$DB_NAME,${LOG_UTIL},${WRITE_RATE_KB},${top_handle:-none},${oldest_start:-none} $WORK_DIR/history.csv # 用尾部24条记录计算1小时前的水位 tail -24 $WORK_DIR/history.csv | head -1 | awk -F, {print 1小时前日志使用率: $3%}报告组装部分就是把上面所有输出整合成一段带风险等级的诊断信息。我建议把报告的最终输出放到脚本最末尾统一打印这样既方便人看也方便后续接入告警平台时直接截取最后几行。脚本整体结构保持采集在前判断在后的顺序任何人拿到输出都能顺着看明白因果链。5. 输出结果怎么读一份诊断报告的三层信息5.1 第一层水位和速率先做量化判断脚本输出的第一层信息是水位和速率把这两个数摆在一起很多问题当场就能定性水位90%、速率接近0说明日志空间是被存量占满的大概率是长事务挂着不提交日志只进不出优先查最老事务水位90%、速率很高说明有大量写入正在发生要么是批处理正在跑要么是应用在做大面积更新优先查事务画像里的日志占用大户水位不高、归档目录持续增长说明在线日志没有异常是归档文件堆积吃磁盘处理方向是归档存储治理和数据库本身关系不大。数值是客观的但解读顺序很关键。我一直建议团队按水位→速率→最老事务→归档状态的顺序看输出不要跳过中间任何一层直接去看某个应用连接——那样容易误伤正常任务。5.2 第二层锁定嫌疑对象的顺序第二层是嫌疑对象的优先级排序脚本已经输出了最老事务和日志占用最大连接实际处置顺序应该是先看归档状态如果归档最近5分钟没有新文件即使现在有长事务也要先把归档问题解决掉否则即使事务提交了在线日志还是无法释放再看最老事务事务开始时间距今超过30分钟基本可以认定为长事务联系应用确认业务状态最后看日志占用最大连接这个连接可能是健康的批量任务也可能是锁等待中的僵尸连接需要结合事务开始时间判断。这个顺序背后是先解除阻塞、再处理消耗的运维原则。归档堵塞是全局阻塞长事务是局部阻塞先解决影响范围大的。5.3 第三层处置建议的生成逻辑脚本本身不自动执行任何修复但会在报告末尾给出建议。建议生成逻辑用简单的条件判断实现如果归档停滞提示检查归档存储空间、归档命令进程、第三方备份软件状态如果有长事务且日志水位超90%提示与应用确认是否可kill对应应用句柄同时给出db2 force application (句柄号)命令示例如果日志配置明显偏小且业务有周期性峰值提示调整LOGSECOND应急并规划LOGPRIMARY的扩容窗口如果发现REORG/LOAD正在执行提示评估操作窗口和日志空间余量必要时暂停操作。这些建议的可执行性比精确性更重要。告警现场的人不需要一篇论文需要的是下一步做什么的明确指引。6. 实测复盘一次日志沾满告警的完整处理6.1 告警现场与快速止血回到开头那个凌晨告警。当时我正在值班脚本已经在测试环境跑过几轮生产环境还是第一次真正面对告警。登录后我先用脚本跑了一遍诊断拿到几个关键数据日志空间使用率92%日志写入速率约380KB/s最老事务是一个应用连接事务开始时间在45分钟前事务日志空间占用1.4GB。归档目录最近5分钟有新增文件归档链路本身是健康的。这个组合很典型水位在涨、速率不高、有一个持续了45分钟的长事务——基本锁定就是那个事务在耗日志。联系了应用值班同事确认该连接对应的是一段数据订正逻辑但负责执行的同事已经下班事务卡在另外一个表的锁等待里。现场几个操作者确认后执行force application把连接强制断开。事务回滚后日志空间在10分钟内从92%降到了35%左右。整个止血过程不到20分钟。如果没有脚本提前把嫌疑对象定位清楚按传统思路可能要先查归档、再查快照、再翻应用日志凌晨三四点的时候这个排查链条会拉得很长。6.2 脚本定位出的根因与验证事后复盘时我们把该事务的SQL和业务代码翻了出来。真正的问题不是SQL写得有多差而是应用的事务边界设计有问题一个存储过程里循环处理几十万行数据每5000行才COMMIT一次循环中间还有一段外部系统接口调用。接口调用超时重试期间事务一直保持打开状态锁等待又拖住了后续操作日志空间就在这个空档里被慢慢吃掉了。脚本输出的价值在于把数据库层面的表象和业务层面的原因连接起来了。数据库层面看到最老事务业务层面才能顺着应用句柄找到对应的代码逻辑。如果没有这个连接点DBA只知道有长事务但说不清是哪个业务功能触发的追责和修复都会卡住。6.3 事后优化与监控进化这次事件之后我们做了两件事。第一件是推动应用改造事务提交粒度把每5000行提交一次改成每500行提交一次同时给外部接口调用增加了超时上限第二件是把监控参数做了调整原来只监控日志空间使用率现在增加了两个指标——最老事务时长和历史趋势斜率任一个超过阈值就提前告警。脚本本身也迭代了一个版本把历史趋势CSV保留周期从7天延长到30天并增加了一个简单的趋势判断函数当连续6次采样也就是半小时内日志使用率持续上升超过5个百分点时会在报告里额外标注持续上涨趋势。这个功能的触发时间比传统空间阈值告警早很多给处理留出了更多余量。我个人的体会是DB2日志占用过高这个问题的难点从来不在怎么修而在怎么快速定位。日志机制本身是透明的命令也都是现成的缺的是一套把分散信息整合成判断结论的流程。这个脚本就是那套流程的固化。如果你也在和DB2日志空间缠斗建议先收集半个月的db2pd -logs和db2 get snapshot输出把正常基线的水位和速率摸清楚再谈阈值和告警你会发现很多突然告警其实早有征兆。