问题
I'm currently having a problem with triggers on a current prototype of a game database i'm working on
So, I have these two tables
CREATE TABLE public.hunters
( id integer NOT NULL,
name character varying(30) COLLATE pg_catalog."default" NOT NULL,
weapon character varying(30) COLLATE pg_catalog."default" NOT NULL,
ranking character varying(30) COLLATE pg_catalog."default" NOT NULL,
nhunts integer NOT NULL,
sex character(1) COLLATE pg_catalog."default" NOT NULL,
title character varying(30) COLLATE pg_catalog."default",
)
CREATE TABLE public.hunts
(
id_h integer NOT NULL,
id_m integer NOT NULL,
id_l integer NOT NULL,
code integer NOT NULL,
huntname character varying(50) COLLATE pg_catalog."default",
)
There are other tables, but the problem revolves around those two.
See, a hunt is a table that contains the id of a hunter, the monster to be hunted and the location where the monster will be hunted - and also the name of que hunting quest. Everytime a hunter hunts some monster, it should increment the "nhunts" value, there on the table hunters.
Many hunters may hunt many different monster, on many different locations. The code column is an ordinal value which represents how recent or old is a hunt (so, if code equals 20, it would be the 20th hunt since "ever" - or at least, since it first became registered)
Problem is I don't know how to create this trigger. I tried everything and the value never is updated
This is what I tried. But it just doesn't update of increment anything
CREATE OR REPLACE FUNCTION Hunter_HuntsIncrement() RETURNS TRIGGER AS $$
DECLARE
idv integer;
BEGIN
idv := TG_ARGV[0];
IF (new.id_h = old.id_h AND new.code = old.code) THEN
UPDATE hunters
SET nhunts = nhunts + 1
WHERE id = idv;
END if;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER updating_nhunts AFTER INSERT ON hunts
FOR EACH STATEMENT EXECUTE PROCEDURE hunter_huntsincrement()
I know it's probably terribly wrong, but I'm new to triggers and I'd really like a helping hand here
回答1:
Maintaining summary value is tricky - it's easy to create a possibility to deadlock your program.
If you really have to do this, because you know that you'll have performance problems otherwise (like nhunts in hundreds or more), then it's better to create a separate summary table for nhunts, something like:
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);
The trigger for hunts:
- runs for each added, removed, updated row;
- adds a row
(id_h, nhunts) = (NEW.id_h, 1)
on each insert; - adds a row
(id_h, nhunts) = (OLD.id_h, -1)
on each delete; - both of the above on update that changes
id_h
.
As the trigger will only add new rows it does not lock existing rows and therefore it can't deadlock.
But this is not enough - as described above the summary table will grow rows as fast or faster than hunts table, so it's not very helpful. So we need to add some way to merge existing rows periodically - some way to change:
id_h nhunts
1 1
1 1
2 1
2 -1
1 1
1 -1
2 1
1 1
2 1
To:
id_h nhunts
1 3
2 2
This should not run on each trigger invocation, as it will then be quite slow, but it can run randomly - for example every 1/1024th invocation at random. This function will use "skip locked" keyword to avoid touching already locked rows, avoiding otherwise possible deadlock.
Such trigger would look something like this:
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();
The trigger runs fast enough on my laptop to insert 1M rows to hunts table in 45s.
This view below will make it easy to extract current nhunts from summary. Querying it will take a small number or ms even if hunts table will be in billions:
create or replace view hunts_summary_view as
select id_h, sum(nhunts) as nhunts
from hunts_summary
group by id_h;
来源:https://stackoverflow.com/questions/56900002/need-to-create-a-trigger-that-increments-a-value-in-a-table-after-insertion