【YashanDB 知识库】存储过程报错 snapshot too old
问题描述
20231127 上午客户反馈绩效系统 20231125、20231126 出现 2 次 YAS-02020 snapshot too old 的问题,测试也有类似问题。
该过程是客户新增的存储过程,目的是通过 PRO_RUN_JOB 作为主控,调度其他存储过程,后续不用其他调度引擎。
原因分析
错误信息收集分析
分析存储过程报错日志,核查 UNDO_RETENTION、undo 表空间
看了相应的优化建议,可以增大参数 UNDO_RETENTION 的值,或者使用更大的 undo 表空间。
客户环境目前 UNDO_RETENTION 配置了 600,该参数单位是秒。临时修改客户测试环境的配置为 3000,试图规避问题。
正常理解,MVCC 用于高并发的情形,会出现该错误,而该存储过程执行是串行的,不符合预期!需要继续分析。
UNDO、MVCC 机制分析
了解背后机制,snapshot too old 是由于 db 需要做多版本控制(mvcc),在数据 commit 之后,仍然会保留 undo 一段时间,在超过这段时间之后 undo 的空间会被复用,如果需要还原的数据超过了这个时间,则还原不了,触发该错误。
崖山 db 快照隔离级别的核心就是 MVCC(Multi-Version Concurrency Control),多版本并发控制
快照本质上就是一个时间点。记录版本的时间点为事务提交的时间点。
可见性可分为 2 类情况:
1、事务内的语句可见性
2、事务间的可见性
由于老版本保留时间的限制,我们可能无法读取到某些很老的版本,这时就会有 snapshot too old 错误。
事务功能梳理 - YashanDB
![](https://static001.geekbang.org/infoq/81/81e774fa21fc9cb5e1a830196cc0dac1.png)
可以确认:
不同事务间,特别是长查询,容易出现该问题;
另一个是专门的快照读,如 Oracle 的快照读 select count(*) from tableA as of timestamp to_timestamp('2013-10-16 08:46:57','yyyy-mm-dd hh24:mi:ss');
疑点一
分析测试环境出现过类似的问题,看到报错的时间点:
2023/11/23 13:21:52 执行的是 PRO_DAILY_DPSIT
2023/11/23 12:04:00 执行的是 PRO_SYS_USER_POST_REL
有个共同点:
PRO_SYS_USER_POST_REL 用的是 merge,同时读和写同个表
PRO_DAILY_DPSIT 用的是 insert into select,insert 和 select 的表有相同的
开始怀疑这里有并发机制,读写自同个表,但是作为同个事务内的,undo 不应该被释放掉,不符合预期!
确认问题
剩下的就是长查询的可能。只有在查询的时候才会报这个错误,update 的时候是不会报这个错误的。
继续分析游标 loop 的结果,如果在游标读取的时候报错,则 remark 也是:更新跑数任务明细表:单个任务成功结束的相关信息
fetch 在一开始就拿到 scn(SCN 即系统改变号(System Change Number)),每次 fetch 都用的 open 时的 scn,由于 loop 过程中还是 update RUN_JOB_DETA 表更新了 db 存储的某一个 block,commit 之后 undo 的数据会保留 undo_retention 的时间,当超过这个时间 undo 的空间会别其他任务复用。
而一个 block 不止一条数据,在后续 loop 的过程,如果继续读取到这个 block 的数据,需要对这个 block 还原到 scn 对应的状态再读取,由于 undo 已经被复用,就会报 snapshot too old 的错误。
undo 机制、MVCC 机制,在 Oracle、DB2 中都是有的,该问题也会存在。崖山的数据块不会存 2 个表的数据,可以使用下面方法规避:
PRO_RUN_JOB 这个存储过程使用游标读取 RUN_JOB_DETA 表做为配置表,loop 过程中需要更新的结果数据放到历史表(主要更新任务状态,开始、结束时间,耗时),配置表和历史结果分开存储,彻底解决问题。
经验教训
Undo 机制中,undo_retention 是一个不容易理解的参数项。设置之后,Oracle 会根据自动 undo 管理的原则进行调节,进行空间拓展,来适应实现用户的期间要求。而崖山 db 需要根据实际的业务频繁度和数量量、以及 undo 表空间设置情况进行综合评估。
存储过程存在游标遍历,需要注意更新目标表不是 fetch 的表,否则存在类似问题。
评论