具有可重复读取隔离的事务中的数据是否可能发生变化?
Is it possible for data to change within a transaction with repeatable read isolation?
我有一些 .NET 代码包含在 repeatable 读取事务中,如下所示:
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
检索存储在数据库中 table 的列中的最大值。我正在使用 repeatable read 因为我不希望两个用户读取和使用相同的值。然后,我只是在内存中创建一个 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;
}
所以这个问题无法明确回答,因为GetNextValueFromTheDatabase
没有显示。我要按照你说的去做:
REPEATABLE READ
in SQL 服务器 S 锁定您已阅读的行。当您读取当前最大值时,大概是从索引中读取的,该行是 S 锁定的。现在,如果出现新的最大值,则该行不受锁定影响。这就是为什么锁定不会阻止其他竞争最大值出现的原因。
如果您通过从 table 中读取最大值来获得最大值,则需要 SERIALIZABLE
隔离。这将导致您的特定情况出现死锁。这可以通过锁定提示或重试来解决。
您也可以保留一个单独的 table 来存储当前最大值。 REPEATABLE READ
在这里就足够了,因为您总是访问 table 的同一行。即使 REPEATABLE READ
没有锁定提示,您也会在这里看到死锁。
重试是解决死锁的有效方法。
我认为你基本上正在经历幻读。
考虑计划执行的两个事务 T1、T2,如下所示。问题是,在 T1 的第一次读取中,您没有获得从事务 T2 插入的值 (X)。第二次,您确实在 select 语句中获得了值 (X)。这就是 repeatable 阅读的可怕之处。如果从中读取某些行,它不会阻止整个 table 中的插入。它只锁定现有行。
T1 T2
SELECT A.X FROM WeirdTable
INSERT INTO WeirdTable TABLE (A) VALUES (X)
SELECT A.X FROM WeirdTable
.
更新
这个答案似乎与这个具体问题无关。它与 repeatable 读取隔离级别有关,与该问题的关键字匹配,但概念上并没有错,所以我将其留在这里。
我终于明白了。如中所述,多个事务可以同时读取相同的最大值(S-Lock)。问题是其中一列是identity
列。 EF 允许您在插入时指定标识列的值,但会忽略您指定的值。所以标识列似乎大部分时间都更新为预期值,但实际上域实体中指定的值恰好与数据库内部生成的值匹配。
因此,例如,假设当前最大数量为 499,事务 A 和事务 B 均读取 499。当事务 A 完成时,它成功地将 500 写入所有三个属性。事务 B 尝试将 500 写入所有 3 列。非标识列已成功更新为 500,但标识列的值会自动递增到下一个可用值(不会引发错误)
几个解决方案
我使用的解决方案是在插入记录时不为任何列设置值。插入记录后,使用数据库分配的标识列的值更新其他两列。
另一种选择是将列的选项更改为 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)
...这会比第一个选项执行得更好,但需要我们建议的更改来缓解锁定问题。
我有一些 .NET 代码包含在 repeatable 读取事务中,如下所示:
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
检索存储在数据库中 table 的列中的最大值。我正在使用 repeatable read 因为我不希望两个用户读取和使用相同的值。然后,我只是在内存中创建一个 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;
}
所以这个问题无法明确回答,因为GetNextValueFromTheDatabase
没有显示。我要按照你说的去做:
REPEATABLE READ
in SQL 服务器 S 锁定您已阅读的行。当您读取当前最大值时,大概是从索引中读取的,该行是 S 锁定的。现在,如果出现新的最大值,则该行不受锁定影响。这就是为什么锁定不会阻止其他竞争最大值出现的原因。
如果您通过从 table 中读取最大值来获得最大值,则需要 SERIALIZABLE
隔离。这将导致您的特定情况出现死锁。这可以通过锁定提示或重试来解决。
您也可以保留一个单独的 table 来存储当前最大值。 REPEATABLE READ
在这里就足够了,因为您总是访问 table 的同一行。即使 REPEATABLE READ
没有锁定提示,您也会在这里看到死锁。
重试是解决死锁的有效方法。
我认为你基本上正在经历幻读。
考虑计划执行的两个事务 T1、T2,如下所示。问题是,在 T1 的第一次读取中,您没有获得从事务 T2 插入的值 (X)。第二次,您确实在 select 语句中获得了值 (X)。这就是 repeatable 阅读的可怕之处。如果从中读取某些行,它不会阻止整个 table 中的插入。它只锁定现有行。
T1 T2
SELECT A.X FROM WeirdTable
INSERT INTO WeirdTable TABLE (A) VALUES (X)
SELECT A.X FROM WeirdTable
.
更新
这个答案似乎与这个具体问题无关。它与 repeatable 读取隔离级别有关,与该问题的关键字匹配,但概念上并没有错,所以我将其留在这里。
我终于明白了。如identity
列。 EF 允许您在插入时指定标识列的值,但会忽略您指定的值。所以标识列似乎大部分时间都更新为预期值,但实际上域实体中指定的值恰好与数据库内部生成的值匹配。
因此,例如,假设当前最大数量为 499,事务 A 和事务 B 均读取 499。当事务 A 完成时,它成功地将 500 写入所有三个属性。事务 B 尝试将 500 写入所有 3 列。非标识列已成功更新为 500,但标识列的值会自动递增到下一个可用值(不会引发错误)
几个解决方案
我使用的解决方案是在插入记录时不为任何列设置值。插入记录后,使用数据库分配的标识列的值更新其他两列。
另一种选择是将列的选项更改为 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)
...这会比第一个选项执行得更好,但需要我们建议的更改来缓解锁定问题。