Поддържането на обобщена стойност е трудно - лесно е да създадете възможност за deadlock вашата програма.
Ако наистина трябва да направите това, защото знаете, че в противен случай ще имате проблеми с производителността (като nhunts в стотици или повече), тогава е по-добре да създадете отделна обобщена таблица за nhunts, нещо като:
CREATE TABLE hunts_summary
(
id_hs bigserial primary key,
id_h integer NOT NULL,
nhunts integer NOT NULL
);
CREATE INDEX hunts_summary_id_h_idx on hunts_summary(id_h);
Тригерът за лов:
- изпълнява се за всеки добавен, премахнат, актуализиран ред;
- добавя ред
(id_h, nhunts) = (NEW.id_h, 1)
на всяка вложка; - добавя ред
(id_h, nhunts) = (OLD.id_h, -1)
при всяко изтриване; - и двете по-горе при актуализация, която променя
id_h
.
Тъй като тригерът ще добавя само нови редове, той не заключва съществуващите редове и следователно не може да блокира.
Но това не е достатъчно - както е описано по-горе, обобщената таблица ще расте редове толкова бързо или по-бързо от таблицата за търсене, така че не е много полезно. Така че трябва да добавим някакъв начин за периодично обединяване на съществуващи редове - някакъв начин за промяна:
id_h nhunts
1 1
1 1
2 1
2 -1
1 1
1 -1
2 1
1 1
2 1
До:
id_h nhunts
1 3
2 2
Това не трябва да се изпълнява при всяко извикване на тригера, тъй като тогава ще бъде доста бавно, но може да се изпълнява на случаен принцип - например всяко 1/1024-то извикване на случаен принцип. Тази функция ще използва ключова дума "skip locked", за да избегне докосването на вече заключени редове, избягвайки в противен случай възможна блокиране.
Такова задействане би изглеждало по следния начин:
create or replace function hunts_maintain() returns trigger
as $hunts_maintain$
begin
if (tg_op = 'INSERT') then
insert into hunts_summary(id_h, nhunts)
values (NEW.id_h, 1);
elsif (tg_op = 'DELETE') then
insert into hunts_summary(id_h, nhunts)
values (OLD.id_h, -1);
elsif (tg_op = 'UPDATE' and NEW.id_h!=OLD.id_h) then
insert into hunts_summary(id_h, nhunts)
values (OLD.id_h, -1), (NEW.id_h, 1);
end if;
if (random()*1024 < 1) then
with deleted_ids as (
select id_hs from hunts_summary for update skip locked
),
deleted_nhunts as (
delete from hunts_summary where id_hs in (select id_hs from deleted_ids) returning id_h, nhunts
)
insert into hunts_summary (id_h, nhunts) select id_h, sum(nhunts) from deleted_nhunts group by id_h;
end if;
return NEW;
end;
$hunts_maintain$ language plpgsql;
create trigger hunts_maintain
after insert or update or delete on hunts
for each row execute procedure hunts_maintain();
Тригерът работи достатъчно бързо на моя лаптоп, за да вмъкне 1 милион реда в таблицата за търсене за 45 секунди.
Този изглед по-долу ще улесни извличането на текущи nhunts от резюмето. Запитването ще отнеме малко число или ms, дори ако таблицата за търсене ще бъде в милиарди:
create or replace view hunts_summary_view as
select id_h, sum(nhunts) as nhunts
from hunts_summary
group by id_h;