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

чытаем мануал:

На механізм спрацоўвання трыгераў таксама ўплывае канфігурацыйная зменная. 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 = ''; -- вернули в исходное состояние

Ведаеце іншыя спосабы? Падзяліцеся ў каментарах.

Крыніца: habr.com

Дадаць каментар