web-dev-qa-db-ja.com

PostgreSQLの一括/バッチ更新/アップロード

モデルをキャッシュし、トランザクションの終了までモデルの保存を延期しようとするDjango-ORM拡張機能を作成しています。ほとんどすべて完了しましたが、SQL構文に予期しない問題が発生しました。

私はあまりDBAではありませんが、私が理解していることから、データベースは多くの小さなクエリに対して効率的に機能しません。より大きなクエリはほとんどありません。たとえば、100個のワンライナーの代わりに、大規模なバッチ挿入(たとえば、一度に100行)を使用することをお勧めします。

今、私が見ることができることから、SQLはテーブルでバッチ更新を実行するためのステートメントを実際に提供していません。この用語はややこしいと思われるので、その意味を説明します。任意のデータの配列があり、各エントリはテーブルの単一の行を記述しています。テーブル内の特定の行を更新し、各行は配列内の対応するエントリのデータを使用します。この考え方は、バッチ挿入に非常に似ています。

例:私のテーブルには2つの列があります"id"および"some_col"。バッチ更新のデータを記述する配列は、3つのエントリ(1, 'first updated')(2, 'second updated')、および(3, 'third updated')。更新前のテーブルには行が含まれています:(1, 'first')(2, 'second')(3, 'third')

私はこの投稿に出くわしました:

なぜバッチの挿入/更新が速いのですか?バッチ更新はどのように機能しますか?

これは私が望むことを行うようですが、最後に構文を実際に理解することはできません。

また、更新が必要なすべての行を削除し、バッチ挿入を使用してそれらを再挿入することもできますが、実際にパフォーマンスが向上するとは信じられません。

私はPostgreSQL 8.4を使用しているため、ここでもいくつかのストアドプロシージャを使用できます。ただし、最終的にプロジェクトをオープンソース化する予定であるため、別のRDBMSで同じことを行うための、より移植性の高いアイデアや方法があれば大歓迎です。

次の質問:バッチの「挿入または更新」/「更新」ステートメントの実行方法

テスト結果

4つの異なるテーブルにまたがる10回の挿入操作を100回実行しました(合計で1000回の挿入)。 PostgreSQL 8.4バックエンドでDjango 1.3でテストしました。

結果は次のとおりです。

  • Django ORM-各パス〜2.45秒)で行われるすべての操作
  • 同じ操作ですが、Django ORM-各パス〜1.48秒)なしで行われます。
  • シーケンス値〜0.72秒をデータベースに照会せずに、挿入操作のみ
  • 10のブロックで実行される挿入操作のみ(合計100ブロック)〜0.19秒
  • 挿入操作のみ、1つの大きな実行ブロック〜0.13秒
  • 挿入操作のみ、ブロックあたり約250ステートメント、〜0.12秒

結論:1つのconnection.execute()で可能な限り多くの操作を実行します。 Django自体はかなりのオーバーヘッドをもたらします。

免責事項:デフォルトの主キーインデックス以外のインデックスは導入していません。そのため、挿入操作がより高速に実行される可能性があります。

44
julkiewicz

バッチトランザクション作業に3つの戦略を使用しました。

  1. SQLステートメントをその場で生成し、セミコロンで連結してから、ステートメントを一度に送信します。この方法で最大100件の挿入を行いましたが、非常に効率的でした(Postgresに対して行われました)。
  2. JDBCには、構成されている場合、バッチ機能が組み込まれています。トランザクションを生成する場合、JDBCステートメントをフラッシュして、1回で処理できるようにすることができます。ステートメントはすべて1つのバッチで実行されるため、この方法ではデータベース呼び出しが少なくて済みます。
  3. Hibernateは、前の例の行に沿ってJDBCバッチ処理もサポートしますが、この場合、基礎となるJDBC接続ではなく、Hibernate Sessionに対してflush()メソッドを実行します。 JDBCバッチ処理と同じことを実現します。

ちなみに、Hibernateはコレクションのフェッチにおけるバッチ処理戦略もサポートしています。コレクションに@BatchSizeアノテーションを付けると、関連付けをフェッチするときに、Hibernateは=の代わりにINを使用し、コレクションをロードするSELECTステートメントが少なくなります。

14
atrain

一括挿入

Ketemaによって3つの列の一括挿入を変更できます。

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メーリングリスト に例があります。私はそれを詳しく説明しようとしました:

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;

また、StackQueryには otherposts があり、サブクエリの代わりにVALUES句を使用してUPDATE...FROM..を説明しています。それらは読みやすいかもしれませんが、固定された行数に制限されています。

56
hagello

一括挿入は次のように実行できます。

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

3行を挿入します。

複数の更新はSQL標準で定義されていますが、PostgreSQLには実装されていません。

見積もり:

「標準に従って、列リストの構文では、サブセレクトなどの単一の行値式から列のリストを割り当てることができます。

アカウントの更新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

12
Ketema

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
nogus

自動コミットをオフにして、最後に1回だけコミットします。プレーンSQLでは、これは開始時にBEGINを発行し、終了時にCOMMITを発行することを意味します。実際のアップサートを行うには、 function を作成する必要があります。

0
aliasmrchips