列/行
...我不需要在整个操作中维护事务完整性,因为我知道我正在更改的列在更新期间不会被写入或读取。
PostgreSQL 的 MVCC 模型UPDATE
中的任何一个都会写入整行的新版本。如果并发事务更改同一行的任何列,则会出现耗时的并发问题。手册中的详细信息。知道并发事务不会触及同一列可以避免一些可能的并发症,但不能避免其他并发症。
指数
为了避免被转移到离题的讨论上,我们假设 3500 万列的所有状态值当前都设置为相同的(非空)值,从而使索引无用。
在更新整个表(或它的主要部分)时,Postgres从不使用索引。当必须读取所有或大多数行时,顺序扫描会更快。相反:索引维护意味着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
指定 no,则使用 NULL。在这两种情况下都不需要重写表。
添加具有 volatile 的列DEFAULT
或更改现有列的类型将需要重写整个表及其索引。[...]
和:
该DROP COLUMN
表单并未物理删除该列,而只是使其对 SQL 操作不可见。表中的后续插入和更新操作将存储该列的空值。因此,删除列很快,但不会立即减少表的磁盘大小,因为被删除列占用的空间不会被回收。随着现有行的更新,空间将随着时间的推移而被回收。
确保您没有依赖于列的对象(外键约束、索引、视图......)。您需要删除/重新创建这些。除此之外,系统目录表上的微小操作就pg_attribute
可以完成这项工作。需要在表上使用排他锁,这对于繁重的并发负载可能是个问题。(就像 Buurman 在他的评论中强调的那样。)除此之外,操作只需几毫秒。
如果您想要保留列默认值,请将其添加回单独的命令中。在同一命令中执行此操作会立即将其应用于所有行。看:
要实际应用默认值,请考虑分批执行:
一般解决方案
dblink
在另一个答案中已经提到。它允许在隐式独立连接中访问“远程”Postgres 数据库。“远程”数据库可以是当前数据库,从而实现“自治事务”:函数在“远程”数据库中写入的内容已提交且无法回滚。
这允许运行单个函数来更新较小部分的大表,并且每个部分单独提交。避免为大量行建立事务开销,更重要的是,在每个部分之后释放锁。这允许并发操作在没有太多延迟的情况下继续进行,并减少死锁的可能性。
如果您没有并发访问权限,这几乎没有用 - 除非ROLLBACK
在异常发生后避免。也考虑SAVEPOINT
这种情况。
免责声明
首先,很多小额交易实际上更贵。这仅对大桌子有意义。甜蜜点取决于许多因素。
如果您不确定自己在做什么:单笔交易是安全的方法。为了使它正常工作,表上的并发操作必须配合。例如:并发写入可以将一行移动到应该已经处理的分区。或者并发读取可以看到不一致的中间状态。你被警告了。
分步说明
附加模块 dblink 需要先安装:
设置与 dblink 的连接很大程度上取决于数据库集群的设置和安全策略。这可能很棘手。稍后的答案与更多如何连接 dblink相关:
按照那里的说明创建 aFOREIGN SERVER
和 aUSER 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 注入:
避免空更新: