最近看到一个比较有意思的例子,是和更新重启动有关。
更新重启动是Oracle中一个隐藏的知识点,当Oracle在读取数据时,会构造读一致性,当读取的内容发生了修改,Oracle会通过UNDO信息来还原数据的前镜像,把数据还原到查询发生的时刻,通过构造一致性的结果来实现读取数据的一致性和隔离性。
Oracle实现的读一致性也被称为多版本读一致性,每个会话都会构造自己的一致性查询版本。但是对于写操作而言,这种方式是不可能的,因为最终的数据只有一份,如要要修改数据,就只能修改唯一的这份数据,所以对读操作,访问的是一致性版本,而对于写操作,修改的永远是当前版本。
既然写操作只能修改当前版本,当写操作执行的过程中,发现要修改的记录发生了变化,破坏了更新发起时刻的一致性,这时就触发了更新重启动,也就是说更新操作会放弃之前的修改,然后重新发起一次新的更新操作。
ITPUB上有一个有意思的案例,展示的就是更新重启动现象,链接如下:http://www.itpub.net/forum.php?mod=viewthread&tid=2102897
这里来重现一下更新重启动,建立一张简单测试用表,在会话一插入初始数据并进行更新操作:
SQL> SET SQLP ‘SQL1> ‘
SQL1> CREATE TABLE T_UPDATE (ID NUMBER);
Table created.
SQL1> INSERT INTO T_UPDATE SELECT ROWNUM FROM DUAL CONNECT BY LEVEL < 4;
3 rows created.
SQL1> COMMIT;
Commit complete.
SQL1> SELECT * FROM T_UPDATE;
ID
———-
1
2
3
SQL1> UPDATE T_UPDATE SET ID = 4 WHERE ID > 2;
1 row updated.
在会话2发起UPDATE语句,更新ID大于0的记录,这时UPDATE操作由于会话1更新了ID为3的记录且未提交,会处于挂起状态:
SQL> SET SQLP ‘SQL2> ‘
SQL2> UPDATE T_UPDATE SET ID = ID + 0.1 WHERE ID > 0;
然后在会话3插入一条新的记录,并提交:
SQL> SET SQLP ‘SQL3> ‘
SQL3> INSERT INTO T_UPDATE VALUES (0.1);
1 row created.
SQL3> COMMIT;
Commit complete.
这时回到会话1,进行提交:
SQL1> COMMIT;
Commit complete.
检查会话2,发现更新重启动发生,UPDATE操作更新了4条记录:
4 rows updated.
SQL2> SELECT * FROM T_UPDATE;
ID
———-
1.1
2.1
4.1
.2
会话2中不仅更新了之前存在的三条记录,连更新语句发起之后插入并提交的第四条记录也一起进行了更新,这说明更新重启动发生在第四条记录插入之后。
这个案例很好的展示了更新重启动现象,不过这个帖子并没有完,除了这个更新重启动的案例外,还展示了一个另外一个案例,操作和这个案例仅有很小的差别,但是结果却大不相同。
首先把数据恢复到初始状态,在会话2回滚之前的更新:
SQL2> ROLLBACK;
Rollback complete.
在会话1,恢复初始的数据情况,然后开始更新数据:
SQL1> DELETE T_UPDATE WHERE ID < 1;
1 row deleted.
SQL1> UPDATE T_UPDATE SET ID = 3 WHERE ID = 4;
1 row updated.
SQL1> COMMIT;
Commit complete.
SQL1> SELECT * FROM T_UPDATE;
ID
———-
1
2
3
SQL1> UPDATE T_UPDATE SET ID = 4 WHERE ID > 2;
1 row updated.
在会话2,对表中所有的记录的ID执行UPDATE操作,和上一个案例的唯一差别是,这里没有使用WHERE语句:
SQL2> UPDATE T_UPDATE SET ID = ID + 0.1;
同样在会话3插入数据并提交:
SQL3> INSERT INTO T_UPDATE VALUES (0.1);
1 row created.
SQL3> COMMIT;
Commit complete.
回到会话1,进行提交:
SQL1> COMMIT;
Commit complete.
发现会话2仅更新了3条记录:
3 rows updated.
SQL2> SELECT * FROM T_UPDATE;
ID
———-
1.1
2.1
4.1
.1
可以清晰的看到,最后一条插入的记录并未被更新,说明更新重启动并未被触发。
为什么带有WHERE条件的更新操作触发了更新重启动,而不带WHERE条件的更新未触发重启动呢,我是这样理解的:当UPDATE操作包含了WHERE条件,那么这个查询的结果要满足WHERE定义的查询一致性,当更新发现一致性不满足的情况下,就会触发更新重启动。而对于不包含WHERE条件或包含WHERE条件但是该条件与表查询无关的情况,这时Oracle的目标只是将表中所有的数据进行一次更新,并不需要考虑一致性的问题。因此,这个不加WHERE条件的UPDATE,感觉其实现方式上类似这种通过ORA_ROWSCN限定来实现:
SQL2> ROLLBACK;
Rollback complete.
回到会话1恢复数据,并检查ORA_ROWSCN的值:
SQL1> DELETE T_UPDATE WHERE ID < 1;
1 row deleted.
SQL1> UPDATE T_UPDATE SET ID = 3 WHERE ID = 4;
1 row updated.
SQL1> COMMIT;
Commit complete.
SQL1> SELECT ID, ORA_ROWSCN FROM T_UPDATE;
ID ORA_ROWSCN
———- ———-
1 231512964
2 231512964
3 231512964
SQL1> UPDATE T_UPDATE SET ID = 4 WHERE ID > 2;
1 row updated.
在会话2,发起包含制定ORA_ROWSCN限制条件的更新:
SQL2> UPDATE T_UPDATE SET ID = ID + 0.1 WHERE ORA_ROWSCN = 231512964;
会话3插入并提交:
SQL3> INSERT INTO T_UPDATE VALUES (0.1);
1 row created.
SQL3> COMMIT;
Commit complete.
回到会话1,进行提交:
SQL1> COMMIT;
Commit complete.
检查会话2:
3 rows updated.
SQL2> SELECT * FROM T_UPDATE;
ID
———-
1.1
2.1
4.1
.1
采用ORA_ROWSCN的效果与直接UPDATE不带任何WHERE条件是一样的,说明更新只关注UPDATE语句发出时刻的数据,不再考虑整体更新结果的一致性问题。
关于更新重启动的更详细的描述,建议参考ASKTOM上的回复:https://asktom.oracle.com/pls/apex/asktom.search?tag=write-consistency