Условието къде ще се спазва по време на състезателна ситуация, но трябва да внимавате как проверявате кой е спечелил състезанието.
Помислете за следната демонстрация как работи това и защо трябва да внимавате.
Първо, настройте някои минимални таблици.
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
играе ролята на id
във вашата таблица, updated_by_connection_id
действа като assignedPhone
и locked
като reservationCompleted
.
Сега нека започнем състезателния тест. Трябва да имате отворени 2 прозореца за команден ред/терминал, свързани с mysql и използване на базата данни, където сте създали тези таблици.
Връзка 1
start transaction;
Връзка 2
start transaction;
Връзка 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
Връзка 2
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
Връзка 2 вече чака
Връзка 1
SELECT * FROM table1 WHERE id = 1;
commit;
В този момент връзка 2 се освобождава, за да продължи и извежда следното:
Връзка 2
SELECT * FROM table1 WHERE id = 1;
commit;
Всичко изглежда добре. Виждаме, че да, клаузата WHERE беше спазена в ситуация на състезание.
Причината, поради която казах, че трябва да бъдете внимателни, е, че в реално приложение нещата не винаги са толкова прости. МОЖЕ да имате други действия, които се извършват в рамките на транзакцията и това всъщност може да промени резултатите.
Нека нулираме базата данни със следното:
delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
И сега, разгледайте тази ситуация, при която SELECT се извършва преди UPDATE.
Връзка 1
start transaction;
SELECT * FROM table2;
Връзка 2
start transaction;
SELECT * FROM table2;
Връзка 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
Връзка 2
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
Връзка 2 вече чака
Връзка 1
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
В този момент връзка 2 се освобождава, за да продължи и извежда следното:
Добре, нека видим кой спечели:
Връзка 2
SELECT * FROM table1 WHERE id = 1;
Чакаме какво? Защо е locked
0 и updated_by_connection_id
NULL??
Това е предпазливостта, която споменах. Виновникът всъщност се дължи на това, че направихме селекция в началото. За да получим правилния резултат, можем да изпълним следното:
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
С помощта на SELECT ... FOR UPDATE можем да получим правилния резултат. Това може да бъде много объркващо (както беше за мен първоначално), тъй като SELECT и SELECT ... FOR UPDATE дават два различни резултата.
Причината това да се случи е поради нивото на изолация по подразбиране READ-REPEATABLE
. Когато е направен първият SELECT, веднага след start transaction;
, се създава моментна снимка. Всички бъдещи неактуализиращи четения ще се извършват от тази моментна снимка.
Следователно, ако просто наивно ИЗБЕРЕТЕ, след като направите актуализацията, тя ще изтегли информацията от тази оригинална моментна снимка, която е преди редът е актуализиран. Като направите SELECT ... FOR UPDATE, вие го принуждавате да получи правилната информация.
Въпреки това, отново, в реално приложение това може да бъде проблем. Да кажем, например, вашата заявка е обвита в транзакция и след извършване на актуализацията искате да изведете някаква информация. Събирането и извеждането на тази информация може да се обработва от отделен код за многократна употреба, който НЕ искате да захвърляте с клаузи FOR UPDATE „за всеки случай“. Това би довело до много разочарование поради ненужно заключване.
Вместо това ще искате да поемете по различен път. Тук имате много опции.
Единият е да се уверите, че извършвате транзакцията, след като АКТУАЛИЗИРАНЕТО приключи. В повечето случаи това е може би най-добрият и прост избор.
Друга възможност е да не се опитвате да използвате SELECT за определяне на резултата. Вместо това може да успеете да прочетете засегнатите редове и да ги използвате (1 ред актуализиран срещу актуализация на 0 реда), за да определите дали АКТУАЛИЗИРАНЕТО е било успешно.
Друг вариант и този, който използвам често, тъй като обичам да поддържам една заявка (като HTTP заявка) напълно обвита в една транзакция, е да се уверя, че първият израз, изпълнен в транзакция, е или UPDATE или ИЗБЕРЕТЕ ... ЗА АКТУАЛИЗИРАНЕ . Това ще накара моментната снимка да НЕ бъде направена, докато връзката не бъде разрешена да продължи.
Нека нулираме тестовата ни база данни отново и да видим как работи това.
delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
Връзка 1
start transaction;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
Връзка 2
start transaction;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
Връзка 2 вече чака.
Връзка 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;
Връзка 2 вече е освободена.
Връзка 2
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
| 1 | 1 | 1 |
+----+--------+--------------------------+
Тук всъщност бихте могли да накарате вашия код от страна на сървъра да провери резултатите от този SELECT и да знаете, че е точен, и дори да не продължите със следващите стъпки. Но за пълнота ще завърша както преди.
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;
Сега можете да видите, че във връзка 2 SELECT и SELECT ... FOR UPDATE дават същия резултат. Това е така, защото моментната снимка, от която SELECT чете, е създадена едва след като връзка 1 не е била ангажирана.
И така, обратно към първоначалния ви въпрос:Да, клаузата WHERE се проверява от оператора UPDATE във всички случаи. Въпреки това, трябва да внимавате с всички SELECTs, които може да правите, за да избегнете неправилно определяне на резултата от тази АКТУАЛИЗИРАНЕ.
(Да, друг вариант е да промените нивото на изолация на транзакциите. Въпреки това, аз всъщност нямам опит с това и някакви gotchya, които може да съществуват, така че няма да навлизам в това.)