使用临时表的高效 PostgreSQL 更新

Tha*_*Guy 3 postgresql performance postgresql-9.1 query-performance

我编写了一个小程序来从文件导入产品详细信息更新,这比预期的要长得多。(为简洁起见,我将使用精简的示例。)

该程序执行以下操作:

  1. 从文件中读入数据。
  2. 执行某些修改并创建一个内存文件。
  3. 创建一个临时表来保存已处理的文件数据。
  4. COPYs 修改后的数据到临时表中。
  5. 从临时表更新实际表。

这一切都很好,除了UPDATE查询需要约 20 秒的时间来处理约 2000 行的小文件。

临时表如下所示:

CREATE TEMPORARY TABLE tmp_products (
  product_id integer,
  detail text
);
Run Code Online (Sandbox Code Playgroud)

我的更新查询非常简单:

UPDATE products
SET detail = t.detail
FROM tmp_products t
WHERE t.product_id = products.product_id
Run Code Online (Sandbox Code Playgroud)

为了加快速度,我尝试了以下方法,但收效甚微:

在临时表上创建 BTREE 索引。

CREATE INDEX tmp_products_idx
  ON tmp_products
  USING BTREE
  (product_id);
Run Code Online (Sandbox Code Playgroud)

创建哈希索引:

CREATE INDEX tmp_products_idx
  ON tmp_products
  USING HASH
  (product_id);
Run Code Online (Sandbox Code Playgroud)

这两个索引都没有显着改善更新时间。然后我想也许对表进行聚类会有所帮助,但这意味着我不能使用 HASH 索引。所以我修改了程序中的查询以使用 BTREE 索引,然后使用 CLUSTER/ANALYZE:

CREATE INDEX tmp_products_idx
  ON tmp_products
  USING BTREE
  (product_id);

-- Program inserts data

CLUSTER tmp_products USING tmp_products_idx;
ANALYZE tmp_products;
Run Code Online (Sandbox Code Playgroud)

这也没有任何帮助。我通过同时使用 BTREE 和 HASH 索引再次尝试了一下,希望 CLUSTER 将使用 BTREE,而 UPDATE 将使用 HASH:

CREATE INDEX tmp_products_btree_idx
  ON tmp_products
  USING BTREE
  (product_id);

CREATE INDEX tmp_products_hash_idx
  ON tmp_products
  USING BTREE
  (product_id);

-- Program inserts data

CLUSTER tmp_products USING tmp_products_btree_idx;
ANALYZE tmp_products;
Run Code Online (Sandbox Code Playgroud)

再一次,没有任何帮助。我仍然在我离开的地方 - 20 秒 2000 行。在我的工作场所,通常 20 秒的更新是可以接受的,但是 2000 行的文件是我用于测试的一个小样本。较大的文件将花费太长时间。

products如果重要的话,有关该表的一些详细信息:

行:~630k
列:54
索引:19
触发器:14
表大小:~1.2GB
索引大小:~2.2GB

我强烈怀疑瓶颈在一个或多个触发器中,但是我无法删除/修改这些触发器。我可以做些什么来提高我的更新效率?

joa*_*olo 5

可以做的事情,尽管它们是否有帮助......是另一个故事:

  1. 鉴于你UPDATE很简单,我的第一个猜测是你的触发器正在损害你的表现。处理触发器通常很耗时,特别是如果它们是用任何解释性语言编写的(这意味着,或多或少,它们不是用 C 编写的)。如果您有可能使用开发机器进行检查,请测试禁用所有触发器,并查看它有什么影响(时间查询!)。然后一一重新启用它们,看看每个对时间有什么影响。你会发现一些伤害(很多)的触发器。如果有几个很费时间,请有资格的人修改,优化它们,甚至必要时用C重写它们。我的经验是,任何一种记录审核您的插入可能会使过程变慢(容易地)10 倍。考虑到,在您的 14 个触发器之上,数据库可能添加了更多触发器以确保满足所有约束CHECK, REFERENCES, UNIQUE, . ...)。尝试禁用它们通常不是一个好主意(如果可能的话,这样做并不简单)。

  2. 尝试找出您是否真的需要设置中的所有索引。在 PostgreSQL wiki 上查看有关未使用索引的解释。您的查询的工作方式(仅更新列detail),如果detail不是任何索引的一部分,则不会产生太大影响。PostgreSQL应该能够执行Heap Only Tuple (HOT)更新,并且索引不会有任何大的影响。

  3. 要使 HOT 更新成功,您的表中需要一些可用空间。因此,请确保您的表fillfactor少于 100。从文档开始CREATE TABLE

    fillfactor (integer)

    表的填充因子是 10 到 100 之间的百分比。100(完全填充)是默认值。当指定较小的填充因子时,INSERT 操作仅将表页打包到指定的百分比;每页上的剩余空间保留用于更新该页上的行。这使 UPDATE 有机会将行的更新副本放置在与原始页面相同的页面上,这比将其放置在不同的页面上效率更高。对于条目从不更新的表,完全填充是最佳选择,但在大量更新的表中,较小的填充因子是合适的。不能为 TOAST 表设置此参数。

    (强调我的)

  4. 考虑在临时表上设置覆盖索引。那是:

    CREATE INDEX tmp_products_idx
    ON tmp_products
    USING BTREE
    (product_id, detail);
    
    ANALYZE tmp_products;
    
    Run Code Online (Sandbox Code Playgroud)

    这只有在长度detail适中时才有意义。我认为这不会有太大的不同......因为这可能允许对更新的部分进行仅索引扫描,但除非您尝试,否则您不会确定。

需要更多有关您的执行计划的信息才能提供更好的建议。