幻像在SELECT / UPDATE场景中读取

时间:2017-06-30 06:18:10

标签: sql sql-server transactions isolation-level

伪代码:

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来实现此目的吗?或者它只影响SELECT?这里最好的解决方案是什么?

有问题的数据库是MSSQL 2012,如果它是相关的。

1 个答案:

答案 0 :(得分: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。

更新您的评论

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

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

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与Qty 5,但是如果您在该事务期间再次执行update语句,则在添加新行之后将发出11个更新