我在使用Django和MySQL 5.5.22时遇到以下问题。
给定一个包含列id,level和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.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正在做一些不同的事情。从查询中看,似乎在更新interdepent多行时,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)
这是一种非常可怕的行为,因为这是一个旨在跨平台使用的库。我的问题是:
修改
问题很明显,但我仍在寻找解决方案。拉出所有值并将其推回对于此特定应用程序来说不是可接受的解决方案。
答案 0 :(得分:12)
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,两个列在更新后都包含相同的值。
至于如何解决这个问题,我会从数据库中提取值,在应用程序内部进行计算(而不是更新语句),然后使用计算值更新数据库。这样您就可以保证计算将以一致的方式执行。
答案 1 :(得分:10)
如MySQL manual中所述:
以下语句中的第二个赋值将
col2
设置为当前(更新的)col1
值,而不是原始col1
值。结果是col1
和col2
具有相同的值。此行为与标准SQL不同。UPDATE 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支持)。