AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79596885
Accepted
dan-kli
dan-kli
Asked: 2025-04-29 00:13:46 +0800 CST2025-04-29 00:13:46 +0800 CST 2025-04-29 00:13:46 +0800 CST

在触发器中锁定一行

  • 772

我有两张 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;来锁定行吗?

sql
  • 1 1 个回答
  • 67 Views

1 个回答

  • Voted
  1. Best Answer
    Zegarek
    2025-04-29T19:08:35+08:002025-04-29T19:08:35+08:00

    我可以用来PERFORM ... FOR UPDATE;锁定行吗?

    update可以,但那只是复制了你已经获取的锁,正如@Iłya Bursov立即指出的那样。你可以在db<>fiddle的演示中看到它。pg_locks

    start transaction;
    create table t(v int primary key);
    insert into t values(1),(2);
    commit and chain;
    update t set v=9 where v=1;
    select mode
         , relation::regclass
         , pid=pg_backend_pid() as is_me
    from pg_locks
    join pg_database on pg_locks.database   = pg_database.oid
                    and pg_database.datname = current_catalog
    where relation::regclass in ('t','t_pkey');
    
    模式 关系 是我
    行排他锁 t_pkey 吨
    行排他锁 吨 吨

    一个在表上,另一个在索引上强制执行主键。


    正如@Frank Heikens所暗示的,您可能希望将 换成upsert ,以确保安全地处理初始插入,并添加以涵盖@Charlieface建议的任何更新和删除:updatecoalesce()

    CREATE OR REPLACE FUNCTION update_size_on_insert() RETURNS TRIGGER AS $$
    BEGIN
      INSERT INTO metadata AS m(storage_id,total_size)
      VALUES(coalesce(NEW.storage_id,OLD.storage_id),coalesce(NEW.size,0))
      ON CONFLICT(storage_id)DO UPDATE
      SET total_size = m.total_size 
                      +coalesce(NEW.size,0) 
                      -coalesce(OLD.size,0);
      RETURN NEW;
    END $$ LANGUAGE plpgsql;
    
    CREATE OR REPLACE TRIGGER trg_update_size_on_insert
    AFTER INSERT OR UPDATE OR DELETE ON objects
    FOR EACH ROW
    EXECUTE FUNCTION update_size_on_insert();
    

    要将其保留为after触发器,您需要更改 FK 约束。同时,请考虑添加一个CHECK约束,以确保没有人会滑入负尺寸的物体:

    size BIGINT NOT NULL CHECK(size>=0),
      FOREIGN KEY (storage_id) REFERENCES metadata(storage_id) DEFERRABLE INITIALLY DEFERRED
    

    可延迟性使得触发器有时间storage_id在约束验证之前添加新内容。如果没有可延迟性,触发器就必须触发before。

    db<>fiddle 上的演示

    insert into objects values
     (gen_random_uuid(),gen_random_uuid(),1)
    ,(gen_random_uuid(),gen_random_uuid(),1)
    returning*;
    
    对象 ID 存储 ID 尺寸
    35176f69-0f1f-4051-b903-9bb436c26b23 4d3d8298-0f2a-415f-b9a5-2784dafed066 1
    bfda9791-2e11-4998-b98f-5b674b647a16 63d59ab2-de3a-442f-b05d-7708fb8d61dd 1

    现在元数据已经自动填充了:

    table metadata;
    
    存储 ID 总大小
    4d3d8298-0f2a-415f-b9a5-2784dafed066 1
    63d59ab2-de3a-442f-b05d-7708fb8d61dd 1

    将对象添加到现有存储:

    insert into objects 
    select gen_random_uuid()
         , storage_id
         , 9 as size
    from objects
    limit 1 --any existing storage will do
    returning*;
    
    对象 ID 存储 ID 尺寸
    B60B5742-45E0-4118-97AB-FD60B915E9C2 4d3d8298-0f2a-415f-b9a5-2784dafed066 9
    table metadata;
    
    存储 ID 总大小
    63d59ab2-de3a-442f-b05d-7708fb8d61dd 1
    4d3d8298-0f2a-415f-b9a5-2784dafed066 10 个
    新对象增加了总大小

    更新任意一个对象的大小:

    update objects 
    set size=5
    where ctid=(select min(ctid)from objects)
    returning*;
    
    对象 ID 存储 ID 尺寸
    35176f69-0f1f-4051-b903-9bb436c26b23 4d3d8298-0f2a-415f-b9a5-2784dafed066 5
    table metadata;
    
    存储 ID 总大小
    63d59ab2-de3a-442f-b05d-7708fb8d61dd 1
    4d3d8298-0f2a-415f-b9a5-2784dafed066 14
    delete from objects
    where ctid=(select min(ctid)from objects)
    returning*;
    
    对象 ID 存储 ID 尺寸
    bfda9791-2e11-4998-b98f-5b674b647a16 63d59ab2-de3a-442f-b05d-7708fb8d61dd 1
    table metadata;
    
    存储 ID 总大小
    4d3d8298-0f2a-415f-b9a5-2784dafed066 14
    63d59ab2-de3a-442f-b05d-7708fb8d61dd 0

    或者,您可以扩展触发器,以便同时处理删除total_size降至零的未使用存储总数。

    要处理truncate事件,您还需要一个单独的语句级触发器 - 即使它删除了表的所有行,它也不会触发on delete触发器。

    @Adrian Klaver的view好事值得考虑:

    1. 触发器会减慢速度,特别是行级触发器,尤其是那些必须锁定事物的触发器。
    2. 如果表不是很大和/或您不经常需要这些总数,那么最好容忍数据库在这些时间运行新的扫描和聚合的一些额外的时间和精力,而不是减慢其上的所有 CRUD 操作。
    3. 如果您不需要完全新鲜、最新、精确和准确的总数,但您确实需要快速获得它们,您可以这样做materialized view并定期缓存它们。
    • 1

相关问题

  • 更新除某些列上具有相同值的行之外的所有行

  • 当我返回 sql 列时,有没有办法只反转数字?(希伯来语)

  • 布尔值之间的 SQL less/greater 比较会产生意外结果

  • 如何根据数组中的匹配更新 Postgres 表中的值

  • 如何在sql server中对列求和

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve