teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

1

間違った回答の訂正と追加情報

2017/03/03 02:48

投稿

shimizukawa
shimizukawa

スコア1847

answer CHANGED
@@ -7,7 +7,9 @@
7
7
  modelのsave等でIntegrityError例外がおきる可能性はあります。
8
8
  しかし、ドキュメントに書いてあるのはそれに限らず、view関数がなんらかの例外を発生させて呼出元に伝搬した場合に何が起きるかを説明しています。
9
9
 
10
+ // 2017/3/3 訂正: ここから間違い(末尾に追記あり)
10
11
  また、ATOMIC_REQUESTS設定の場合、全てのviewが [atomic](https://docs.djangoproject.com/en/1.10/topics/db/transactions/#django.db.transaction.atomic) となるため、各viewが複数のリクエストで同時に実行されないことを保障できます。
12
+ // 2017/3/3 訂正: ここまで
11
13
 
12
14
  > そもそもDjangoは自動コミットだからあまりトランザクションを意識しなくても良い?のでしょうか。
13
15
  > トランザクションの話と自動コミットはあんまり関係ないような気がするんですが、理解不足で混乱しております。
@@ -15,4 +17,65 @@
15
17
  DBではかならずSQL発行時にトランザクションが作られますが、自動コミット設定では、各SQL発行時に自動的にトランザクションを開始して、そのSQL実行後に自動的にコミット or ロールバックが行われます。参考: [autocommit](https://docs.djangoproject.com/en/1.10/topics/db/transactions/#why-django-uses-autocommit)
16
18
 
17
19
 
18
- モデルのsave時のエラーで画面表示を切り替えたいのであれば、view関数内で `with atomic():` ブロックを用いて行うのがよさそうです。参考: [atomic](https://docs.djangoproject.com/en/1.10/topics/db/transactions/#django.db.transaction.atomic)
20
+ モデルのsave時のエラーで画面表示を切り替えたいのであれば、view関数内で `with atomic():` ブロックを用いて行うのがよさそうです。参考: [atomic](https://docs.djangoproject.com/en/1.10/topics/db/transactions/#django.db.transaction.atomic)
21
+
22
+ -------------
23
+
24
+ 2017/3/3 追記
25
+
26
+ ATOMIC_REQUESTS で各viewが複数のリクエストで同時に実行されないことを保障、というのは間違いです(別件の記憶を元に間違えて回答していました)
27
+
28
+ > ATOMIC_REQUESTSがTrueならトランザクション異常発生した場合、Djangoが自動(!)でトランザクションエラー発生してないように振る舞うんでしょうか?
29
+ > 同時アクセスしたユーザーはトランザクションエラーが発生したとは気づかずに穏便に結果を受け取る、という振る舞いになる?(DBのCRUDも正しく処理され終わる?)
30
+
31
+ いいえ。
32
+ トランザクションエラーが発生した場合はそのまま例外としてユーザーまで結果がもどります。
33
+ 間違った説明をしてしまい、すみませんでした。
34
+
35
+ `ATOMIC_REQUESTS=True` としている場合のview関数内(または、 `with transaction.atomic()` のwithブロック中)に例外が発生すると、その時点でsave済みのモデルも破棄(rollback)されます
36
+
37
+ ```Python
38
+ def view(request):
39
+ m = SomeModel.objects.create() # ここで保存される
40
+ do_some_process() # raise some exception!
41
+ return render(...)
42
+ ```
43
+
44
+ このコードの場合、 `ATOMIC_REQUESTS=True` を **設定していない** 場合は、do_some_process()が例外を発生させたとしても「ここで保存される」で本当にDBに保存されているので、DBは更新されます。
45
+ これに対して `ATOMIC_REQUESTS=True` と *設定している* 場合は、「ここで保存される」のはトランザクション上の話になるので、do_some_process()で例外が起きれば、rollbackされ、DBは更新されません。
46
+
47
+ もしtry/exceptで例外をview関数の外にもれないようにすれば、 `ATOMIC_REQUESTS=True` であってもDBは更新されます。
48
+
49
+ ```Python
50
+ # ATOMIC_REQUESTS=True
51
+ def view(request):
52
+ m = SomeModel.objects.create() # ここで保存される
53
+ try:
54
+ do_some_process() # raise some exception!
55
+ except:
56
+ pass # 例外にぎりつぶし
57
+ return render(...)
58
+ # viewk関数は正常に完了し、「ここで保存される」のデータはview関数を出たところでcommitされる
59
+ ```
60
+
61
+ まとめ
62
+
63
+ > Model操作前後でtry-exception句で例外をcatchし、何か処理を行う。
64
+ > 例えば、ユーザーに再入力を促すtemplateを表示、またはview関数を成功するまでリトライし続ける。
65
+
66
+ ユーザーに再入力を促すtemplateを表示するのは以下のように実装できます
67
+
68
+ ```Python
69
+ # ATOMIC_REQUESTS=True
70
+ def view(request):
71
+ try:
72
+ m1 = RelModel.objects.create(...) # 一旦save
73
+ m2 = SomeModel(pk=1, m1=m1) # id重複
74
+ m2.save() # raise
75
+ except:
76
+ render(...) # エラーなので再入力してねというテンプレート. m1はrollbackされる
77
+ return render(...) # 更新できましたというテンプレート
78
+ # viewk関数は正常に完了し、「ここで保存される」のデータはview関数を出たところでcommitされる
79
+ ```
80
+
81
+ view関数を成功するまでリトライし続ける実装は、同一トランザクション中だと(DBのISOLATION LEVELにもよるかもしれませんが)できないかもしれません(未確認です)。