質問: 今回、「競合状態」という言葉を調べるにあたって、悲観的ロック、楽観的ロックというDB操作に関わる用語を知りました。 voteの値を正しく更新する事が目的の場合、上記のF()を使用していればいずれのロックも不要でしょうか? ※ 逆にF()を使っているにも関わらず、ロックが必要となるのはどのような場合でしょうか?
回答: > > 疑問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 はトランザクションも排他ロックも取り扱えますが、そもそもデータベースの基礎知識が必要になってくるので、厳格な同時アクセス制御を行う場合は別途学習することをおすすめします。