Oracle中死锁与等待
在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(即S锁)。当数据对象被加上排它锁时,其他的事务不能不
能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发
控制。
死锁的第一种情况:
一个用户A访问表A(锁住了表A),然后又访问表B; 另一个用户B访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住
表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁产生了。
解决方法:
这种死锁比较常见,是由于程序的BUG产生的,除了调整程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量
按照同样的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理,必须同时锁定两个资源时,要保
证在任何时刻都应该按照相同的顺序来锁定资源。
死锁的第二种情况
用户A查询一条记录,然后修改该条记录;这时用户B修改该条记录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户
B里的独占锁由于A有共享锁存在必须等A释放掉共享锁,而A由于B的独占锁而无法上升到独占锁也就不可能释放共享锁,于是出现了死锁。这
种死锁比较隐蔽,但在稍大点的项目种经常发生,如在某项目中,页面上的按钮点击后,没有使按钮立刻失效,使得用户会多次快速点击同
一按钮,这样同一段代码对数据库同一条记录进行多次操作,很容易就出现这种死锁的情况。
解决方法:
1、对于按钮等控件,点击后使其立刻失效,不让用户重复点击,避免对同时对同一条记录操作。
2、使用乐观锁进行控制。乐观锁大多是基于数据版本(version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决
方案中,一般是通过为数据库增加一个“version”字段来实现。读取处数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,
将提交的数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否
则认为是过期数据。乐观锁机制避免了长事务中的数据库加锁开销(用户A和用户B操作过程中,都没有对数据库加锁),大大提升了大并发
量下的系统整体性表现。 Hibernate在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于乐观锁机制是我们的系统中实现,来自外
部系统的用户更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。
3、使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的select.......for update语句,以保证操作最大程度
的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读
取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味整个操作过程中(从操
作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见
,如果面对成百上千个并发,这样的情况将导致灾难性的结果。所以,采用悲观锁进行控制时一定要考虑清楚。
死锁的第三种情况
如果在事务种执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行之后,就很容易发生死
锁和阻塞。类似的情况还有当表种的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢,
最终发生阻塞或死锁。
解决方法:
SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行 分析,对于有全表扫描的SQL语句,建立相应的索引进行优化