sql >> Databasteknik >  >> RDS >> Mysql

MySQL-transaktion:SELECT + INSERT

Vad du behöver är låsning . Transaktioner är verkligen "inte strikt nödvändiga".

Du kan välja mellan "pessimistisk låsning" och "optimistisk låsning". Beslutet om vilken av dessa två möjligheter är upp till dig och måste utvärderas med hänsyn till:

  • nivån av samtidighet som du har
  • varaktigheten av de måste-att-vara-atomära operationerna på databasen
  • komplexiteten i hela operationen

Jag rekommenderar att du läser dessa två för att bygga upp en uppfattning om de inblandade sakerna:

Ett exempel för att förklara bättre

Detta kanske inte är så elegant utan är bara ett exempel som visar hur det är möjligt att göra allt utan transaktion (och även utan UNIKA begränsningar). för att kontrollera antalet berörda rader. Om antalet berörda rader är 1 så har det lyckats på annat sätt (om det är 0) har det skett en kollision och den andra parten har vunnit.

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

Detta är ett exempel på Optimistisk låsning som erhålls utan transaktioner och med en enda SQL-operation.

Som det står skrivet har det problemet att det måste finnas minst en rad redan i slot tabell för att det ska fungera (annars kommer SELECT-satsen alltid att returnera en tom postuppsättning och i så fall infogas ingenting evei om det inte finns några kollisioner. Det finns två möjligheter att få det att faktiskt fungera:

  • infoga en dummy-rad i tabellen kanske med datumet i det förflutna
  • skriv om så att huvuddelen FROM refererar till vilken tabell som helst som har minst en rad eller bättre skapa en liten tabell (kanske heter dummy ) med endast en kolumn och endast en post i den och skriv om enligt följande (observera att det inte längre behövs för GROUP BY-satsen)

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

Här följer en serie instruktioner som om du bara kopierar/klistrar visar idén i praktiken. Jag har antagit att du kodar datum/tider i int-fält som ett tal med siffrorna för datum och tid sammanlänkade.

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

Detta är helt klart ett extremt exempel på Optimistic Locking men är mycket effektivt i slutändan eftersom allt görs med endast en SQL-instruktion och med låg interaktion (datautbyte) mellan databasservern och php-koden. Dessutom finns det praktiskt taget ingen "riktig" låsning.

...eller med pessimistisk låsning

Samma kod kan bli en bra pessimistisk låsningsimplementering som bara omger med explicita instruktioner för tabelllåsning/upplåsning:

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;

Naturligtvis i det här fallet (pessimistisk låsning) kan SELECT och INSERT separeras och någon php-kod exekveras däremellan. Den här koden förblir dock väldigt snabb att exekvera (inget datautbyte med php, ingen mellanliggande php-kod) och därför är varaktigheten av Pessimistic Lock den kortaste möjliga. Att hålla Pessimistic Lock så kort som möjligt är en nyckelpunkt för att undvika att applikationen saktar ner.

Hur som helst måste du kontrollera antalet påverkade poster som returnerar värde för att veta om det lyckades eftersom koden är praktiskt taget densamma och så att du får information om framgång/misslyckande på samma sätt.

Här http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html de säger att "MySQL tillåter inte samtidiga infogningar för INSERT ... SELECT-satser" så det borde inte behövas Pessimistic Lock men hur som helst kan detta vara ett bra alternativ om du tror att detta kommer att förändras i framtida versioner av MySQL.

Jag är "optimistisk" att detta inte kommer att ändras;-)




  1. MySQL:ERROR 1215 (HY000):Kan inte lägga till främmande nyckel-begränsning

  2. Hur får man en mysql-fråga att ständigt uppdatera i PHP?

  3. Automatisk ökning på partiell primärnyckel med Entity Framework Core

  4. En översikt över strömmande replikering för TimescaleDB