将 TransactionScope 与 MySQL 和读锁结合使用

发布于 2024-07-23 09:42:30 字数 1158 浏览 3 评论 0原文

我有以下情况:

如果有一个带有 InnoDB 表的 MySQL 数据库,我用它来存储唯一的数字。 我启动一个事务,读取值(例如 1000471),将该值存储在另一个表中并更新增量值(100472)。 现在我想避免其他人在我的事务运行时读取该值。

如果我使用普通 MySQL,我会这样做:

Exceute("LOCK tbl1 READ");
执行("从 tbl1 选择 ...");
执行(“插入到tbl2”);
执行(“解锁表”);

但由于我使用 SubSonic 作为 DAL 并且代码应该独立于 mysql,所以我必须使用 TransactionScope。

我的代码:

        TransactionOptions TransOpt = new TransactionOptions();
        TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
        TransOpt.Timeout = new TimeSpan(0, 2, 0);

        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, TransOpt))
        {

             // Select Row from tbl1

             // Do something

             ts.Complete();
        }

根据 TransactionOptions

的帮助system.transactions.isolationlevel

我想要达到的效果可以用 IsolationLevel.ReadCommited 来实现,但我仍然可以从事务外部读取该行(如果我尝试更改它,我会获得锁定,因此事务正在工作)

有人有建议吗? TransactionScope 是否可以实现读锁

I have the following situation:

If have a MySQL db with a InnoDB table which I use to store unique numbers.
I start a transaction, read the value (eg. 1000471), store this value in another table and update the incremented value (100472). Now I want to avoid that somebody else even reads the value while my transaction is running.

If I would use plain MySQL I would do something like this:

Exceute("LOCK tbl1 READ");
Execute("SELECT ... from tbl1");
Execute("INSERT into tbl2");
Execute("UNLOCK TABLES");

but since I am using SubSonic as a DAL and the code should be independent from mysql, I have to use the TransactionScope.

My code:

        TransactionOptions TransOpt = new TransactionOptions();
        TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
        TransOpt.Timeout = new TimeSpan(0, 2, 0);

        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, TransOpt))
        {

             // Select Row from tbl1

             // Do something

             ts.Complete();
        }

According to the help of TransactionOptions

system.transactions.isolationlevel

The effect I want to reach could be implemented with IsolationLevel.ReadCommitted, but I can still read the row from outside the transaction (If I try to change it, I get a lock, so the transaction is working)

Does anybody has a suggestion? Is a read lock even possible with TransactionScope

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

段念尘 2024-07-30 09:42:30

如果有人感兴趣,这就是 TransactionOptions 如何影响 MySql:

可以说我有两种方法。

Method1 启动一个事务,从表中选择一行,增加值并更新表。

方法2是一样的,但是在select和update之间我添加了1000ms的睡眠。

现在假设我有以下代码:

    Private Sub Button1_Click(sender as Object, e as System.EventArgs) Handles Button1.Click

        Dim thread1 As New Threading.Thread(AddressOf Method1)
        Dim thread2 As New Threading.Thread(AddressOf Method2)

        thread2.Start() // I start thread 2 first, because this one sleeps
        thread1.Start()

    End Sub

如果没有事务,就会发生这种情况:
thread2 启动,读取值 5,然后休眠,
线程1启动,读取值5,将值更新为6,
thread2 也将值更新为 6。

效果:我有两次唯一的号码。

我想要什么:
thread2 启动,读取值 5,然后休眠,
thread1 启动,尝试读取值,但获得锁并休眠,
thread2将值更新为6,
thread1 继续,读取值 6,将值更新为 7

这就是如何使用 TransactionScope 启动事务:

        TransactionOptions Opts = new TransactionOptions();
        Opts.IsolationLevel = IsolationLevel.ReadUncommitted;

        // start Transaction
        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, Opts))
        {
            // Do your work and call complete
            ts.Complete();
        }

甚至可以管理分布式事务。 如果抛出异常,则永远不会调用 ts.Complete,并且作用域的 Dispose() 部分将回滚事务。

以下概述了不同的 IsolationLevels 如何影响事务:

  • IsolationLevel.Chaos
    抛出 NotSupportedException - 不支持混沌隔离级别

  • IsolationLevel.ReadCommited
    事务不会互相干扰(两次相同的读取,不好)

  • IsolationLevel.ReadUncomfilled
    事务不会相互干扰(两次相同的读取,不好)

  • IsolationLevel.RepeatableRead
    事务不会互相干扰(两次相同的读取,不好)

  • IsolationLevel.Serialized
    抛出 MySqlException - 尝试获取锁时发现死锁; 尝试在更新期间重新启动事务

  • IsolationLevel.Snapshot
    抛出 MySqlException - SQL 语法有错误; 检查与您的 MySQL 服务器版本相对应的手册,了解在 Connection.Open() 期间第 1 行 '' 附近使用的正确语法

  • IsolationLevel.Unspecified
    抛出 MySqlException - 尝试获取锁时发现死锁; 尝试在更新期间重新启动事务

  • TransactionOptions 未设置
    抛出 MySqlException - 尝试获取锁时发现死锁; 尝试在更新期间重新启动事务

If anyone is interested, this is how TransactionOptions affect MySql:

Lets say I have two methods.

Method1 starts a transaction, selects a row from my table, increments the value and updates the table.

Method2 is the same, but between select and update I added a sleep of 1000ms.

Now imagine I have the following code:

    Private Sub Button1_Click(sender as Object, e as System.EventArgs) Handles Button1.Click

        Dim thread1 As New Threading.Thread(AddressOf Method1)
        Dim thread2 As New Threading.Thread(AddressOf Method2)

        thread2.Start() // I start thread 2 first, because this one sleeps
        thread1.Start()

    End Sub

Without transactions this would happen:
thread2 starts, reads the value 5, then sleeps,
thread1 starts, reads the value 5, updates the value to 6,
thread2 updates the value to 6, too.

Effect: I have the unique number two times.

What I want:
thread2 starts, reads the value 5, then sleeps,
thread1 starts, trys to reads the value, but get a lock and sleeps,
thread2 updates the value to 6,
thread1 continues, reads the value 6, updates the value to 7

That's how to start transaction with the TransactionScope:

        TransactionOptions Opts = new TransactionOptions();
        Opts.IsolationLevel = IsolationLevel.ReadUncommitted;

        // start Transaction
        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, Opts))
        {
            // Do your work and call complete
            ts.Complete();
        }

That can even manage distributed transactions. If an Exception is thrown ts.Complete is never called and the Dispose() Part of the Scope rolls back the transaction.

Here's an overview how the different IsolationLevels affect the transaction:

  • IsolationLevel.Chaos
    Throws a NotSupportedException - Chaos isolation level is not supported

  • IsolationLevel.ReadCommited
    The transactions do not interfere each other (two identical reads, bad)

  • IsolationLevel.ReadUncommitted
    The transactions do not interfere each other (two identical reads, bad)

  • IsolationLevel.RepeatableRead
    The transactions do not interfere each other (two identical reads, bad)

  • IsolationLevel.Serializable
    Throws a MySqlException - Deadlock found when trying to get lock; try restarting transaction during Update

  • IsolationLevel.Snapshot
    Throws a MySqlException - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1 during Connection.Open()

  • IsolationLevel.Unspecified
    Throws a MySqlException - Deadlock found when trying to get lock; try restarting transaction during Update

  • TransactionOptions not set
    Throws a MySqlException - Deadlock found when trying to get lock; try restarting transaction during Update

许仙没带伞 2024-07-30 09:42:30

我的第一个猜测是使用SELECT FOR UPDATE,经过快速搜索后,我找到了一个关于锁定读取

如果我理解正确的话,这与使用的隔离级别无关。 请注意 - 隔离级别仅说明当前事务如何受到其他事务更改的影响。 它没有告诉其他交易可以做什么。 然而,更高的隔离级别需要更多限制性的锁。

My first guess was to use SELECT FOR UPDATE and after a quick search I found a page about locking reads in the MySQL 5 reference.

If I understand correctly this is independent from the isolation level used. And take care - the isolation level just tells how the current transaction is affected by changes in other transactions. It does not tell what other transactions can do. However, more restricting locks are required for higher isolation levels.

看海 2024-07-30 09:42:30

由于我没有找到一种方法来锁定一行以使用 InnoDB 和 TransactionScope 进行读取(我可能是错的),这应该可以工作:

如果我同时运行两个事务(没有 TransactionOptions)并且一个完成,另一个则无法完成,因为“死锁”异常。

根据 MySQL 文档,预计会出现死锁并重新启动事务。

如果您设置:

    TransactionOptions TransOpt = new TransactionOptions();
    TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;

对于您的事务,您不会遇到死锁异常,但是,就我而言,这会导致重复的唯一编号,这更糟糕。

As I didn't find a way to lock a row for reading with InnoDB and TransactionScope (I may be wrong) this should work:

If I run two Transactions simultaniously (without TransactionOptions) and one finishes, the other cannot compleate because of a "Deadlock" Exception.

Instead of avoiding this exception it seems to be best practice, according to the MySQL documentation, to expect a deadlock and restart the transaction.

if you set:

    TransactionOptions TransOpt = new TransactionOptions();
    TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;

for your transaction you don't get the deadlock exception, but, in my case, this would result in a dublicate unique number, which is worse.

太傻旳人生 2024-07-30 09:42:30

由于 SubSonic 似乎不支持 SELECT ... FOR UPDATE ,并且在我看来使用隔离级别将是一种误用 - 那么拥有一个 用户定义函数,它返回一个新的 id? 该函数将使用 SELECT ... FOR UPDATE 读取 tbl1 中的当前值,更新该行并返回该值。

在您插入新值的应用程序代码中,您只需使用:

insert into tbl2 (id, ....) values (next_id(), ...)

Since SubSonic doesn't seem to support SELECT ... FOR UPDATE and using isolation levels would be a misuse in my opinion - what about having a user defined function, which returns a new id? This function would read the current value from tbl1 with a SELECT ... FOR UPDATE, udpate the row and return the value.

In your application code where you insert a new value you would just use:

insert into tbl2 (id, ....) values (next_id(), ...)
只是在用心讲痛 2024-07-30 09:42:30

好吧,我决定使用“后门”,现在使用内联查询:

        // start transaction
        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            using (SharedDbConnectionScope scope = new SharedDbConnectionScope(DB._provider))
            {

                try
                {

                    Record r = new InlineQuery().ExecuteAsCollection<RecordCollection>(
                        String.Format("SELECT * FROM {0} WHERE {1} = ?param FOR UPDATE",
                                        Record.Schema.TableName,
                                        Record.Columns.Column1), "VALUE")[0];

                    // do something

                    r.Save();
                 }
             }
         }

我同时启动了 10 个线程,它按预期工作。 感谢鲁道夫森的“选择更新”提示。

Ok, I decided to use the "backdoor" and now use a inline query:

        // start transaction
        using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            using (SharedDbConnectionScope scope = new SharedDbConnectionScope(DB._provider))
            {

                try
                {

                    Record r = new InlineQuery().ExecuteAsCollection<RecordCollection>(
                        String.Format("SELECT * FROM {0} WHERE {1} = ?param FOR UPDATE",
                                        Record.Schema.TableName,
                                        Record.Columns.Column1), "VALUE")[0];

                    // do something

                    r.Save();
                 }
             }
         }

I started 10 threads simultaniously and it works as expected. Thx to rudolfson for the "SELECT FOR UPDATE" hint.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文