问题
So we're looking for a way to enforce constraints that span multiple tables.
We've come across this old blog post, which suggests:
Create a materialized view to select data that violates the desired constraint. The MV must be defined with
REFRESH COMPLETE ON COMMIT
so that it is updated before the end of the transaction.Create a check constraint on the materialized view that always evaluates to FALSE – e.g.
CHECK (1=0)
That’s it. Whenever the underlying tables are updated, the materialized view is refreshed. If the update violates the rule, then a row will be inserted into the materialized view; but the check constraint on the MV disallows any inserts into it, and so the transaction fails.
And although there are some performance questionmarks, the idea sounds reasonable enough.
However, postgresql does not -- to our knowledge -- support something like REFRESH ON COMMIT
.
What we can do, of course, is install triggers on the tables that form the view that will trigger a refresh on update/delete/insert.
But not only would that potentially mean having to execute a refresh for each of the tables involved, we may also have long ended the transaction by the time the refresh is executed.
Maybe we could do something with locking, but then it becomes an issue of locking the right thing quick enough, which sounds like a terrible idea.
So is there something we can do or are we better off forgetting about this?
What's the closest we can get to "refresh before commit" behaviour?
回答1:
Don't create a materialized view, roll it by hand.
As an example, we have two tables a
and b
, and we want to make sure that the sum of the rows in these two tables is less than 1000:
BEGIN;
CREATE TABLE counter (
total bigint NOT NULL,
CHECK (total < 1000)
);
CREATE FUNCTION count_trig() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
CASE
WHEN TG_OP = 'INSERT' THEN
UPDATE counter SET total = total + 1;
RETURN NEW;
WHEN TG_OP = 'DELETE' THEN
UPDATE counter SET total = total - 1;
RETURN OLD;
END CASE;
END;$$;
CREATE TRIGGER count_trig AFTER INSERT OR DELETE ON a
FOR EACH ROW EXECUTE PROCEDURE count_trig();
CREATE TRIGGER count_trig AFTER INSERT OR DELETE ON b
FOR EACH ROW EXECUTE PROCEDURE count_trig();
INSERT INTO counter (total)
VALUES ((SELECT count(*) FROM a) + (SELECT count(*) FROM b));
COMMIT;
来源:https://stackoverflow.com/questions/59952825/whats-the-closest-we-can-get-to-refresh-complete-on-commit-in-postgresql