1、事务:
语句级回滚和可恢复空间的分配问题:
语句级的回滚一般发生在一条sql语句执行出现错误的时候,这一条语句的回滚不会影响整个事务,在此语句之前的ddl语句隐式提交的工作都是有效的;
当处于可恢复空间分配模式的事务需要分配空间,但是由于空间不足或者达到最大数据扩展的限制时,oracle数据库不会直接报错,它会将此事务挂起,相关占用的空间保留,等到别的事务提交,释放空间,此事务需要的空间足够了,就会再次执行。。(我觉得这样形成类似死锁的资源争用很容易,正好两个事务都挂起了,都等着对方释放资源怎么办?)
在一个修改了数据的事务提交之前,oracle会做以下工作:
1、在撤销表空间内生成撤销信息,这些信息包括sql语句所修改信息的原始值(以备撤销时使用),
2、oracle在SGA的重做日志缓冲区(redo log buffer)中生成重做日志条目(redo log entry)这些条目的重做日志记录(redo log record)中包含了对数据块和回滚块所进行的修改操作,这些记录可能在提交事务之前就被写入磁盘。
已经提交的事务对数据的修改存储在SGA的数据库缓冲区(buffer cache)中,他们不一定立即被后台写入进程(DBWn)写入数据文件中,oracle会自己选择合适的机会写入,以保证数据库的效率。
在一个事务被提交后,oracle做一下工作:
1、撤销表空间(undo tablespace)内部的对应撤销段内的事务表(transaction table)将记录此次提交,oracle将为此事务分配一个唯一的系统变化编号(system change number 即SCN)并将其记录在事务表中。
2、重做日志写进程(LGWR)将SGA中的重做日志缓冲区中的重做日志条目写入重做日志文件,同时也将此事务的SCN好写入在线重做日志文件
这两个操作构成原子事件,标志着一个事务的完整提交。
3、oracle释放加在表和数据行上的锁。
4、oracle 将事务标记为完成。
在默认状态下,LGWR 将重做信息(redo log)写入联机重做日志文件(online redo log file)的工作应与事务(transaction commit)提交同步,重做信息写入磁盘后系统才能通知用户提交(commit)结束。但是为了缩短事务提交带来的延迟,应用开发者可以设定事务提交 与重做信息写入异步地执行,即事务提交无需等待重做信息被写入磁盘就可以结束。
2、ITL 事物槽
什么是Interested Transaction List(ITL) ?
ITL是block中的相关事务信息的记录(在块首部的一个区域内,用来记录该块发生的所有事务),一个itl可以看做是一个记录,它记录了在某一个时间这个块上的事务(包括已经提交和 未提交的事务) 。当然,如果这个事务已经提交,那此事务对应的itl槽位就可以被覆盖重用。
ITL 是 consistents reads(一致性读) 的基础,根据ITL (内部有uba)才能去回滚段中找变化前的数据 。 如果这个事务已经提交,那么这个ITL的位置就可以被反复使用了,因为ITL类似记录,所以,有的时候也叫itl槽位。
如果一个事务没有提交,那么此事务对应的itl将一直占用这个itl槽位,itl里面记录了此
事务的信息(包括此事务ID(xid),list编号(这个list是一个与事务相关的提交列表,每个表有20个数据块)、事务类型、事务状态[提交否]、事务在该block影响的记录条数等)
回滚段地址(uba)(这个地址指向回滚段中的某一个块,可以根据这个块中回滚段的地址找到数据变化前的块的那个映像数据,不一定是一个整块哦)。
块上锁的状态(lck) SCN等。
dump一个块可以看到ITL信息类似如下:
Itl Xid Uba
Flag Lck Scn/Fsc
0x01 0x0006.002.0000158e 0x0080104d.00a1.6e --U- 734 fsc 0x0000.6c9deff0
0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
Xid:事务id,在回滚段事务表(关于这个回滚段事务表,我们将会在以后专门有介绍)中有一条记录和这个事务对应
Uba:回滚段地址,该事务对应的回滚段地址
第一段地址:回滚数据块的地址,包括回滚段文件号和数据块号
第二段地址:回滚序列号
第三段地址:回滚记录号
SELECT UBAFIL 回滚段文件号,UBABLK 数据块号,UBASQN 回滚序列号,UBAREC 回滚记录号 FROM v$transaction —— 查看UBA
Flag:事务标志位。这个标志位就记录了这个事务的操作,各个标志的含义分别是:
- - - - =》 事务是活动的,或者在块清除前提交事务(还没有执行块清除)。
C--- =》 事务已经提交并且清除了行锁定。
-B-- = 》this undo record contains the undo for this ITL entry
--U- =》 事务已经提交(SCN已经是最大值),但是锁定还没有清除(快速清除)。
---T =》当块清除的SCN被记录时,该事务仍然是活动的,块上如果有已经提交的事务,那么在clean ount的时候,块会被进行清除,但是这个块里面的事务不会被清除。
Lck:影响的记录数
Scn/Fsc:快速提交(Fast Commit Fsc)的SCN或者Commit SCN。
每条记录中的行级锁对应于Itl列表中的序号,即哪个事务在该记录上产生的锁。
什么是ITL等待?
ITL个数其最小值为1,由参数initrans控制(由于兼容性的原因,oracle会在对象的存储块分配两个itl,所以initrans的最小值实际上为2),最大值为255,由参数maxtrans控制,最大值参数在10g以后不能被修改,itl是block级的概念,一个itl占用块46B的空间,参数initrans意味着块中除去block header外一部分存储空间无法被记录使用(46B*initrans),当块中还有一定的free space时,oracle可以使用free space构建itl供事务使用,如果没有了free space,那么,这个块因为不能分配新的itl,所以就可能发生itl等待。
发生等待的场景:
1.超过maxtrans配置的最大ITL数
2.initrans不足,没有足够的free space来扩展ITL
如何诊断ITL等待?怎样调整INITRANS 的值 ?
解决方法:
1.maxtrans不足:这一情况是由高并发引起的:同一数据块上的事务量已经超出了其实际允许的ITL数。因此,要解决这类问题就需要从应用着手,减少事务的并发量;长事务,在保证数据完整性的前提下,增加commit的频率,修改为短事务,减少资源占用事件。而对于OLAP系统来说(例如,其存在高并发量的数据录入模块),可以考虑增大数据块大小。
2.initrans不足:数据块上的ITL数量并没有达到MAX TRANS的限制,发生这种情况的表通常会被经常UPDATE,从而造成预留空间(PCTFREE)被填满。如果我们发现这类ITL等待对系统已经造成影响,可以通过增加表的INITRANS或者PCTFREE来解决(视该表上的并发事务量而定,通常,如果并发量高,建议优先增加INITRANS,反之,则优先考虑增加PCTFREE)。
要注意的一点是,如果是使用ALTER TABLE的方式修改这2个参数的话,只会影响新的数据块,而不会改变已有数据的数据块——要做的这一点,需要将数据导出/导入、重建表。
如果在并发量特别大的系统中,最好分配足够的itl个数,其实它并浪费不了太多的空间,或者,设置足够的pctfree,保证itl能扩展,但是pctfree有可能是被行数据给消耗掉的,如update,所以,也有可能导致块内部的空间不够而导致itl等待。
每一个ITL 对应一个 SCN 。如果这个事务已经提交,那么,ITL槽位中还保存的有这个事务提交时候的SCN号(但是这个SCN号也已经写入了redo日志里了,不然一会就覆盖了)。
对于已经提交的事务,itl槽位最好不要马上被覆盖,因为一致性读可能会用到这个信息,一致性读的时候,可能需要从这里获得回滚段的入口,并从回滚段中获得一致性读。
如何减少ITL等待?
如果想增加initrans个数,参数可以动态修改,但是,只是针对以后的新块起效,以前的块如果想生效,需要在新参数下,重整表数据,如重建该表,或者move该表。
需要同时修改一个block数据的session超过maxtrans参数的限制, 或者由于block空间使用过多导致数据库无法扩展itl, 就会发生itl的等待了。
如果itl已经重用,那如何进行一致性读?
ORACLE通过ITL条目中记录的回滚段地址找到回滚段,实现读一致性,如果事务已提交,ITL就可以被重用,但是若前一个ITL被重用,前一个ITL的读一致性是如何实现的呢?
假定block只有一个itl,假定第一个事务的时候产生了 ITL-0 (即第一个itl)
第二个事务来了,产生了 ITL-1 ,ITL-1 里面的UBA (undo block address)可以找到回滚段地址,回滚段中除了记录了 block用户数据的 before image 外还记录了 ITL-0 的信息。
第三个事务来了,产生了 ITL-2 , ITL-2 中 UBA 指向回滚段,回滚段中 也记录了 ITL-1 的信息。
这样当一个查询若需要ITL-0时候的信息,则找到当前block,发现是 ITL-2 ,根据UBA找到回滚段进行 roll 得到 变化前 block ,这个时候发现block中是 ITL-1 . 还不能满足需求。 于是再根据 ITL-1 中的 UBA 又去回滚段中找到数据来进行roll,得到一个block 数据,这个时候block中就有了 ITL-0。
通过根据当前ITL进行递归的方式找到数据,实现之前ITL的独一致性。
行锁原理:
Oracle的锁机制是一种轻量级的锁定机制,不是通过构建锁列表来进行数据的锁定管理,而是直接将锁作为数据块的属性,存储在数据块首部。
这个是通过ITL来实现的,一个事务要修改块中的数据,必须获得该块中的一个itl(通过initrans预先分配的或者是通过free space构建的)。通过itl和undo segment header(undo段的头部)中的transaction table(事务表),可以知道事务处于活动阶段(我觉得可以根据itl里的flag列知道该事务是否是活动的),还是已经完成。
事务在修改块时(其实就是在修改行)会检查行中row header中的标志位,如果该标志位为0(该行没有被活动的事务锁住,这时可能要进行deferred block cleanout等工作),就把该标志位修改为事务在该块获得的itl的序号,这样当前事务就获得了对记录的锁定,然后就可以修改行数据了,这也就是oracle行锁实现的原理。
3、事务中的保存点:
保存点(savepoint):在一个事务中可以设置任意多个保存点,一般比较聪明的办法就是在一个事务的每个函数开始前设置一个保存点,这样假如这个函数执行失败,那么就可以恢复到这个函数之前的保存点,之前的数据不会被改变。回到保存点时,之前的表级锁行级锁都会存在,但是刚刚返回的那部分的锁将释放。
4、分布式事务和自治事务:
分布式事务(distributed transaction)指同一个事务(transaction)中的一个或多个 SQL 语句同时更新(update)分布式数据库(distributed database)中不同节点(node)的数据。
两步提交机制(two-phase commit mechanism)可以保证所有参与分布式事务(distributed transaction)的数据库或者同时提交(commit),或者同时撤销(undo)事务中 SQL 语句的操作。两步提交机制还确保了由完整性约束(integrity
constraint),远程过程调用(remote procedure call),及触发器(trigger)执行的隐式 DML 操作正常工作。Oracle 两步提交机制(two-phase commit mechanism)对于提交分布式事务(distributed transaction)的用户来说完全透明。用户甚至无需知道其事务是分布式的。当事务由一个 COMMIT 语句标志结束后会自动地触发两步式提交机制来提交此事务。数据库应用程序中无需使用任何代码或复杂的
SQL 语法就能处理分布式事务。
后台进程(recoverer,RECO)能够自动地处理系统中出现的不可信的分布式事务(in-doubt
distributed transaction)。不可信的分布式事务指因为各种系统或网络故障而阻碍了提交(commit)操作的分布式事务。当故障修复,通信恢复后,每个 Oracle 数据库本地的 RECO 进程将自动地提交(commit)或回滚(roll back)不可信的分布式事务,并保证所有参与分布式事务的节点协调一致。
如果系统中的故障暂时无法恢复,Oracle允许数据库 DBA 在本地手工地提交(commit)或撤销(undo)此故障导致的不可信的分布式事务(in-doubt distributed transaction)。这个功能使本地的 DBA 可以释放被不可信的分布式事务锁住的资源
如果一个分布式环境中的数据库需要恢复(recovery),其他节点的 DBA 可以使用 Oracle 的恢复功能将他们各自管理的数据库也恢复到相同的时间点。这个功能保证了分布式环境中所有数据库的数据一致性(consistent)。
用户可以通过 pragma 指令 AUTONOMOUS_TRANSACTION 将一个 PL/SQL 程序结构设定为自治事务(autonomous transaction)。pragma 是一个编译器指令(compiler directive)。用户可以将以下类型的 PL/SQL 程序结构定义为自治的:
- 服务器端(stored)的过程(procedure)或函数(function)
- 本地的(local)过程或函数
- 包(package)
- 类型方法(type method)
- *匿名块(Top-level anonymous block)
SET TRANSACTION
COMMIT
ROLLBACK
SAVEPOINT
ROLLBACK TO SAVEPOINT
5、自治事务与内嵌事务的区别:
自治事务虽然是由别的事务启动,但是它也不是一个内嵌事务,具体区别如下:
1、自治事务不会和主事务分享资源,如:锁。
2、不会受制于主事务,如:主事务回滚,内嵌事务就会回滚,但是自治事务就不会回滚。
3、自治事务提交改变对于别的事务是立即可见的,但是对于内嵌表,只有等到主事务提交了之后才能可见。
4、在自治事务中得exceptions 是事务级别的回滚,不是一个声明级别的回滚。
注意:
1、如果一个自治事务视图去读取一个主事务持有的资源,一个死锁就这样诞生了。。。这时,oracle产生一个exception,如果此异常不被捕获,那就回滚这个自治事务。
2、oracle初始化 TRANSACTIONS 参数的时候会声明最大的并行事务数量。这个值在自治事务与主事务一块运行(我估计是和别的主事务,自己也没搞懂啊)的话可能要超出。
3、如果试图让一个还没有回滚或者提交的事务退出,oracle就会产生一个异常。如果异常不能被捕获,那就回滚事务。