在Postgres中对大表进行重复检查的高效插入

时间:2016-04-17 11:19:35

标签: sql performance postgresql sql-insert

我目前正致力于从现场无线调制解调器网络收集大量数据的项目。我们有一张表'读数'看起来像这样:

CREATE TABLE public.readings (
  id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('readings_id_seq'::regclass),
  created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
  timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
  modem_serial CHARACTER VARYING(255) NOT NULL,
  channel1 INTEGER NOT NULL,
  channel2 INTEGER NOT NULL,
  signal_strength INTEGER,
  battery INTEGER,
  excluded BOOLEAN NOT NULL DEFAULT false
);
CREATE UNIQUE INDEX _timestamp_modemserial_uc ON readings USING BTREE (timestamp, modem_serial);
CREATE INDEX ix_readings_timestamp ON readings USING BTREE (timestamp);
CREATE INDEX ix_readings_modem_serial ON readings USING BTREE (modem_serial);

对于系统的完整性来说,重要的是我们永远不会从具有相同时间戳的同一调制解调器获得两个读数,因此是唯一索引。

我们目前的挑战是找到一种高效的插入读数的方法。在我们引入历史数据时,我们经常需要插入数百万行,当添加到1亿多读数的现有基数时,这可能会变慢。

我们目前的方法是将10,000个读数的批量导入到temporary_readings表中,该表基本上是未经索引的读数副本。然后,我们运行以下SQL将其合并到主表中并删除重复项:

INSERT INTO readings (created, timestamp, modem_serial, channel1, channel2, signal_strength, battery)
SELECT DISTINCT ON (timestamp, modem_serial) created, timestamp, modem_serial, channel1, channel2, signal_strength, battery
FROM temporary_readings
WHERE NOT EXISTS(
    SELECT * FROM readings
    WHERE timestamp=temporary_readings.timestamp
    AND modem_serial=temporary_readings.modem_serial
)
ORDER BY timestamp, modem_serial ASC;

这很好用,但每10,000个行块需要大约20秒才能插入。我的问题有两个:

  1. 这是解决问题的最佳方法吗?我对具有这些性能要求的项目相对较新,所以我很想知道是否有更好的解决方案。
  2. 我可以采取哪些步骤来加快插入过程?
  3. 提前致谢!

2 个答案:

答案 0 :(得分:3)

您的查询提示没问题。我会尝试在批次中为100,000行计时,以便开始了解最佳批量大小。

然而,distinct on正在减慢速度。这有两个想法。

首先要假设批量重复是非常罕见的。如果是这样,请尝试插入不带distinct on的数据。如果失败,则使用distinct on再次运行代码。这使插入逻辑变得复杂,但它可能使平均插入更短。

第二个是在temporary_readings(timestamp, modem_serial)(不是唯一索引)上构建索引。 Postgres将利用此索引进行插入逻辑 - 有时构建索引并使用它比其他执行计划更快。如果这确实有效,您可以尝试更大的批量。

有第三个解决方案是使用on conflict。这将允许插入本身忽略重复值。但这只适用于Postgres 9.5。

答案 1 :(得分:1)

添加到已经包含1亿个索引记录的表格将会很慢,无论您​​通过重新审视索引可能会略微提高速度。

CREATE UNIQUE INDEX _timestamp_modemserial_uc ON readings USING BTREE (timestamp, modem_serial);
CREATE INDEX ix_readings_timestamp ON readings USING BTREE (timestamp);
CREATE INDEX ix_readings_modem_serial ON readings USING BTREE (modem_serial);

目前您有三个索引,但它们位于同一组列中。您是否只使用唯一索引进行管理?我不知道你的其他查询是什么样的,但你的WHERE NOT EXISTS查询可以利用这个独特的索引。

如果您有使用WHERE子句的查询,则仅对modem_serial字段进行过滤。您的唯一索引不太可能被使用。但是,如果你翻转那个索引中的列,那就是!

CREATE UNIQUE INDEX _timestamp_modemserial_uc ON readings USING BTREE (timestamp, modem_serial);

引用manual

  

多列B树索引可以与查询条件一起使用   涉及索引列的任何子集,但索引最多   在领先(最左边)有限制时有效   列。