当前位置: 首页 > 知识库问答 >
问题:

在 PostgreSQL 9.2 中更新数据库行而不锁定表

李睿
2023-03-14

尝试使用PostgreSQL 9.2在表上运行这样的更新语句:

UPDATE table
    SET a_col = array[col];

我们需要能够在 ~10M 行表上运行它,而不是让它锁定表(因此在更新运行时仍然可以进行正常操作)。我相信使用游标可能是正确的解决方案,但我真的不知道它是否是正确的,或者我应该如何使用游标实现它。

我想出了这个光标代码,我认为这可能很好。

编辑:添加光标功能

CREATE OR REPLACE FUNCTION update_fields() RETURNS VOID AS $$
DECLARE
        cursor CURSOR FOR SELECT * FROM table ORDER BY id FOR UPDATE;
BEGIN
        FOR row IN cursor LOOP
                UPDATE table SET
                        a_col = array[col],
                        a_col2= array[col2]
                WHERE CURRENT OF cursor;
        END LOOP;
END;
$$ LANGUAGE plpgsql;

共有1个答案

鲁博赡
2023-03-14

首先,如果“正常操作”由 SELECT 查询组成,MVCC 模型将自动处理它。UPDATE 不会阻止 SELECT,反之亦然。SELECT 只看到已提交的数据(或在同一事务中执行的操作),因此大 UPDATE 的结果在完成(提交)之前对其他事务不可见。

如果没有其他对象引用该表,
并且没有并发写入操作(这将丢失!)
而且您可以在表上提供一个非常短的独占锁,
并且您当然有额外的磁盘空间:
您可以通过在后台创建表的更新版本来将锁定保持在最小。确保它有所有可以替换的东西,然后删除原始文件并重命名重复文件。

CREATE TABLE tbl_new (LIKE tbl_org INCLUDING CONSTRAINTS);

INSERT INTO tbl_new 
SELECT col_a, col_b, array[col] aS col_c
FROM   tbl_org;

我正在使用创建表(如..包括约束),因为(在此处引用手册):

非空约束总是被复制到新表中。只有在指定了< code > INCLUDING CONSTRAINTS 时,才会复制< code>CHECK约束;其他类型的约束永远不会被复制。

确保新桌子准备好了。然后:

DROP tbl_org;
ALTER TABLE tbl_new RENAME TO tbl_org;

导致非常短的时间窗口,其中表以独占方式锁定。

这实际上只是关于性能。它会很快地创建一个没有任何膨胀的新表。如果你有外键或视图,你仍然可以走这条路,但你必须准备一个脚本来删除和重新创建这些对象,可能会创建额外的独占锁。

对于并发写操作,您真正能做的就是将您的更新拆分成块。您不能在单个事务中做到这一点,因为锁只在事务结束时释放。

您可以使用dblink,它可以在另一个数据库(包括数据库本身)上启动独立的事务。这样,您可以在一个do语句或一个带循环的plpgsql函数中完成所有操作。下面是一个松散相关的答案,其中包含有关dblink的更多信息:

  • 从PostgreSQL中的存储过程删除或创建数据库

函数中的光标不会给你带来任何东西。任何函数都自动包含在一个事务中,所有的锁只在事务结束时释放。即使您使用了< code>CLOSE cursor(实际上您并没有这样做),它也只会释放一些资源,而不会释放表上获得的锁。我引用手册上的话:

< code>CLOSE关闭打开的光标下的门户。这可用于在事务结束之前释放资源,或者释放游标变量以便再次打开。

您需要运行单独的事务或(滥用)使用为您执行此操作的dblink。

 类似资料:
  • 问题内容: 鉴于: 我想自动执行以下操作:更新客户(如果已经存在);否则,插入一个新客户。 从理论上讲,这听起来似乎非常适合SQL- MERGE, 但是我正在使用的数据库不支持将AUTO_INCREMENT列与MERGE配合使用。 似乎表明,如果对不存在的行执行查询或更新语句,数据库将锁定索引,从而防止并发插入。 SQL标准可以保证这种行为吗?是否有任何数据库无法以这种方式运行? 更新 :对不起,

  • 问题内容: 我使用下面的代码 来更新查询 使用。 但是我越来越。 我尝试搜索一些SO链接,建议关闭数据库,但是我再次遇到相同的错误。我已经提到我在代码中出现错误的地方。 问题答案: 通常,如果您同时进行多个查询(您没有完成一些早期的SQL语句,或者您打开了多个线程,或者您多次打开了数据库),就会得到此信息。 这段代码对和的使用有些混乱,(和缺少),这可能是问题的根源。 在《 SQLite C /

  • 我有一个cron作业每30秒运行一次,检查未结束的游戏。我将用于固定线程池。cron作业调用此方法: 其中是。在方法内部,我在循环中调用一个api,直到没有下一个页面。在这个方法中,我想更新数据库,以记住我最后看到的页面。 是否可以在一个线程中更新数据库,然后启动另一个线程,其中数据库再次更新?

  • 问题内容: 我正在使用()查找PostLikes表中的(PostId,UserId),如果找到该行,则用于生成delete指令并删除基础行,如果未找到该行,则用于生成insert命令并使用将该行插入表中。但是该行未插入数据库中的表中。这是我到目前为止所做的 和表 PostLikes(LikeId,PostId,UserId) 问题答案: 有几个问题: 您正在寻找重复使用相同的命令,这两个检测行是否

  • 我是Android的新手,需要一些帮助。 我的问题是我到底什么时候调用命令,以及room如何知道哪一行发生了更改?我不想更新到整个表。 如果我应该用另一种方式解释,请告诉我。

  • 我目前正在重构遗留代码,以使用Android架构组件,并在一种存储库模式中设置一个房间数据库和截取请求。因此,表示层/域层要求存储库获取LiveData对象进行观察,或告诉他与服务器同步,然后删除旧的db条目,并从服务器中重新提取所有当前条目。 我已经写了同步部分的测试,所以我确信,对象被正确地获取并插入到数据库中。但是当写一个测试来观察db表的条目时(并测试对象是否被正确保存,以及在将它们放入d