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

标签 mysql sql django sqlite

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

给定一个包含列 id、level 和存储为 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(无原始查询)做些什么来规范这种行为?

编辑

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

最佳答案

PostgreSQL、Oracle 和 SQL Server 都将此视为原子操作。 See the following SQL Fiddle, and switch the server to see the behavior of the following SQL :

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;

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

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

关于MySQL更新更改多列是非原子的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10692884/

相关文章:

django - 在 Nginx 配置中设置自定义 header 并将其传递给 gunicorn

mysql - 在 MySQL 中存储 "spreadsheet data"的最佳方式是什么

sql - 带有子查询的 Hive 更新

mysql - 如何比较同一个表的行并将另一个表的权重应用于每列

SQL Server : How to use Cursor to delete records

python - Django Celery 缓存锁不起作用?

mysql - 将数据存储在 JSON 而不是 mySQL 中

php - 如何计算foreach中相同键的存在次数php

mysql - 重叠日期的 SQL 触发器

python - 为什么 django Unittest 在比较 numpy.float64 的两个实例时会抛出断言错误?