Bulk_createを使用して数千または行をpostgresql DBにロードしています。残念ながら、一部の行はIntegrityErrorを引き起こし、bulk_createプロセスを停止しています。 Djangoにそのような行を無視してバッチのできるだけ多くを保存するように指示する方法があったかどうか疑問に思っていましたか?
Django 2.2は documentation からignore_conflicts
メソッドに新しいbulk_create
オプションを追加します:
これをサポートするデータベース(PostgreSQL <9.5とOracleを除くすべて)では、ignore_conflictsパラメータをTrueに設定すると、重複する一意の値などの制約を満たさない行の挿入の失敗を無視するようにデータベースに指示します。このパラメーターを有効にすると、各データベースでの主キーの設定が無効になります(データベースで通常サポートされている場合)。
例:
Entry.objects.bulk_create([
Entry(headline='This is a test'),
Entry(headline='This is only a test'),
], ignore_conflicts=True)
(注:私はDjangoを使用しないので、より適切なフレームワーク固有の回答があるかもしれません)
Djangoでは、PostgreSQLが最初のエラーでトランザクション全体を中止するため、INSERT
エラーを無視するだけではこれを行うことはできません。
Djangoはこれらのアプローチの1つを必要とします:
INSERT
各行を個別のトランザクションで処理し、エラーを無視します(非常に遅い);SAVEPOINT
を作成します(スケーリングの問題が発生する可能性があります);COPY
データをTEMPORARY
テーブルに入れ、それをメインテーブルのサーバー側にマージします。アップサートのようなアプローチ(3)は良い考えのように見えますが、 アップサートと挿入-存在しない場合は驚くほど複雑です です。
個人的には、私は(4)を使用します。新しい個別のテーブル(おそらくUNLOGGED
またはTEMPORARY
)に一括挿入し、手動でSQLを実行します。
LOCK TABLE realtable IN EXCLUSIVE MODE;
INSERT INTO realtable
SELECT * FROM temptable WHERE NOT EXISTS (
SELECT 1 FROM realtable WHERE temptable.id = realtable.id
);
LOCK TABLE ... IN EXCLUSIVE MODE
行を作成する同時挿入が、上記のステートメントによって実行された挿入と競合して失敗することを防ぎます。同時SELECT
sを防止します防止しませんだけですSELECT ... FOR UPDATE
、INSERT
、UPDATE
およびDELETE
なので、テーブルからの読み取りは通常どおり続行されます。
同時書き込みを長時間ブロックする余裕がない場合は、代わりに書き込み可能なCTEを使用して、行の範囲をtemptable
からrealtable
にコピーし、失敗した場合は各ブロックを再試行します。
手動のSQLと一時テーブルを含まない、このための迅速で汚い回避策の1つは、データの一括挿入を試みることです。失敗した場合は、シリアル挿入に戻ります。
objs = [(Event), (Event), (Event)...]
try:
Event.objects.bulk_create(objs)
except IntegrityError:
for obj in objs:
try:
obj.save()
except IntegrityError:
continue
エラーがたくさんある場合、これはそれほど効率的ではない可能性があります(一括して挿入するよりも逐次挿入に時間がかかります)が、重複の少ない高カーディナリティのデータセットを使用しているため、これによりほとんどの問題が解決されます問題。
または5.分割して征服する
私はこれを徹底的にテストまたはベンチマークしませんでしたが、それは私にとってはかなりうまくいきます。 YMMV。特に、一括操作で予想されるエラーの数に依存します。
def psql_copy(records):
count = len(records)
if count < 1:
return True
try:
pg.copy_bin_values(records)
return True
except IntegrityError:
if count == 1:
# found culprit!
msg = "Integrity error copying record:\n%r"
logger.error(msg % records[0], exc_info=True)
return False
finally:
connection.commit()
# There was an integrity error but we had more than one record.
# Divide and conquer.
mid = count / 2
return psql_copy(records[:mid]) and psql_copy(records[mid:])
# or just return False
前の回答Django 2.2プロジェクト:
私は最近この状況に遭遇し、一意性をチェックするための2番目のリスト配列で自分の道を見つけました。
私の場合、モデルには一意の一括チェックがあり、一括作成の配列に重複データがあるため、一括作成は整合性エラー例外をスローします。
そこで、オブジェクトの一括作成リストの他にチェックリストを作成することにしました。これがサンプルコードです。一意のキーはownerおよびbrandであり、この例では所有者はユーザーオブジェクトインスタンスとブランドは文字列インスタンスです。
create_list = []
create_list_check = []
for brand in brands:
if (owner.id, brand) not in create_list_check:
create_list_check.append((owner.id, brand))
create_list.append(ProductBrand(owner=owner, name=brand))
if create_list:
ProductBrand.objects.bulk_create(create_list)
Django 1.11でも、これを行う方法はありません。RawSQLを使用するよりも良いオプションを見つけました。それは djnago-query-builder を使用しています。これには- psert メソッド
from querybuilder.query import Query
q = Query().from_table(YourModel)
# replace with your real objects
rows = [YourModel() for i in range(10)]
q.upsert(rows, ['unique_fld1', 'unique_fld2'], ['fld1_to_update', 'fld2_to_update'])
注:ライブラリはpostgreSQLのみをサポートします
以下は Gist です。これは、IntegrityErrorsの無視をサポートし、挿入されたレコードを返す一括挿入に使用します。