61 postgresql dblink transactions plpgsql sql-update
我想在PostgreSQL中对表进行大量更新,但我不需要在整个操作中维护事务完整性,因为我知道我正在更改的列不会被写入或读取更新.我想知道psql控制台中是否有一种简单的方法可以更快地完成这些类型的操作.
例如,假设我有一个名为"orders"的表,有3500万行,我想这样做:
UPDATE orders SET status = null;
Run Code Online (Sandbox Code Playgroud)
为避免被转移到offtopic讨论,让我们假设3500万列的所有状态值当前都设置为相同(非空)值,从而使索引无用.
此语句的问题是需要很长时间才能生效(仅因为锁定),并且所有更改的行都将被锁定,直到整个更新完成.此更新可能需要5个小时,而类似
UPDATE orders SET status = null WHERE (order_id > 0 and order_id < 1000000);
Run Code Online (Sandbox Code Playgroud)
可能需要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) " + ")");
}
Run Code Online (Sandbox Code Playgroud)
此操作可能仅在几分钟内完成,而不是35分钟.
所以这归结为我真正的要求.我不想写一个怪异的脚本来分解操作,每次我想做这样一个大的一次性更新.有没有办法在SQL中完成我想要的东西?
Erw*_*ter 38
...我不需要在整个操作中维护事务完整性,因为我知道我正在更改的列不会在更新期间写入或读取.
任何UPDATE
在PostgreSQL的MVCC模型写入新版本的整行.如果并发事务更改同一行的任何列,则会出现耗时的并发问题.手册中的详细信息.并发事务不会触及相同的列,避免了一些可能的并发症,而不是其他的.
为避免被转移到offtopic讨论,让我们假设3500万列的所有状态值当前都设置为相同(非空)值,从而使索引无用.
更新整个表(或其主要部分)时,Postgres 从不使用索引.当必须读取所有或大多数行时,顺序扫描更快.相反:指数维护意味着额外的成本UPDATE
.
例如,假设我有一个名为"orders"的表,有3500万行,我想这样做:
Run Code Online (Sandbox Code Playgroud)UPDATE orders SET status = null;
我知道你的目标是更通用的解决方案(见下文).但要解决实际问题:无论表大小如何,都可以在几毫秒内处理:
ALTER TABLE orders DROP column status
, ADD column status text;
Run Code Online (Sandbox Code Playgroud)
添加
ADD COLUMN
列时,表中的所有现有行都使用列的默认值进行初始化(NULL
如果未DEFAULT
指定子句).如果没有DEFAULT子句,这只是一个元数据更改......
和:
该
DROP COLUMN
形式不物理删除列,但只是让无形的SQL操作.表中的后续插入和更新操作将为列存储空值.因此,删除列很快,但不会立即减少表的磁盘大小,因为已删除列所占用的空间不会被回收.随着现有行的更新,该空间将随着时间的推移而被回收.(这些语句在删除系统oid列时不适用;这是通过立即重写完成的.)
确保根据列不具有对象(外键约束,索引,视图......).您需要删除/重新创建它们.除此之外,系统目录表上的微小操作可以pg_attribute
完成这项工作.需要对表进行独占锁定,这可能是重度并发负载的问题.因为它只需要几毫秒,你应该仍然没问题.
如果您希望保留列默认值,请将其添加回单独的命令中.在同一个命令中执行此操作会立即将其应用于所有行,从而消除效果.然后,您可以批量更新现有列.请按照文档链接阅读手册中的注释.
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; -- 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;
Run Code Online (Sandbox Code Playgroud)
呼叫:
SELECT f_update_in_steps();
Run Code Online (Sandbox Code Playgroud)
您可以根据需要参数化任何部分:表名,列名,值,...只需确保清理标识符以避免SQL注入:
关于避免空UPDATE:
您应该将此列委托给另一个表,如下所示:
create table order_status (
order_id int not null references orders(order_id) primary key,
status int not null
);
Run Code Online (Sandbox Code Playgroud)
那么你设置 status=NULL 的操作将是即时的:
truncate order_status;
Run Code Online (Sandbox Code Playgroud)
Postgres 使用 MVCC(多版本并发控制),因此如果您是唯一的写入者,则可以避免任何锁定;任意数量的并发读取器都可以在该表上工作,并且不会有任何锁定。
因此,如果确实需要 5 小时,则一定是出于不同的原因(例如,您确实有并发写入,与您声称没有并发写入相反)。
归档时间: |
|
查看次数: |
28015 次 |
最近记录: |