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 = 1
True であると想定されており、それによると、行は次のようになっているはずです。
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)
これはクロスプラットフォームで使用することを意図したライブラリであるため、これは非常に恐ろしい動作です。私の質問は次のとおりです。
- MySQL と SQLite のどちらが間違っているのでしょうか? これはバグと見なすことができますか?
- 他の主要なデータベース (Oracle、PostgreSQL、および SQLServer) には何が期待できますか?
- この動作を正規化するために、Django ORM (生のクエリなし) で何ができますか?
編集
問題は明らかですが、私はまだ解決策を探しています。すべての値を引き出して戻すことは、この特定のアプリケーションでは受け入れられる解決策ではありません。