1

我有以下情况:

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

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

执行(“锁定 tbl1 读取”);
执行(“从 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 甚至可以实现读锁吗

4

5 回答 5

2

如果有人感兴趣,这就是 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 - 尝试获取锁时发现死锁;尝试在更新期间重新启动事务

于 2009-06-03T09:05:09.467 回答
1

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.

于 2009-06-02T11:20:15.533 回答
1

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

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

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

如果你设置:

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

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

于 2009-06-02T13:27:21.907 回答
0

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

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

insert into tbl2 (id, ....) values (next_id(), ...)
于 2009-06-04T06:51:07.367 回答
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”提示。

于 2009-06-04T07:27:56.747 回答