我有两张 PostgreSQL 表:objects
和metadata
。每个对象都有一个size
属性,表示其大小(以字节为单位),并且可以存储在一个唯一的位置storage_id
。元数据中维护了total_size
每个存储中所有对象的 ,每个存储的 都具有给定的storage_id
。简化表格:
CREATE TABLE IF NOT EXISTS objects (
object_id UUID PRIMARY KEY,
storage_id UUID NOT NULL,
size BIGINT NOT NULL,
FOREIGN KEY (storage_id) REFERENCES metadata(storage_id)
);
CREATE TABLE IF NOT EXISTS metadata (
storage_id UUID PRIMARY KEY,
total_size BIGINT DEFAULT 0
);
为了维护表total_size
中的内容metadata
,我有一个触发器,每当插入或删除对象时,total_size
就会更新,即这里是插入的触发器:
CREATE OR REPLACE FUNCTION update_size_on_insert() RETURNS TRIGGER AS $$
BEGIN
UPDATE metadata
SET total_size = total_size + NEW.size
WHERE storage_id = NEW.storage_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER trg_update_size_on_insert
AFTER INSERT ON objects
FOR EACH ROW
EXECUTE FUNCTION update_size_on_insert();
问题是并发插入/删除操作可能会覆盖 ,total_size
从而导致数据无效。如何更改触发器,使其在 执行时包含行级SET total_size = total_size + NEW.size
锁?我考虑过FOR UPDATE
语句,但这需要一个SELECT
。我可以使用PERFORM ... FOR UPDATE;
来锁定行吗?
update
可以,但那只是复制了你已经获取的锁,正如@Iłya Bursov立即指出的那样。你可以在db<>fiddle的演示中看到它。pg_locks
一个在表上,另一个在索引上强制执行主键。
正如@Frank Heikens所暗示的,您可能希望将 换成upsert ,以确保安全地处理初始插入,并添加以涵盖@Charlieface建议的任何更新和删除:
update
coalesce()
要将其保留为
after
触发器,您需要更改 FK 约束。同时,请考虑添加一个CHECK
约束,以确保没有人会滑入负尺寸的物体:可延迟性使得触发器有时间
storage_id
在约束验证之前添加新内容。如果没有可延迟性,触发器就必须触发before
。db<>fiddle 上的演示
现在元数据已经自动填充了:
将对象添加到现有存储:
新对象增加了总大小
更新任意一个对象的大小:
或者,您可以扩展触发器,以便同时处理删除
total_size
降至零的未使用存储总数。要处理
truncate
事件,您还需要一个单独的语句级触发器 - 即使它删除了表的所有行,它也不会触发on delete
触发器。@Adrian Klaver的
view
好事值得考虑:materialized view
并定期缓存它们。