如何加快PostgreSQL中的更新/替换操作?

时间:2009-06-07 17:28:57

标签: python sql postgresql psycopg2

我们有一个非常具体的应用程序,它使用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);
    
  3. 这两者都加快了更新速度,虽然后者减慢了插入量:

    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
    

    关于测试运行的随机说明:

    • 所有测试都在数据库所在的同一台计算机上运行;连接到localhost。
    • 插入和更新以500件商品的批量应用于数据库,每件都在自己的交易中发送(更新)。
    • 所有更新/替换测试都使用与数据库中已有的相同的值。
    • 使用psycopg2 adapt()函数转义所有数据。
    • 所有表格在使用前都被截断并抽真空(已添加,在以前的运行中仅发生截断)
    • 表格如下:

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

    所以,真正的问题是:如何加快更新/替换操作的速度? (我认为这些调查结果可能“足够好”,但我不想在没有利用SO人群的情况下放弃:)

    任何人都暗示更优雅的replace_item(),或者证明我的测试完全被破坏是非常受欢迎的。

    如果您想尝试重现,测试脚本可用here。请记住首先检查它...但它是WorksForMe,但是......

    您需要编辑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和jug / jwp中的merlin83,我有另一个带有insert-to-temp / delete / insert方法的测试(上面列为“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脚本包含所有更新以及那些想要检查它的人。它虽然相当丑陋,所以不要忘记你的护目镜:)

    修改

    andres in #postgresql @ freenode指出我应该使用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
    

    修改

    可能是最终编辑: 我更改了我的脚本以更好地匹配我们的加载方案,并且即使在稍微扩展一些并添加一些随机性时,似乎数字也保持不变。如果有人从其他场景得到非常不同的数字,我会有兴趣了解它。

6 个答案:

答案 0 :(得分:4)

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

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

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

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

答案 1 :(得分:2)

几个月前我遇到了类似情况,最终从调整的块/交易规模中获得了最大的速度提升。您可能还希望在测试期间检查日志以查看检查点警告并进行适当调整。

答案 2 :(得分:2)

听起来你会看到使用WAL(写前瞻记录)与UPS在磁盘写入之间缓存更新的好处。

  

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

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

答案 3 :(得分:1)

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)

如评论中所述,这可能无济于事。因此,我必须添加的是,您始终可以通过删除索引来加快INSERT / UPDATE性能。这可能不是你想要做的事情,除非你发现你的表被过度索引,但至少应该检查出来。

答案 4 :(得分:1)

在Oracle中,锁定表肯定会有所帮助。您也可以尝试使用PostgreSQL。

答案 5 :(得分:1)