数据是否可以在具有可重复读取隔离的事务中更改

本文关键字:隔离 事务 读取 是否 数据 复读 | 更新日期: 2023-09-27 18:36:43

我有一些 .NET 代码包装在一个可重复的读取事务中,如下所示:

using (
                var transaction = new TransactionScope(
                    TransactionScopeOption.Required,
                    new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead },
                    TransactionScopeAsyncFlowOption.Enabled))
            {
                int theNextValue = GetNextValueFromTheDatabase();
                var entity = new MyEntity
                               {
                                   Id = Guid.NewGuid(),
                                   PropertyOne = theNextValue, //An identity column
                                   PropertyTwo = Convert.ToString(theNextValue),
                                   PropertyThree = theNextValue,
                                   ...
                               };
                DbSet<MyEntity> myDbSet = GetEntitySet();
                myDbSet.Add(entity);
                await this.databaseContext.Entities.SaveChangesAsync();
                transaction.Complete();
            }

第一种方法 GetNextValueFromTheDatabase 检索存储在数据库中表中的列中的最大值。 我使用可重复读取,因为我不希望两个用户读取并使用相同的值。 然后,我只需在内存中创建一个Entity并调用SaveChangesAsync()将值写入数据库。

偶尔,我看到了实体的价值。属性一,实体。属性二和实体。属性三彼此不匹配。 例如,实体。属性一的值为 500,但实体。属性二和实体。属性三的值为 499。 这怎么可能? 即使代码没有包装在事务中,我也希望值匹配(如果两个用户同时运行,则可能会在实体之间重复)。

我正在使用实体框架 6 和 Sql Server 2008R2。

编辑:
这是GetNextValueFromTheDatabase的代码

public async Task<int> GetNextValueFromTheDatabase()
{
    return await myQuerable
        .OrderByDescending(x => x.PropertyOne) //PropertyOne is an identity column (surprise!)
        .Select(x => x.PropertyOne)
        .Take(1)
        .SingleAsync() + 1;
}

数据是否可以在具有可重复读取隔离的事务中更改

所以这个问题不能得到明确的回答,因为没有显示GetNextValueFromTheDatabase。我正在离开你说的它的作用:

REPEATABLE READ SQL Server 中的 S 锁会锁定您已读取的行。当您读取当前最大值(大概是从索引中读取)时,该行是 S 锁定的。现在,如果出现新的最大值,则该行不受锁的影响。这就是为什么锁不会阻止其他竞争最大值出现的原因。

如果通过从表中读取最大值来获取最大值SERIALIZABLE则需要隔离。这将导致特定情况下的死锁。这可以通过锁定提示或重试来解决。

您还可以保留一个单独的表来存储当前最大值。 REPEATABLE READ在这里就足够了,因为您始终访问该表的同一行。即使没有锁定提示REPEATABLE READ,您也会在这里看到死锁。

重试是解决死锁的合理方法。

我认为您基本上正在体验幻影阅读

考虑两个事务 T1、T2,它们被取消执行,如下所示。问题是,在 T1 的第一次读取中,您不会获得从事务 T2 插入的值 (X)。第二次,您会在 select 语句中获取值 (X)。这就是可重复读取的可怕之处。如果从中读取某些行,它不会阻止在整个表中插入。它只锁定现有行。

T1                                     T2
SELECT A.X FROM WeirdTable
                                       INSERT INTO WeirdTable TABLE (A) VALUES (X)
SELECT A.X FROM WeirdTable

.

更新

似乎这个答案与这个特定问题无关。它与可重复的读取隔离级别有关,与这个问题的关键字匹配,并且没有假设的错误,所以我将把它留在这里。

我终于想通了。 如usr的响应中所述,多个事务可以同时读取相同的最大值(S-Lock)。问题是其中一列是identity列。 EF 允许您在插入时指定标识列的值,但忽略您指定的值。 因此,标识列似乎在大多数情况下都使用预期值进行更新,但实际上域实体中指定的值恰好与数据库内部生成的值相匹配。

例如,假设当前的最大数字为 499,事务 A 和事务 B 都读取 499。 事务 A 完成后,它成功地将 500 写入所有三个属性。 事务 B 尝试将 500 写入所有 3 列。 非标识列已成功更新为 500,但标识列的值会自动递增到下一个可用值(不会引发错误)

一些解决方案

我使用的解决方案是在插入记录时不设置任何列的值。 插入记录后,使用数据库分配的标识列的值更新其他两列。

另一种选择是将列的选项更改为.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)...这将比第一个选项表现更好,但需要 USR 建议的更改来缓解锁定问题。