Utrzymanie wartości podsumowania jest trudne - łatwo jest stworzyć możliwość zakleszczenia Twój program.
Jeśli naprawdę musisz to zrobić, ponieważ wiesz, że w przeciwnym razie będziesz mieć problemy z wydajnością (np. nhunty na setki lub więcej), lepiej utworzyć osobną tabelę podsumowującą dla nhuntów, na przykład:
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);
Spust do polowań:
- działa dla każdego dodanego, usuniętego, zaktualizowanego wiersza;
- dodaje wiersz
(id_h, nhunts) = (NEW.id_h, 1)
na każdej wstawce; - dodaje wiersz
(id_h, nhunts) = (OLD.id_h, -1)
przy każdym usunięciu; - obie powyższe w aktualizacji, która zmienia
id_h
.
Ponieważ wyzwalacz doda tylko nowe wiersze, nie blokuje istniejących wierszy i dlatego nie może się zakleszczyć.
Ale to nie wystarczy - jak opisano powyżej, tabela podsumowująca będzie rosła tak szybko lub szybciej niż tabela polowań, więc nie jest zbyt pomocna. Musimy więc dodać jakiś sposób na okresowe scalanie istniejących wierszy - jakiś sposób na zmianę:
id_h nhunts
1 1
1 1
2 1
2 -1
1 1
1 -1
2 1
1 1
2 1
Do:
id_h nhunts
1 3
2 2
Nie powinno to działać przy każdym wywołaniu wyzwalacza, ponieważ będzie wtedy dość powolne, ale może działać losowo - na przykład co 1/1024 wywołanie losowo. Ta funkcja użyje słowa kluczowego „pomiń zablokowane”, aby uniknąć dotykania już zablokowanych wierszy, unikając w przeciwnym razie możliwego zakleszczenia.
Taki wyzwalacz wyglądałby mniej więcej tak:
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();
Wyzwalacz działa wystarczająco szybko na moim laptopie, aby wstawić 1 mln wierszy do tabeli polowań w ciągu 45 sekund.
Poniższy widok ułatwi wyodrębnienie bieżących nhuntów z podsumowania. Zapytanie zajmie niewielką liczbę lub ms, nawet jeśli tabela polowań będzie liczona w miliardach:
create or replace view hunts_summary_view as
select id_h, sum(nhunts) as nhunts
from hunts_summary
group by id_h;