简单案例描述:
在业务运行过程中,某张主要表格发生大规模的TX锁冲突,导致业务完全挂起。原因非常简单,某个维护人员通过PLSQL发起了一个select for update操作,导致表格大量的行被锁定。
事务锁,我们又经常表述为应用锁,也就是说该问题是由于应用不良所导致的(自我锁定除外)。在高并发的业务系统中,业务之间顺序等待锁定是一个必然的事件,是业务特征决定的。应用程序设计和开发的任务主要是包含以下两点:(1)、尽可能减少对于相同行的更新操作。(2)、锁持有时间尽可能的减少。
Oracle数据库在事务锁定方面已经表现的足够优秀,行锁和查询不加锁使开发者几乎不用太考虑锁定的并发性问题。尽管如此,我们还是在大量的业务系统中会发现在高吞吐量环境下的锁等待,甚至由于锁等待导致业务系统完全挂起。
我们先简单描述一下一个事物过程:
比如我们发布一个update语句更新表格ABC中的一样,该行的file=4 block=1023 row=5
Step 1:寻找合适的回滚段
Step 2: 写Log Buffer
Step 3:在Undo Segment Header中事务表获得事务槽
在Undo Segment中获得UBA
Step 4: 在Undo Segment Block中获得Slot,获得xid
Step 5: 写Log Buffer
Step 6: 在DataBlock Header获得事务itl(xid|UBA)
Step 7:在datarow设置锁定标记
Step 8: 更新行
Step 9: 写redo数据到Log file
Step 10: 在Undo Segment header中更新提交标记
在以上过程中,大家可以发现为了保持一致性需要做一下几件事情:
(1)、表格对象为了避免在更新的时候被DDL操作,需要在表格对象上增加共享的TM锁,TM锁会一直持续到事务结束。
(2)、在Undo Segment Header访问的时候需要获得独占的事务表访问
(3)、在分配Undo Block的时候需要获得独占的Undo segment header的Free Block Pool,在必要的时候还会发生过期Block访问甚至跨越回滚段的空闲块窃取操作
没有经过充分验证,在(2)和(3)过程中究竟是被Enqueue保护还是buffer lock还是latch保护。个人更加倾向于buffer lock保护,buffer lock的效率比较高。但是对于空间的扩展和回收这块,必然是被锁所保护的。大家在锁列表中会发现一个US Lock,这个更多的考虑应该是DROP Undo Segment|online undo Segment|offline Undo Segment等需要的保护锁。相信Undo segment free pool的维护和expired pool的维护也会采用锁来处理,很有可能就是US Lock。具体是否如此,让权威吕海波先生去确认,呵呵。
保护是采用latch或者lock的一个基本判断在于Oracle意外Crash之后是否需要通过redo做恢复。采用latch保护操作不需要写redo,而采用锁保护的需要写Redo(个别非持久性对象的锁实现除外)。
(4)、在更新DataBlock ITL的时候需要获得ITL Lock
(5)、在更新行的时候需要获得TX Lock
好的,我们大致知道了以上过程,主要会涉及到TM,TX锁,极有可能会涉及到US Lock。
我们再来考虑并发性问题:
假设我们200个用户共同更新某一行,更新一行的事务需要100ms完成。谁都知道100ms再正常不过。200个transaction以队列形式排队,每个处理100ms,到达第200个transation的时候,他的事务处理时间为200*100ms = 20000ms =20s。这下问题来了,20s的响应几乎在所有的oltp系统都无法接受。当然这是极端的情况,不过各位需要考虑的是性能问题都是在极端的情况下发生。
ok,有人可能会说,业务程序怎么可能会出现这样的业务逻辑呢!是的,这样的业务逻辑不存在。但是以下的业务逻辑是普遍存在的,否则也就不存在锁定问题了。
2~3个transaction更新相同的行,每2~3个transaction形成相互依赖,大家发现没有,事实上这种情况的200个transaction并发和200个transaction作用于相同行的transaction并发没有任何区别。大家这个时候可以发现,业务确实需要这样操作,这也是Oracle为什么会有处理上限的根本原因之一。
我们如何来优化以上高并发导致的锁定问题:
(1)、尽可能的理解业务,避免不必要的锁定。事实上确实也是的,在oltp系统中,很少会存在不同Session更新相同行的问题,出现了这种问题很有可能是业务逻辑理解或者业务逻辑设计出现问题(比如外键等问题)。
(2)、尽可能的加快事务处理,长事务变短事务,同步变异步。
(3)、变悲观锁定为乐观锁定。
我们在设计中往往会存在以下问题:
(1)、在更新之后进行大量的业务处理,最后进行提交。事实上你在后面的业务处理中压根不再和你前面的更新发生关系,也不存在一致性问题。比如更新之后进行大量查询验证和报表处理工作。
(2)、极为频繁的database link操作
database link操作的任何操作,包括查询都构成事务,都会写undo和redo,意味着大量的database link对于commit的效率具有很大的影响,自然就增加了锁定的持有时间。
(3)、悲观锁定设计
在有些情况下,我们业务过程中需要保证某个值不会发生变化。这个时候,我们往往会采用select for update操作来锁定改行,在一大堆处理之后然后再更新或者结束事务。这个时候一定要确认业务是否需要如此,业务实现逻辑是否一定要如此。如果确实如此,在高吞吐量系统中考虑乐观锁定。
关于ITL Lock
每个事务都需要在受影响的数据块首先获得ITL,而一个数据块中的ITL是有限的,特别是受到PCT Free的影响。当ITL Lock不足的时候Session将等待其他事务提交释放ITL。一般来说出现ITL Lock的问题基本都发生数据插入操作中,毕竟在相同的数据块进行大规模的并发更新的行为还是比较少见的。
为什么Insert会出现ITL Lock问题?
原因也比较简单,表格是堆形式组织的,也就是大家都是在堆尾上进行数据插入操作,这样在同一个数据块中就出现了大量的插入事务并发。解决这种问题相对也比较简单,使表格具备多个数据插入点。Oracle采用Freelist来进行管理提供多路插入点,后来又通过ASSM进行了进一步简化管理,一般情况下在ASSM下面对于insert的处理还是比较好的。即使如此,在事务长时间持有的时候还是可能会出现itl lock的问题,这个时候我们可以进一步使用分区来分离插入热点。特别在RAC环境中,对于相同表格的批量插入会导致数据块不停的在节点间传输,使insert操作的效率极低。这个时候大家可以采用分区,使本节点的数据仅仅插入在本节点来降低RAC冲突。这个似乎和ITL没有关系了。
关于ITL Lock和索引
索引由于规模比较小,一个Index leaf block中存储的行,可用的空间比较数据块大很多,也就是说会有更多的事务在索引块上活动。特别对于insert操作,现代设计往往维护一个单调递增的代理主键,意味着所有的记录都忘相同的数据块插,而且无法实现ASSM管理,也无法实现freelist管理。大家看到这里,可以知道ITL Lock在索引上,尤其是单调递增或者下降的主键上将成为一个普遍的存在。在发生这种问题的时候,几乎唯一的办法就是partition分离,或者尽可能的加快事务处理以避免itl lock的发生。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/92650/viewspace-1061575/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/92650/viewspace-1061575/