bulk_createを実際に使用してみたのでそれがなぜ早いのか調べてみました。
公式ドキュメントより
This method inserts the provided list of objects into the database in an efficient manner (generally only 1 query, no matter how many objects there are):
ざっくり、オブジェクトのリストをDBに効率的に入れます。クエリは一つです。とのこと。
結論から言うと、トランザクションとして処理されているからです。
トランザクションとは
トランザクションは一言で言うと処理のまとまり
処理を具体例とともに見ます。
-
AさんはBさんに1万円送る。
-
Bさんの口座に1万円入る。
これはこの順番で両方とも実行完了されるか、両方とも実行完了されないと口座の計算が合わなくてとんでもないことになります。
この両方とも実行完了するか、両方とも実行完了されないという特性を原子性と言います。
少し道はそれましたがこの1, 2が処理です。
このまとまりがトランザクションです。
トランザクションはどのように作られるのか
- 開始文を書く
- SQLを書く
- 終了文を書く
このようにして作られます。
例
START TRANSACTION; # 1. 開始文を書く
INSERT INTO user (name) VALUES ('hoge'); # 2. SQLを書く
COMMIT; 3. 終了文を書く
COMMIT
トランザクションを確定させる処理。
ROLLBACK
トランザクションを取り消す処理。
ACID特性
トランザクションには4つの特性があります。
- 原子性(Atomicity)
トランザクションが全て実行されたか、全て実行されていない状態で終わることを保証する性質。
COMMITするかROLLBACKするかのどっちか。
- 一貫性(Consistency)
トランザクションに含まれる処理はそれぞれの制約(Not Null, primary keyなど)を満たす性質。
- 独立性(Isolation)
トランザクションが実行中の場合、その他のトランザクションの影響を受けない性質。
この辺はRDBMSがうまくやっているらしい。ここでは触れません。
- 永続性(Durability)
トランザクションが完了した後、そのデータ状態が保存され失われることは無いという性質。
何か怒ってもそれまでの結果は保証しますよ。
この4つの特性があります。
create
とbulk_create
の違い
bulk_create
を説明する上でcreate
を引っ張ってきて説明する方が楽なので比べました。
例えば以下のようなモデルがあったとします。
models.py
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=30)
レコードを挿入しようとするときDjangoでは以下のように書く。
Person.objects.create(name='hoge')
次に、最初にユーザーを1万人入れておきたい。となった場合どうするだろうか。
一番考えやすいのは以下のようなコードだと思います。
for i in range(10000):
User.objects.create(name='fuga') #ここでDBに問い合わせ
これで1万人のfugaさんが入る。
ただ、これには欠点があります。とても時間がかかることです。
bulk_createの場合は以下のようになる。
users = []
for i in range(10000):
user = User(name='fuga')
users.append(user)
User.objects.bulk_create(users) #ここでDBに問い合わせ
bulk_createの内部実装
...(略)
with transaction.atomic(using=self.db, savepoint=False):
...
そう、ここにトランザクションを生成している。
まとめ
DBに問い合わせをしているため処理に時間がかかるが、bulk_createの場合、トランザクションとしてDBの問い合わせは一回なので時間早い。