列/行
...我不需要在整个操作中维护事务的完整性,因为我知道我要更改的列在更新期间不会被写入或读取。
任何UPDATE
在PostgreSQL的MVCC模型写入新版本的整行。如果并发事务更改同一行的任何列,则会出现耗时的并发问题。手册中的详细信息。知道并发事务不会触及同一列可以避免一些可能的复杂性,但不会避免其他复杂性。
指数
为了避免转移到主题之外的讨论,我们假定当前3500万列的所有status值都设置为相同(非null)值,从而使索引无用。
在更新整个表(或其主要部分)时,Postgres永远不会使用index。当必须读取所有或大多数行时,顺序扫描会更快。相反:索引维护意味着的额外费用UPDATE
。
性能
例如,假设我有一个名为“ orders”的表,其中有3500万行,我想这样做:
UPDATE orders SET status = null;
我了解您的目标是寻求更通用的解决方案(请参见下文)。但是要解决实际提出的问题:无论表大小如何,都可以在几毫秒内解决:
ALTER TABLE orders DROP column status
, ADD column status text;
手册(最多Postgres 10):
当使用添加ADD COLUMN
列时,表中的所有现有行都将使用列的默认值进行初始化(NULL
如果未DEFAULT
指定任何子句)。如果没有DEFAULT
子句,这仅仅是元数据更改[...]
手册(自Postgres 11起):
当添加了列ADD COLUMN
并DEFAULT
指定了非易失性时,将在声明时评估默认值,并将结果存储在表的元数据中。该值将用于所有现有行的列。如果未DEFAULT
指定,则使用NULL。无论哪种情况都不需要重写表。
添加具有volatile的列DEFAULT
或更改现有列的类型将需要重写整个表及其索引。[...]
和:
该DROP COLUMN
窗体不会物理删除该列,而只是使其对SQL操作不可见。表中随后的插入和更新操作将为该列存储一个空值。因此,删除列很快,但是不会立即减小表的磁盘大小,因为删除的列所占用的空间不会被回收。随着现有行的更新,空间将随着时间的推移而回收。
确保没有依赖于该列的对象(外键约束,索引,视图等)。您将需要删除/重新创建那些。除非这样,pg_attribute
否则系统目录表上的微小操作即可完成工作。在表上需要排他锁,这可能会导致大量并发负载。(就像Buurman在其评论中强调的那样。)除非如此,该操作只需几毫秒。
如果您要保留默认的列,则将其添加回单独的命令中。在同一命令中执行此操作会立即将其应用于所有行。看到:
要实际应用默认值,请考虑分批执行:
一般解决方案
dblink
在另一个答案中已经提到。它允许在隐式的单独连接中访问“远程” Postgres数据库。“远程”数据库可以是当前数据库,从而实现“自主事务”:该函数在“远程”数据库中写入的内容已提交且无法回滚。
这样就可以运行一个函数,以较小的部分更新一个大表,并且每个部分都单独提交。避免增加大量行的事务开销,更重要的是,在每个部分之后释放锁。这允许并发操作继续进行而没有太多延迟,并且使死锁的可能性降低。
如果您没有并发访问权限,那么这几乎没有用-除非要避免ROLLBACK
出现异常。还要考虑SAVEPOINT
这种情况。
免责声明
首先,许多小额交易实际上更昂贵。这只对大桌子有意义。最佳位置取决于许多因素。
如果您不确定自己在做什么,那么单笔交易是安全的方法。为了使其正常工作,必须同时执行表上的并发操作。例如:并发写入可以将行移动到应该已经处理的分区。或同时读取可能会看到不一致的中间状态。你被警告了。
分步说明
首先需要安装附加模块dblink:
使用dblink设置连接在很大程度上取决于数据库集群的设置和适当的安全策略。这可能很棘手。相关的稍后答案,以及更多如何与dblink连接的信息:
按照此处的指示创建FOREIGN SERVER
和USER MAPPING
,以简化和简化连接(除非您已经拥有一个)。
假设serial PRIMARY KEY
有或没有一些差距。
CREATE OR REPLACE FUNCTION f_update_in_steps()
RETURNS void AS
$func$
DECLARE
_step int;
_cur int;
_max int;
BEGIN
SELECT INTO _cur, _max min(order_id), max(order_id) FROM orders;
_step := ((_max - _cur) / 100) + 1;
PERFORM dblink_connect('myserver');
FOR i IN 0..200 LOOP
PERFORM dblink_exec(
$$UPDATE public.orders
SET status = 'foo'
WHERE order_id >= $$ || _cur || $$
AND order_id < $$ || _cur + _step || $$
AND status IS DISTINCT FROM 'foo'$$);
_cur := _cur + _step;
EXIT WHEN _cur > _max;
END LOOP;
PERFORM dblink_disconnect();
END
$func$ LANGUAGE plpgsql;
呼叫:
SELECT f_update_in_steps();
您可以根据需要对任何部分进行参数化:表名,列名,值等,只需确保清理标识符以避免SQL注入:
避免空的更新: