MySQL中的乐观锁定

时间:2013-07-02 16:52:17

标签: mysql sql locking database optimistic

我在MySQL中找不到有关乐观锁定的任何细节。 我读到,启动事务会保持同步的两个实体的更新,但是它不会阻止两个用户同时更新数据而导致冲突。

显然乐观锁定会解决这个问题吗?这是如何在MySQL中应用的。这有SQL语法/关键字吗?或者MySQL有默认行为吗?

谢谢你们。

1 个答案:

答案 0 :(得分:95)

重点是乐观锁定不是数据库功能,不适用于MySQL,也不适用于其他人:乐观锁定是使用带有标准指令的数据库应用的一种做法。

让我们有一个非常简单的例子,并说你想在多个用户/客户端可以同时运行的代码中执行此操作:

  1. 从具有一个ID字段(iD)和两个数据字段(val1,val2)的行中选择数据
  2. 可选择使用数据进行计算
  3. 更新该行的数据
  4. NO LOCKING方式为:

    注意:所有代码{在curl brakets之间}都应该在应用程序代码中,而不是(必要地)在SQL端

    - SELECT iD, val1, val2
           FROM theTable
           WHERE iD = @theId;
     - {code that calculates new values}
     - UPDATE theTable
           SET val1 = @newVal1,
               val2 = @newVal2
           WHERE iD = @theId;
     - {go on with your other code}
    

    OPTIMISTIC LOCKING方式是:

    - SELECT iD, val1, val2
           FROM theTable
           WHERE iD = @theId;
     - {code that calculates new values}
     - UPDATE theTable
           SET val1 = @newVal1,
               val2 = @newVal2
           WHERE iD = @theId
               AND val1 = @oldVal1
               AND val2 = @oldVal2;
     - {if AffectedRows == 1 }
     -     {go on with your other code}
     - {else}
     -     {decide what to do since it has gone bad... in your code}
     - {endif}
    

    请注意,关键点在UPDATE指令的结构中,并检查后续受影响的行数。正是这两件事让你的代码意识到有人已经在执行SELECT和UPDATE之间修改了数据。 请注意,所有事情都没有交易!这是可能的(没有交易)只是因为这是一个非常简单的例子,但这也说明乐观锁定的关键点不在交易本身。

    那么TRANSACTIONS呢?

     - SELECT iD, val1, val2
           FROM theTable
           WHERE iD = @theId;
     - {code that calculates new values}
     - BEGIN TRANSACTION;
     - UPDATE anotherTable
           SET col1 = @newCol1,
               col2 = @newCol2
           WHERE iD = @theId;
     - UPDATE theTable
           SET val1 = @newVal1,
               val2 = @newVal2
           WHERE iD = @theId
               AND val1 = @oldVal1
               AND val2 = @oldVal2;
     - {if AffectedRows == 1 }
     -     COMMIT TRANSACTION;
     -     {go on with your other code}
     - {else}
     -     ROLLBACK TRANSACTION;
     -     {decide what to do since it has gone bad... in your code}
     - {endif}
    

    最后一个示例显示,如果您在某个时刻检查了冲突,并且在已经修改了其他表/行时发现了冲突.. ..然后使用事务,您可以回滚所有更改。从一开始就做了。 显然,取决于您(知道您的应用程序正在做什么)来决定每次可能的冲突回滚的操作量有多大,基于此决定放置事务边界的位置以及检查与特殊冲突的位置的位置UPDATE + AffectedRows检查。

    在这种情况下,对于事务,我们将执行UPDATE的时刻与提交时的时刻分开。那么当“其他进程”在这段时间内执行更新时会发生什么? 要知道究竟发生了什么,需要深入了解隔离级别的细节(以及如何在每个引擎上进行管理)。 以Micosoft SQL Server为例,使用READ_COMMITTED更新了行 直到COMMIT被锁定所以“其他进程”不能对那些行做任何事情(保持等待),既不是SELECT(事实上它只能READ_COMMITTED)。 因此,由于“其他进程”活动被推迟,因此UPDATE将失败。

    VERSIONING OPTIMISTIC LOCKING选项:

     - SELECT iD, val1, val2, version
           FROM theTable
           WHERE iD = @theId;
     - {code that calculates new values}
     - UPDATE theTable
           SET val1 = @newVal1,
               val2 = @newVal2,
               version = version + 1
           WHERE iD = @theId
               AND version = @oldversion;
     - {if AffectedRows == 1 }
     -     {go on with your other code}
     - {else}
     -     {decide what to do since it has gone bad... in your code}
     - {endif}
    

    这里显示的是,不是检查所有字段的值是否仍然相同,我们可以使用专用字段(每次我们进行更新时修改)以查看是否有人比我们更快并更改了我们的SELECT和UPDATE之间的行。 这里没有事务是由于第一个例子中的简单性而与版本列的使用无关。 此列的使用再次取决于应用程序代码中的实现,而不是数据库引擎功能。

    除此之外还有其他一些观点,我认为这个答案太长了(已经太长了),所以我现在只提到一些参考文献:

    • 事务隔离级别(here for MySQL)关于对SELECT的事务影响。
    • 对于主键未自动生成(或唯一约束)的表上的INSERT,它将自动失败,无需特别检查两个进程是否尝试插入必须唯一的相同值。
    • 如果您没有id列(主键或唯一约束),单个SELECT + UPDATE也需要事务,因为您可能会遇到意外,而不是在其他人进行修改之后,有多于预期的行匹配UPDATE的WHERE子句的条件

    如何检查实践并获得自信

    由于隔离级别值和实现可能不同,因此最佳建议(通常在此站点中)是对使用的平台/环境执行测试。

    这可能看起来很难但实际上它可以很容易地从任何DB开发环境中使用两个单独的窗口完成,然后从每个窗口开始一个事务然后逐个执行命令。

    在某些时候,您将看到命令执行无限期地继续。 然后当在另一个窗口上时,它被称为COMMIT或ROLLBACK,它完成了执行。

    以下是一些非常基本的命令,可以像刚刚描述的那样进行测试。

    使用这些来创建表和一个有用的行:

    CREATE TABLE theTable(
        iD int NOT NULL,
        val1 int NOT NULL,
        val2 int NOT NULL
    )
    INSERT INTO theTable (iD, val1, val2) VALUES (1, 2 ,3);
    

    然后在两个不同的窗口上逐步进行以下操作:

    BEGIN TRAN
    
    SELECT val1, val2 FROM theTable WHERE iD = 1;
    
    UPDATE theTable
      SET val1=11
      WHERE iD = 1 AND val1 = 2 AND val2 = 3;
    
    COMMIT TRAN
    

    然后以您可能想到的任何顺序更改命令的顺序和执行顺序。