【问题标题】:How can I speed up update/replace operations in PostgreSQL?如何加快 PostgreSQL 中的更新/替换操作?
【发布时间】:2010-11-01 00:47:29
【问题描述】:

我们有一个相当具体的应用程序,它使用 PostgreSQL 8.3 作为存储后端(使用 Python 和 psycopg2)。我们对重要表执行的操作在大多数情况下是插入或更新(很少删除或选择)。

出于理智的原因,我们创建了自己的类似Data Mapper 的层,该层工作得相当好,但它有一个大瓶颈,即更新性能。当然,我并不期望更新/替换方案会像“插入空表”那样快速,但如果能再接近一​​点就好了。

请注意,此系统没有并发更新

我们总是在更新时设置每行的所有字段,这可以从我在测试中使用“替换”一词的术语中看出。到目前为止,我已经尝试了两种方法来解决我们的更新问题:

  1. 创建一个需要更新行数组的replace() 过程:

    CREATE OR REPLACE FUNCTION replace_item(data item[]) RETURNS VOID AS $$
    BEGIN
        FOR i IN COALESCE(array_lower(data,1),0) .. COALESCE(array_upper(data,1),-1) LOOP
           UPDATE item SET a0=data[i].a0,a1=data[i].a1,a2=data[i].a2 WHERE key=data[i].key;
        END LOOP;
    END;
    $$ LANGUAGE plpgsql
    
  2. 创建一个insert_or_replace 规则,以便除偶尔删除之外的所有内容都变为多行插入

    CREATE RULE "insert_or_replace" AS
        ON INSERT TO "item"
        WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key)
        DO INSTEAD
            (UPDATE item SET a0=NEW.a0,a1=NEW.a1,a2=NEW.a2 WHERE key=NEW.key);
    

这两个都加快了更新速度,尽管后者减慢了插入速度:

Multi-row insert           : 50000 items inserted in  1.32 seconds averaging 37807.84 items/s
executemany() update       : 50000 items updated  in 26.67 seconds averaging  1874.57 items/s
update_andres              : 50000 items updated  in  3.84 seconds averaging 13028.51 items/s
update_merlin83 (i/d/i)    : 50000 items updated  in  1.29 seconds averaging 38780.46 items/s
update_merlin83 (i/u)      : 50000 items updated  in  1.24 seconds averaging 40313.28 items/s
replace_item() procedure   : 50000 items replaced in  3.10 seconds averaging 16151.42 items/s
Multi-row insert_or_replace: 50000 items inserted in  2.73 seconds averaging 18296.30 items/s
Multi-row insert_or_replace: 50000 items replaced in  2.02 seconds averaging 24729.94 items/s

关于测试运行的随机注释:

  • 所有测试都在数据库所在的同一台计算机上运行;连接到本地主机。
  • 插入和更新以每 500 项为一批的方式应用于数据库,每一项都在自己的事务中发送(已更新)。
  • 所有更新/替换测试使用的值与数据库中已有的值相同。
  • 使用 psycopg2 adapt() 函数转义了所有数据。
  • 所有表在使用前都被截断和清理(添加,在之前的运行中只发生了截断)
  • 表格如下所示:

    CREATE TABLE item (
        key MACADDR PRIMARY KEY,
        a0 VARCHAR,
        a1 VARCHAR,
        a2 VARCHAR
    )
    

所以,真正的问题是:如何才能加快更新/替换操作的速度? (我认为这些发现可能“足够好”,但我不想在不挖掘 SO 人群的情况下放弃 :)

也欢迎任何人暗示更优雅的 replace_item(),或者我的测试完全被破坏的证据。

如果您想尝试复制,可以使用测试脚本here。不过记得先检查一下……它适用于我,但是……

您需要编辑 db.connect() 行以适合您的设置。

编辑

感谢 #postgresql @ freenode 中的 andres 我有另一个单查询更新测试;很像多行插入(上面列为 update_andres)。

UPDATE item
SET a0=i.a0, a1=i.a1, a2=i.a2 
FROM (VALUES ('00:00:00:00:00:01', 'v0', 'v1', 'v2'), 
             ('00:00:00:00:00:02', 'v3', 'v4', 'v5'),
             ...
      ) AS i(key, a0, a1, a2)
WHERE item.key=i.key::macaddr

编辑

感谢 #postgresql @ freenode 中的 merlin83 和下面的 jug/jwp 我有另一个使用插入到临时/删除/插入方法的测试(上面列为“update_merlin83 (i/d/i)”)。

INSERT INTO temp_item (key, a0, a1, a2)
    VALUES (
        ('00:00:00:00:00:01', 'v0', 'v1', 'v2'),
        ('00:00:00:00:00:02', 'v3', 'v4', 'v5'),
        ...);

DELETE FROM item
USING temp_item
WHERE item.key=temp_item.key;

