为什么 SQL Server 中读取器 (SELECT) 会被写入器 (DML) 阻塞?

问题描述 投票:0回答:1

有人可以向我解释为什么在 SQL Server 中 SELECT 可以被 UPDATE 语句阻止吗?我认为,MVCC(通过将修改后的数据复制到 tempdb 在 SQL Server 中实现)可以防止读取器在所有情况下被阻止。我错了吗?为什么需要将 ISOLATION LEVEL 更改为 SNAPSHOT 才能释放锁定?

这就是它在 Oracle 中的工作原理:

写入器永远不会阻塞读取器。当写入器更改行时,数据库使用撤消数据为读取器提供该行的一致视图。

PostgreSQL

使用并发控制的 MVCC 模型而不是锁定的主要优点是,在 MVCC 中,为查询(读取)数据获取的锁与为写入数据获取的锁不会冲突,因此 读永远不会阻塞写,写永远不会阻塞读 .

sql sql-server transactions locking mvcc
1个回答
0
投票

默认情况下,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

© www.soinside.com 2019 - 2024. All rights reserved.