前言:

        首先,这次故障颠覆了我对Oracle的认知,在笔者的一贯认知中,Oracle是完全可靠的,只要数据不损坏,他能从任意的掉电,宕机等异常状况下恢复,而redo和undo的存在,不会让用户出现脏读和错读的情况。

但此次的故障,让我对O事务处理的流程产生了一定的怀疑,事务的原子性、一致性、隔离性、持久性未能得到保障。本次故障开始是同事处理的,后经过大家一起的探讨和研习,终于搞清了事情的本末,也确认了这是一个无法避免的现象,因为总要有个先后,O也无法避免这样问题的发生。但由于Oracle闭源,无法得知其内部的处理流程,以下部分内容基于猜测,进行了验证。

 

故障说明:(涉及系统名称都用通用性名称代替)

系统分为 2 个数据库: 交易库、核心库

交易路径: ①一笔交易需要进行 ---> ②交易信息插入交易库,设置交易状态为 “初始”  ---> ③去核心库查询是否付款情况 ---> ④如果付款成功,修改交易库中交易状态为 “成功”  ---> ⑤查询交易库中该交易的状态 ---> ⑥如果状态为成功,则进行发货

当日故障现象: 程序查询交易状态,显示“成功”,于是进行了发货操作。但问题时段交换机出现问题,导致IO无法写入,引发了db的重启。 重启后查看刚才的交易发现状态为“初始”。

 

我们dba无法了解 应用程序的细节。 但业务说过,他们 查询发货 和 交易修改 两个程序绝对不可能是同一个session,因此不会出现同一session中未提交但可读取的情况,到这里就比较奇怪了。

所以简化一下问题:

session1 update数据并commit ----> session2 看到了session1 update后的数据(说明session1 commit成功,正常道理来讲redo已经写入磁盘,undo事务表已经更新)  ----> 由于IO问题db重启 ---->  session1 update 并commit成功的操作丢失了

 

下面我们进行一个测试:

1、创建一张测试表(id name),并插入数据:

 

 

2、session1 更新数据 (sid 761)

update t set name = 'LeBron' where id=1; 不提交

 

 

3、session2 (sid 760)查询相关数据,应该看不到session1修改的记录。还是1,Kobe

 

 

4、另起一个session,模拟交换机故障,IO被卡住的情况,这里我们使用oradebug suspend命令hang住 lgwr进程,不让他写日志。

 

 

5、session1 进行提交操作,由于lgwr无法写日志,commit操作会hang住。

 

6、session2 查看session1 已经发出commit,但是没有提交成功的记录。此时,我们会发现虽然session1 还未提交成功,但是session2 已经能看到session1 修改的数据了。

 

 

7、重启数据库,模拟异常重启,因为lgwr被hang住,无法完成chkpoint,这个使用abort模拟异常宕机。

 

 

8、再次登录其他session,查看t表中的数据。发现session1中的commit操作,实际上丢失了。

 

 

 

 总结一下:

与常规和常识不符的地方:   session2 看到了 session1 未提交成功的数据。

原理分析:

session1 commit后实际只在内存中生效,lgwr并未将缓存重做日志条目写到磁盘,所以此时其他session来查询时查询的结果只是内存中的数据。之后db重启,因之前的操作并未实际
落盘,所以重启后实际数据仍为修改前的数据。
   执行COMMIT时,会完成以下操作:
   1)为更新undo事务表生成一条更改向量
   2)将更改向量复制到日志缓冲区
   3)在undo端头块上应用更改向量
   4)通知lgwr写日志
   5)lgwr 将 log buffer中的日志写入redo
   5)返回 Commit complete.
    在第四步和第五步中间,其他的会话能看到事务已经改变,尽管提交记录还没有写入到磁盘。如果实例在此时崩溃,那么
在实例重启后,你可能会发现已经请求提交的事务没有被恢复。
 
 
我比较好奇,于是另外做了一个实验,想看一下,如果在io出问题之前更改的记录已经刷回磁盘,重启后会是什么样子的:
1、session1 更新数据
2、session2 alter system flush buffer_cache;将内存中的数据写入磁盘 (此时ITL应该标志事务未提交)
3、session3 hang住lgwr
4、session1 commit不成功 hang住 (更新了内存中的undo事务表,但写日志操作失败)
5、session4 查看,能看到session1修改的数据
6、重启db,重新连接,发现session1 的修改还是会丢失。
 
进行了flush buffer cache操作,磁盘中的block已经为新数据。 hang住lgwr后进行commit,内存中的undo事务表已经标志为提交,但是由于lgwr被hang,undo事务表无法写入磁盘。 重启后磁盘中的数据根据undo进行了回滚。