数据库中两种基本的锁类型
排它锁(Exclusive Locks,即X锁):当数据对象被加上排它锁时,其他的事务不能对它读取和修改。
共享锁(Share Locks,即S锁):加了共享锁的数据对象可以被其他事务读取,但不能修改。
死锁产生的原因
1.事务之间对资源访问顺序的交替
- 一个用户A 访问表A(锁住了表A),然后又访问表B
- 另一个用户B 访问表B(锁住了表B),然后企图访问表A
- 这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。
解决方法:修改程序
这种死锁比较常见,是由于程序的BUG产生的,除了调整程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。
2.并发修改同一记录
- 用户A查询一条纪录,然后修改该条纪录;
- 这时用户B修改该条纪录,
- 这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,
- 而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,
- 而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。
- 这种死锁由于比较隐蔽,但在稍大点的项目中经常发生。
一般更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享 (S) 锁,然后修改行,此操作要求锁转换为排它 (X) 锁。如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必须等待一段时间,因为一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由于两个事务都要转换为排它 (X) 锁,并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。
解决方法:
a. 乐观锁,实现写-写并发
b. 悲观锁:使用悲观锁进行控制。Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
3.索引不当导致的死锁:全表扫描,表锁
- 如果在事务中执行了一条不满足条件的语句,执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。
- 类似的情况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢,最终发生阻塞或死锁。
解决方法:少关联,建立索引,执行计划
SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。
避免死锁:
- 以固定的顺序访问表和行。
- 大事务拆小。
- 同一事物的资源尽量一次锁定。
- 降低隔离级别。避免间隙锁 产生的死锁。
- 添加合理的索引。