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

时间:2015-01-07 21:17:05

标签: c# sql-server entity-framework transactions transactionscope

我有一些.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()将值写入数据库。

偶尔,我看到entity.PropertyOne,entity.PropertyTwo和entity.PropertyThree的值彼此不匹配。例如,entity.PropertyOne的值为500,但entity.PropertyTwo和entity.PropertyThree的值为499.这怎么可能?即使代码没有包含在事务中,我也希望这些值匹配(如果两个用户同时运行,则可能在实体之间重复)。

我正在使用Entity Framework 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;
}

3 个答案:

答案 0 :(得分:0)

因此,无法明确回答此问题,因为GetNextValueFromTheDatabase未显示。我会说你说的是什么:

SQL Server中的

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

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

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

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

答案 1 :(得分:0)

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

考虑两个事务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

<强>更新

对于这个具体问题,这个答案似乎是无关紧要的。它与可重复的读隔离级别相关,匹配此问题的关键字,虽然不是特别错误,所以我将把它留在这里。

答案 2 :(得分:0)

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

因此,例如,让我们说当前最大数字是499,事务A和事务B都读取499.当事务A完成时,它成功地将500写入所有三个属性。事务B尝试向所有3列写入500。非标识列成功更新为500,但标识列的值会自动增加到下一个可用值(不会抛出错误)

一些解决方案

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

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