Уникайте унікального порушення в атомній транзакції


15

Чи можливо створити атомну транзакцію в PostgreSQL?

Поміркуйте, у мене є категорія таблиці з цими рядками:

id|name
--|---------
1 |'tablets'
2 |'phones'

І назва стовпця має унікальне обмеження.

Якщо я спробую:

BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;

Я отримую:

ERROR:  duplicate key value violates unique constraint "category_name_key"
DETAIL:  Key (name)=(tablets) already exists.

Відповіді:


24

На додаток до того, що надав @Craig (і виправляючи деякі з них):

Ефективне Postgres 9.4 , UNIQUE, PRIMARY KEYі EXCLUDEобмеження перевіряються відразу після кожного рядка , коли визначено NOT DEFERRABLE. Це відрізняється від інших видів NOT DEFERRABLEобмежень (лише в даний час REFERENCES(зовнішній ключ)), які перевіряються після кожного оператора . Ми все це опрацювали під цим пов'язаним питанням щодо SO:

Це НЕ досить для UNIQUE(або PRIMARY KEYчи EXCLUDEобмеження) , щоб бути , DEFERRABLEщоб зробити ваш код , представлений з кількома звітності роботи.

І використовувати для цього не можна ALTER TABLE ... ALTER CONSTRAINT. За документацію:

ALTER CONSTRAINT

Ця форма змінює атрибути обмежень, які були створені раніше. Наразі можуть бути змінені лише обмеження зовнішніх ключів .

Сміливий акцент мій. Використовуйте замість цього:

ALTER TABLE t
   DROP CONSTRAINT category_name_key
 , ADD  CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;

Опустіть і додайте обмеження назад в одне твердження, щоб не було вікна часу, щоб хтось прокрався в ображаючі рядки. Для великих таблиць було б заманливо зберегти базовий унікальний індекс якось, оскільки його дорого видалити та відтворити. На жаль, це не здається можливим за допомогою стандартних інструментів (якщо у вас є рішення для цього, будь ласка, повідомте нас!):

Для одного твердження, що робить обмеження відкладеним, достатньо:

UPDATE category c
SET    name = c_old.name
FROM   category c_old
WHERE  c.id     IN (1,2)
AND    c_old.id IN (1,2)
AND    c.id <> c_old.id;

Запит із CTE також є одним твердженням:

WITH x AS (
    UPDATE category SET name = 'phones' WHERE id = 1
    )
UPDATE category SET name = 'tablets' WHERE id = 2;

Однак для вашого коду з декількома висловлюваннями вам (додатково) потрібно фактично відкласти обмеження - або визначити його як INITIALLY DEFERREDабо, як правило, дорожче, ніж вище. Але це може бути непросто зв'язати все в одне твердження.

BEGIN;
SET CONSTRAINTS category_name_key DEFERRED;
UPDATE category SET name = 'phones'  WHERE id = 1;
UPDATE category SET name = 'tablets' WHERE id = 2;
COMMIT;

Однак пам’ятайте про обмеження у зв'язку з FOREIGN KEYобмеженнями. За документацію:

Колонки, на які посилаються, повинні бути стовпцями обмеження унікального або первинного ключа, що не відкладається, у таблиці з посиланням.

Отже, ви не можете мати обох одночасно.


13

Як я розумію, ваше питання тут полягає в тому, що обмеження перевіряється після кожного оператора, але ви хочете, щоб він перевірявся в кінці транзакції, тому він порівнює попередній стан із післяпоточним станом, ігноруючи проміжні стани.

Якщо так, то це можливо з відкладеним обмеженням .

Див. SET CONSTRAINTSТа DEFERRABLEобмеження, як це зафіксовано в CREATE TABLE.

Зауважте, що відкладені обмеження мають витрати - система повинна вести список їх, щоб перевірити їх на час фіксації, тому вони не годиться для транзакцій, які вносять величезні набори змін. Вони також повільніше перевіряються.

Тож я думаю, що ти, мабуть, хочеш:

ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;

Зауважте, що, мабуть, існує обмеження щодо ALTER TABLEвстановлення обмежень DEFERRABLE; вам, можливо, доведеться замість цього DROPі зменшити ADDобмеження.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.