列/行
...我不需要维护事务完整性
整个操作,因为我知道我要更改的列是
在更新期间不会被写入或读取。
PostgreSQL's MVCC model 中的任何 UPDATE 都会写入 整行 的新版本。如果并发事务更改同一行的任何列,则会出现耗时的并发问题。 Details in the manual. 知道并发事务不会触及相同的列可以避免一些可能的复杂情况,但不能避免其他情况。
索引
为了避免被转移到离题的讨论上,我们假设
当前设置了 3500 万列的所有状态值
到相同的(非空)值,从而使索引无用。
在更新整个表(或其主要部分)时,Postgres 从不使用索引。当必须读取所有或大多数行时,顺序扫描会更快。相反:索引维护意味着UPDATE 的额外成本。
性能
例如,假设我有一个名为“orders”的表,其中包含 3500 万
行,我想这样做:
UPDATE orders SET status = null;
我了解您的目标是更通用的解决方案(见下文)。但要解决实际问题:无论表大小如何,这都可以在几毫秒内处理:
ALTER TABLE orders DROP column status
, ADD column status text;
The manual (up to Postgres 10):
当用ADD COLUMN添加列时,表中的所有现有行
使用列的默认值初始化(NULL 如果没有 DEFAULT
条款被指定)。如果没有DEFAULT 子句,这只是元数据更改[...]
The manual (since Postgres 11):
当使用ADD COLUMN 和非易失性DEFAULT 添加列时
指定时,在语句时评估默认值
并将结果存储在表的元数据中。将使用该值
对于所有现有行的列。如果没有指定DEFAULT,
使用 NULL。在这两种情况下都不需要重写表。
添加带有 volatile DEFAULT 的列或更改类型
现有列将需要整个表及其索引
重写。 [...]
还有:
DROP COLUMN 表单不会物理删除列,但
只是使它对 SQL 操作不可见。随后的插入和
表中的更新操作将存储该列的空值。
因此,删除一列很快,但不会立即减少
表的磁盘大小,作为被删除的空间
不回收列。随着时间的推移,空间将被回收
现有行已更新。
确保您没有依赖于列的对象(外键约束、索引、视图...)。您需要删除/重新创建这些。除此之外,系统目录表pg_attribute 上的微小操作就可以完成这项工作。需要在表上使用独占锁,这可能是并发负载过重的问题。 (就像 Buurman 在他的 comment 中强调的那样。)除此之外,操作只需几毫秒。
如果您想要保留列默认值,请将其添加回来在单独的命令中。在同一命令中执行此操作会立即将其应用于所有行。见:
要实际应用默认值,请考虑分批进行:
一般解决方案
dblink 已在另一个答案中提及。它允许在隐式独立连接中访问“远程”Postgres 数据库。 “远程”数据库可以是当前数据库,从而实现“自治事务”:函数在“远程”数据库中写入的内容已提交,无法回滚。
这允许运行单个函数来更新较小部分的大表,并且每个部分单独提交。避免为大量行建立事务开销,更重要的是,在每个部分之后释放锁。这允许并发操作在没有太多延迟的情况下继续进行,并减少死锁的可能性。
如果您没有并发访问权限,这几乎没有用 - 除了在异常之后避免ROLLBACK。这种情况也可以考虑SAVEPOINT。
免责声明
首先,大量小额交易实际上更昂贵。这只对大表有意义。最佳位置取决于许多因素。
如果您不确定自己在做什么:单笔交易是安全的方法。为了使它正常工作,表上的并发操作必须配合。例如:并发 writes 可以将一行移动到应该已经处理的分区。或者并发读取可以看到不一致的中间状态。 您已被警告。
分步说明
需要先安装附加模块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; -- 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 注入:
避免空更新: