使用TransactionScope与MySQL和读锁

时间:2009-06-02 10:13:49

标签: c# mysql subsonic transactions transactionscope

我有以下情况:

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

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

Exceute(“LOCK tbl1 READ”);
执行(“SELECT ... from tbl1”);
执行(“INSERT into tbl2”);
执行(“UNLOCK TABLES”);

但由于我使用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.ReadCommitted实现,但我仍然可以从事务外部读取行(如果我尝试更改它,我会获得锁定,因此事务正在运行)

有人有建议吗?使用TransactionScope

即可实现读锁定

5 个答案:

答案 0 :(得分:2)

如果有人有兴趣,这就是TransactionOptions影响MySql的方式:

假设我有两种方法。

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

Method2是相同的,但在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,然后睡觉,
thread1启动,读取值5,将值更新为6,
thread2也将值更新为6。

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

我想要的是什么:
thread2启动,读取值5,然后睡觉,
thread1启动,trys读取值,但获取锁定并休眠,
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();
        }

甚至可以管理分布式事务。如果抛出异常,则永远不会调用完成,并且Scope的Dispose()部分回滚事务。

以下是不同IsolationLevels如何影响交易的概述:

  • IsolationLevel.Chaos
    引发NotSupportedException - 不支持混沌隔离级别

  • IsolationLevel.ReadCommited
    交易不会互相干扰(两个相同的读数,不好)

  • IsolationLevel.ReadUncommitted
    交易不会互相干扰(两个相同的读数,不好)

  • IsolationLevel.RepeatableRead
    交易不会互相干扰(两个相同的读数,不好)

  • IsolationLevel.Serializable
    抛出MySqlException - 尝试获取锁定时发现死锁;尝试在更新期间重新启动交易

  • IsolationLevel.Snapshot
    抛出MySqlException - 您的SQL语法出错;检查与MySQL服务器版本对应的手册,以便在Connection.Open()期间在第1行附近使用正确的语法。(
  • IsolationLevel.Unspecified
    抛出MySqlException - 尝试获取锁定时发现死锁;尝试在更新期间重新启动交易

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

答案 1 :(得分:1)

我的第一个猜测是使用SELECT FOR UPDATE并在快速搜索后在MySQL 5参考中找到了一个关于locking reads的页面。

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

答案 2 :(得分:1)

由于我没有找到一种方法来锁定行以便使用InnoDB和TransactionScope进行阅读(我可能错了)这应该可行:

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

根据MySQL documentation,似乎最好的做法是避免出现这种异常,而是期望出现死锁并重启事务。

如果你设置:

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

对于你的事务你没有得到死锁异常,但是,在我的情况下,这会导致一个dublicate唯一的数字,这更糟。

答案 3 :(得分:0)

由于SubSonic似乎不支持SELECT ... FOR UPDATE,并且在我看来使用隔离级别会被滥用 - 如果有user defined function,它会返回一个新ID?此函数将使用SELECT ... FOR UPDATE从tbl1读取当前值,并对该行进行udpate并返回该值。

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

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

答案 4 :(得分:0)

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

        // 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个线程,它按预期工作。请向rudolfson索取“SELECT FOR UPDATE”提示。