PostgreSQL中的批量/批量更新/ upsert

时间:2011-08-11 01:23:11

标签: sql database postgresql insert sql-update

我正在编写一个Django-ORM增强版,它尝试缓存模型并推迟模型保存直到事务结束。这几乎已经完成了,但是我在SQL语法中遇到了意想不到的困难。

我不是一名DBA,但据我所知,数据库对于许多小型查询并不能真正有效地工作。几个更大的查询要好得多。例如,最好使用大批量插入(比如一次100行)而不是100个单行插入。

现在,从我所看到的,SQL并没有真正提供任何语句来对表执行批量更新。这个词似乎是令人困惑所以,我会解释我的意思。我有一个任意数据数组,每个条目描述一个表中的一行。我想更新表中的某些行,每个行都使用数组中相应条目的数据。这个想法非常类似于批量插入。

例如:我的表可以有两列"id""some_col"。现在,描述批量更新数据的数组包含三个条目(1, 'first updated')(2, 'second updated')(3, 'third updated')。在更新之前,该表包含行:(1, 'first')(2, 'second')(3, 'third')

我来到这篇文章:

Why are batch inserts/updates faster? How do batch updates work?

这似乎做了我想要的,但我最终无法弄清楚语法。

我还可以删除所有需要更新的行,并使用批量插入重新插入它们,但我发现很难相信这实际上会有更好的表现。

我使用PostgreSQL 8.4,因此这里也可以使用一些存储过程。然而,当我计划最终开源项目时,我们非常欢迎任何更便携的想法或在不同的RDBMS上做同样事情的方法。

跟进问题:如何批处理“插入或更新”/“upsert”语句?

测试结果

我已经执行了100次10次插入操作,分布在4个不同的表中(总共1000个插入)。我使用PostgreSQL 8.4后端在Django 1.3上进行了测试。

结果如下:

  • 通过Django ORM完成的所有操作 - 每次通过 ~2.45秒
  • 相同的操作,但没有Django ORM - 每次传递 ~1.48秒
  • 仅插入操作,而不查询数据库的序列值 ~0.72秒
  • 仅插入操作,以10块为单位执行(总共100块) ~0.19秒
  • 仅插入操作,一个大的执行块 ~0.13秒
  • 仅插入操作,每块约250条语句, ~0.12秒

结论:在单个connection.execute()中执行尽可能多的操作。 Django本身带来了巨大的开销。

免责声明:除了默认主键索引之外,我没有引入任何索引,因此插入操作可能会因此而运行得更快。

5 个答案:

答案 0 :(得分:50)

批量插入

您可以通过Ketema修改三列的批量插入:

INSERT INTO "table" (col1, col2, col3)
  VALUES (11, 12, 13) , (21, 22, 23) , (31, 32, 33);

变成:

INSERT INTO "table" (col1, col2, col3)
  VALUES (unnest(array[11,21,31]), 
          unnest(array[12,22,32]), 
          unnest(array[13,23,33]))

用占位符替换值:

INSERT INTO "table" (col1, col2, col3)
  VALUES (unnest(?), unnest(?), unnest(?))

您必须将数组或列表作为参数传递给此查询。这意味着你可以在不进行字符串连接的情况下进行大量的批量插入(以及它的所有谜题和危险:sql注入和引用地狱)。

批量更新

PostgreSQL已将FROM扩展添加到UPDATE。您可以这样使用它:

update "table" 
  set value = data_table.new_value
  from 
    (select unnest(?) as key, unnest(?) as new_value) as data_table
  where "table".key = data_table.key;

手册缺少一个很好的解释,但postgresql-admin mailing list上有一个例子。我试着详细说明一下:

create table tmp
(
  id serial not null primary key,
  name text,
  age integer
);

insert into tmp (name,age) 
values ('keith', 43),('leslie', 40),('bexley', 19),('casey', 6);

update tmp set age = data_table.age
from
(select unnest(array['keith', 'leslie', 'bexley', 'casey']) as name, 
        unnest(array[44, 50, 10, 12]) as age) as data_table
where tmp.name = data_table.name;

StackExchange上还有other posts使用UPDATE...FROM..子句而不是子查询来解释VALUES。它们可能更容易阅读,但仅限于固定数量的行。

答案 1 :(得分:13)

我使用了3种策略进行批量交易工作:

  1. 动态生成SQL语句,用分号连接它们,然后一次性提交语句。我用这种方式完成了100次插入,效率很高(针对Postgres完成)。
  2. JDBC具有内置的批处理功能(如果已配置)。如果生成事务,则可以刷新JDBC语句,以便它们一次性进行事务处理。这种策略需要较少的数据库调用,因为这些语句都是一批执行的。
  3. Hibernate也支持前一个示例中的JDBC批处理,但在这种情况下,您对Hibernate flush()执行Session方法,而不是基础JDBC连接。它完成了与JDBC批处理相同的事情。
  4. 顺便提一下,Hibernate还支持集合提取中的批处理策略。如果使用@BatchSize注释集合,则在获取关联时,Hibernate将使用IN而不是=,从而导致更少的SELECT语句来加载集合。

答案 2 :(得分:12)

批量插入可以这样完成:

INSERT INTO "table" ( col1, col2, col3)
  VALUES ( 1, 2, 3 ) , ( 3, 4, 5 ) , ( 6, 7, 8 );

将插入3行。

多重更新由SQL标准定义,但未在PostgreSQL中实现。

引用:

  

“根据标准,列列表语法应该允许列表   从单个行值表达式分配的列,例如   一个子选择:

     

UPDATE帐户SET(contact_last_name,contact_first_name)=       (SELECT last_name,first_name FROM salesmen        sales在哪里salesmen.id = accounts.sales_id);“

参考:http://www.postgresql.org/docs/9.0/static/sql-update.html

答案 3 :(得分:2)

将json填充到记录集(postgresql 9.3 +)

非常快
big_list_of_tuples = [
    (1, "123.45"),
    ...
    (100000, "678.90"),
]

connection.execute("""
    UPDATE mytable
    SET myvalue = Q.myvalue
    FROM (
        SELECT (value->>0)::integer AS id, (value->>1)::decimal AS myvalue 
        FROM json_array_elements(%s)
    ) Q
    WHERE mytable.id = Q.id
    """, 
    [json.dumps(big_list_of_tuples)]
)

答案 4 :(得分:0)

关闭自动提交,最后只进行一次提交。在纯SQL中,这意味着在开始时发出BEGIN,在结尾发出COMMIT。您需要创建一个function才能进行实际的upsert。