马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?用户注册
×
Sybase ASE 事务日志段满应急实战:从 1105 到 last-chance threshold
一、具体的问题
凌晨两点收到值班电话:结算库所有写入全部挂起,前台报 Can't allocate space for object 'syslogs' in database 'fin_db' because the 'logsegment' is full,也就是常见的 1105。isql 登上去看,sp_who 里几十个连接停在 AWAITING COMMAND 后面的挂起态,应用重试风暴把连接池也拖垮了。
这种场景的麻烦在于:日志满之后,连用来"清日志"的事务本身都可能写不进日志,操作不当会让库彻底卡死。更糟的是很多同事的第一反应是 dump transaction ... with no_log,清完不备份就继续跑,把恢复链断了。
这篇文章把 ASE 日志段满的应急处理、根因排查和 threshold 自动化防御完整走一遍,所有命令在 ASE 15.7 / 16.0 实测可用。
二、核心原理
先讲清楚 ASE 的日志机制和别的数据库不一样在哪:
- 日志是一张实打实的表。ASE 的事务日志就是 syslogs,物理上放在 log segment 里。只要还有活跃事务要求写日志、而 log segment 没空间,事务就挂起等待——这是 1105 的直接来源。SQL Server 满了自动截断重用,MySQL 的 redo 循环写,都和它行为不同。
- 截断靠 dump transaction。日志空间被"已提交但未备份"的记录占着,只有 dump transaction(或 truncate_only)之后,日志头部才能重用。数据库选项 truncate log on checkpoint 打开后虽会自动截断,但那样日志链就断了,出事只能恢复到上一次全备——生产库禁用。
- last-chance threshold 是 ASE 特有的保险丝。当 log segment 剩余空间低于 LCT(last-chance threshold)时,ASE 自动执行挂在这个阈值上的存储过程(默认 sp_thresholdaction),用它来做 dump transaction,抢在真正满之前把空间放出来。很多库 1105 反复发作,就是因为这条保险丝要么没配、要么阈值过程写错了。
- truncation point 决定日志能不能降。syslogshold 显示的最旧活动记录,可能是长事务、复制(Replication Server 的 LTM 截断点)、甚至忘记回滚的 dump。只要它不推进,你怎么 dump 都白搭——这是排查 1105 的核心抓手,和"空间不够就扩盘"的直觉正好相反。
- 1105 和 1104 要分清。1105 是 logsegment 满,1104 是 data segment 满,处理方向完全不同:前者清/截日志,后者扩数据空间或归档。看报错里是 syslogs/logsegment 还是普通表名。
三、实例参考(动手步骤)
步骤 0:确认现状基线
- sp_helpsegment logsegment -- 看 log segment 剩余 free pages
- sp_spaceused syslogs -- 日志实际占用
- sp_helpdb fin_db -- 确认 log on 大小与 device 分布
复制代码
步骤 1:找到"卡脖子"的最旧事务
- dbcc opentran(fin_db) -- 显示最旧的活动事务
- select * from syslogshold -- 所有截断点,含复制位点
复制代码
syslogshold 的 starttime 减去当前时间,就是最老事务的"年龄"。如果 name 列是 Replication Server Truncation Point,说明是复制端没跟上,不是业务事务的锅。
步骤 2:应急疏通(按顺序,别跳步)
- -- 第一优先:清掉可以清的日志(日志链保留到上一份 dump database)
- dump transaction fin_db with truncate_only
- go
复制代码
绝大多数场景这一步就能放空间、解开挂起事务。只有当日志已经 100% 满、连 truncate_only 自己的日志都写不下时,才用极端手段:
- -- 极端手段:完全不留日志记录。危险,见下
- dump transaction fin_db with no_log
- go
- -- 用完必须立刻做,否则日志链断,无法恢复到点:
- dump database fin_db to '/backup/fin_db_emergency.dmp'
- go
复制代码
with no_log 是断链操作:之后到下一次 dump database 之间的任何数据丢失都无法补救。执行前必须短信/电话通报值班负责人,执行后立即全备。
如果挂起事务里有明确该杀的(比如跑了四小时的失控报表):
- select spid, cpu, physical_io, blocked, cmd from master..sysprocesses where dbid = db_id('fin_db')
- kill <spid> -- 回滚可能较久,用 sp_who 持续观察
复制代码
步骤 3:根因排查——日志为什么会被灌满
最常见的三个根因,逐一对号:
- 大事务一把梭:一条 delete 千万行的 SQL 全程占日志。改成分批:
- set rowcount 5000
- while (1=1)
- begin
- delete from fin_detail where stat_date < '2024-01-01'
- if @@rowcount = 0 break
- dump transaction fin_db with truncate_only -- 每批放日志(非复制库)
- end
- set rowcount 0
复制代码
- 复制截断点不动:syslogshold 里 LTM 位点停滞,去查 Replication Server 的队列和 primary 连接,别在数据库端反复 truncate。
- 有人忘了提交:客户端断网但会话还挂着未提交事务,syslogshold 里 age 一直涨,kill 对应 spid。
步骤 4:把保险丝装回去——threshold 自动化
检查阈值配置:
- sp_helpthreshold fin_db -- 没有 last-chance 阈值行(status 无 LCT)就是失守
复制代码
没有就建一个专用过程并挂到阈值上:
- use sybsystemprocs
- go
- create procedure sp_thresholdaction
- @dbname varchar(30), @segment varchar(30), @space int, @status int
- as
- begin
- -- LCT 触发:dump 放日志并留痕。注意:过程内不要做大量写日志操作
- dump transaction @dbname with truncate_only
- print 'LCT fired: %1! log pages left, dump done', @space
- end
- go
- -- 挂到阈值:剩余约 512 页(1GB 日志 ≈ 25% 处)时触发
- sp_addthreshold fin_db, logsegment, 512, sp_thresholdaction
- go
复制代码
要点:阈值过程本身要轻,只做 dump 和告警打印;想在触发时通知运维,用 sp_logevent 或外部脚本轮询错误日志,别在过程里直接 exec @sender 发重量级查询。
步骤 5:长效治理
- 日志和数据分离到独立 device,alter database fin_db log on logdev = '512M' 按峰值调日志大小(大事务高峰期的 1.5 倍)。
- 监控侧每天巡检 syslogshold 的最大 age,超过 30 分钟告警。
- 复制库把"截断点 age"纳入 Replication Server 队列监控,两边一起看。
四、实操检查清单
- 每个生产库都有 last-chance threshold 且阈值过程存在、能成功执行 dump(sp_helpthreshold 确认)。
- truncate log on checkpoint 必须为 false(sp_dboption 查证),保证日志链完整。
- 日志 device 与数据 device 物理分离,日志大小按大事务峰值 × 1.5 评估。
- 所有批量 DELETE/UPDATE 作业走 set rowcount 分批 + 批间截断/备份节奏。
- 巡检脚本覆盖两项:sp_spaceused syslogs 占用率 > 70% 告警;syslogshold 最旧事务 age > 30 分钟告警。
- 复制环境的 truncation point age 单独监控,与数据库端日志水位联动看。
- with no_log 只允许在 1105 已完全堵死且 truncate_only 失败时使用,事后 5 分钟内必须完成 dump database,并写入变更记录。
几个容易踩的坑
- truncate_only 在日志全满时也会报 1105——它自己写日志。此时才是 with no_log 的唯一合理场景,不要一上来就用。
- 清完不做 dump database:with no_log 后的库处于断链状态,第二天磁盘坏了就只剩上次全备。这条必须写进应急手册,用流程约束而不是靠人记。
- 阈值过程里写日志:在 sp_thresholdaction 里建临时表、插记录表,会让阈值过程本身再写日志,触发"阈值过程失败→再次触发"的循环。过程只做 dump + print。
- 复制库反复 truncate 没用:truncation point 在 Replication Server 手里,数据库端 dump 放不掉空间,必须去修复制链路。
- 把 1105 当 1104 处理:报错里是 syslogs/logsegment 就是日志满,扩数据盘毫无意义;反之 1104 去清日志也救不了。
治理前后对比
| 指标 | 治理前 | 治理后 | | 1105 挂起次数 | 每月 3~7 次 | 0 次(连续 4 个月) | | 日志段占用峰值 | 100%(靠人工抢救) | 68%(LCT 自动放空间) | | 最旧事务 age 巡检 | 无 | >30 分钟自动告警 | | 恢复能力 | 断链风险(no_log 后未全备 1 次) | 日志链完整,可恢复到任意时点 | | 单次应急处理时长 | 40~90 分钟(含误操作返工) | 10 分钟内(阈值自动兜底) |
核心就一句话:ASE 的日志满从来不是"空间不够",而是"截断点不动 + 保险丝失守"。把 last-chance threshold 装好、把 syslogshold 看住,1105 就从深夜惊魂变成一条普通巡检告警。 |