【问题标题】:How to implement Serializable Isolation Level in SQL Server如何在 SQL Server 中实现可序列化的隔离级别
【发布时间】:2019-04-23 11:48:41
【问题描述】:

我需要在 SQL Server 中实现可序列化的隔离级别,但我尝试了很多方法,但都没有。

我需要在一个事务中锁定 1 行(锁定整个表并不重要)。因此,另一个事务甚至无法选择行(不要读取)。

我尝试的最后一件事:

对于交易 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 给了我行。

如果我错过了什么,谁能解释我?

【问题讨论】:

  • 您的假设部分正确(下面的答案解释了原因)。将第一个查询中的选择更改为更新或插入操作并注释掉 COMMIT 部分。执行后,使用 select 运行第二个查询,它将被第一个事务阻止。然后,执行 COMMIT 部分,您将看到第二个事务很快完成
  • 查找 ROWLOCK、HOLDLOCK、UPDLOCK

标签: sql sql-server transactions isolation-level


【解决方案1】:

SQL Server 符合 Serializable 查询的严格定义。也就是说,必须有一个逻辑上可以生成的结果IF两个查询都按顺序运行 - 事务 1 在事务 2 开始之前完成,反之亦然。

这会导致某些效果与您的预期不同。 SQLPerformance.com 对 Serializable 隔离级别有很好的解释,清楚地说明了这种逻辑可串行化最终的含义。 (非常有用的网站,就是那个。)

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

TL;DR - SQL Server 希望尽量减少阻塞,因此它使用逻辑分析来查看可序列化隔离级别需要哪些类型的锁,并且它(尝试)使用实现所需的最小数量和强度的锁它的目标。

现在我们已经解决了这个问题 - 我只能想到两种方法来锁定一行,以便其他人无法读取它:使用 XLOCK + TABLOCK(锁定整个表 - 不推荐的做法)或者在您启动进程时在每行更新某种形式的字段 - 例如 SPID 字段或锁定的位标志。当您在事务中更新它时,只有带有 NOLOCK 提示的 SELECT 才能读取它。

显然,这些都不是最优的。我推荐“这一行很忙 - 走开”标志,因为这可能是我对一行(几乎)绝对锁定所采取的方法。

【讨论】:

  • 感谢您的回答,这对我帮助很大。如您所说,该行仅在事务 1 中有更新操作时被锁定。
【解决方案2】:

根据documentation

SERIALIZABLE 指定以下内容:

  • 语句无法读取已被其他事务修改但尚未提交的数据。
  • 在当前事务完成之前,没有其他事务可以修改当前事务已读取的数据。
  • 其他事务不能插入新行,其键值将落在任何语句读取的键范围内 当前事务直到当前事务完成。

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

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

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN

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

COMMIT TRAN

【讨论】:

  • ROWLOCK 不会阻止读取 - 即使使用 HOLDLOCK。奇怪,但真实。
【解决方案3】:

也许你可以用这样的一些技巧来解决这个问题?

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

所以你创建了一个表 someTableForThisHack 并在其中插入一行。

【讨论】:

  • 这将非常类似于将 TABLOCK 添加到查询中,有效地锁定表中的所有行,直到第一次读取完成。我不认为这个版本的并发性会很好。
猜你喜欢
  • 2017-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-02
  • 2020-07-16
相关资源
最近更新 更多