#取り組んでいること
公式ドキュメント「はじめての Django アプリ作成、その 4」の「簡単なフォームを書く」について、以下の動作を試作しています。
以下の選択を表示し、ラジオボタン選択後Voteをクリック → 次ページに移動し集計結果を表示
↓
この機能の問題点について、チュートリアルに以下の説明がありました。(わかりやすくするためvoteの数を10としました)
(注釈)これまで作ってきた vote() ビューのコードは、小さな問題を抱えています。最初にデータベースから selected_choice オブジェクトを取得し、そこで votes の新しい値を計算し、データベースにそれを戻して保存します。もしウェブサイトのユーザー 2 人が まったく同時に 投票しようとすると、誤りが発生します。votes の元の値が 10 だったとしましょう。その時、両方のユーザーに対して新しい値として 11 が計算され保存されます、しかし 12 が本来想定される値です。
この問題は、「競合状態」と呼ばれています。この問題に興味がある人は、Avoiding race conditions using F() を読むと、この問題の解決方法がわかります。
上記のような「競合状態」について、3点疑問があります。
#疑問1
上記の問題はvoteの値である「10」をpythonが直接読み込んでしまうことが要因のように思います。voteの値に1を加えたいだけであれば、DBに対して直接「現在のvoteの値に1を加えろ」という命令を出すことができれば、問題が無いように思います。この考え方は間違っていますでしょうか?
なお、上記のvoteの値である「10」をpythonが直接読み込んで1を加えて返すという、問題のコードは以下のとおりです」
polls/views.py def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) selected_choice = question.choice_set.get(pk=request.POST['choice'] selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(redirect_to=reverse(viewname='polls:results', args=(question.id,)))
#疑問2
この問題への対応として、チュートリアルの注釈ではF()を使えとあります。疑問1で考えたように、python上で処理せずにDBに対して直接「現在のvoteの値に1を加えろ」という命令を出す仕組み」は、このF()を使った方法で実現できるのでしょうか?
F()を使って自分で書いた以下のコードでは、「selected_choice.votes = F('votes') + 1」の部分でpython上にvoteの値を読み込んでしまっており、次の行の「selected_choice.save()」が実行されるまでに他のユーザーが同じ操作をしてしまうと、結局同じ問題が発生するように見えます。しかし、実際は見かけとは異なり、python上に具体的にvoteの「10」という値が読み込まれるのではなく、一連の動作を変換して、DBへ「現在のvoteの値に1を加えろ」という命令を行ってくれているという認識で良いでしょうか?
polls/views.py def vote(request, question_id): from django.db.models import F question = get_object_or_404(Question, pk=question_id) selected_choice = question.choice_set.get(pk=request.POST['choice']) selected_choice.votes = F('votes') + 1 selected_choice.save() return HttpResponseRedirect(redirect_to=reverse(viewname='polls:results', args=(question.id,)))
#疑問3
今回、「競合状態」という言葉を調べるにあたって、悲観的ロック、楽観的ロックというDB操作に関わる用語を知りました。
voteの値を正しく更新する事が目的の場合、上記のF()を使用していればいずれのロックも不要でしょうか?
※ 逆にF()を使っているにも関わらず、ロックが必要となるのはどのような場合でしょうか?
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2017/07/23 11:45