问题描述
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
可以确认:
- 不同事务间,特别是长查询,容易出现该问题;
- 另一个是专门的快照读,如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的表,否则存在类似问题。