【问题标题】:Phantom read in a SELECT / UPDATE scenarioSELECT / UPDATE 场景中的幻读
【发布时间】:2017-06-30 06:18:09
【问题描述】:

伪代码:

var data = ExecuteMSSQLQuery(
  "SELECT Id FROM Table WHERE Status='not_processed'");

if(ProcessData(data))
{
    ExecuteMSSQLQuery(
      "UPDATE Table SET Status='processed' WHERE Status='not_processed'");
}

我想确保UPDATE 查询中更新的行与SELECT 返回的行完全相同。我知道一种解决方案是使用临时表。但我心中的问题是——我可以通过将事务隔离级别设置为serilized 来实现这一点吗?还是只影响SELECTs?这里最好的解决方案是什么?

相关数据库是 MSSQL 2012(如果相关)。

【问题讨论】:

  • 从逻辑上讲,我会从select 获取ID,并将更新为WHERE Id = <value fetched>
  • 绝对是一种选择。我想我的问题更多是关于 SQL 服务器为此目的提供的工具。当然,以编程方式生成具有所有所需 ID 的此类查询并不难。
  • 我明白了。好吧,我也可以尝试使用 SSIS 来做到这一点。不管怎样,你得到的答案一定会丰富我的知识。

标签: sql sql-server transactions isolation-level


【解决方案1】:

在多用户环境中可能存在三个不一致问题:

脏读:一个事务从其他事务读取未提交的(脏)数据 未提交的交易。

不可重复读取:后续尝试从内部读取相同数据 相同的事务返回不同的结果。出现此数据不一致问题 当其他事务在读取之间修改甚至删除数据时 由受影响的事务完成。

幻读 :这种现象发生在 相同的事务返回新行(事务之前未读取的行)。 当另一个事务在两个事务之间插入新数据时会发生这种情况 由受影响的事务完成的读取。

此表将显示每个事务级别可能存在的不一致:

+---------------+-------------+----------------------+---------------+
|Isolation Level| Dirty Reads | Non-Repeatable Reads | Phantom Reads |
|Read Uncommited|      YES    |        YES           |     YES       |
|Read Commited  |      NO     |        YES           |     YEs       |
|Repeatable Read|      NO     |        NO            |     YES       |
|Serializable   |      NO     |        NO            |     NO        |
|Snapshot       |      NO     |        NO            |     NO        |
+---------------+-------------+----------------------+---------------+

在您的情况下,问题在于不可重复读取:您在 select 语句(等 50)期间发现了一个具有特定值的元素,并且您希望将其更新 10%(55),但同时有人已经打开它并更改为 100 ,当您提交事务时,它将是 110。这可以通过 REPEATABLE READ 轻松解决,而无需使用高锁定隔离级别 (Serializable) 会阻止您在期间读取/插入/更新表上的任何内容一个事务,即使你只是更新一行,或者使用 tempdb 保持行版本的快照,即使很难。


更新您的评论

使用 SNAPSHOT 或 SERIALIZABLE 将通过不更新更新语句启动后添加的行来提高一致性。然而,这两者之间的区别在于,SNAPSHOT 将为您提供乐观读取,并且您仍然可以读取原始数据,而可序列化则不会,它只会锁定整个对象并保留它直到事务完成。

例如,如果我们有 10 条 ProdutType 1 的记录:

SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
Update Products
SET  Qty = 45 
where ProductType = 1

另一个事务插入 ProductType = 1 的行,直到第一个事务完成才会提交,它只会影响 10 行。

您可以在不使用可重复读取锁定的情况下获得相同的结果:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
    BEGIN TRAN
    Update Products
    SET  Qty = 45 
    where ProductType = 1

我们更新了 10 行,并插入了另一行 ProductType = 1 和 Qty 5 - 没有锁定。 提交这将导致 10 行更改和一行 ProductType 10 数量为 5,但是如果您在该事务期间再次执行更新语句,在添加新行后它将发出 11 次更新

【讨论】:

  • 我会说你描述的问题不是我要解决的问题。我的问题是,当ProcessData 正在运行时,有人将向表中添加新行,而我的UPDATE 将更新比SELECT 最初返回的行更多的行。我实际上并没有遇到有人会连续更新某个字段的值的问题。
  • 我明白了..快照或可序列化正如你所说,只要确保你测试两者,看看使用可序列化有多少并发回归,以及使用快照敲击了多少 tempdb,但本质上是其中之一两个就可以了
  • 因此,可序列化和快照都锁定了我在WHERE 中指定的整个范围,从而确保UPDATE 将看到与SELECT 返回的所有相同结果。不返回可能符合条件的新行。谢谢。如果您更新答案以反映这一点,那么我将能够将其标记为已接受:)
猜你喜欢
  • 1970-01-01
  • 2014-03-10
  • 1970-01-01
  • 2014-04-25
  • 1970-01-01
  • 1970-01-01
  • 2011-06-28
  • 2015-01-21
  • 1970-01-01
相关资源
最近更新 更多