关于INSERT的速度:
您的客户端程序执行的每个查询都会导致通过网络进行通信,或者在 localhost 上使用 unix 套接字,这会产生一些开销。
另外,这非常重要,如果您使用自动提交并且不在事务中包装大量 INSERT,那么每个 INSERT 都将在自己的事务中,并且 postgres 不会向客户端确认 INSERT 已完成直到数据以一种保证在服务器崩溃时仍然存在的方式写入磁盘。这种刷新到磁盘显然需要一段时间,在旋转磁盘上预计至少需要 10 毫秒,对于 SSD 来说更少,但仍然不是零。如果你做了很多,你真的不想为每个 INSERT 都这样做,所以首先要做的是将整个事情包装在 BEGIN/COMMIT 中,以便它在单个事务中运行。
接下来要做的就是 INSERT INTO ... VALUES () 并在其中放入大量值,而不仅仅是一行,因此查询开销分摊到多行。
对于剩余的范围,我生成每个网络号,因此三个八位字节或 24 位网络号并将它们写入数据库。
因此,显而易见的下一个优化是在服务器上生成范围。例如
INSERT INTO table VALUES (1)
...lots of INSERTS...
INSERT INTO table (column) VALUES (100)
这样会更快:
INSERT INTO table (column) VALUES (1),(2),...lots of values...,(100)
这会更快,但 COPY 仍然需要解析所有数据:
\copy table (column) FROM stdin
1
2
...etc
还有这个:
INSERT INTO table (column) SELECT n FROM generate_series(1,100) n;
会更快,因为它不必将所有文本解析为值,它只会生成它们。如果要插入范围,并且在客户端使用循环来执行此操作,请将循环替换为服务器上的 generate_series。
接下来,如果您不需要实际存储每个 IP 地址,而只对范围感兴趣,那么为什么不直接存储范围而不是所有 IP? Postgres 有一个 RANGE 特性。这也将大大减小表格的大小。
接下来,您可以使用 UNLOGGED 表来加快插入速度。它不会有崩溃恢复,这意味着 postgres 不会费心将恢复日志数据写入磁盘,这会大大加快 INSERT 和 UPDATE 的速度。生成所有数据后,您始终可以将其切换回 LOGGED,因此它可以在崩溃中幸存下来。
接下来,在插入所有数据后创建索引。从头开始创建索引比在插入数据时一点一点构建索引要快得多。
我刚刚找到this。所以,
CREATE OR REPLACE FUNCTION all_ips(cidr)
RETURNS SETOF inet LANGUAGE SQL IMMUTABLE AS
$$
select $1 + s from generate_series(1, broadcast($1) - (network($1)) - 1) s;
$$;
BEGIN;
CREATE UNLOGGED TABLE foo (ip INET NOT NULL);
INSERT INTO foo SELECT all_ips('10.0.0.0/8');
COMMIT;
从 2008 年开始,这 1600 万行 INSERT 在 Core 2 Quad 上需要 44 秒。它主要使用 CPU,可能在内部 INET/CIDR 函数中,这意味着您可以在每个 CPU 内核中并行运行其中一个,以提高速度。 .
CREATE INDEX foo_ip ON foo(ip);
需要额外的 17 秒,
ALTER TABLE foo SET LOGGED;
大约需要一分钟,因为它必须向 WAL 写入大约 1 GB 的表和索引。