像前面的例子 http://bibaoke.com/post/29 那样,阻塞事务的语句是必须的:
--延长处理时间 waitfor delay '0:00:10'
这个语句代表在查询中等待 10秒,这样我们才能模拟并发的情况,如果事务很快执行完毕了,就无法重现并发的情况。
我在网上看到有些例子是使用 C#、Java 等语言启动多个线程去模拟并发,而不是使用阻塞,虽然这样更接近生产环境,但并不利于简化问题。多个线程模拟并发,取决于线程数和事务执行的速度,难以确认并发发生了,而且编码量稍多。
然后是使用 sys.dm_tran_locks 视图去查看锁:
--查看 id 54 的会话持有的锁 select * from sys.dm_tran_locks where request_session_id = 54
这大概是防止在数据库有活动会话时,对数据库进行一些影响数据一致性的更改的。
在 id 54 的会话中,使用 http://bibaoke.com/post/25 例子中的 app_user 表,执行下面的语句:
--开始事务 begin transaction --查询指定用户 select * from app_user(updlock) where mobile = '13800002222' --延长处理时间 waitfor delay '0:00:10' --提交事务 commit transaction
在 10秒内,查看锁:
可以看到会话多持有了三个锁,一个页面意向更新锁,一个键更新锁,一个对象意向排它锁,锁的范围和类型问题比较复杂,而且不同的数据库产品的实现细节不同,对这个有兴趣的可以搜索相关的内容。
有趣的一点是,如果上面这个语句不使用 updlock 表提示,是看不到加锁的情况的。原因是在 read committed 默认的隔离级别下,select 语句放置的共享锁是不会一直持有到事务结束的。repeatable read 和 serializable 的共享锁则会一直持有到事务结束。
使用这个方法,就可以看到在不同隔离级别下,使用不同的表提示,数据库的加锁情况。