Djangoでオブジェクトを一括作成・更新【bulk_create, bulk_update】

266, 2021-06-09

目次

Djangoのbulk_createでオブジェクトを一括作成

Djangoではモデルを通してオブジェクトをデータベースに作成することができます。
この用途によく使われるのがModel.objects.create()です。

しかし大量のモデルを作成しようとするとModel.objects.create()はパフォーマンスを発揮してくれません。
こういう時に使うのがModel.objects.bulk_create()です。

bulk_create()を使うと大量のモデルを一括でデータベースに挿入することができます。
普通のcreate()に比べて非常に高速です。

結論から言うとbulk_create()でオブジェクトを一括作成するには↓のようなコードを書きます。

from core.models import Article


objs = []
for i in range(10000):
    obj = Article(title=f'{i} article')
    objs.append(obj)

Article.objects.bulk_create(objs)

この記事ではbulk_create()bulk_update()の詳細について詳しく解説します。
具体的には↓を見ていきます。

  • create()を使ったオブジェクトの作成

  • bulk_create()を使ったオブジェクトの作成

  • bulk_update()を使ったオブジェクトの更新

create()を使ったオブジェクトの作成

最初に従来の方法を見てみます。
bulk_create()を使わない場合はModel.objects.create()などでオブジェクトを作成するのが普通でした。
↓のようなコードです。

from core.models import Article


for i in range(10000):
    Article.objects.create(title=f'{i} article')

↑のコードは実行してみると非常に遅いのがわかります。
10000件のオブジェクトをデータベースに挿入するわけですが、クエリをcreate()の呼び出しごとに発行しているので、非常に効率が悪いです。
筆者の環境では数分待っても処理が終わりませんでした。

クエリを分割して実行するのではなく、1つのクエリにまとめれば早くなりそうです。
これを実現するのがbulk_create()です。

bulk_create()を使ったオブジェクトの作成

bulk_create()は↓のような構造を持っています。

bulk_create(objs, batch_size=None, ignore_conflicts=False)

第1引数のobjsにはオブジェクトのリストを渡します。
第2引数にはbatch_sizeで、一度のクエリで作成するオブジェクトの数を指定します。デフォルトでは(SQLiteとOracleを除き)すべてのオブジェクトを一回のクエリで作成します。
第3引数のignore_conflictsTrueにすると、bulk_create()は一意なキーに由来するエラーなどを無視するようになります。

bulk_create()は↓のように使います。

from core.models import Article


objs = []
for i in range(10000):
    obj = Article(title=f'{i} article')
    objs.append(obj)

Article.objects.bulk_create(objs)

最初にリストにオブジェクトを1万個保存しておいて、そのリストを↑のようにbulk_create()に渡します。
こうするとbulk_create()がリスト(objs)を使って1つのクエリにまとめて発行してくれます。
こちらのコードは数秒で処理が終わりました。
先ほどのcreate()のコードが5分ぐらいだとすると最低でも60倍は早いということになります。

batch_sizeの挙動は↓のコードで確かめられます。

from core.models import Article


objs = []
for i in range(10):
    obj = Article(title=f'{i} article')
    objs.append(obj)

articles = Article.objects.bulk_create(objs, batch_size=2)
print(articles)

↑のコードを実行すると↓のような結果になります。

[<Article: Article object (None)>, <Article: Article object (None)>, <Article: Article object (None)>, <Article: Article object (None)>, <Article: Article object (None)>, <Article: Article object (None)>, <Article: Article object (None)>, <Article: Article object (None)>, <Article: Article object (None)>, <Article: Article object (None)>]

batch_sizeを小さくしても最終的に作成されるオブジェクトのサイズは変わりません。
↓はSqlite3でデータベースを見た場合です。

sqlite> select * from core_article;
1|0 article|
2|1 article|
3|2 article|
4|3 article|
5|4 article|
6|5 article|
7|6 article|
8|7 article|
9|8 article|
10|9 article|

bulk_update()を使ったオブジェクトの更新

bulk_update()は↓のような構造になっています。

bulk_update(objs, fields, batch_size=None)

第1引数には更新させたいオブジェクトのリストを渡します。
第2引数には更新させたいフィール名をリストと文字列で指定します。
第3引数のbatch_sizeは一度のクエリで作成するオブジェクトの数を制限します。デフォルトでは(SQLiteとOracleを除き)すべてのオブジェクトを一回で更新します。

bulk_updateは↓のように使います。

from core.models import Article


objs = [
    Article.objects.create(title='article 1'),
    Article.objects.create(title='article 2'),
]

objs[0].title = '記事 1'
objs[1].title = '記事 2'

Article.objects.bulk_update(objs, ['title'])

↑の場合、最初にobjsに2つオブジェクトを保存しています。それぞれtitleフィールドがarticle 1article 2になっています。
その後にobjs[0].title = '記事 1'のようにタイトルを日本語に変更します。
この状態のオブジェクトはまだデータベースに保存されていませんが、bulk_update()を使うと一括して更新できます。
bulk_update()の第1引数にオブジェクトのリストを渡し、第2引数に更新させたいフィールドを指定します。
結果的にデータベース(SQLite)の中身は↓のようになります。

sqlite> select * from core_article;
1|記事 1|
2|記事 2|

おわりに

今回はDjangoのbulk_create()bulk_update()について見てみました。
大量のオブジェクトを作成・更新したいときはこれらのメソッドを使うと吉ですね。

bulkは「大部分」とか「大半」って意味だよ

bulk_create()で大量挿入!