Dette er et vanskeligt problem. Men det kan gøres med per-kolonne-triggere og betinget trigger-udførelse introduceret i PostgreSQL 9.0 .
Du skal bruge et "opdateret" flag pr. række for denne løsning. Brug en boolean
kolonne i samme tabel for nemheds skyld. Men det kunne være i en anden tabel eller endda en midlertidig tabel pr. transaktion.
Den dyre nyttelast udføres én gang pr. række hvor tælleren opdateres (en gang eller flere gange).
Dette bør også virke godt, fordi ...
- ... det undgår flere kald af triggere ved roden (skalerer godt)
- ... ændrer ikke yderligere rækker (minimer tabelbloat)
- ... kræver ikke dyr undtagelseshåndtering.
Overvej følgende
Demo
Testet i PostgreSQL 9.1 med et separat skema x
som testmiljø.
Tabeller og dummy-rækker
-- DROP SCHEMA x;
CREATE SCHEMA x;
CREATE TABLE x.tbl (
id int
,counter int
,trig_exec_count integer -- for monitoring payload execution.
,updated bool);
Indsæt to rækker for at demonstrere, at det fungerer med flere rækker:
INSERT INTO x.tbl VALUES
(1, 0, 0, NULL)
,(2, 0, 0, NULL);
Triggerfunktioner og triggere
1.) Udfør dyr nyttelast
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_1()
RETURNS trigger AS
$BODY$
BEGIN
-- PERFORM some_expensive_procedure(NEW.id);
-- Update trig_exec_count to count execution of expensive payload.
-- Could be in another table, for simplicity, I use the same:
UPDATE x.tbl t
SET trig_exec_count = trig_exec_count + 1
WHERE t.id = NEW.id;
RETURN NULL; -- RETURN value of AFTER trigger is ignored anyway
END;
$BODY$ LANGUAGE plpgsql;
2.) Markér række som opdateret.
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_2()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE x.tbl
SET updated = TRUE
WHERE id = NEW.id;
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
3.) Nulstil "opdateret" flag.
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_3()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE x.tbl
SET updated = NULL
WHERE id = NEW.id;
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
Triggernavne er relevante! Kaldet til den samme begivenhed udføres de i alfabetisk rækkefølge.
1.) Nyttelast, kun hvis den ikke er "opdateret" endnu:
CREATE CONSTRAINT TRIGGER upaft_counter_change_1
AFTER UPDATE OF counter ON x.tbl
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
WHEN (NEW.updated IS NULL)
EXECUTE PROCEDURE x.trg_upaft_counter_change_1();
2.) Marker rækken som opdateret, kun hvis den ikke er "opdateret" endnu:
CREATE TRIGGER upaft_counter_change_2 -- not deferred!
AFTER UPDATE OF counter ON x.tbl
FOR EACH ROW
WHEN (NEW.updated IS NULL)
EXECUTE PROCEDURE x.trg_upaft_counter_change_2();
3.) Nulstil flag. Ingen endeløs loop på grund af triggertilstand.
CREATE CONSTRAINT TRIGGER upaft_counter_change_3
AFTER UPDATE OF updated ON x.tbl
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
WHEN (NEW.updated) --
EXECUTE PROCEDURE x.trg_upaft_counter_change_3();
Test
Kør UPDATE
&SELECT
separat for at se den udskudte effekt. Hvis det udføres sammen (i én transaktion), vil SELECT vise den nye tbl.counter
men den gamle tbl2.trig_exec_count
.
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
Opdater nu tælleren flere gange (i én transaktion). Nyttelasten vil kun blive udført én gang. Voilá!
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;