有人可以向我解释为什么在 SQL Server 中 SELECT 可以被 UPDATE 语句阻止吗?我认为,MVCC(通过将修改后的数据复制到 tempdb 在 SQL Server 中实现)可以防止读取器在所有情况下被阻止。我错了吗?为什么需要将 ISOLATION LEVEL 更改为 SNAPSHOT 才能释放锁定?
这就是它在 Oracle 中的工作原理:
写入器永远不会阻塞读取器。当写入器更改行时,数据库使用撤消数据为读取器提供该行的一致视图。
使用并发控制的 MVCC 模型而不是锁定的主要优点是,在 MVCC 中,为查询(读取)数据获取的锁与为写入数据获取的锁不会冲突,因此 读永远不会阻塞写,写永远不会阻塞读 .
默认情况下,SQL Server 使用
READ COMMITTED
隔离级别。其行为方式取决于您是否启用了数据库设置 READ_COMMITTED_SNAPSHOT
。来自文档:
已提交阅读
指定语句无法读取已包含的数据 已被其他事务修改但未提交。这可以防止 脏读。数据可以通过其他交易之间的更改 当前事务中的各个语句,导致 不可重复读取或幻像数据。该选项是SQL Server 默认。READ COMMITTED 的行为取决于 READ_COMMITTED_SNAPSHOT 数据库选项:
如果 READ_COMMITTED_SNAPSHOT 设置为 OFF(SQL Server 上的默认值),数据库引擎将使用共享锁来防止其他 当前事务正在修改行时发生的事务 运行读取操作。共享锁也会阻塞该语句 从读取被其他事务修改的行直到另一个 交易完成。共享锁类型决定何时会发生 被发布。在处理下一行之前释放行锁。 页锁在读取下一页时释放,表锁 声明结束后发布。
如果 READ_COMMITTED_SNAPSHOT 设置为 ON(Azure SQL 数据库上的默认设置),则数据库引擎使用行版本控制来呈现每个 具有事务一致的数据快照的语句 存在于声明的开头。锁不是用来保护的 来自其他事务更新的数据。
您最有可能想要的是在数据库上启用
READ_COMMITTED_SNAPSHOT
,以便您可以查询正在更新的行,并访问它们的 prior 值。这可以使用以下命令启用:
ALTER DATABASE YourDatabase SET READ_COMMITTED_SNAPSHOT ON;
我们可以通过以下内容演示
READ_COMMITTED_SNAPSHOT
开启或关闭时的这些不同行为。首先,让我们创建几个数据库:
CREATE DATABASE SnapShotDB;
GO
CREATE DATABASE NoSnapShotDB;
GO
ALTER DATABASE SnapShotDB SET READ_COMMITTED_SNAPSHOT ON;
GO
现在我将
CREATE
一个表,然后尝试 UPDATE
事务中的某一行,但不是 COMMIT
:
USE NoSnapShotDB;
GO
CREATE TABLE dbo.MyTable (ID int PRIMARY KEY, C char(1));
GO
INSERT INTO dbo.MyTable
VALUES(1,'a'),
(2,'b');
GO
BEGIN TRANSACTION;
UPDATE dbo.MyTable
SET C = 'c'
WHERE ID = 1;
现在,在新的查询窗口中(无需关闭其他窗口),运行以下查询:
USE NoSnapShotDB;
GO
SELECT *
FROM dbo.MyTable
WHERE ID = 1;
此查询将不会完成,它会“挂起”。但是,如果您返回到其他查询窗口并在 in 中运行
COMMIT
,则该查询将完成并显示以下结果:
身份证 | C |
---|---|
1 | c |
因此该表在
C
列中包含新值。
现在我们可以做同样的事情,但是对于
SnapShotDB
:
USE SnapShotDB;
GO
CREATE TABLE dbo.MyTable (ID int PRIMARY KEY, C char(1));
GO
INSERT INTO dbo.MyTable
VALUES(1,'a'),
(2,'b');
GO
BEGIN TRANSACTION;
UPDATE dbo.MyTable
SET C = 'c'
WHERE ID = 1;
USE SnapShotDB;
GO
SELECT *
FROM dbo.MyTable
WHERE ID = 1;
这会立即返回结果集,其中包含旧值:
身份证 | C |
---|---|
1 | a |
如果您然后
COMMIT
并重新运行后一个查询,那么您将获得 'c'
列的 C
。