1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Djangoマイグレーションの `atomic` を理解する

1
Last updated at Posted at 2026-06-28

はじめに

Django のマイグレーションには atomic という属性があり、そのマイグレーションをトランザクションでどう囲むかを決めます。

atomic を理解すると、次のような挙動を意図して制御できるようになります。

  • マイグレーションが途中で失敗したときに、どこまで残り、どこから消えるのか
  • 大量データ移行を、1つの巨大なトランザクションにまとめるか、バッチに分割するか
  • 失敗後に、安全に再実行できるか

この記事では、atomic の基本から、大きいデータ移行を安全に書く方法までをまとめています。

(バージョンは Django 5.2 LTS を基準にします)

atomic とは

原子性

atomic(アトミック)は「原子的」、つまりそれ以上分割できない一つのまとまりという意味です。
データベースでは、トランザクションが満たすべき性質 ACID の頭文字「A」= Atomicity(原子性) がこれにあたります。

原子性とは、トランザクション内の処理は「全部成功する」か「全部なかったことにする(ロールバック)」かのどちらかで、中途半端な状態を残さないという保証です。「all-or-nothing」とも言われます。

原子性についてACID特性の記事で書いています。

Django マイグレーションの atomic は、この原子性(全部成功か、全部ロールバックか)をマイグレーション全体に効かせるかどうかを決める設定です。

マイグレーションの atomic 属性

atomicMigration クラスの属性で、デフォルトは True です。

class Migration(migrations.Migration):
    atomic = False
    operations = [...]

atomic = True は、そのマイグレーション内のすべての操作(テーブルの変更、RunPython によるデータの書き換え)を、まとめて1つのトランザクションで囲むことです。
つまり、マイグレーション全体が「全部成功するか、全部ロールバックするか」のまとまりになるということです。逆に atomic = False は、全体を1つにまとめません。

このように atomic は、マイグレーションのどこまでを1つのトランザクションにするかという境界を決めます。

TrueFalse で何が変わるか

atomic = True(デフォルト)

atomic = True では、マイグレーション全体が1つのトランザクションになります。

途中で失敗すると、以下の図のようになります。

image.png

全体が1トランザクションなので、途中のどこかで失敗すれば、それまでの変更もすべてロールバックされます。中途半端な状態が残らないのが利点です。

一方で、大量データを更新するマイグレーションでは、デメリットもあります。

  • 全体が完了するまで何もコミットされない(途中経過を保存できない)
  • 失敗すると最初からやり直しになる

atomic = False

atomic = False では、マイグレーション全体を1つのトランザクションにしません。

途中で失敗すると、以下の図のようになります。

image.png

マイグレーションを1つのトランザクションで囲まないので、各操作は個別に確定されます。そのため、次のようなメリットがあります。

  • 途中で失敗しても、それまでに更新できたデータはそのまま残る
  • 処理済みのレコードをスキップする作り(冪等)にしておけば、再実行で続きから処理できる

大きいテーブルのデータ移行は、Django 公式ドキュメントでも atomic = False の使いどころとして挙げられています。

大きいデータ移行を安全に書く

atomic = False にすると、大きいデータ移行を「途中で失敗してもそこまで残り、再実行で続きから処理できる」形で書けます。

以下の例(公式の gen_uuid =各行に UUID を生成して埋めるデータ移行)では、「1000件ずつ」を1トランザクションにまとめています。

import uuid

from django.db import migrations, transaction

def gen_uuid(apps, schema_editor):
    MyModel = apps.get_model("myapp", "MyModel")
    while MyModel.objects.filter(uuid__isnull=True).exists():
        with transaction.atomic(): # このブロックだけ1トランザクション
            for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
                row.uuid = uuid.uuid4()
                row.save()

class Migration(migrations.Migration):
    atomic = False

    operations = [
        migrations.RunPython(gen_uuid),
    ]
  • while ... .exists()[:1000]:一度に全件ではなく、1000件ずつ、未処理がなくなるまで繰り返す
  • filter(uuid__isnull=True):まだ uuid が空の行だけを対象にするので、再実行すれば残りだけ処理される
  • transaction.atomic():1000件を1回でコミットする。1件ずつより速く、各バッチは「全部成功 or 全部失敗」になる
  • atomic = False:マイグレーション全体を1つのトランザクションにしない。途中で失敗しても、それまでの分は残る

atomic = False でないと、マイグレーション全体が1つのトランザクションになり、transaction.atomic() は独立してコミットされず、マイグレーション全体が終わるまで確定が保留されてしまいます。

補足:DB による前提

本記事は、トランザクショナル DDL に対応した PostgreSQL・SQLite を前提にしています。

MySQL・Oracle はスキーマ変更(DDL)をトランザクションで囲めません。そのため、図のような「マイグレーション全体を1つのトランザクションにまとめる」挙動にはなりません。

一方、データ更新(UPDATE など)のトランザクションには対応しているので、バッチごとに transaction.atomic() でコミットする書き方は MySQL でも動きます。

まとめ

atomic は、マイグレーションを1つのトランザクションで囲むかどうかを決める設定です。

  • 全件を「すべて成功 or すべて失敗」にしたい → デフォルトの atomic = True
  • 大きいデータ移行で途中保存・再実行したい → atomic = False(バッチを transaction.atomic() で囲み、冪等な処理にする)

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?