2012-05-21 12 views
8

MySQL 5.5.22でDjangoを使用して次の問題が発生しています。列番号、レベルおよびA11として記憶2x2の行列とテーブルを考える複数の列を変更するMySQLの更新は非アトミックですか?

、A12、A21、A22、私はこの行を持っている:

:クエリセット適量与えられる

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

、私は、次の更新を行います

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`) 
:Djangoは次のクエリを(db.connection.queriesからそれを得た、簡潔にするためにwhere句を削除)生成するために
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) 
    ) 

a12*a21 - a11*a22 = 1が真であると想定され、そしてそれに応じて、行をすることになった、任意の行については

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

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

これは

そして、私の行は、その後、このようになります。 Djangoが同じクエリを生成しているので、SQLiteには何が得られますか?そして、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) 

列は、クエリによって与えられたのと同じ順序で、一度に1つの更新された場合:

>>> 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 manualで述べたよう

+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

次の文の2番目の割り当ては、(更新)現在col1値ではなく、元のcol1値にcol2を設定します。その結果、col1col2の値は同じになります。この動作は標準SQLとは異なります。

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

マニュアルが言うようにそのため、あなたのケースでは、表現`a11` = (2 * `storage`.`a11`) + (-1 * `storage`.`a21`)を評価するとき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でサポートされている場合)は、トランザクション内でこれらの2回の更新を行うべきです。

+0

参考になりました。それはそれを明らかにする。私はおそらく、この動作を変更する設定があったと思っていた。 –

+0

@PedroWerneck:この更新された回答はまったく役に立ちませんか? – eggyal

+0

よく見られる解決策!例えば、SQL Serverは 'inserted'と' deleted'疑似テーブルと同じように動作します。おかげさまで –

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 Fiddleは私のために新しく、非常に便利です。残念なことに、dbからすべてのデータを取得して元に戻すと、このライブラリの目的はすべて失われます。私がそれをやりたいなら、それができることをするためのよりよい方法があります。それ以外の方法はありませんが、ちょっとハックしたとしても、より良い解決策があることを願っています。 –

関連する問題