简体   繁体   English

交易范围和乐观并发

[英]TransactionScope and Optimistic concurrency

I would like to use optimistic concurrency with TransactionScope . 我想对TransactionScope使用乐观并发。 Here's the code I come up so far: 到目前为止,这是我提出的代码:

var options = new TransactionOptions {IsolationLevel = IsolationLevel.ReadCommitted};
using (var scope = new TransactionScope(TransactionScopeOption.Required, options))
{
    using (var connection = new SqlConnection(_connectionString))
    {
        // ... execute some sql code here

        // bump up version
        var version = connection.ExecuteScalar<DateTime>(@"
            DECLARE @version datetime2 = SYSUTCDATETIME();
            UPDATE [Something].[Test]
                SET [Version] = @version
                WHERE Id = @Id
            SELECT @version
        ", new {Id = id});

        // ... execute more sql code here

        // check if version has not changed since bump up
        // NOTE: version is global for the whole application, not per row basis
        var newVersion = connection.ExecuteScalar<DateTime>("SELECT MAX([Version]) FROM [Something].[Test]");
        if (newVersion == version) scope.Complete(); // looks fine, mark as completed
    }
} // what about changes between scope.Complete() and this line?

Unfortunately this code has one serious issue. 不幸的是,这段代码有一个严重的问题。 Between version check and transaction commit there might be some change in the database. 在版本检查和事务提交之间,数据库中可能会有一些更改。 It's a standard time of check to time of use bug. 是检查使用时间错误的标准时间 The only way I can see to resolve it is to execute version check and transaction commit as a single command. 我可以看到解决此问题的唯一方法是将版本检查和事务提交作为单个命令执行。

Is it possible to execute some SQL code along with transaction commit using TransactionScope ? 是否可以使用TransactionScope执行一些SQL代码以及事务提交? If no then what other solution could be used? 如果没有,那么可以使用什么其他解决方案?

EDIT1: Version needs to be per application, not per row. EDIT1:版本需要针对每个应用程序,而不是每一行。

EDIT2: I could use serializable isolation level, but it is not an option due to performance issues this would cause. EDIT2:我可以使用可序列化的隔离级别,但是由于这会导致性能问题,因此这不是一个选择。

Unfortunately this code has one serious issue. 不幸的是,这段代码有一个严重的问题。 Between version check and transaction commit there might be some change in the database. 在版本检查和事务提交之间,数据库中可能会有一些更改。

This is simply not true. 这是不正确的。 The default constructor of TransactionSope , as in the code you posted, uses the Serializable isolation level. 就像您发布的代码中一样, TransactionSope的默认构造函数使用Serializable隔离级别。 While this is arguably a problem , it does have the side effect of preventing any modification to any row you queried . 尽管这可以说是一个问题 ,但它确实具有防止对您查询的任何行进行任何修改的副作用。 It is pessimistic concurrency control. 这是悲观的并发控制。

You are right that you should use optimistic concurrency control, though. 您应该使用乐观并发控制,这是正确的。 You need to use a TransactionScope constructor that accepts TransactionOptions and pass in the option to use a more decent isolation level , eg. 您需要使用一个接受TransactionOptionsTransactionScope构造函数,并传递该选项以使用更合理的隔离级别 ,例如。 read committed. 读已提交。 As for the row version, use a simple int that you increment with each write in the app. 至于行版本,请使用一个简单的int,您在应用程序中的每次写入都将其递增。

UPDATE [Something].[Test]
 SET ..., [Version] = @new_version
 OUTPUT Inserted.Id
 WHERE Id = @Id AND [Version] = @old_version;

@old_version is the version you found on the record when you query it. @old_version是您在查询记录时发现的版本。 @new_version is @old_version+1 . @new_version@old_version+1 If the row was modified after you read it then the WHERE will not find it and your result will be an empty set, so you know you have to read, refresh and try again (a conflict occurred). 如果在读取后修改了该行,则WHERE将找不到该行,并且结果将为空集,因此您必须读取,刷新并重试(发生冲突)。 This is a well known optimistic control scheme . 这是众所周知的乐观控制方案

Note though that optimistic concurrency control makes more sense where the read and the write span two different transactions (eg. read in T1, display to user form, then write in T2). 请注意,尽管乐观并发控制在读取和写入跨越两个不同事务的地方更有意义(例如,在T1中读取,以用户形式显示,然后在T2中写入)。 When the read and the write occur in the same transaction then you better leave it to the engine. 当读取和写入发生在同一事务中时,最好将其留给引擎。 I would simply use snapshot isolation level which solves the problem out-of-the-box. 我只是简单地使用快照隔离级别 ,它可以立即解决问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM