阻塞原因
在默认事务隔离情况下,数据库事务越长,一方面独占锁被持有的时间越长,写操作阻塞读操作的机会就越多;另一方面,在默认的读提交隔离模式下,读操作使用共享锁与独占锁不兼容,读操作也会阻塞写操作。
阻塞也是死锁产生的基本条件,改善了阻塞就能有效减少死锁。
在软件开发后期,在对大数据量的集成测试工程中,通过活动查看器可以观察到阻塞情况,主要产生阻塞的原因就是读和写相互阻塞在对同一个大表的操作上。因此对于读写阻塞问题需要加以足够考虑。
减少阻塞一些指导原则
整体原则
*归结起来也依赖于代码、sql的优化,一方面要使逻辑代码优化到最快。另一方面,一个耗时较长的sql语句将会阻塞全部用户等待几十秒甚至几分钟,针对查询sql语句的优化也是最重要的。
*修改批量操作的需求,批量操作耗时和记录数量是成正比的。为此设计时要避免在同一个自动事务服务方法中做批量的循环操作,可以将循环操作放到UI控制端,这一就使一个长事务变成多个短小的事务,将减少阻塞的机会。
*减少读写操作使用锁的数量,比如减少批更新操作中修改行的数量,保证行锁定少,同时减少锁升级至表锁的机会。
*一些耗时大、锁定数据多的操作需要避免和正常业务操作冲突,可以使用调度计划在系统闲置的时候来运行,或者使用互斥机制来保证其它用户暂退出操作独立运行,视业务情况而定。
读写锁阻塞的处理原则
*减少读操作需要的共享锁
1、将事务隔离级别由默认读提交(ReadCommited)修改成读未提交(ReadUnCommited),将不会有读写阻塞,但是会造成读取其它事务未提交的数据。
方法一:如果服务方法为自动事务,则在服务方法特性SerivceMethod指定IsolationLevel属性为ReadUnCommited
方法二:对于使用手工事务的情况,使用DBSession接口带隔离级别参数的方法session.beginTransaction(IsolationLevel), level值为 ReadUnCommited.
2、在select语句中带NoLock提示,事务内无锁提示也不会有读写阻塞,与上面一样也会有脏读。
Select上加无锁控制,在执行select操作时加 with(nolock)。如:SELECT * FROM person WITH(NoLock)
上面两种方式需要平台和业务涉及人员根据情况使用。事务隔离级别控制粒度较粗,使用时需要考虑对多个select语句的影响,NoLock提示控制单个select语句,粒度更细。
*在sqlserver2005中使用基于行版本的快照隔离模式
在快照模式下,读取数据不再使用共享锁,阻塞的现象能大大减少。
方法:在建立数据库后执行下面命令:
ALTER DATABASE 替换的数据库名 SET READ_COMMITIED_SNAPSHOT ON;
ALTER DATABASE 替换的数据库名 SET ALLOW_SNAPSHOT_ISOLATION ON;
注意:sqlserver2005和with(no lock)语句,这两种方案都能够避免阻塞,但是这两种方式是有区别的。
举个小例子说明一下:比如数据库表T1中有两个字段Col1且其默认值为1.此时恰好有个A事务通过Update语句修改表T1的Col字段值为2,但还未提交,如下:
A事务:
{
Begion tran
Update T1 set Col1=2
//Commit;//注释掉此句,模拟A事务未提交。
}
这时如果另一个事务如果使用Sql server2005的快照模式获取T1表Col1字段的值则取到的值是之前默认的1;而使用with(no lock)的方式获取到Col1字段的值为2.
其实不管使用哪种方式,得到的数据都不确保是准确的,这要取决于A事务是否执行并提交成功。