Посочените от вас гаранции се прилагат в този прост случай, но не непременно в малко по-сложни заявки. Вижте края на отговора за примери.
Простият случай
Ако приемем, че col1 е уникален, има точно една стойност „2“ или има стабилен ред, така че всеки UPDATE
съвпада със същите редове в същия ред:
Това, което ще се случи с тази заявка е, че нишките ще намерят реда с col=2 и всички ще се опитат да хванат заключване за запис на този кортеж. Точно един от тях ще успее. Другите ще блокират в очакване транзакцията на първата нишка да се ангажира.
Този първи tx ще запише, поеме и ще върне брой редове от 1. Поемането ще освободи заключването.
Другите tx отново ще се опитат да хванат ключалката. Един по един ще успеят. Всяка транзакция на свой ред преминава през следния процес:
- Получаване на заключване за запис на оспорвания кортеж.
- Проверете отново
WHERE col=2
състояние след получаване на ключалката. - Повторната проверка ще покаже, че условието вече не отговаря, така че
UPDATE
ще пропусне този ред. UPDATE
няма други редове, така че ще отчете нула актуализирани редове.- Ангажиране, освобождаване на заключването за следващото предаване, опитвайки се да го хванете.
В този прост случай заключването на ниво ред и повторната проверка на условието ефективно сериализират актуализациите. В по-сложни случаи не толкова.
Можете лесно да демонстрирате това. Отворете четири psql сесии. В първия заключете таблицата с BEGIN; LOCK TABLE test;
. В останалите сесии изпълнете идентичен UPDATE
s - те ще блокират на нивото на масата. Сега освободете заключването чрез COMMIT
започвайки първата си сесия. Гледайте ги как се състезават. Само един ще докладва брой редове 1, останалите ще докладват 0. Това е лесно автоматизирано и скриптирано за повторение и мащабиране до повече връзки/нишки.
За да научите повече, прочетете правила за едновременно писане , страница 11 от Проблеми с паралелността на PostgreSQL - и след това прочетете останалата част от тази презентация.
А ако col1 не е уникален?
Както Кевин отбеляза в коментарите, ако col
не е уникален, така че може да съпоставите няколко реда, след това различни изпълнения на UPDATE
може да получи различни поръчки. Това може да се случи, ако изберат различни планове (да кажем, че единият е чрез PREPARE
и EXECUTE
а друг е директен, или се забърквате с enable_
GUC) или ако планът, който всички използват, използва нестабилен вид равни стойности. Ако получат редовете в различен ред, тогава tx1 ще заключи един кортеж, tx2 ще заключи друг, след което всеки ще се опита да получи заключвания на вече заключените кортежи на другия. PostgreSQL ще прекъсне един от тях с изключение на блокиране. Това е още една добра причина всички кодът на вашата база данни трябва да винаги бъдете готови да опитате отново транзакции.
Ако сте внимателни, за да се уверите, че UPDATE
Ако винаги получавате едни и същи редове в същия ред, можете да разчитате на поведението, описано в първата част на отговора.
Разочароващо, PostgreSQL не предлага UPDATE ... ORDER BY
така че гарантирането, че вашите актуализации винаги избират едни и същи редове в същия ред, не е толкова просто, колкото бихте искали. A SELECT ... FOR UPDATE ... ORDER BY
последвано от отделна UPDATE
често е най-безопасен.
По-сложни заявки, системи за опашка
Ако правите заявки с множество фази, включващи множество кортежи или условия, различни от равенство, можете да получите изненадващи резултати, които се различават от резултатите на серийно изпълнение. По-специално, едновременни изпълнения на нещо като:
UPDATE test SET col = 1 WHERE col = (SELECT t.col FROM test t ORDER BY t.col LIMIT 1);
или други усилия за изграждане на проста система за "опашка" ще *не успява* да работи както очаквате. Вижте документите на PostgreSQL относно паралелността и тази презентация за повече информация.
Ако искате работна опашка, подкрепена от база данни, има добре тествани решения, които се справят с всички изненадващо сложни ъглови случаи. Един от най-популярните е PgQ . Има полезен PgCon документ по темата и търсене в Google за „postgresql queue“ е пълен с полезни резултати.
BTW, вместо LOCK TABLE
можете да използвате SELECT 1 FROM test WHERE col = 2 FOR UPDATE;
за да получите заключване за запис само на това в tuple. Това ще блокира актуализациите срещу него, но няма да блокира писането в други кортежи или да блокира всяко четене. Това ви позволява да симулирате различни видове проблеми с паралелността.