高并发与锁(二)
上文我们介绍了高并发状态下会产生的一些数据冲突和锁的一些基本分类,这次我们继续讨论。
如何添加乐观锁? 在高并发情况下,如何高效、健康地给select 语句加上行锁?SQL Server是如何控制并发冲突的?
1、添加乐观锁
在J2EE中,Hirbernate提供了悲观所和乐观锁,但悲观锁的使用同样也限制了读取的并发性,因此很少使用,二使用最多的是添加乐观锁,在记录中添加版本version或者时间戳字段,这样做并不影响读取,只是按照版本控制的思维来限制低版本记录修改操作。
对于以上两种方式,hibernate自带实现方式:在使用乐观锁的字段前加annotation: @Version, Hibernate在更新时自动校验该字段。
那么有人要问,低版本记录有新的数据,怎么才能更新?
其实也很简单:开启事务->更新记录前,读取一次最新的数据->提交->回滚或者提交事务。
又该如何保持version和timestamp呢,在提交的过程中,只需将version或者timestramp进行更新即可。
2、在高并发情况下,如何高效、健康地给select 语句加上行锁?
高并发情况下,简单地添加行锁,数据库可能会支持不住,可以:
(1)对请求做个队列,先进先出,不会重复,但是速度上会不会变慢?
(2)避免实时读取数据库,把数据缓存起来;数据用队列装起来,读走就没了。也可以单独启用一个线程,采用异步方式实现;
(3)环境中有Redis的话直接使用自带的队列接口就行,按一定顺序装进去,取得时候是原子操作,取完就没有了。
3、SQL Server是如何控制并发冲突的?
悲观与乐观机制下,数据库设定了隔离级别:
SQL Server2005及以上版本支持5种隔离级别来控制冲突。其中三种只在悲观并发模式中使用,一种只在乐观并发模式中使用,另一个可以在两种模式中使用。
(1)未提交读(Uncommitted Read)
未提交读只能防止“丢失更新”问题,其它问题不能防止。
未提交读是针对阻塞太频繁的悲观并发控制,因为它只是忽略了锁,而不保障事务的一致性。
(2)已提交读(Read Committed)
已提交读既可以是乐观的也可以是悲观的,这取决于数据 库的read_committed_snapshot设置。默认情况下这个 选项是关闭的,所以该隔离级别默认情况下是采用悲观并发控制。
已提交读可以防止脏读问题。
(3)可重复读(Repeatable Read)
可重复读是一种悲观的隔离级别。它在已提交读的基础上增加了新特性:确保当事务重新访问数据或查询被再一次执行时,数据将不会再发生改变。
可重复读不但可以防止脏读问题,还可以防止不可重复读问题,但是不能防止幻读问题。
注意,可重复读的资源开销是很大的,事务中所有的数据必须等待事务完成之后才能访问。
(4)快照(Snapshot)
快照是一种乐观隔离级别。
Snapshot事务中任何语句所读取的记录,都是事务启动时的数据。
这相当于事务启动时,数据库为事务生成了一份专用“快照”。
在当前事务中看到不其它事务在当前事务启动之后所进行的数据修改。
Snapshot事务不会读取记录时要求锁定,读取记录的Snapshot事务不会锁住其它事务写入记录,写入记录的事务也不会锁住Snapshot事务读取数据。
快照隔离级别的事务不是串行执行的,两个进程同时使用快照隔离,如果它们执行多次,可能最终产生的结果不会一致。(这段话要证实)
(5)可串行化(Serializable)
可串行化是一种悲观隔离级别。它在可重复读的基础上增加了新的特性:确保在两次查询的中间,不会增加新的行。
可串行化是最健壮的悲观隔离级别,因为它防止了并发冲突产生的4个问题。
可串行化也是资源开销最大的措施。当使用可串行化隔离时,如果SQL的条件字段没有索引,那么SQL Server会产生表级锁。