如何在PostgreSQL中执行大的非阻塞更新?
我想对PostgreSQL中的表进行大量更新,但是我不需要在整个操作中维护事务完整性,因为我知道我正在更改的列不会被写入或读取更新。我想知道在psql控制台中有一个简单的方法,使这些类型的操作更快。
例如,假设我有一个名为“订单”的表,有3500万行,我想这样做: UPDATE orders SET status = null; 为了避免转向一个偏离的讨论,让我们假设3500万列的所有状态值当前设置为相同(非空)值,从而使索引无用。 此语句的问题是它需要很长时间才能生效(仅因为锁定),并且所有已更改的行都将被锁定,直到完成整个更新。此更新可能需要5小时,而类似的 UPDATE orders SET status = null WHERE (order_id > 0 and order_id < 1000000); 可能需要1分钟。超过3500万行,做上面的和打破它的35块只需要35分钟,救我4小时25分钟。 我可以用一个脚本(使用伪码在这里)进一步分解: for (i = 0 to 3500) { db_operation ("UPDATE orders SET status = null WHERE (order_id >" + (i*1000)" + " AND order_id <" + ((i+1)*1000) " + ")"); } 此操作可能只在几分钟内完成,而不是35分钟。 所以这归结为我真正要求的。我不想写一个错误的脚本来分解操作,每次我想做一个大的一次性更新,像这样。有没有办法完成我想完全在SQL内?
列/行
PostgreSQL’s MVCC model中的任何UPDATE写入一个新版本的整行。如果并发事务更改同一行的任何列,则会出现耗时的并发问题。 Details in the manual.知道同一列不会被并发事务触及,避免了一些可能的复杂情况,但不是其他。 指数
当更新整个表(或其主要部分)时,Postgres从不使用索引。当必须读取所有或大多数行时,顺序扫描更快。相反:索引维护意味着UPDATE的额外成本。 性能
我理解你正在寻求一个更一般的解决方案(见下文)。但是为了解决实际的问题:这可以在毫秒级处理,不管表大小: ALTER TABLE orders DROP column status,ADD column status text; Per documentation:
和:
根据列(外键约束,索引,视图,…),确保没有对象。你需要删除/重新创建那些。除此之外,系统目录表pg_attribute上的小操作执行作业。需要在表上的独占锁,这可能是重并发负载的问题。因为它只需要几毫秒,你应该还是很好。 如果您有要保留的列默认值,请将其添加回单独的命令。在相同的命令中执行它将立即应用于所有行,使效果无效。 一般解决方案
这允许运行单个函数来更新较小部分中的大表,并且单独提交每个部分。避免为非常大量的行创建事务开销,更重要的是,在每个部分之后释放锁。这允许并发操作没有太多延迟地进行,并且使得死锁不太可能。 如果没有并发访问,这几乎没有用 – 除了在异常后避免ROLLBACK。也考虑 免责声明 首先,许多小交易实际上更昂贵。这只适用于大表。甜蜜点取决于许多因素。 如果你不确定你在做什么:单个事务是安全的方法。为了正常工作,在表上的并发操作必须发挥。例如:并发写可以将一行移动到应该已经处理的分区。或者并发读取可以看到不一致的中间状态。你被警告了。 分步说明 需要首先安装附加模块dblink: > How to use (install) dblink in PostgreSQL? 使用dblink设置连接非常依赖于DB集群的设置和安全策略。这可能很棘手。相关更多回答与更多如何连接dblink: > Persistent inserts in a UDF even if the function aborts 按照指示创建FOREIGN SERVER和USER MAPPING以简化和简化连接(除非您已经有一个连接)。 CREATE OR REPLACE FUNCTION f_update_in_steps() RETURNS void AS $func$ DECLARE _step int; -- size of step _cur int; -- current ID (starting with minimum) _max int; -- maximum ID BEGIN SELECT INTO _cur,_max min(order_id),max(order_id) FROM orders; -- 100 slices (steps) hard coded _step := ((_max - _cur) / 100) + 1; -- rounded,possibly a bit too small -- +1 to avoid endless loop for 0 PERFORM dblink_connect('myserver'); -- your foreign server as instructed above FOR i IN 0..200 LOOP -- 200 >> 100 to make sure we exceed _max PERFORM dblink_exec( $$UPDATE public.orders SET status = 'foo' WHERE order_id >= $$ || _cur || $$ AND order_id < $$ || _cur + _step || $$ AND status IS DISTINCT FROM 'foo'$$); -- avoid empty update _cur := _cur + _step; EXIT WHEN _cur > _max; -- stop when done (never loop till 200) END LOOP; PERFORM dblink_disconnect(); END $func$ LANGUAGE plpgsql; 呼叫: SELECT f_update_in_steps(); 您可以根据您的需要参数化任何部分:表名称,列名称,值,…只是确保清理标识符,以避免SQL注入: > Table name as a PostgreSQL function parameter 关于避免空UPDATE: > How do I (or can I) SELECT DISTINCT on multiple columns? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |