目录
ORACLE—图灵小队—核心知识点1——undo、redo、insert/update/commit/rollback
一、“延迟段”创建表
11gR2之后,会默认使用延迟段方式创建,表创建了,但并未分配段,只有在第一条数据插入时才会创建。
create table t(x int) segment creation deferred ----延迟段创建
create table t(x int) segment creation immediate----立即创建段
可以通过此语句查看:
select extent_id, bytes, blocks from user_extents where segment_name = 'T' order by extent_id;
二、redo 重做日志的触发条件:
--用户提交(commit) --有 1/3 重做日志缓冲区未被写入磁盘(磁盘是指redo01.log等) --有大于1m 的重做日志缓冲区未被写入磁盘 --3 秒超时 --dbwr 需要写入的数据的 scn 大于 lgwr 记录的 scn,dbwr 触发 lgwr 写入。
三、insert数据
1、insert之后的系统状态【此时重做日志的触发条件未被触发】
插入数据后,undo块(存放索引,表修改前的数据,又因为是插入数据,所以当前行插入前并没有数据记录,比update后存放的数据少)、索引块、表数据块都会先进入块缓冲区缓存,所有这些都会写入重做日志redo log缓冲区,进行数据保护,等重做日志的触发条件触发后,才会写入redo log文件。
上述情况,
(1)如果系统崩溃,即使SGA被清空,因为用户并没有提交数据( 用户commit后,肯定会写入redo log中(这是redolog的触发条件),即使此时没有写入数据data文件中,只要写入redo log就能恢复,启动后把数据写入到数据文件中。如果用户不进行commit,达到重做日志触发条件,也会写入redolog中。但对于用户而言,用户commit的,才是他最需要的。如果用户没有commit的但已经写入到redolog中的,实例启动后,先redo前滚到数据文件中,再通过undo回滚到没有commit的状态,也不影响真实数据),我们不需要使用这些undo和redo信息进行实例恢复。
(2)如果缓冲区缓存满了或其他触发redo log的条件(见2)达到了,LGWR会将redlog缓冲区的信息刷到磁盘上去(redo01.log,redo02.log等),保存了undo、索引,表的信息
2、insert之后的系统状态【此时重做日志的触发条件被触发】
redo log缓冲区的数据,因达到触发条件,而被写入到磁盘中
四、Update数据【delete与update非常类似】
update和insert差不多,唯一区别就是,update之前,当前行是有数据的,undo需要存放update之前的数据信息。而insert动作之前,表中当前行是空的,undo块只需要存放少量空数据标记。
解析:
如果redo的黑块,表示上述三中insert插入的数据,此时实例崩溃了,重启实例做如下步骤:
1、系统崩溃时,系统所处的状态是:insert的语句已经写入到在线重做日志中(这部分redo也会包含insert语句相关的undo段)。
2、而此时Update语句的redo还没来得及写入到磁盘中,而依旧在日志缓冲区中(在系统崩溃时这部分redo会丢失)。这没问题,我们的事务还没有提交(commit),而磁盘上的数据状态就是update之前的状态。
3、此时insert语句的内容已经在系统崩溃之前写到redolog(磁盘)上了,此时oracle会利用这些redo信息“前滚”插入到数据文件,当前滚结束时,系统的状态将于上图9-1非常类似,缓冲区缓存里面有修改过的undo块(用于回滚未提交的insert)、表块(insert后的状态)及索引块(insert后的状态)。
4、由于这是oracle正处于自动的实例恢复过程中,它会回滚所有未提交的事务,包括我们刚才的那个insert语句的事务。
5、当oracle回滚我们的这个未提交的insert时,它会首先取到刚才提到的前滚过程中构建的undo数据,然后将这部分undo应用到数据以及索引块,从而使得数据和索引块“恢复”为insert发生前的样子。
6、现在一切都恢复到了insert之前的状态,不过磁盘上的数据块可能是inert之前,也可能是insert之后的状态(这取决于在崩溃前是否已经将块刷新(flush buffer_cache)输出)。
(1)如果磁盘上的块是insert之后的状态,那么当下次这个块从缓冲区缓存刷到磁盘时,数据文件就会反映出insert已撤销状态。
(2)如果磁盘上的块insert之前的状态,那就不用去管它-----这些块以后肯定还会被覆盖。
五、应用回滚事务--假想场景
1、应用回滚事务流程
当应用发出ROLLBACK命令时,Oracle会去查找这个事务的undo信息,它可能在缓存的undo段的块中(通常是这样),也可能已经刷新输出到磁盘上(对于非常大的事务,就往往是这种情况)。接下来Oracle会把undo信息应用到缓冲区缓存中的数据和索引块上,或者倘若数据和索引块已经不在缓存中,则要从磁盘将数据和索引块读入缓存,再对其应用undo。这些块会恢复为其原来的状态,并在以后会刷新输出到数据文件。
2、需要注意的是:
(1)回滚过程从不会涉及重做日志,只有恢复和归档是才会读取重做日志。这对于调优意义重大:
(2)意义重大:重做日志是用来写的,不是用来读的,oracle 不会在正常的处理中读取重做日志。
(3)只要你有足够的设备,使得ARCn读文件时,LGWR能写到另一个不同的设备,就不存在重做日志竞争。
(4)许多其他的数据库(非oracle)都把日志文件处理为“事务日志”,这些数据库没有把redo和undo分开。对于这些系统,回滚可能是灾难性的,回滚进程必须读取日志,而日志写入器也可能会在同时写这个日志,这就像系统中最薄弱的环节引入了竞争。
(5)oracle的目标是可以顺序地写日志,而且在写日志时别人不会读日志。
六、commit流程
(1)当事务进行提交时,oracle会把重做日志缓冲区刷新(flush buffer_cache)输出到磁盘(redo log),如图(9-4)
(2)此时已修改的块放在缓冲区缓存中,可能有一些块已经被刷新输出到磁盘上(flush buffer_cache,即使不提交,缓存块也会在一定条件下写入到数据文件data中。提交了也不一定写入到data中)。
(3)重做这个事务所需的全部redo都安全地存放在磁盘上,现在我们所做的修改已经具有持久性了。
(4)此时如果我们从数据文件直接读取数据,可能会看到块还是事务发生前的样子(通过dump出当前事务的文件和块,可以看到数据文件还是update前的数据),因为很有可能DBWn还没有(从缓冲区缓存)刷出这些块。这没有关系。
(5)如果出现宕机,那么在实例恢复的过程中,数据库可以利用重做日志文件来讲这些块更新为最新状态。
(6)数据库会一直保留着undo信息,除非undo段回绕并重用这些undo块。
(7)oracle会使用这些undo信息为需要这些对对象的会话提供一致性读。
七、COMMIT需要做的事情是有哪些?
1、commit提交,是多次小提交,还是一次大提交?
你可能认为,一个事务越大(换句话说,它影响的数据越多),commit需要的时间就越长。不是这样的,不论事务有多大,commit的相应时间一般都很“稳定“,这是因为commit并没有太多的工作去做,不过他所做的确实至关重要。
2、commit一次还是多次的案例
假设我们commit一行的update需要x个时间,那我们提交1000行的update基本也需要x个时间。如果我们commit一个1行update,执行1000次,那么需要的时间就是1000*X个时间。
3、commit的真实的情况
提交越多,耗时越久(.net,C,COBOL等),但pl/sql除外,为什么呢?为什么呢?
PL/SQL也是一种程序语言,叫做过程化SQL语言(Procedural Language/SQL)。PL/SQL是Oracle数据库对SQL语句的扩展。在普通SQL语句的使用上增加了编程语言的特点,所以PL/SQL把数据操作和查询语句组织在PL/SQL代码的过程性单元中,通过逻辑判断、循环等操作实现复杂的功能或者计算。
4、commit的相应时间不会随着事物的大小而改变
这是因为在数据库执行commit之前,困难的工作都已经做了。99%的工作都已经完成。
5、commit之前,数据库做了哪些工作?
(1)已经在SGA中生成了undo块;
(2)已经在SGA中生成了已修改数据块;
(3)已经在SGA中生成了对应前两项的redo缓存;
(4)如果前三项比较大且耗时较长,那这三项中的某些数据可能已经被刷新到磁盘;(通过update后,不提交,找到这个v$transaction中的文件号和块号,dump出,可以找到这个数据块的记录已经更新最新数据了,大约需要5分钟左右会自动刷新)
(5)已经得到了所需的全部锁。
6、执行commit时,只剩下如下的工作:
(1)为事务生成一个SCN
(2)LGWR将所有未写入磁盘的重做日志条目写至磁盘,并把SCN记录到在线重做日志文件中。
其中这里是最耗时的,但因为LGWR一致不停地将重做日志换成弄去的内容刷到磁盘上去。在你修改数据的过程中,redo不会一致在日志缓冲区中不断积累,LGWR会在你修改的同时以增量方式将日志缓冲区的内容写到磁盘上,这就避免了commit需要等待很长时间来一次性刷新输出到所有的redo。
(3)v$lock中会记录我这我们的会话持有的锁,这些锁都会被释放,而排队等待这些锁的会话都会被唤醒,从而可以继续完成它们的工作。
(4)如果事务修改的某些块还在缓冲区缓存中,oracle就会以一种快速的模式访问并“清理”。
八、rollback做什么?
我们将上述commit改为rollback,可能会得到完全不同的结构。回滚时间一定是和修正数据量成正比的。
1、commit/rollback之前,数据库做了哪些工作?
(1)已经在SGA中生成了undo块;
(2)已经在SGA中生成了已修改数据块;
(3)已经在SGA中生成了对应前两项的redo缓存;
(4)如果前三项比较大且耗时较长,那这三项中的某些数据可能已经被刷新到磁盘;(通过update后,不提交,找到这个v$transaction中的文件号和块号,dump出,可以找到这个数据块的记录已经更新最新数据了,大约需要5分钟左右会自动刷新)
(5)已经得到了所需的全部锁。
2、ROLLback 时,还需要做如下工作:
(1)撤销所有修改。Oracle数据库会读取undo段,然后将undo数据应用到数据块一撤销我们的修改,并将相应的undo条目标记为已应用。如果先前插入了一行,rollback会将其删除;如果更新了一行,回滚就会取消更新;如果删除了一行,回滚将把它再次插入。
(2)会话持有的所有锁,都将被释放,如果有会话在排队等待我们的持有的锁,就会被唤醒。
相比之下,commit只是将重做日志缓冲区中剩余的数据刷新输出到磁盘,它比rollback的工作量要小的多。
所以除非不得已,否则不要轻易回滚。