【问题标题】:How to delete many rows from frequently accessed table如何从经常访问的表中删除许多行
【发布时间】:2016-05-13 08:24:14
【问题描述】:

我需要删除一个非常大的表(比如 5m 行)的大部分(比如 90%)。该表的另外 10% 经常被读取,但不被写入。

从“Best way to delete millions of rows by ID”中,我得知我应该删除我要删除的 90% 上的任何索引,以加快进程(除了我用来选择要删除的行的索引)。

从“PostgreSQL locking mode”中,我看到这个操作会在整个表上获得一个ROW EXCLUSIVE 锁。但既然我只是阅读剩下的 10%,这应该没关系。

那么,在一个命令中删除所有内容是否安全(即DELETE FROM table WHERE delete_flag='t')?我担心如果删除一行失败,触发 巨大 回滚,那么它会影响我从表中读取的能力。批量删除会不会更明智?

【问题讨论】:

  • 先给个小建议:不管做什么,先备份数据库,然后处理到删除DELETE FROM table WHERE delete_flag='t'
  • 与 Oracle 不同,rollback 在 Postgres 中实际上相当便宜
  • 要添加到@a_horse_with_no_name 的评论中:“实际上很便宜”意味着 PostgreSQL 在回滚的情况下甚至不会触及您的表。只需将交易标记为这样。 O(1) 使用大 O 表示法。

标签: postgresql indexing transactions locking postgresql-performance


【解决方案1】:
  1. 索引对于 90% 的行的操作通常是无用的。无论哪种方式,顺序扫描都会更快。 (外来例外情况适用。)

  2. 如果您需要允许并发读取,则不能对表进行排他锁。所以你也不能在同一个事务中删除任何索引。

  3. 可以在单独的事务中删除索引,以将排他锁的持续时间保持在最短。在 Postgres 9.2 或更高版本中,您还可以使用DROP INDEX CONCURRENTLY,它只需要最少的锁。稍后使用CREATE INDEX CONCURRENTLY 在后台重建索引 - 并且只需要一个非常短暂的排他锁。

如果您有一个稳定的条件来识别保留的 10%(或更少)的行,我建议仅在这些行上使用 partial index 以获得最佳效果:

  • 读取查询可以随时快速访问表(使用部分索引)。
  • 大的DELETE 根本不会修改部分索引,因为DELETE 中不涉及任何行。
CREATE INDEX foo (some_id) WHERE delete_flag = FALSE;

假设delete_flagboolean。您必须在查询中包含相同的谓词(即使它看起来在逻辑上是多余的)以确保 Postgres 可以部分索引。

【讨论】:

  • 我非常喜欢在 10% 上添加部分索引的想法,在不影响删除运行时间的情况下加快读取速度(在 10% 上)。但是由于您没有在回答中提及它,我是否应该假设此任务不需要批量删除?
  • @SimonLepkin:我在问题中看不到任何需要批量删除的内容。通常,删除中途出错的可能性极小。如果您有不同的情况(可能是可能干扰的触发器?),那么批量删除可能是有意义的。还有一个问题,如果只删除了一些行的中间状态对世界其他地方可见 - 如果不是,您需要在单个事务中完成。
【解决方案2】:

使用特定大小的批量删除并在删除之间休眠:

create temp table t as
select id from tbl where ...;
create index on t(id);

do $$
declare sleep int = 5;
declare batch_size int = 10000;
declare c refcursor;
declare cur_id int = 0;
declare seq_id int = 0;
declare del_id int = 0;
declare ts timestamp;
begin
    <<top>>
    loop
        raise notice 'sleep % sec', sleep;
        perform pg_sleep(sleep);
        raise notice 'continue..';
        open c for select id from t order by id;
        <<inn>>
        loop
            fetch from c into cur_id;
            seq_id = seq_id + 1;
            del_id = del_id + 1;
            if cur_id is null then
                raise notice 'goin to del end: %', del_id;
                ts = current_timestamp;
                close c;
                delete from tbl tb using t where tb.id = t.id;
                delete from t;
                commit;
                raise notice 'ok: %s', current_timestamp - ts;
                exit top;
            elsif seq_id >= batch_size then
                raise notice 'goin to del: %', del_id;
                ts = current_timestamp;
                delete from tbl tb using t where t.id = tb.id and t.id <= cur_id;
                delete from t where id <= cur_id;
                close c;
                commit;
                raise notice 'ok: %s', current_timestamp - ts;
                seq_id = 0;
                exit inn;
            end if;
        end loop inn;
    end loop top;
end;
$$;

【讨论】:

    猜你喜欢
    • 2020-02-20
    • 1970-01-01
    • 1970-01-01
    • 2017-11-30
    • 2019-01-06
    • 2013-03-08
    • 2011-11-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多