在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。
典型的冲突有:
l 丢失更新:丢失更新分为两类,
一类是A事务撤销时,把已经提交的B事务的更新数据覆盖了;
时间
|
取款事务A
|
转账事务B
|
T1
|
开始事务
|
|
T2
|
|
开始事务
|
T3
|
查询账户余额为1000元
|
|
T4
|
|
查询账户余额为1000元
|
T5
|
|
汇入100元把余额改为1100元
|
T6
|
|
提交事务
|
T7
|
取出100元把余额改为900元
|
|
T8
|
撤销事务
|
|
T9
|
余额恢复为1000
元(丢失更新)
|
|
另一类是A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失:
时间
|
转账事务A
|
取款事务B
|
T1
|
|
开始事务
|
T2
|
开始事务
|
|
T3
|
|
查询账户余额为1000元
|
T4
|
查询账户余额为1000元
|
|
T5
|
|
取出100元把余额改为900元
|
T6
|
|
提交事务
|
T7
|
汇入100元
|
|
T8
|
提交事务
|
|
T9
|
把余额改为1100
元(丢失更新)
|
|
l 脏读:当一个事务A对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务B也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务B读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的(当事物A发生回滚的时候)。
为了解决这些并发带来的问题。 我们需要引入并发控制机制。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。[1]
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。[1]乐观锁不能解决脏读的问题。
1. 使用自增长的整数表示数据版本号。更新时检查版本号是否一致,比如数据库中数据版本为6,更新提交时version=6+1,使用该version值(=7)与数据库version+1(=7)作比较,如果相等,则可以更新,如果不等则有可能其他程序已更新该记录,所以返回错误。
2. 使用时间戳来实现.
注:对于以上两种方式,Hibernate自带实现方式:在使用乐观锁的字段前加annotation: @Version, Hibernate在更新时自动校验该字段。
需要使用数据库的锁机制,比如SQL SERVER 的TABLOCKX(排它表锁) 此选项被选中时,SQL Server 将在整个表上置排它锁直至该命令或事务结束。这将防止其他进程读取或修改表中的数据。
SqlServer中使用
Begin Tran
select top 1 @TrainNo=T_NO
from Train_ticket with (UPDLOCK) where S_Flag=0
update Train_ticket
set T_Name=user,
T_Time=getdate(),
S_Flag=1
where T_NO=@TrainNo
commit
我们在查询的时候使用了with (UPDLOCK)选项,在查询记录的时候我们就对记录加上了更新锁,表示我们即将对此记录进行更新. 注意更新锁和共享锁是不冲突的,也就是其他用户还可以查询此表的内容,但是和更新锁和排它锁是冲突的.所以其他的更新用户就会阻塞.
在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法.
[1]Concurrent Control http://en.wikipedia.org/wiki/Concurrency_control
[2] Oracle的悲观锁和乐观锁http://space.itpub.net/12158104/viewspace-374745
[3] timestamp应用——乐观锁和悲观锁【转】http://hi.baidu.com/piaokes/blog/item/9b0c6854e4909050564e00b3.html