如何在 SQL 服务器中实现可序列化隔离级别

How to implement Serializable Isolation Level in SQL Server

我需要在 SQL 服务器中实现一个可序列化的隔离级别,但我已经尝试了很多方法,但我不明白。

我需要在一个事务中锁定 1 行(锁定完整的行并不重要 table)。因此,另一笔交易甚至无法 select 该行(请勿阅读)。

我尝试的最后一件事:

对于事务 1:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN

SELECT code FROM table1 WHERE code = 1

-- Here I select in another instance the same row

COMMIT TRAN

对于交易 2:

BEGIN TRAN
SELECT code FROM table1 WHERE code = 1
COMMIT TRAN

我希望事务 2 等到事务 1 提交操作,但事务 2 给了我行。

如果我遗漏了什么,有人可以解释我吗?

SQL 服务器符合可序列化查询的严格定义。也就是说,必须有一个逻辑上可以生成的结果 IF 两个查询 运行 按顺序 - T运行saction 1 在 T[=31= 之前完成]action 2可以开始,反之亦然。

这会产生一些与您预期不同的效果。在 SQLPerformance.com 上对可序列化隔离级别有很好的解释,清楚地说明了这种逻辑可序列化性最终的含义。 (非常有用的网站,那个。)

对于您的上述查询,没有逻辑要求阻止第二个查询读取与第一个查询相同的行。无论查询 运行 的顺序如何,它们都会 return 相同的数据而不修改它。由于查询分析器可以识别这一点,因此没有理由在数据上放置读锁。但是,如果其中一个查询对数据执行了更新,那么(警告 - 这里的逻辑假设,因为我实际上不知道 SQL 服务器如何处理这个问题的内部机制)QA 将设置更强的锁定选定的行。

TL;DR - SQL 服务器想要最小化阻塞,所以它使用逻辑分析来查看可序列化隔离级别需要什么类型的锁,并且它(尝试)使用最小数量和实现其目标所需的锁强度。

既然我们已经解决了这个问题——我只能想到两种方法来锁定一行,这样其他人就无法读取它:使用 XLOCK + TABLOCK(锁定整个 table - 不推荐的做法)或在每行上有某种形式的字段在您开始流程时更新 - 类似于 SPID 字段,或锁定的位标志。当您在 t运行saction 中更新它时,只有带有 NOLOCK 提示的 SELECT 才能读取它。

显然,这些都不是最优的。我推荐 "This row is busy - go away" 标志,因为这可能是我对一行(几乎)绝对锁定所采用的方法。

根据documentation

SERIALIZABLE Specifies the following:

  • Statements cannot read data that has been modified but not yet committed by other transactions.
  • No other transactions can modify data that has been read by the current transaction until the current transaction completes.
  • Other transactions cannot insert new rows with key values that would fall in the range of keys read by any statements in the current transaction until the current transaction completes.

如果您不对事务 1 中带有 INSERTUPDATEDELETE 的数据进行任何更改,SQL 将在之后释放共享锁读取完成。

您可能想要尝试的是添加一个 table 命中以防止在事务 1 结束之前释放行锁。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN

SELECT code 
FROM table1 WITH(ROWLOCK, HOLDLOCK)
WHERE code = 1

COMMIT TRAN

也许你可以用这样的 hack 来解决这个问题?

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
UPDATE someTableForThisHack set val = CASE WHEN val = 1 THEN 0 else 1 End
SELECT code from table1.....
COMMIT TRANSACTION

因此您创建了一个 table someTableForThisHack 并向其中插入一行。