8

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 = 1True であると想定されており、それによると、行は次のようになっているはずです。

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 (生のクエリなし) で何ができますか?

編集

問題は明らかですが、私はまだ解決策を探しています。すべての値を引き出して戻すことは、この特定のアプリケーションでは受け入れられる解決策ではありません。

4

2 に答える 2

12

PostgreSQL、Oracle、およびSQL Serverはすべて、これを不可分操作として扱います。 次のSQLFiddleを参照し、サーバーを切り替えて次の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でした。

これをどのように解決するかについては、代わりにデータベースから値を取得し、(updateステートメントではなく)アプリケーション内で計算を行ってから、計算された値でデータベースを更新します。このようにして、計算が一貫した方法で実行されることを保証できます。

于 2012-05-21T21:43:32.317 に答える
10

MySQLマニュアルに記載されているように:

次のステートメントの2番目の割り当ては、元の値ではなくcol2、現在の(更新された)col1値に設定されcol1ます。結果はそれcol1col2あり、同じ値を持ちます。この動作は、標準のSQLとは異なります。

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

したがって、あなたの場合、a21式を評価するときに使用される`a11` = (2 * `storage`.`a11`) + (-1 * `storage`.`a21`)値は、元の値5ではなく、新しく更新された値4です。マニュアルに記載されているように、この動作は標準SQLとは異なります

代わりに、複数テーブル構文で自己結合を使用することもできますがUPDATE、DjangoORMを使用してこのようなものを実装できるかどうかはわかりません。

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

同時実行の問題を防ぐには、トランザクション内でこれら2つの更新を実行する必要があります(RDBMSでサポートされている場合)。

于 2012-05-21T21:42:44.923 に答える