25

使用 SQL Server 的事务隔离级别,您可以避免某些不需要的并发问题,例如脏读等。

我现在感兴趣的是丢失更新——事实上两个事务可以在没有人注意到的情况下覆盖彼此的更新。我看到并听到关于我必须选择至少哪个隔离级别来避免这种情况的相互矛盾的陈述。

Kalen Delaney 在她的“SQL Server Internals”一书中说(第 10 章 - 事务和并发 - 第 592 页):

在 Read Uncommitted 隔离中,前面描述的所有行为,除了丢失更新之外,都是可能的。

另一方面,给我们上课的独立 SQL Server 培训师告诉我们,我们至少需要“可重复读取”以避免丢失更新。

那么谁是对的??为什么?

4

6 回答 6

19

我不知道现在回答是否为时已晚,但我只是在大学学习事务隔离级别,作为我研究的一部分,我遇到了这个链接:

微软技术网

具体来说,有问题的段落是:

丢失更新

丢失的更新可以用两种方式之一来解释。在第一种情况下,当一个事务更新的数据在第一个事务提交或回滚之前被另一个事务覆盖时,就认为发生了丢失更新。 这种类型的丢失更新在 SQL Server 2005 中不会发生,因为它在任何事务隔离级别下都是不允许的。

丢失更新的另一种解释是当一个事务(事务#1)将数据读入其本地内存,然后另一个事务(事务#2)更改此数据并提交其更改。在此之后,事务#1 根据在事务#2 执行之前读入内存的内容更新相同的数据。在这种情况下,事务#2 执行的更新可以被认为是丢失更新。

所以本质上两个人都是对的。

就个人而言(我很容易犯错,所以请在我刚刚学习时纠正我)我从中得出以下两点:

  1. 事务环境的全部意义在于防止丢失更新,如上段所述。因此,如果即使是最基本的事务级别也无法做到这一点,那么为什么还要费心使用它。

  2. 当人们谈论丢失更新时,他们知道第一段适用,因此一般来说是指第二种丢失更新。

再次,如果这里有任何问题,请纠正我,因为我也想理解这一点。

于 2012-01-11T01:28:40.587 回答
14

书中的示例是 Clerk A 和 Clerk B 接收 Widget 的货物。

他们都检查了当前的库存,看到有 25 个。职员 A 有 50 个小部件并更新到 75,职员 B 有 20 个小部件,因此更新到 45 会覆盖之前的更新。

我想她的意思是这种现象可以通过 Clerk A 在所有隔离级别上避免

UPDATE Widgets
SET StockLevel = StockLevel + 50
WHERE ...

和店员 B 做

UPDATE Widgets
SET StockLevel = StockLevel + 20
WHERE ...

当然,如果SELECTandUPDATE是作为单独的操作完成的,您需要repeatable read避免这种情况,因此S在事务期间保持行上的锁(在这种情况下会导致死锁)

于 2011-11-20T13:26:26.267 回答
7

即使读取和写入是在不同的事务中,也可能会丢失更新,例如当用户将数据读入网页然后更新时。在这种情况下,没有隔离级别可以保护您,尤其是当连接池中的连接被重用时。我们应该使用其他方法,例如 rowversion。 这是我的罐头答案。

于 2011-11-21T02:17:25.450 回答
3

My experience is that with Read Uncommitted you no longer get 'lost updates', you can however still get 'lost rollbacks'. The SQL trainer was probably referring to that concurrency issue, so the answer you're likely looking for is Repeatable Read.

That said, I would be very interested if anyone has experience that goes against this.

于 2011-11-20T12:44:14.753 回答
0

正如 Francis Rodgers 所指出的,您可以依赖 SQL Server 实现的是,一旦事务更新了某些数据,每个隔离级别总是对数据发出“更新锁”,并拒绝来自另一个事务的更新和写入,无论它是什么隔离级别是。您可以确定涵盖了这种丢失的更新。

但是,如果情况是一个事务读取了一些数据(具有不同于可重复读取的隔离级别),那么另一个事务能够更改此数据并提交它的更改,如果第一个事务然后更新相同的数据但这次,根据他制作的内部副本,管理系统无法为保存它做任何事情。

在那种情况下,您的答案是在第一个事务中使用可重复读取,或者可能在第一个事务中对数据使用一些读锁(我真的不知道以一种自信的方式。我只知道这个的存在锁,你可以使用它们。也许这会帮助任何对这种方法感兴趣的人Microsoft Designing Transactions and Optimizing Locking)。

于 2015-03-01T00:11:36.270 回答
0

以下引自 70-762 Developing SQL Databases (p. 212)

当两个进程读取同一行然后用不同的值更新该数据时,可能会出现另一个潜在问题。如果事务首先将值读入变量,然后在后续步骤的更新语句中使用该变量,则可能会发生这种情况。执行此更新时,另一个事务会更新相同的数据。这些事务中的任何一个首先提交都会成为丢失的更新,因为它被另一个事务中的更新替换。您不能使用隔离级别来更改此行为,但您可以编写一个专门允许丢失更新的应用程序。

因此,在这种情况下,似乎没有任何隔离级别可以帮助您,您需要在代码本身中解决问题。例如:

DROP TABLE IF EXISTS [dbo].[Balance];

CREATE TABLE [dbo].[Balance]
(
    [BalanceID] TINYINT IDENTITY(1,1)
   ,[Balance] MONEY
   ,CONSTRAINT [PK_Balance] PRIMARY KEY
   (
        [BalanceID]
   )
);

INSERT INTO [dbo].[Balance] ([Balance])
VALUES (100);

-- query window 1
BEGIN TRANSACTION;

    DECLARE @CurrentBalance MONEY;

    SELECT @CurrentBalance = [Balance]
    FROM [dbo].[Balance]
    WHERE [BalanceID] = 1;

    WAITFOR DELAY '00:00:05'

    UPDATE [dbo].[Balance]
    SET [Balance] = @CurrentBalance + 20
    WHERE [BalanceID] = 1;

COMMIT TRANSACTION;

-- query window 2
BEGIN TRANSACTION;

    DECLARE @CurrentBalance MONEY;

    SELECT @CurrentBalance = [Balance]
    FROM [dbo].[Balance]
    WHERE [BalanceID] = 1;

    UPDATE [dbo].[Balance]
    SET [Balance] = @CurrentBalance + 50
    WHERE [BalanceID] = 1;

COMMIT TRANSACTION;

创建表,在单独的查询窗口中执行代码的每一部分。更改隔离级别无济于事。例如,read committed和之间的唯一区别repeatable read是最后一个事务在第一个事务完成时阻塞第二个事务,然后覆盖该值。

于 2018-12-03T16:52:56.747 回答