2012-05-21 32 views
8

我使用Django與MySQL 5.5.22有以下問題。更新多列的MySQL更新是非原子的?

給出列編號,級別和存儲A11一個2x2矩陣,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應該是真實的,根據的是,該行應該是:

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(無原始查詢)對此行爲進行規範化?

編輯

的問題是明確的,但我仍然在尋找一個解決方案。提取所有值並將其推回對於此特定應用程序來說不是可接受的解決方案。

+1

這是一個有趣的問題。我在[sqlfiddle](http://sqlfiddle.com/#!2/7f14b/2)上玩過它,似乎MySQL是唯一這樣表現的人。 – Chad

+0

相關/重複:http://stackoverflow.com/questions/2203202/sql-update-order-of-evaluation – pilcrow

+0

看到我下面更新的答案。 – eggyal

回答

10

正如MySQL manual說:

下面的語句中的第二個分配集合col2當前(已更新)col1價值,而不是原來的col1值。結果是col1col2具有相同的值。此行爲不同於標準SQL。

UPDATE t1 SET col1 = col1 + 1, col2 = col1;

因此,在你的情況下,計算表達式`a11` = (2 * `storage`.`a11`) + (-1 * `storage`.`a21`)由4個新的,更新的,值而不是5的初始值作爲手冊說,此當被用於a21值行爲與標準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支持)。

+0

感謝您的參考。這很清楚。我希望也許有一個設置來改變這種行爲。 –

+0

@PedroWerneck:這個更新的答案根本沒有幫助嗎? – eggyal

+0

看得見的解決方案!例如,SQL Server的工作方式與「插入」和「刪除」僞表相同。 –

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

謝謝。 SQL小提琴對我來說是新的,非常有用。不幸的是,從數據庫中提取所有數據並將其推回來會破壞這個庫的全部目的。如果我願意這樣做,有更好的方法來做它的功能。如果沒有其他辦法,那很好,但我希望有一個更好的解決方案,即使有點駭人聽聞。 –