Мат і Ервін мають рацію, і я лише додаю ще одну відповідь, щоб далі розгорнути те, що вони сказали, таким чином, який не впишеться в коментар. Оскільки їх відповіді, здається, не всі задовольняють, і висловилася думка про те, що слід звернутися до розробників PostgreSQL, і я такий, я докладно докладу.
Тут важливим моментом є те, що згідно зі стандартом SQL, в рамках транзакції, що виконується на READ COMMITTED
рівні ізоляції транзакцій, обмеження полягає в тому, що робота неперевірених транзакцій не повинна бути видимою. Коли робота вчинених транзакцій стає видимою, залежить від впровадження. Те, що ви вказуєте, - це різниця в тому, як два продукти обрали для цього реалізацію. Жодна реалізація не порушує вимог стандарту.
Ось, що відбувається в PostgreSQL, докладно:
Прогони S1-1 (1 ряд видалено)
Старий рядок залишається на місці, оскільки S1 може все-таки повернутись назад, але S1 тепер містить блокування на рядку, так що будь-який інший сеанс, який намагається змінити рядок, буде чекати, щоб побачити, чи S1 здійснює чи повертається назад. Будь-які читання таблиці все ще можуть бачити старий рядок, якщо вони не намагаються заблокувати його з SELECT FOR UPDATE
або SELECT FOR SHARE
.
S2-1 працює (але заблоковано, оскільки у блоку запису S1 блокування)
Тепер S2 доводиться чекати, щоб побачити результат S1. Якщо S1 повертається назад, а не фіксує, S2 видалить рядок. Зауважте, що якби S1 вставив нову версію перед відкат, нова версія ніколи б не була там з точки зору будь-якої іншої транзакції, а також стару версію не було б видалено з точки зору будь-якої іншої транзакції.
Прогони S1-2 (вставлено 1 ряд)
Цей ряд не залежить від старого. Якщо б було оновлення рядка з id = 1, старі та нові версії були б пов'язані, і S2 міг видалити оновлену версію рядка, коли він розблокувався. Це означає, що новий рядок має ті самі значення, що і ряд, який існував у минулому, не робить його таким же, як оновлена версія цього рядка.
S1-3 працює, звільняючи блокування запису
Тож зміни S1 зберігаються. Одного ряду немає. Додано один рядок.
S2-1 працює, тепер, коли він може отримати замок. Але звіти 0 рядків видалено. HUH ???
Що відбувається всередині, це те, що є вказівник від однієї версії рядка до наступної версії того самого рядка, якщо він оновлюється. Якщо рядок буде видалено, наступної версії немає. Коли READ COMMITTED
транзакція прокидається з блоку конфлікту при записі, випливає, що ланцюжок оновлення до кінця; якщо рядок не була видалена і якщо вона все ще відповідає критеріям вибору запиту, вона буде оброблена. Цей рядок видалено, тому запит S2 продовжується.
S2 може або не може потрапити до нового рядка під час сканування таблиці. Якщо це так, він побачить, що новий рядок був створений після DELETE
запуску оператора S2 , і тому він не є частиною набору видимих йому рядків.
Якщо PostgreSQL перезапустив увесь оператор DELETE S2 з самого початку з новим знімком, він поводитиметься так само, як SQL Server. Спільнота PostgreSQL не обрала цього робити з міркувань продуктивності. У цьому простому випадку ви ніколи не помітите різниці в продуктивності, але якби у вас було DELETE
заблоковано десять мільйонів рядків, коли вас заблокували, ви, безумовно, зробили б це. Тут є компроміс, де PostgreSQL вибрав продуктивність, оскільки більш швидка версія все ще відповідає вимогам стандарту.
S2-2 працює, повідомляє про унікальне порушення обмеження ключа
Звичайно, рядок вже існує. Це найменш дивовижна частина картини.
Незважаючи на те, що тут є деяка дивна поведінка, все відповідає стандарту SQL і в межах того, що є "специфічним для реалізації" відповідно до стандарту. Це, звичайно, може бути дивовижним, якщо ви припускаєте, що деяка інша поведінка впровадження буде присутня у всіх реалізаціях, але PostgreSQL дуже намагається уникнути помилок серіалізації на READ COMMITTED
рівні ізоляції і дозволяє певні поведінки, які відрізняються від інших продуктів, щоб досягти цього.
Зараз особисто я не є великим прихильником READ COMMITTED
рівня ізоляції транзакцій у впровадженні будь-якого продукту. Всі вони дозволяють умовам перегонів створити дивовижні поведінки з трансакційної точки зору. Після того, як хтось звикає до дивної поведінки, дозволеної одним продуктом, вони, як правило, вважають, що "нормальні" і компроміси, обрані іншим продуктом, непарні. Але кожен продукт повинен робити якийсь компроміс для будь-якого режиму, фактично не реалізованого як SERIALIZABLE
. Там, де розробники PostgreSQL вирішили провести межу, READ COMMITTED
це мінімізувати блокування (читання не блокує запис, а запис не блокує читання) та мінімізація ймовірності відмов серіалізації.
Стандарт вимагає, щоб SERIALIZABLE
транзакції були за замовчуванням, але більшість продуктів цього не роблять, оскільки це призводить до досягнення ефективності над більш низьким рівнем ізоляції транзакцій. Деякі продукти навіть не забезпечують дійсно серіалізаційних транзакцій, коли SERIALIZABLE
вибрано - особливо Oracle та версії PostgreSQL до 9.1. Але використання справді SERIALIZABLE
транзакцій - це єдиний спосіб уникнути дивовижних ефектів від перегонових умов, і SERIALIZABLE
транзакції завжди повинні або блокуватись, щоб уникнути перегонових умов, або скасовувати деякі транзакції, щоб уникнути розвиваються умов гонки. Найпоширенішою реалізацією SERIALIZABLE
транзакцій є сувора двофазова блокування (S2PL), яка має як блокування, так і помилки серіалізації (у вигляді тупиків).
Повне розкриття: я працював з Dan Ports з MIT, щоб додати справді серіалізаційні транзакції до PostgreSQL версії 9.1, використовуючи нову методику, що називається Serializable Snapshot Isolation.