sql >> Databasteknik >  >> RDS >> Mysql

MySQL-uppdateringsfråga - Kommer "var"-villkoret att respekteras för tävlingskondition och radlåsning? (php, PDO, MySQL, InnoDB)

Var villkoret kommer att respekteras under en tävlingssituation, men du måste vara försiktig hur du kontrollerar för att se vem som vann loppet.

Tänk på följande demonstration av hur detta fungerar och varför du måste vara försiktig.

Först, ställ in några minimala tabeller.

CREATE TABLE table1 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY,
`locked` TINYINT UNSIGNED NOT NULL,
`updated_by_connection_id` TINYINT UNSIGNED DEFAULT NULL
) ENGINE = InnoDB;

CREATE TABLE table2 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY
) ENGINE = InnoDB;

INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);

id spelar rollen som id i din tabell, updated_by_connection_id fungerar som assignedPhone och locked som reservationCompleted .

Låt oss nu börja racetestet. Du bör ha två kommandorads-/terminalfönster öppna, anslutna till mysql och använda databasen där du har skapat dessa tabeller.

Anslutning 1

start transaction;

Anslutning 2

start transaction;

Anslutning 1

UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;

Anslutning 2

UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;

Anslutning 2 väntar nu

Anslutning 1

SELECT * FROM table1 WHERE id = 1;
commit;

Vid denna tidpunkt släpps anslutning 2 för att fortsätta och matar ut följande:

Anslutning 2

SELECT * FROM table1 WHERE id = 1;
commit;

Allt ser bra ut. Vi ser att ja, WHERE-klausulen respekterades i en rassituation.

Anledningen till att jag sa att du måste vara försiktig är dock att saker och ting inte alltid är så enkelt i en riktig applikation. Du KAN ha andra åtgärder på gång inom transaktionen, och det kan faktiskt ändra resultaten.

Låt oss återställa databasen med följande:

delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);

Och nu, överväg den här situationen, där en SELECT utförs före UPPDATERING.

Anslutning 1

start transaction;

SELECT * FROM table2;

Anslutning 2

start transaction;

SELECT * FROM table2;

Anslutning 1

UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;

Anslutning 2

UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;

Anslutning 2 väntar nu

Anslutning 1

SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;

Vid denna tidpunkt släpps anslutning 2 för att fortsätta och matar ut följande:

Okej, låt oss se vem som vann:

Anslutning 2

SELECT * FROM table1 WHERE id = 1;

Vänta, va? Varför är locked 0 och updated_by_connection_id NULL??

Det här är att vara försiktig jag nämnde. Boven beror faktiskt på att vi gjorde ett urval i början. För att få rätt resultat kan vi köra följande:

SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;

Genom att använda SELECT ... FÖR UPPDATERING kan vi få rätt resultat. Detta kan vara mycket förvirrande (som det var för mig, ursprungligen), eftersom en SELECT och en SELECT ... FÖR UPPDATERING ger två olika resultat.

Anledningen till att detta händer är på grund av standardisoleringsnivån READ-REPEATABLE . När det första VALET görs, direkt efter start transaction; , skapas en ögonblicksbild. Alla framtida icke-uppdateringsläsningar kommer att göras från den ögonblicksbilden.

Därför, om du bara naivt VÄLJER efter att du har gjort uppdateringen, kommer den att hämta informationen från den ursprungliga ögonblicksbilden, som är före raden har uppdaterats. Genom att göra ett VÄLJ ... FÖR UPPDATERING tvingar du den att få korrekt information.

Men återigen, i en verklig applikation kan detta vara ett problem. Säg till exempel att din förfrågan är insvept i en transaktion och efter att ha utfört uppdateringen vill du mata ut lite information. Samla in och mata ut den information som kan hanteras av separat, återanvändbar kod, som du INTE vill skräpa med FOR UPDATE-klausuler "för säkerhets skull." Det skulle leda till mycket frustration på grund av onödig låsning.

Istället vill du ta ett annat spår. Du har många alternativ här.

En är att se till att du genomför transaktionen efter att UPPDATERING har slutförts. I de flesta fall är detta förmodligen det bästa, enklaste valet.

Ett annat alternativ är att inte försöka använda SELECT för att fastställa resultatet. Istället kanske du kan läsa de berörda raderna och använda den (1 rad uppdaterad mot 0 raduppdatering) för att avgöra om UPPDATERINGEN var en framgång.

Ett annat alternativ, och ett som jag använder ofta, eftersom jag gillar att hålla en enskild begäran (som en HTTP-begäran) helt inlindad i en enda transaktion, är att se till att den första satsen som körs i en transaktion är antingen UPPDATERING eller en VÄLJ ... FÖR UPPDATERING . Det gör att ögonblicksbilden INTE tas förrän anslutningen tillåts fortsätta.

Låt oss återställa vår testdatabas igen och se hur detta fungerar.

delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);

Anslutning 1

start transaction;

SELECT * FROM table1 WHERE id = 1 FOR UPDATE;

Anslutning 2

start transaction;

SELECT * FROM table1 WHERE id = 1 FOR UPDATE;

Anslutning 2 väntar nu.

Anslutning 1

UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;

Anslutning 2 är nu släppt.

Anslutning 2

+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+

Här kan du faktiskt låta din serversida kontrollera resultaten av denna SELECT och veta att den är korrekt, och inte ens fortsätta med nästa steg. Men för fullständighetens skull avslutar jag som tidigare.

UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;

Nu kan du se att i Connection 2 ger SELECT och SELECT ... FÖR UPPDATERING samma resultat. Detta beror på att ögonblicksbilden som SELECT läser från inte skapades förrän efter att anslutning 1 hade begåtts.

Så, tillbaka till din ursprungliga fråga:Ja, WHERE-satsen kontrolleras i alla fall av UPDATE-satsen. Du måste dock vara försiktig med eventuella SELECT-er du gör för att undvika att felaktigt fastställa resultatet av den UPPDATERING.

(Ja ett annat alternativ är att ändra transaktionsisoleringsnivån. Jag har dock inte riktigt erfarenhet av det och eventuella gotchyas som kan finnas, så jag tänker inte gå in på det.)



  1. valt värde få från db till rullgardinsmenyn välj rutan alternativ med php mysql fel

  2. Varje härledd tabell måste ha sitt eget alias - fel från kombinationen fallande MySQL

  3. Använder Order By i codeigniter

  4. Parametrar filnamnet i MYSQL LOAD DATA INFILE