在ITPUB上看到有人提出了这个问题。在Sqlserver或一些其他的数据库中,DDL语句也是可以回滚的,那么Oracle为什么不能回滚DDL语句呢。
要说明这个问题,首先需要说明什么是DDL语句。DDL语句是数据定义语句,包括各种数据对象的创建、修改和删除,以及授权等操作。
在Oracle中DDL语句将转化为修改数据字典表的DML语句。一个简单的修改表的DDL语句,会导致Oracle在后台通过递归SQL语句进行大量的查询和修改的操作。
如果有兴趣,可以通过SQL_TRACE根据一下DDL语句,检查一下Oracle后台实际执行了哪些操作。
在Oracle中,Oracle执行DDL前会发出一个COMMIT语句,然后执行DDL操作,最后再发出一个COMMIT操作。
前面提到了对于Oracle而言,DDL实际上是数据字典表的一系列的修改,也就是数据字典表的DML操作,那么理论上讲Oracle是完全有能力实现DDL语句的回滚的,那么Oracle为什么设计成现在的工作方式。要知道Oracle以灵活和强大的可定制性著称,但是Oracle没有给用户任何回滚DDL的可能性,显示是存在着十分充分的理由。
首先分析一下Oracle为什么要在DDL语句之前和之后各执行一次COMMIT,其实道理很简单,Oracle是为了将用户的读写操作和数据字典的修改隔离开,用户数据的读写不应该和数据字典的操作放在同一个事务中。
为了说明Oracle为什么不回滚DDL语句,下面假设Oracle可以回滚DDL语句,看看这会给Oracle数据库带来什么影响。
本栏目更多精彩内容:http://www.bianceng.cn/database/Oracle/
从现在开始,假设DDL并不会自动提交,而是事务中的一部分。
那么DDL就要满足READ COMMIT隔离机制,也就是说,用户执行的DDL语句在提交前,其他用户是无法看到的。比如A用户执行CREATE TABLE T的语句,然后对T执行了一些DML。而这时其他会话是无法看到T表的。
那么考虑这样的情况,存在表T,包含两个列,一个ID列,一个CREATED列。
A会话执行了ALTER TABLE T MODIFY CREATED DEFAULT SYSDATE NOT NULL,然后对T表进行了一些插入,但是没有提交。
这时B会话尝试插入T表,如果DDL语句不是事务的一部分,那么B的插入和A会话的插入之间没有冲突,但是现在情况不同,由于A执行了T表的修改,为CREATED列增加了默认值并设置为NOT NULL,而且这个修改B会话当前是看不到的,因为A并没有提交修改。这时如果B会话的插入没有提供CREATED列的值,则插入操作将被锁定。对于B而言,表结构中CREATED列仍然是可空的,因此允许插入CREATED列为空的记录,但是由于A已经设置T的CREATED列非空,且包含默认值,因此B的插入必须被锁定,否则如果A和B全部提交,A会话会发现即使执行了DDL语句,T表中仍然存在CREATED为空的记录。Oracle为了实现DDL可以回滚的功能,且实现多版本读一致性,那么就必须在DDL发生后,将修改的表锁定,避免其他会话的访问造成不一致。这会导致Oracle中出现锁升级的情况,并且严重的影响Oracle的并发性,而且会大大增加死锁产生的几率。
也许有人奇怪SQLSERVER或一些其他的数据库为什么可以实现DDL语句的回滚。事实上,前面提到了Oracle也是有能力实现DDL回滚的,只是这会极大的影响Oracle的并发性。要知道,Oracle的锁机制和多版本读一致性使得Oracle的并发性在所有数据库产品中首屈一指。显然为了实现DDL的回滚而损失最值得称道的并发性,Oracle认为得不偿失。其他数据库之所以可以实现,是因为这些数据库的锁机制本身就存在一定缺陷,比如大量的锁会占用系统的资源、读写操作互相阻塞、行级锁可能自动升级为表级锁。由于已经存在这些问题,所以实现DDL的回滚并不会在很大程度上使得并发性恶化,因为即使DDL不将行锁升级为表锁,可能其他的因素也会导致这种情况的发生。