当前位置: 首页 > 面试题库 >

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

袁建木
2023-03-14
问题内容

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

给定一个具有列ID,级别和存储为a11,a12,a21,a22的2x2矩阵的表,我具有以下行:

id   a11   a12   a21   a22   level
324  3     2     5     3     2

给定一个查询集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.queries中获取该查询,为简洁起见,删除where子句):

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

然后,我的行如下所示:

id   a11   a12   a21   a22   level
324  2     1     4     3     1

对于任何一行,a12*a21 - a11*a22 = 1都应该为True,并且据此,该行应该为:

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)

这确实是一种可怕的行为,因为这是一个旨在跨平台使用的库。我的问题是:

  1. 哪一个做错了,MySQL或SQLite?可以将其视为错误吗?
  2. 我对其他主要数据库(Oracle,PostgreSQL和SQLServer)有什么期望?
  3. 我该如何使用Django ORM(无原始查询)来规范此行为?

编辑

问题很明显,但我仍在寻找解决方案。对于此特定应用程序,将所有值拉回并推回它们不是可接受的解决方案。


问题答案:

如MySQL手册所述:

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

更新t1 SET col1 = col1 + 1,col2 = col1;

因此,在您的情况下,用于a21评估表达式的值`a11 = (2 * storage.a11) + (-1 * storage.a21)`是新的,更新的值4,而不是原始值5。正如手册所述, 此行为不同于标准SQL

您可以改为使用带有多表UPDATE语法的自联接,但是我不知道是否可以使用Django ORM来实现类似的功能:

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);

在sqlfiddle上看到它。

我唯一的另一种想法(肯定应该在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支持)。



 类似资料:
  • 问题内容: 我想使用一个case语句更新表中的多个列,但是我找不到如何执行此操作的方法(甚至有可能)。我提出了以下无效的参考查询: 有什么方法可以通过有效的SQL达到预期的结果吗? 问题答案:

  • 问题内容: 我知道您可以一次插入多行,是否可以在MySQL中一次更新多行(如在一个查询中)? 编辑:例如,我有以下内容 我想将以下所有更新合并到一个查询中 问题答案: 是的,这是可能的-您可以在插入的键更新中使用INSERT…。 使用您的示例:

  • 问题内容: 更新“订单”列的最佳方法是什么? 假设我有一个ID列,其范围是0到9,而订购列的范围是0到9。 当前,它在数据库中的位置为:0 0、1、1、2、2等 我的HTML页面发布了新顺序:0 8、1 3、2 6等(这完全是随机的,由用户决定)。 进行更新的最佳方法是什么? 我可以遍历并运行每个更新。我还可以创建一个包含所有对的临时表,然后基于子查询进行更新。 但是我觉得我忘记了一些琐碎的事情,

  • 我需要根据一个子查询的结果更新一个表,该子查询带来了1个以上的值,但在下面的查询中,我得到错误返回“subquery returns mouth 1 row”。我想知道是否有可能做一个“循环”,以更新值与我下面的子查询中呈现的每个结果。 完成查询 返回2个或更多值的子查询。 结果 在理想的场景中,我的查询将执行第一个值,在第二个之后...第三个之后...而不重复前面的值。

  • 问题内容: 这种类型的东西之前已经被问过几次了,但并不是我要找的东西。我需要两行等于子查询的不同部分。 我目前正在使用: WHERE语句显然得到了简化……但是基本上它是相同的子查询,但是我不认为我应该运行两次? 我想做类似… 问题答案: 您只需将表加入子查询中即可进行一些计算,

  • 问题内容: 如何在MySQL中更新多个值? 这不起作用: 问题答案: 您需要在两个不同的值之间添加一个逗号。例如: