menu
Webを活用してお客様のビジネス課題を解決します。札幌・東京を拠点にWebコンサルティングをコアにした、Web制作・システム開発・サーバ構築会社です。

関係データベース書き込みで利用する排他制御手法について

シェア
ツイート
シェア
ブックマーク
タイトルとURLをコピー

最終更新日:2025/01/23   公開日:2025/01/21

はじめに

ウェブシステムを構築する多くの場面で、MySQLなどの関係データベースが利用されています。このようなシステムを構築・運用する上では、「複数のユーザーが同時に同じデータを更新する」ケースについて考慮する必要があります。例えば、利用者Aさんと利用者Bさんがほぼ同時に同じデータを更新しようとした場合、システムはどのように動作すべきでしょうか?

この記事では、こうしたデータの更新に伴う課題について議論し、実際に利用されている代表的な解決手法を解説します。

どのように動作すべきか?

「複数のユーザーが同時に同じデータを更新する」場合、基本的には以下のいずれかの動作を選択することになります。

  • 優先度制御:
    何らかの方法で優先度を決めて、優先度が高い方がデータを更新できるようにします。例えば、先にデータの編集を始めた方が優先度が高い、または権限の強い方が優先度が高い、といった基準が考えられます。
  • 事前バリデーション:
    更新前にデータベース上のデータを確認します。この際、データが他の操作で更新されていなければ正常に更新できますが、更新が行われていた場合は競合が発生したとして処理を中止します。
  • 後勝ち:
    サーバーにデータが到達した順に更新を行い、後から送られた更新を優先します。この場合、先に送信されたデータは、履歴が残っていなければ上書きされて失われる可能性があります。

このように複数の動作が考えられますが、結論として「こうするべき」という唯一の正解はありません。最適な選択肢は、システムが求める要件や利用ケースに大きく依存します。具体的には以下のようなケースではデータ整合性を強く保つ必要があります。 

  • 銀行口座の金額の管理:
    金額に誤りがあると非常に大きな問題となるため、複数の操作が同時に行われないようにする仕組みが望ましいです。システム側で操作順序を調整し、データの整合性を確実に保つ必要があります。
  • オンライン販売の在庫管理:
    在庫数に誤りが生じると重大な影響があるため、購入手続き時に在庫数を適切に確認し、過剰販売が発生しない仕組みを組み込む必要があります。特に、同時購入が発生しても問題が起きないようにすることが重要です。

排他制御について

このように、システム内で同じデータに複数のユーザーやプロセスが同時にアクセスする場合、データの整合性を保つ仕組みが必要になるケースがあります。このようにデータの整合性を担保する仕組みを「排他制御」と呼びます。排他制御は、データの「矛盾」や「競合」を防ぐために欠かせない重要な概念です。

一方で、プログラム実装時に排他制御を考慮しない場合、データの更新はアプリケーションに到着した順に処理されるため、一般的に「後勝ち」の状態になります。しかし、先に挙げた例のように、データの整合性が求められるシナリオでは、後勝ちの状態では重大な問題を引き起こす可能性があります。そのため、適切な排他制御を導入し、データ整合性を確保することが不可欠です。

悲観ロックと楽観ロック

排他制御を実現するためのテクニックとして、悲観ロックと楽観ロックがあります。 それぞれ、以下のような性質とメリット・デメリットがあります。

悲観ロック

悲観ロックは、データの競合が発生する可能性が高い場合に使用される手法です。更新対象のデータ(レコード単位またはカラム単位)にロックをかけ、他のトランザクションが同時にデータを変更できないようにします。ロック中のデータへのアクセスが可能かどうかは、ロックの種類やデータベースの動作設定によって異なります。ロックは通常、トランザクション終了時まで保持され、更新処理が終了するまで他のトランザクションを待機させます。

悲観ロックの主な利点は、競合を完全に防ぐことでデータの整合性を強く保てる点です。また、SELECT … FOR UPDATE のようなSQL文を使用することで簡単にロックを実現できるため、実装の負担も比較的軽いことも利点といえます。

