【问题标题】:Efficient inserts with duplicate checks for large tables in Postgres在 Postgres 中对大型表进行重复检查的高效插入
【发布时间】:2016-04-17 11:19:34
【问题描述】:

我目前正在从事一个项目,该项目从现场的无线调制解调器网络收集大量数据。我们有一个如下所示的“读数”表:

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 个读数批量导入到一个临时读数表中,该表本质上是读数的未索引副本。然后我们运行以下 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. 我可以采取哪些步骤来加快插入过程?

提前致谢!

【问题讨论】:

  • 您能否详细描述一下您的用例 - 您需要实时对读数进行重复数据删除,还是要为分析构建一个仓库?
  • temporary_readings 表是否有任何结构或约束(例如 PK 或 UNIQUE 约束)?

标签: sql performance postgresql sql-insert


【解决方案1】:

您的查询想法没问题。我会尝试对批处理中的 100,000 行进行计时,以开始了解最佳批处理大小。

但是,distinct on 正在减慢速度。这里有两个想法。

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

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

还有第三种解决方案是使用on conflict。这将允许插入本身忽略重复值。不过,这仅在 Postgres 9.5 中可用。

【讨论】:

  • 谢谢戈登!很好的建议 - 我将做一些实验,删除不同的 on 子句并按照您的建议添加索引。会回来报告让你知道我的进展:-)
【解决方案2】:

添加到已经包含 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 field,则不太可能使用您的唯一索引。但是,如果您翻转该索引中的列,它将是!

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

引用manual

多列 B 树索引可以与以下查询条件一起使用 涉及索引列的任何子集,但索引是最 当前导(最左边)有约束时有效 列。

索引中列的顺序很重要。

【讨论】:

    猜你喜欢
    • 2018-01-12
    • 1970-01-01
    • 2012-10-16
    • 2014-08-06
    • 2021-05-24
    • 1970-01-01
    • 2013-01-24
    • 2014-12-23
    • 1970-01-01
    相关资源
    最近更新 更多