【问题标题】:Using TransactionScope with MySQL and Read Lock将 TransactionScope 与 MySQL 和读锁一起使用
【发布时间】:2009-06-02 10:13:48
【问题描述】:

我有以下情况:

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

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

Exceute("LOCK tbl1 READ");
执行("SELECT ... from 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.ReadCommitted 来实现,但是我仍然可以从事务外部读取行(如果我尝试更改它,我会得到一个锁,所以事务正在工作)

有人有建议吗? TransactionScope 甚至可以实现读锁

【问题讨论】:

  • 请注意,将 TransactionScope 与远程 MySQL 服务器一起使用时,本地和远程计算机上都需要一个分布式事务协调器。
  • 这不是真的,需要 DTC a) 如果实现需要它 b) 总是如果您使用多个事务,例如using (TransactionScope ts = new TransactionScope()) { - 删除文件(在 vista 文件操作支持 TransactionScope 上) - 从服务器 1 上的表中选择 * - 插入到服务器 2 上的表中 - 从服务器 1 上的表中删除 }

标签: c# mysql subsonic transactions transactionscope


【解决方案1】:

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

假设我有两种方法。

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

Method2 是一样的,但是在 select 和 update 之间我加了一个 1000ms 的 sleep。

现在假设我有以下代码:

    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 启动,尝试读取值,但获得锁并休眠,
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 并且 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 - 尝试获取锁时发现死锁;尝试在更新期间重新启动事务

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 正如我所提到的,我使用 SubSonic 来实现持久性。

      我可以对我的 MySQL 数据库运行纯 SQL,但这会破坏 subsonic 的多数据库概念。 thx 的链接,但我仍然没有找到一个解决方案来锁定一行以使用 mysql 读取这也值得一读:dev.mysql.com/doc/refman/5.1/en/innodb-lock-modes.html 我发现,如果我没有设置 IsolationLevel,我会得到一个“尝试时发现死锁获得锁定;尝试重新启动事务” - MySqlException 这似乎是一件好事(见我的回答)
    • 我阅读了您提供的链接,在我看来,SELECT FOR UPDATE 是显式锁定一行的正确方法。我不知道 SubSonic(猜想它是一个 ORM 工具/持久性框架) - 如果它支持在读取实体时指定锁定模式,您是否检查过它的文档?我从其他 ORM 工具中知道这种可能性,例如休眠(Java)。如果我告诉 Hibernate 使用例如LockMode.UPGRADE,这将导致 SELECT FOR UPDATE。
    • 你是对的。 SELECT FOR UPDATE 是纯 SQL 中的正确语法。问题似乎是,使用 TransactionScope 方法,TransactionOption IsolationLevel 只能控制可以从当前事务中读取哪些数据(旧的、未提交的、新的)以及如果数据发生更改(如您在帖子中提到的)写入会发生什么) 但不能调用 UPDATE 锁。看看我发现了什么:stackoverflow.com/questions/190666/…
    【解决方案3】:

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

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

    根据 MySQL documentation,最好的做法似乎不是避免此异常,而是预期出现死锁并重新启动事务。

    如果你设置:

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

    对于您的交易,您不会遇到死锁异常,但在我的情况下,这会导致重复的唯一编号,这会更糟。

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

      • 将是一个可能的解决方案,但在我的情况下,数字不仅仅是一个数字,而是一个字符串值,我以编程方式与掩码字符串组合以确定新值(例如当前数字 200905123如果今天执行,掩码 YYYYMM### 将产生一个新数字 200906001),我不想从我的应用程序中提取该逻辑。
      【解决方案5】:

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

              // 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”提示。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多