INSERT INTO item (key, a0, a1, a2)
    SELECT key, a0, a1, a2
    FROM temp_item;

我的直觉是,这些测试并不能很好地代表真实场景中的性能,但我认为这些差异足以说明最有希望进行进一步调查的方法。 perftest.py 脚本还包含所有更新,供那些想要查看它的人使用。虽然它相当丑陋,所以不要忘记你的护目镜:)

编辑

#postgresql @freenode 中的 andres 指出我应该使用 insert-to-temp/update 变体进行测试(上面列为“update_merlin83 (i/u)”)。

INSERT INTO temp_item (key, a0, a1, a2)
    VALUES (
        ('00:00:00:00:00:01', 'v0', 'v1', 'v2'),
        ('00:00:00:00:00:02', 'v3', 'v4', 'v5'),
        ...);

UPDATE item
SET a0=temp_item.a0, a1=temp_item.a1, a2=temp_item.a2
FROM temp_item
WHERE item.key=temp_item.key

编辑

可能是最终编辑: 我更改了我的脚本以更好地匹配我们的负载场景,并且似乎数字即使在放大一点​​并添加一些随机性时也保持不变。如果有人从其他场景中得到非常不同的数字,我会很想知道。

【问题讨论】:

  • 相关表中的索引如何?有外键吗?
  • 不在测试脚本中,没有。在现实世界中,一个。
  • 你能发布一个EXPLAIN ANALYZEUPDATE 吗?我想知道估算员认为应该发生什么。
  • 不是真的,自从我提出这个问题以来,我们在这两年里取得了长足的进步 :)

标签: python sql postgresql psycopg2


【解决方案1】:

我在 pg 中做这些事情的常用方法是:使用复制、合并(有趣的部分)、利润将原始数据匹配目标表加载到临时表(无约束)。

我专门针对这些情况写了一个merge_by_key函数:

http://mbk.projects.postgresql.org/

文档不是非常友好,但我建议给它一个的外观。

【讨论】:

  • 一般过程的要点是:加载到 temp 以避免任何网络往返成本并避免为每个加载的行创建多个游标(门户)(是的,executemany 速度很快,但是 COPY wtf-pwns-it wrt 效率)。使用合并函数/过程来避免创建改变插入语句语义的规则/触发器。我已经完成了这两种方式,而且我一直更喜欢合并过程,因为它是明确的。如果合并过程不够高效,则需要查看索引挂起(重新创建)/分区或pgfoundry.org/projects/pgbulkload
【解决方案2】:

几个月前我遇到过类似的情况,最终通过调整块/事务大小获得了最大的速度提升。您可能还想在测试期间检查日志以获取检查点警告并进行适当调整。

【讨论】:

  • 我一定会寻找检查点警告,它看起来非常相关;谢谢!
【解决方案3】:

听起来您会看到使用带有 UPS 的 WAL(预写日志)在磁盘写入之间缓存更新的好处。

wal_buffers 这个设置决定了 WAL(Write ahead Log) 可以拥有的缓冲区数量。如果您的数据库有许多写入事务,则将此值设置为高于默认值可能会更好地使用磁盘空间。试验和决定。一个好的开始是大约 32-64 对应于 256-512K 内存。

http://www.varlena.com/GeneralBits/Tidbits/perf.html

【讨论】:

    【解决方案4】:

    在您的insert_or_replace 中。试试这个:

    WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key LIMIT 1)
    

    而不是

    WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key)
    

    如 cmets 中所述,这可能无济于事。那么,我要补充的是,您始终可以通过删除索引来加快 INSERT/UPDATE 性能。除非您发现您的表被过度索引,否则这可能不是您想要做的事情,但至少应该检查一下。

    【讨论】:

    • 这很可能是不必要的——摘自文档 (postgresql.org/docs/current/static/…):“子查询通常只会执行到足以确定是否至少返回一行,而不是一直到完成。 "
    • 键是唯一的,所以它只会返回一行。尽管如此,我尝试过,无论哪种方式,性能都没有明显变化。不过谢谢!
    【解决方案5】:

    在 Oracle 中,锁定表肯定会有所帮助。您可能也想在 PostgreSQL 上尝试一下。

    【讨论】:

    • 我尝试在所有事务中锁定表的情况下运行所有​​测试;没有变化。
    【解决方案6】:

    对于更新,您可以降低表和索引的填充因子,这可能会有所帮助

    http://www.postgresql.org/docs/current/static/sql-createtable.html

    http://www.postgresql.org/docs/current/static/sql-createindex.html

    【讨论】:

      猜你喜欢
      • 2011-08-22
      • 1970-01-01
      • 2013-05-05
      • 1970-01-01
      • 2020-05-26
      • 1970-01-01
      • 1970-01-01
      • 2016-03-09
      • 1970-01-01
      相关资源
      最近更新 更多