一方で、悲観ロックにはいくつかの問題点もあります。ロックがかかっている間、別のトランザクションはデータの書き込み(ロック取得の方法次第では読み込みも)を行うことができないためシステム全体の処理効率が低下する可能性があります。特にウェブサーバーが並列でリクエストを処理している場合、データ更新部分が順次処理となることで待機時間が増加して全体のパフォーマンスが悪化します。またロックの順序を誤ったり解放処理を忘れるとデッドロックが発生するリスクもあります。デッドロックが起きるとリソースが解放されず、アプリケーション全体が停止するような重大な問題につながる場合もあります。

悲観ロックはその強力な競合防止能力からデータの不整合が許容されない状況では有効ですが、パフォーマンスへの影響やデッドロックの発生を考慮すると必要以上の利用は避けるべきです。適切な場面で慎重に適用することで、システム全体の効率と信頼性を両立させることが求められます。

楽観ロック

楽観ロックはデータの競合が少ないと仮定した場合に使用される手法です。この手法ではデータ更新時に「競合が発生しているか」を確認し、競合が検出された場合にのみ更新を中止します。具体的にはレコードに「バージョン番号」や「更新時刻」といった管理用のカラムを追加し、更新前の状態と一致しているかを確認することでデータの整合性を保証します。 

なお、楽観ロックと呼ばれる理由は、概念提唱の論文で「Optimistic Methods」として紹介されており、これが翻訳時に「楽観的~」と訳され、以後利用されているようです。 この手法の適応対象では競合が起こりにくいという楽観的な仮定に基づいて設計されているため、この用語が利用されたのではないかと思います(Wikipedia, 論文URL)。

楽観ロックの大きな利点は、データベースにロックをかける必要がないため、データベース部分がボトルネックにならず、デッドロックも発生しない点です。

一方、楽観ロックを導入するには、適切な管理用カラムを設計し、アプリケーション側で競合検出の仕組みを実装する必要があります。 また、同一カラムに対する更新が多すぎる場合には、多数の競合が発生し、リトライ処理が頻発することで逆に効率が低下する可能性があります。そのため、楽観ロックは、データの更新競合が発生する頻度が低いシステムに適しており、競合が多い場面では適切に機能しない場合もあります。

楽観ロックの具体的な実装方法としては、テーブルにバージョン番号や更新時刻を表すカラムを用意し、更新時にWHERE句でこれらのカラムとレコードの主キーを条件に指定します。たとえば、テーブルのデータが他のトランザクションから変更されていない場合、更新処理は正常に成功し、更新件数として1が返されます。一方、管理カラムが既に別のトランザクションによって更新されていた場合、WHERE条件を満たさず、更新件数が0件となるため、競合が発生したことを検知できます。 Wiki などのシステムでは競合が発生した場合、本当に更新を反映するかの確認画面に遷移するなど、競合を前提とした機能が導入されています。

まとめ

データベースにおける同時更新の問題はシステム設計において避けて通れない重要な課題です。この課題に対する解決策として、悲観ロック、楽観ロック、そして後勝ちの3つの手法を紹介しました。それぞれの手法には適用場面や特性があり、どの方法を選択するかはシステムの要件や利用ケースに大きく依存します。

最適な手法を選択するためには、システムの特性や要件を明確に把握し、設計や実装の初期段階で次の観点を十分に検討することが重要です。

データの整合性の重要度
データの整合性が最優先される場合、悲観ロックや競合を検知できる楽観ロックが適しています。特に、データの不整合が致命的な結果を招くシステムでは、悲観ロックが適しています。

競合の発生頻度
競合が稀な場合、楽観ロックが効果的です。一方で、競合が頻繁に発生する場合は、悲観ロックの利用を検討するべきです。ただし、そもそも同一レコードを更新する設計を避け、新規レコードの追加や別の方法でデータを管理することで競合を回避できる場合があります。このような観点から、ロックに頼らず競合を解決できる設計への変更を検討するのも一つの方法です。

システムのパフォーマンス要件
高いスループットが求められる場合には、後勝ちや楽観ロックが適しています。これらの手法は、ロックによるパフォーマンスの低下を回避し、効率的な処理を可能にします。ただし、後勝ちではデータの整合性が保証されない場合があるため、適用するシステムの更新手法は後勝ちで問題ないのかの十分な検討が必要です。