PostgreSQL Antipatterns: змяняем дадзеныя ў абыход трыгера
Рана ці позна многія сутыкаюцца з неабходнасцю нешта масава выправіць у запісах табліцы. Я ўжо расказваў, як гэта рабіць лепш, а як - лепш не рабіць. Сёння раскажу пра другі аспект масавага абнаўлення. аб спрацоўцы трыгераў.
Напрыклад, на табліцы, у якой вам трэба нешта паправіць, вісіць зласлівы трыгер ON UPDATE, які пераносіць усе змены ў якія-небудзь агрэгаты. А вам трэба ўсё абнаўляць (новае поле праініцыялізаваць, напрыклад) так акуратна, каб гэтыя агрэгаты не закранулі.
Давайце проста адключым трыгеры!
BEGIN;
ALTER TABLE ... DISABLE TRIGGER ...;
UPDATE ...; -- тут долго-долго
ALTER TABLE ... ENABLE TRIGGER ...;
COMMIT;
Уласна, тут і ўсё - усё ўжо вісіць.
Таму што ALTER TABLE накладвае AccessExclusive-блакіроўку, пад якой ніхто раўналежна выконваецца, нават просты SELECT, нічога з табліцы прачытаць не зможа. Гэта значыць, пакуль гэтая транзакцыя не скончыцца, усе жадаючыя нават «проста пачытаць» будуць чакаць. А мы памятаем, што UPDATE у нас да-о-олгі…
Давайце тады хутка адключым, потым хутка ўключым!
BEGIN;
ALTER TABLE ... DISABLE TRIGGER ...;
COMMIT;
UPDATE ...;
BEGIN;
ALTER TABLE ... ENABLE TRIGGER ...;
COMMIT;
Тут сітуацыя ўжо лепшая, час чакання істотна меншы. Але ўсяго дзве праблемы псуюць усю прыгажосць:
ALTER TABLE сам чакае ўсе іншыя аперацыі на табліцы, у тым ліку доўгія SELECT
Пакуль трыгер выключаны, «праляціць міма» любую змену у табліцы, нават не наша. І ў агрэгаты ну ніяк не патрапіць, хоць і павінна. Бяда!
Упраўленне зменнымі сесіі
Такім чынам, на папярэднім варыянце мы натыкнуліся на прынцыповы момант - трэба неяк навучыць трыгер адрозніваць "нашы" змены ў табліцы ад "не нашых". "Нашы" прапускаць як ёсць, а на "не нашы" - спрацоўваць. Для гэтага можна скарыстацца зменнымі сесіі.
На механізм спрацоўвання трыгераў таксама ўплывае канфігурацыйная зменная. session_replication_role. Уключаныя без дадатковых указанняў (па змаўчанні) трыгеры будуць спрацоўваць, калі роля рэплікацыі - "origin" (па змаўчанні) або "local". Трыгеры, уключаныя ўказаннем ENABLE REPLICA, будуць спрацоўваць, толькі калі бягучы рэжым сеанса - «replica», а трыгеры, уключаныя ўказаннем ENABLE ALWAYS, будуць спрацоўваць незалежна ад бягучага рэжыму рэплікацыі.
Асоба падкрэслю, што налада ставіцца да не да ўсіх-усім адразу, як ALTER TABLE, а толькі да нашага асобнага спец-канэкту. Разам, каб не спрацоўвалі ніякія прыкладныя трыгеры:
SET session_replication_role = replica; -- выключили триггеры
UPDATE ...;
SET session_replication_role = DEFAULT; -- вернули в исходное состояние
Умова ўнутры трыгера
Але прыведзены вышэй варыянт працуе для ўсіх трыгераў адразу (ці трэба "альтэрыць" загадзя трыгеры, якія не жадаецца адключаць). А калі нам трэба "выключыць" адзін канкрэтны трыгер?
Імёны параметраў пашырэнняў запісваюцца наступным чынам: імя пашырэння, кропка і затым уласна імя параметра, падобна поўным імёнам аб'ектаў у SQL. Напрыклад: plpgsql.variable_conflict.
Так як пазасістэмныя параметры могуць быць устаноўлены ў працэсах, якія не загружаюць адпаведны модуль пашырэння, PostgreSQL прымае значэння для любых імёнаў з двума кампанентамі.
Спачатку дапрацоўваем трыгер, прыкладна так:
BEGIN
-- процессу конвертации можно делать все
IF current_setting('mycfg.my_table_convert_process') = 'TRUE' THEN
IF TG_OP IN ('INSERT', 'UPDATE') THEN
RETURN NEW;
ELSE
RETURN OLD;
END IF;
END IF;
...
Дарэчы, гэта можна зрабіць "нажывую", без блакіровак, праз CREATE OR REPLACE для трыгернай функцыі. А потым у спец-канэкце ўзводзім «сваю» зменную:
SET mycfg.my_table_convert_process = 'TRUE';
UPDATE ...;
SET mycfg.my_table_convert_process = ''; -- вернули в исходное состояние