2012-05-21 17 views
8

Występuje następujący problem przy użyciu Django z MySQL 5.5.22.Aktualizacja MySQL zmieniająca wiele kolumn jest nieatomowa?

Biorąc pod uwagę tabelę z kolumnami id, poziomu i matrycy 2x2 przechowywane jako A11, A12 A21, A22 mam ten wiersz:

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

otrzymał QS queryset, mam następującą aktualizację:

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

dla których Django generuje następujące zapytanie (musi je z db.connection.queries, usunąć, gdy klauzula dla uproszczenia)

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

A mój wiersz wygląda to potem:

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

Dla każdego rzędu, a12*a21 - a11*a22 = 1 ma być prawda, i zgodnie z tym, rząd miał być:

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

Jest co dostaję na SQLite, z Django generującym to samo zapytanie i zajęło mi dużo czasu, aby zorientować się, że MySQL robi coś innego. Z kwerendy wydaje się, że podczas aktualizacji interdepent wielu wierszy, MySQL nie traktuje go jako pojedynczej operacji atomowej, a ponieważ kolumny są aktualizowane, wpływają na wartości zależne od nich. I potwierdził to wydaje się być to, co dzieje się za pomocą następującego kodu w wierszu 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) 

Jeśli kolumny są aktualizowane po jednym na raz, w tej samej kolejności podanej w zapytaniu:

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

ten jest naprawdę przerażającym zachowaniem, ponieważ jest to biblioteka przeznaczona do użycia na różnych platformach. Moje pytania to:

  1. Który z nich robi źle, MySQL lub SQLite? Czy można to uznać za błąd?
  2. Czego mogę oczekiwać od innych głównych baz danych (Oracle, PostgreSQL i SQLServer)?
  3. Co mogę zrobić z ORM Django (bez zapytań nieprzetworzonych), aby znormalizować to zachowanie?

edit

Problem jest jasny, ale ciągle szukam rozwiązania. Wyciąganie wszystkich wartości i odpychanie ich nie jest rozwiązaniem dopuszczalnym dla tej konkretnej aplikacji.

+1

To interesujące pytanie. Grałem z nią na [sqlfiddle] (http://sqlfiddle.com/#!2/7f14b/2) i wygląda na to, że MySQL jest jedynym, który zachowuje się w ten sposób. – Chad

+0

Powiązane/duplikaty: http://stackoverflow.com/questions/2203202/sql-update-order-of-valuation – pilcrow

+0

Wyświetl moją zaktualizowaną odpowiedź poniżej. – eggyal

Odpowiedz

10

Jak stwierdzono w MySQL manual:

Drugie zadanie w następującym stwierdzeniem ustawia col2 do bieżącego (Aktualizacja) col1 wartości, a nie pierwotnej wartości col1. W rezultacie wartości col1 i col2 mają tę samą wartość. To zachowanie różni się od standardowego SQL.

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

Dlatego w przypadku, wartość używana do a21 podczas oceny ekspresji `a11` = (2 * `storage`.`a11`) + (-1 * `storage`.`a21`) jest nowa, zaktualizowana wartość od 4 raczej niż oryginalna wartość 5. W instrukcji mówi, ten zachowanie różni się od standardowego SQL.

Można zamiast użyć samosprzężenie ze składnią wielokrotnego tabeli UPDATE, jednak nie wiem, czy coś w tym może być realizowane za pomocą 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); 

Zobacz na sqlfiddle.

Moja jedyna myśl (która z pewnością powinna zostać wdrożona w Django) polega na podzieleniu aktualizacji na oddzielne części, definiując pola zaktualizowane w późniejszych częściach w odniesieniu do nowych (zamiast starych) wartości tych pól, które mają zostały zaktualizowane we wcześniejszych częściach:

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

aby uniknąć problemów współbieżności, powinieneś wykonać te dwie aktualizacje w obrębie transakcji (jeśli jest obsługiwane przez RDBMS).

+0

Dzięki za referencję. To wyjaśnia. Miałem nadzieję, że może jest miejsce, w którym można zmienić to zachowanie. –

+0

@PedroWerneck: czy ta zaktualizowana odpowiedź w ogóle nie pomaga? – eggyal

+0

Dobrze widziane rozwiązanie! Na przykład SQL Server działa tak samo z pseudo-tabelami 'insert' i' deleted'. –

12

PostgreSQL, Oracle i SQL Server traktują to jako operację atomową. 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 był tylko RBDMS który implementuje ten z obu kolumn zawierających tę samą wartość po aktualizacji.

Jeśli chodzi o sposób rozwiązania tego problemu, zamiast tego pobierałbym wartości z bazy danych, wykonuję obliczenia wewnątrz aplikacji (zamiast instrukcji aktualizacji), a następnie aktualizuję bazę danych o wyliczone wartości. W ten sposób możesz zagwarantować, że obliczenia będą wykonywane w spójny sposób.

+1

Dzięki. SQL Fiddle jest dla mnie nowy i bardzo przydatny. Niestety, pobieranie wszystkich danych z bazy danych i przesuwanie jej z powrotem pokonuje cały cel tej biblioteki. Są lepsze sposoby robienia tego, co robią, jeśli chcę to zrobić. Jeśli nie ma innej drogi, dobrze, ale mam nadzieję, że jest lepsze rozwiązanie, nawet jeśli trochę hackish. –