Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Transakcja MySQL:SELECT + INSERT

Potrzebujesz blokowania . Transakcje rzeczywiście „nie są ściśle potrzebne”.

Możesz wybierać między „pesymistycznym” a „optymistycznym”. Decyzja o tym, która z tych dwóch możliwości należy do Ciebie i należy ją ocenić, biorąc pod uwagę:

  • poziom Twojej współbieżności
  • czas trwania operacji „niezbędnych atomowych” na bazie danych
  • złożoność całej operacji

Polecam przeczytanie tych dwóch, aby zbudować pojęcie o zaangażowanych rzeczach:

Przykład do lepszego wyjaśnienia

To może nie jest takie eleganckie, ale jest tylko przykładem, który pokazuje, jak można zrobić wszystko bez transakcji (a nawet bez ograniczeń UNIQUE). Wystarczy użyć poniższej połączonej instrukcji INSERT + SELECT i po jej wykonaniu aby sprawdzić liczbę wierszy, których dotyczy problem. Jeśli liczba wierszy, których dotyczy problem, wynosi 1, oznacza to, że udało się w inny sposób (jeśli jest to 0), doszło do kolizji i druga strona wygrała.

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= @endTime AND `end` >= @startTime
    AND `devices_id` = @deviceId)
GROUP BY (1);

To jest przykład blokowania optymistycznego uzyskanego bez transakcji i za pomocą jednej operacji SQL.

Jak napisano, problem polega na tym, że w slot musi być już co najmniej jeden wiersz tabeli, aby działał (w przeciwnym razie klauzula SELECT zawsze zwróci pusty zestaw rekordów i w takim przypadku nic nie zostanie wstawione, jeśli nie ma kolizji. Istnieją dwie możliwości, aby faktycznie działał:

  • wstaw jeden fikcyjny wiersz w tabeli, może z datą z przeszłości
  • przepisz tak, aby główna klauzula FROM odnosiła się do dowolnej tabeli, która ma co najmniej jeden wiersz lub lepiej, stwórz jedną małą tabelę (może nazwać dummy ) z tylko jedną kolumną i tylko jednym rekordem i przepisz w następujący sposób (zauważ, że nie ma już potrzeby stosowania klauzuli GROUP BY)

    INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
    SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
    FROM `dummy`
    WHERE NOT EXISTS (
        SELECT `id` FROM `slot`
        WHERE `start` <= @endTime AND `end` >= @startTime
        AND `devices_id` = @deviceId);
    

Oto seria instrukcji, które po prostu skopiuj/wklej, pokazują pomysł w działaniu. Założyłem, że kodujesz datę/czas w polach int jako liczbę z połączonymi cyframi daty i godziny.

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
VALUES (1008141200, 1008141210, 11, 2, 'Dummy Record', 14)

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141206, 1408141210, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= 1408141210 AND `end` >= 1408141206
    AND `devices_id` = 14)
GROUP BY (1);

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141208, 1408141214, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= 1408141214 AND `end` >= 1408141208
    AND `devices_id` = 14)
GROUP BY (1);

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141216, 1408141220, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= 1408141220 AND `end` >= 1408141216
    AND `devices_id` = 14)
GROUP BY (1);

SELECT * FROM `slot`;

Jest to oczywiście skrajny przykład blokowania optymistycznego, ale w końcu jest bardzo wydajny, ponieważ wszystko odbywa się za pomocą tylko jednej instrukcji SQL i przy niewielkiej interakcji (wymiany danych) między serwerem bazy danych a kodem php. Co więcej, praktycznie nie ma „prawdziwego” blokowania.

...lub z blokowaniem pesymistycznym

Ten sam kod może stać się dobrą implementacją Pessimistc Locking, która jest otoczona wyraźnymi instrukcjami blokowania/odblokowywania tabeli:

LOCK TABLE slot WRITE, dummy READ;

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
FROM `dummy`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= @endTime AND `end` >= @startTime
    AND `devices_id` = @deviceId);

UNLOCK TABLES;

Oczywiście w tym przypadku (blokada pesymistyczna) SELECT i INSERT mogą być rozdzielone i jakiś kod php wykonywany pomiędzy. Jednak ten kod pozostaje bardzo szybki do wykonania (brak wymiany danych z php, brak pośredniego kodu php), więc czas trwania blokady pesymistycznej jest najkrótszy z możliwych. Utrzymywanie jak najkrótszej blokady pesymistycznej jest kluczowym punktem, aby uniknąć spowolnienia aplikacji.

W każdym razie musisz sprawdzić liczbę zwracanych rekordów, których dotyczy problem, aby wiedzieć, czy się udało, ponieważ kod jest praktycznie taki sam, a więc otrzymujesz informacje o sukcesie/porażce w ten sam sposób.

Tutaj http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html mówią, że "MySQL nie zezwala na jednoczesne wstawianie instrukcji INSERT... SELECT" więc nie powinna być potrzebna blokada pesymistyczna, ale i tak może to być dobra opcja, jeśli uważasz, że zmieni się to w przyszłych wersjach MySQL.

Jestem „optymistą” że to się nie zmieni;-)




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Czy istnieje skrypt aktualizacji Spring Batch 3 dla MySQL?

  2. Procedura składowana MySQL a funkcja, której powinienem użyć, kiedy?

  3. Pobierz wartości z ostatnich 6 miesięcy w mysql

  4. Dodatkowe pola z SQL MIN() i GROUP BY

  5. MySQL miesięczna Wyprzedaż z ostatnich 12 miesięcy, w tym miesiące bez wyprzedaży