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

jul*_*icz 45 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').

我来到这篇文章:

为什么批量插入/更新更快?批量更新如何工作?

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

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

我使用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本身带来了巨大的开销.

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

hag*_*llo 61

批量插入

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

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

它成为了:

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

用占位符替换值:

INSERT INTO "table" (col1, col2, col3)
  VALUES (unnest(?), unnest(?), unnest(?))
Run Code Online (Sandbox Code Playgroud)

您必须将数组或列表作为参数传递给此查询.这意味着你可以在不进行字符串连接的情况下进行大量的批量插入(以及它的所有谜题和危险: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;
Run Code Online (Sandbox Code Playgroud)

该手册缺少一个很好的解释,但postgresql-admin邮件列表上有一个例子.我试着详细说明:

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;
Run Code Online (Sandbox Code Playgroud)

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

  • 发现`update``` ...`from`示例特别有用,谢谢。 (2认同)
  • 我可以保证这种批量插入方法。我注意到用逗号分隔多个值语句与完全独立的插入语句相比有一个数量级的改进。我的特定插入在几分钟内完成,而不是像以前那样需要一个小时。 (2认同)
  • 对于上面所示的Insert用法,是否可以添加“ON CONFLICT”子句?如果是这样,它是如何运作的? (2认同)

atr*_*ain 14

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

  1. 动态生成SQL语句,用分号连接它们,然后一次性提交语句.我用这种方式完成了100次插入,效率很高(针对Postgres完成).
  2. 如果已配置,JDBC具有内置的批处理功能.如果生成事务,则可以刷新JDBC语句,以便它们一次性进行事务处理.这种策略需要较少的数据库调用,因为语句都是在一个批处理中执行的.
  3. Hibernate还支持前一个示例中的JDBC批处理,但在这种情况下,您执行flush()针对Hibernate 的方法Session,而不是基础JDBC连接.它完成了与JDBC批处理相同的功能.

顺便说一下,Hibernate还支持集合提取中的批处理策略.如果您使用@BatchSize,在获取关联时使用Hibernate来IN代替,则会=导致使用较少的SELECT语句来加载集合.


Ket*_*ema 12

批量插入可以这样做:

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

将插入3行.

SQL标准定义了多次更新,但PostgreSQL中没有实现.

引用:

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

UPDATE帐户SET(contact_last_name,contact_first_name)=(SELECT last_name,first_name FROM salesmen WHERE salesmen.id = accounts.sales_id);"

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


nog*_*gus 6

将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)]
)
Run Code Online (Sandbox Code Playgroud)