MySQL更新更改多个列是非原子的吗?

MySQL更新更改多个列是非原子的吗?,mysql,sql,django,sqlite,Mysql,Sql,Django,Sqlite,我在MySQL 5.5.22中使用Django时遇到以下问题 给定一个包含列id、级别和2x2矩阵(存储为a11、a12、a21、a22)的表,我有以下行: id a11 a12 a21 a22 level 324 3 2 5 3 2 给定queryset qs,我执行以下更新: qs.update( a11=(b12 * a21 - b11 * a22) * F('a11') + (b11 * a12 - b12 * a11) *

我在MySQL 5.5.22中使用Django时遇到以下问题

给定一个包含列id、级别和2x2矩阵(存储为a11、a12、a21、a22)的表,我有以下行:

id   a11   a12   a21   a22   level
324  3     2     5     3     2
给定queryset qs,我执行以下更新:

qs.update(
    a11=(b12 * a21 - b11 * a22) * F('a11') + (b11 * a12 - b12 * a11) * F('a21'),
    a12=(b12 * a21 - b11 * a22) * F('a12') + (b11 * a12 - b12 * a11) * F('a22'),
    a21=(b22 * a21 - b21 * a22) * F('a11') + (b21 * a12 - b22 * a11) * F('a21'),
    a22=(b22 * a21 - b21 * a22) * F('a12') + (b21 * a12 - b22 * a11) * F('a22'),
    level=(F('level') - 1)
    )
django为其生成以下查询(从db.connection.querys获得,为简洁起见,请删除where子句):

在那之后,我的那一排看起来像这样:

id   a11   a12   a21   a22   level
324  2     1     4     3     1
对于任何一行,
a12*a21-a11*a22=1
都应该是真的,根据这一点,该行应该是:

id   a11   a12   a21   a22   level
324  1     1     4     3     1
这就是我在SQLite上得到的结果,Django生成了相同的查询,我花了很多时间才发现MySQL做了一些不同的事情。从查询中可以看出,当更新相互依赖的多个行时,MySQL似乎不会将其视为单个原子操作,当更新列时,它们会影响依赖于它们的值。我通过Python提示符上的以下代码确认了这一点:

>>> a11, a12, a21, a22 = (3, 2, 5, 3)
>>> (2 * a11) + (-1 * a21),\
... (2 * a12) + (-1 * a22),\
... (3 * a11) + (-1 * a21),\
... (3 * a12) + (-1 * a22)
(1, 1, 4, 3)
如果一次更新一个列,则按照查询给出的相同顺序:

>>> a11, a12, a21, a22 = (3, 2, 5, 3)
>>> a21 = (3*a11) + (-1*a21)
>>> a22 = (3*a12) + (-1*a22)
>>> a11 = (2*a11) + (-1*a21)
>>> a12 = (2*a12) + (-1*a22)
>>> (a11, a12, a21, a22)
(2, 1, 4, 3)
这真是可怕的行为,因为这是一个跨平台使用的库。我的问题是:

  • MySQL和SQLite哪一个做错了?这算是一个bug吗
  • 我能从其他主要数据库(Oracle、PostgreSQL和SQLServer)中得到什么
  • 如何使用Django ORM(无原始查询)来规范这种行为 编辑

    问题很清楚,但我仍在寻找解决办法。对于此特定应用程序,拉取所有值并将其推回是不可接受的解决方案。

    如中所述:

    下面语句中的第二个赋值将
    col2
    设置为当前(更新的)
    col1
    值,而不是原始的
    col1
    值。结果是
    col1
    col2
    具有相同的值。此行为不同于标准SQL

    UPDATE t1 SET col1 = col1 + 1, col2 = col1; 在电视上看

    我唯一的另一个想法(在Django中肯定可以实现)是将更新拆分为单独的部分,根据在早期部分中更新的字段的新(而不是旧)值定义在后续部分中更新的字段:

    UPDATE storage
    SET    a21   = (3 * a11) + (-1 * a21),
           a22   = (3 * a12) + (-1 * a22),
           level = level - -1;
    
    UPDATE storage
    SET    a11   = (2 * a11) + (-1 * (3*a11 - a21)),
           a12   = (2 * a12) + (-1 * (3*a12 - a22));
    

    为了防止并发问题,您应该在事务中执行这两个更新(如果RDBMS支持)。

    PostgreSQL、Oracle和SQL Server都将其视为一个原子操作:

    MySQL是唯一一个在更新后两列都包含相同值的RBDM


    至于如何解决这个问题,我将从数据库中提取值,在应用程序中进行计算(而不是update语句),然后用计算出的值更新数据库。这样,您可以保证以一致的方式执行计算。

    这是一个有趣的问题。我在上面玩过,似乎MySQL是唯一一个这样做的。Related/duplicate:谢谢。SQL Fiddle对我来说是新的,非常有用。不幸的是,从数据库中提取所有数据并将其推回会破坏此库的全部功能。如果我愿意的话,有更好的方法去做。如果没有其他方法,很好,但我希望有更好的解决方案,即使有点黑客。谢谢参考。这就清楚了。我希望可能有一个改变这种行为的环境。好的解决方案!例如,SQL Server与插入的
    和删除的
    伪表的工作原理相同。
    
    UPDATE storage AS old
      JOIN storage AS new USING (id)
    SET
      new.a21   = (3 * old.a11) + (-1 * old.a21),
      new.a22   = (3 * old.a12) + (-1 * old.a22),
      new.level = old.level - -1,
      new.a11   = (2 * old.a11) + (-1 * old.a21),
      new.a12   = (2 * old.a12) + (-1 * old.a22);
    
    UPDATE storage
    SET    a21   = (3 * a11) + (-1 * a21),
           a22   = (3 * a12) + (-1 * a22),
           level = level - -1;
    
    UPDATE storage
    SET    a11   = (2 * a11) + (-1 * (3*a11 - a21)),
           a12   = (2 * a12) + (-1 * (3*a12 - a22));
    
    CREATE TABLE Swap (
      a CHAR(1),
      b CHAR(1)
    );
    
    INSERT INTO Swap (a, b) VALUES ('a', 'b');
    
    UPDATE Swap SET a = b, b = a;
    
    SELECT * FROM Swap;