SQL Server - 如何同时实现 READCOMMITED 和 NOLOCK?

Mat*_*ari 5 sql-server isolation-level

如果我有一个用户表:

id | name   | age
1  | Mateus | 27
Run Code Online (Sandbox Code Playgroud)

第一个事务执行更新,并使事务保持打开状态,不提交或回滚:
update User set name = 'John' where id = 1;

同时,第二个事务执行一个选择:
select * from User where id = 1;
该命令将等待第一个事务通过提交或回滚释放锁,除非第二个事务使用表提示with(nolock),如下所示:
select * from User with(nolock) where id = 1;
这将返回记录而不锁定事务,但是它将返回未提交的值John而不是原始值Mateus

据我所知,只有两种方法可以在不锁定当前事务的情况下返回锁定的记录,一种可以使用with(nolock)将返回记录但带有未提交值的方法,并且with(readpast)不会返回记录。

有没有办法可以在不锁定表的情况下返回记录并返回其“旧”值?

Eri*_*ing 14

您正在寻找的是乐观的隔离级别,例如快照隔离或读取提交的快照隔离。

代码示例:

USE Crap;

CREATE TABLE dbo.users (id INT, username NVARCHAR(40));

INSERT dbo.users ( id, username )
VALUES ( 1, N'Jimbo' )

/*To turn on Snapshot*/
ALTER DATABASE Crap SET ALLOW_SNAPSHOT_ISOLATION ON;

/*To turn on RCSI*/
ALTER DATABASE Crap SET READ_COMMITTED_SNAPSHOT ON;

UPDATE dbo.users 
SET username = 'Dimbo'
WHERE id = 1;

/*Snapshot needs this, RCSI doesn't*/
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
SELECT *
FROM dbo.users AS u
WHERE u.id = 1;
Run Code Online (Sandbox Code Playgroud)

需要注意的事项:

差异

快照隔离和 RCSI 之间的一个重要区别是内部事务:

  • 在快照隔离下,BEGIN TRAN 标记事务中的所有查询将从版本存储中读取的时间点。

  • 在 RCSI 下,BEGIN TRAN 之后的每个语句都将读取该语句执行时的版本存储。

另一个区别是快照隔离可以应用于修改查询,而 RCSI 不能。更准确地说,SI 检测写冲突并自动回滚冲突事务之一。RCSI 下的更新在定位要更新的数据时不使用行版本,但这仅适用于目标表。同一删除或更新语句中的其他表,包括对目标表的附加引用,将继续使用行版本。