モデルをキャッシュし、トランザクションの終了までモデルの保存を延期しようとする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でテストしました。
結果は次のとおりです。
結論:1つのconnection.execute()で可能な限り多くの操作を実行します。 Django自体はかなりのオーバーヘッドをもたらします。
免責事項:デフォルトの主キーインデックス以外のインデックスは導入していません。そのため、挿入操作がより高速に実行される可能性があります。
バッチトランザクション作業に3つの戦略を使用しました。
Session
に対してflush()
メソッドを実行します。 JDBCバッチ処理と同じことを実現します。ちなみに、Hibernateはコレクションのフェッチにおけるバッチ処理戦略もサポートしています。コレクションに@BatchSize
アノテーションを付けると、関連付けをフェッチするときに、Hibernateは=
の代わりにIN
を使用し、コレクションをロードするSELECT
ステートメントが少なくなります。
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..
を説明しています。それらは読みやすいかもしれませんが、固定された行数に制限されています。
一括挿入は次のように実行できます。
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
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)]
)
自動コミットをオフにして、最後に1回だけコミットします。プレーンSQLでは、これは開始時にBEGINを発行し、終了時にCOMMITを発行することを意味します。実際のアップサートを行うには、 function を作成する必要があります。