Joh*_*ohn 3 sql-server transaction locking snapshot-isolation
SQL Server 中的读已提交快照和快照隔离级别消除了大多数锁定,除了以下一种情况:一个写入者仍会锁定其他写入者。
该文档小心翼翼地说了这么多,随后没有记录任何其他值得了解的内容:
它真的只是被独占锁定的修改行吗?或者它也可以是不相关的行(例如索引中相邻的行)或页面?
我确实查看了其中的锁sys.dm_tran_locks
,并且在未提交的事务期间只看到了修改行上的独占锁 - 页面仅被锁定为IX
.
我还测试了两个事务是否可以在一个非常小的表(可能适合一页)中的两个未提交事务期间同时修改两个不同的行,并且效果也很好。
如果确实只有修改的行被独占锁定,那么如果确保没有两个连接同时写入同一行,那么这将为具有数据库独占访问权限的应用程序提供无锁写入的保证。
在我想到的场景中这是可能的 - 但如果页面锁发挥作用,几乎没有办法做类似的事情,因为无法预测哪些行到底会受到影响。
关于锁和锁升级的一般规则:
行级锁可以升级为表级(或分区级)锁。因此,在某些情况下,不相关的行可能会被锁定。
这些情况记录在此处。另请注意,可以使用表选项禁用锁升级LOCK_ESCALATION
。
此外,一些查询计划使用页锁,并且一些查询计划需要读取由另一个事务锁定的行以确定是否应该修改它。例如,update foo where someUnindexedColumn = 'someval'
需要读取每一行,因此将被任何其他写入器阻止。
请注意这一点(我们在最后一段需要):
(一) 重要事项
选择事务隔离级别不会影响为保护数据修改而获取的锁。事务始终对其修改的任何数据获取独占锁,并保持该锁直到事务完成,无论为该事务设置的隔离级别如何。对于读操作,事务隔离级别主要定义免受其他事务修改影响的保护级别。
术语“快照”反映了这样一个事实:基于事务开始时数据库的状态,事务中的所有查询都会看到相同的数据库版本或快照。快照事务中的基础数据行或数据页上不会获取锁,这允许其他事务执行而不会被先前未完成的事务阻塞。修改数据的事务不会阻止读取数据的事务,并且读取数据的事务也不会阻止写入数据的事务,就像它们通常在 SQL Server 中默认的 READ COMMITTED 隔离级别下一样。这种非阻塞行为还显着降低了复杂事务发生死锁的可能性。
简而言之,在这个隔离级别下:
但是,文档没有提到“写入器不会阻止写入器”,因此我们可以安全地假设写入器确实可以在 SNAPSHOT 隔离级别中阻止写入器。
有关快照如何工作的更多信息可以在事务锁定和行版本控制中找到:
快照隔离还使用行版本控制,它在读取操作期间不使用共享锁。
...
当 READ_COMMITTED_SNAPSHOT 或 ALLOW_SNAPSHOT_ISOLATION 数据库选项为 ON 时,将为数据库中执行的所有数据修改维护逻辑副本(版本)。每次特定事务修改一行时,SQL Server 数据库引擎实例都会在 tempdb 中存储该行先前提交的映像的版本。...
及以下
修改数据时的行为
...
在快照隔离下运行的事务对数据修改采取乐观的方法,即在执行修改之前获取数据锁,以强制执行约束。否则,在数据被修改之前不会获取数据的锁。当数据行满足更新条件时,快照事务将验证该数据行是否未被快照事务开始后提交的并发事务修改。如果数据行在快照事务之外被修改,则会发生更新冲突并终止快照事务。更新冲突由 SQL Server 数据库引擎处理,无法禁用更新冲突检测。
上面的这一段,特别是在要修改数据之前确实获取锁的部分解释了阻塞(写入者到写入者)行为(并考虑顶部提到的“重要”注释):
所以在SNAPSHOT
隔离状态下: