質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

1回答

6610閲覧

Djangoチュートリアルにおける競合状態について

退会済みユーザー

退会済みユーザー

総合スコア0

Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2017/07/23 06:01

#取り組んでいること

公式ドキュメント「はじめての 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()を使っているにも関わらず、ロックが必要となるのはどのような場合でしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

疑問1: DB に対し votes = votes + 1 と実行さればよい

認識の通りです。この現象は一般的にロストアップデートと呼ばれています。今回のように相対的な表現で記述したり、サブクエリを使うなどして1回のSQL文で処理を完結出来る場合、データベースサーバー側で適切な排他処理を行うためロストアップデートは起きません。


疑問2:Django データモデルの F() expressions

これも認識の通りです。F() を使った場合、Django 側はこの値を取得したり加工することはしません。save() を発行したときに、UPDATE "question" SET "vote" = ("question"."votes" + 1) といった感じのSQL文が発行されます。

ちなみにアップデートしたあとの値を Django で利用したい場合は、question.refresh_from_db() としてデータベースから再度値を取得する必要があります。


疑問3:ロックの必要性

今回のケースでは、F() を使って相対的な加算を行えばロストアップデートは起きませんのでロックは不要です。

考慮が必要になってくるのはデータベースと複数回やりとりする場合です。よくある例えに銀行の振り込み処理があります。

  1. Aさんの口座が有効で、かつ残高が50万円以上あることを確認
  2. Bさんの口座が有効であることを確認
  3. Aさんの口座から50万円を引く
  4. Bさんの口座に50万円を足す

まず、これらの処理が途中で止まってしまうと大変なことになってしまいますよね。そこでこれらの処理を絶対にひとまとめにして処理させる仕組みであるトランザクション を使います。

さらに処理中にAさんの口座からお金が引き落とされて残高がマイナスになったりしないよう、Aさんの口座残高に対して排他ロックをかけたりします。

Django の ORM はトランザクションも排他ロックも取り扱えますが、そもそもデータベースの基礎知識が必要になってくるので、厳格な同時アクセス制御を行う場合は別途学習することをおすすめします。

投稿2017/07/23 11:15

miyahan

総合スコア3095

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/07/23 11:45

とてもわかり易い解説をありがとうございました。 手元の入門書にはロックの説明がなく、トランザクションの説明も少しだけだったため、教えて頂き勉強になりました。 DBについてもう少し学習が必要なようなので、より詳しい本を探してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問