MySQL 悲観的ロックと楽観的ロックの実装

MySQL 悲観的ロックと楽観的ロックの実装

序文

悲観的ロックと楽観的ロックは、同時実行の問題を解決するために使用される 2 つのアイデアであり、異なるプラットフォームで独自の実装が行われます。たとえば、Java では、 synchronized は悲観的ロック (厳密ではなく、ロックのアップグレード プロセスがあり、重量ロックにアップグレードされた場合にのみ重量ロックと見なされます) の実装と見なすことができ、 Atomic*** アトミック クラスは楽観的ロックの実装と見なすことができます。

悲観的ロック

排他性と排他性が強く、処理プロセス全体を通じてデータがロックされます。これは通常、システムのミューテックスを通じて実現されます。他のスレッドがロックを取得しようとすると、ロックを保持しているスレッドがロックを解放するまでブロックされます。

楽観的ロック

データの変更とアクセスについては、競合が発生しないと仮定して楽観的に考えてください。データが更新のために送信されたときにのみ、競合がチェックされます。競合がない場合、更新はスムーズに送信されます。競合がある場合は、すぐに失敗し、ユーザーにエラーが返され、次に何をするかをユーザーが選択できるようになります。一般的に、更新が正常に送信されるまで、失敗後も再試行が続けられます。

MySQL 自体はロック機構をサポートしています。たとえば、「最初にクエリを実行してから書き込む」という要件がある場合、プロセス全体がアトミック操作であり、途中で中断されないことが望まれます。このとき、クエリされたデータ行に「排他ロック」を追加することでこれを実現できます。現在のトランザクションがロックを解放しない限り、MySQL は現在のトランザクションがロックを解放するまで他のトランザクションが排他ロックを取得するのをブロックします。 MySQL の下部にあるこの種の排他ロックは、「悲観的ロック」と呼ばれます。

MySQL 自体は楽観的ロック機能を提供していないため、開発者が自分で実装する必要があります。一般的な方法は、データ行のバージョンをマークするためにテーブルにバージョン列を追加することです。データを更新する必要がある場合は、バージョンを比較する必要があります。バージョンが一貫している場合、その期間中にデータが他のトランザクションによって変更されていないことを意味します。そうでない場合は、データが他のトランザクションによって変更されており、再試行する必要があることを意味します。

実際の戦闘

データベースに製品テーブルと注文テーブルという 2 つのテーブルがあるとします。

注文後、ユーザーは次の 2 つの操作を実行する必要があります。

  1. 在庫を除いたアイテムテーブル。
  2. Orders テーブルにレコードを作成します。

初期データ: ID 1 の商品の在庫は 100 個で、注文テーブルのデータは空です。

クライアントは 10 個のスレッドを開始して同時に注文を行います。ロックフリー、悲観的、楽観的ロックのシナリオでのパフォーマンスはどうなるでしょうか?

以下はテーブルを作成するための SQL ステートメントです。

-- 商品テーブル CREATE TABLE `goods` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `goods_name` varchar(50) NOT NULL,
  `price` 小数点(10,2) NOT NULL,
  `stock` int(11) デフォルト '0',
  `version` int(10) unsigned NOT NULL DEFAULT '0',
  主キー (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 デフォルト CHARSET=utf8

-- 注文テーブル CREATE TABLE `t_order` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `goods_id` bigint(20) NOT NULL,
  `order_time` 日時 NOT NULL、
  BTREE を使用した主キー (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 デフォルト CHARSET=utf8

1. ロックなし

何もアクションは実行されません。

//注文する private boolean order(){
    商品 goods = goodsMapper.selectById(1L);
    ブール値の成功 = false;
    (goods.getStock() > 0) の場合 {
        商品の在庫数を1に設定します。
        // 在庫を更新する goodsMapper.updateById(goods);
        // 注文を作成します orderMapper.save(goods.getId());
        成功 = true;
    }
    成功を返します。
}

コンソール出力は次のとおりです。

2. 悲観的ロック

商品を照会する際に、FOR UPDATE を追加し、データ行に排他ロックを追加します。こうすることで、再度照会する際に他のスレッドがブロックされます。現在のスレッドのトランザクションがコミットされ、ロックが解除されるまで、他のスレッドは注文を続行できます。この方法では同時実行パフォーマンスが低くなります。

SQL文

@Select("SELECT * FROM goods WHERE id = #{id} FOR UPDATE")
商品selectForUpdate(Long id);

コンソール出力は次のとおりです。

注意: FOR UPDATE が有効になるにはトランザクション内にある必要があり、クエリと更新は同じトランザクション内にある必要があります。 ! !

3. 楽観的ロック

実装のアイデアは、更新が行われるたびにバージョン番号を確認することです。バージョン番号が一貫している場合、その期間中に他のスレッドによってデータが変更されていないことを意味し、現在のスレッドは更新を正常に送信できます。そうでない場合は、データが他のスレッドによって変更されたことを意味し、現在のスレッドは、ビジネスが成功するまでスピンして再試行する必要があります。

データを更新するときはバージョン番号を増やす必要があります。 ! !

@Update("商品を更新します。SET stock = #{stock}、version = version+1 WHERE id = #{id} AND version = #{version}")
int updateByVersion(Long id、Integer stock、Integer version);

ビジネスコード

ブール順序(){
    商品 goods = goodsMapper.selectById(1L);
    ブール値の成功 = false;
    (goods.getStock() > 0) の場合 {
        商品の在庫数を1に設定します。
        // バージョン番号で在庫を更新します int result = goodsMapper.updateByVersion(goods.getId(), goods.getStock(), goods.getVersion());
        結果 <= 0 の場合
            // 更新に失敗しました。期間中に他のスレッドによってデータが変更されており、再帰的に再試行する必要があることを示しています。 return order();
        }
        // 注文を作成します orderMapper.save(goods.getId());
        成功 = true;
    }
    成功を返します。
}

コンソール出力は次のとおりです。

要約する

これで、MySQL の悲観的ロックと楽観的ロックのソリューションに関するこの記事は終わりです。MySQL の悲観的ロックと楽観的ロックに関するより関連性の高いコンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • MySQLのロック機構に関する最も包括的な説明
  • MySQLの関連ロックについての簡単な理解
  • MySQLのさまざまなロックに関する詳細な理解

<<:  Linux での wget コマンドの基本的な使い方

>>:  ウォーターフォールフローレイアウトを実装する3つの方法

推薦する

Nodejs と Socket.IO を組み合わせて Websocket の即時通信を実現

目次WebSocketを使用する理由ソケット.ioオープンソースプロジェクト効果プレビューアプリイン...

Docker+K8S クラスタ環境構築と分散アプリケーション展開

1. Dockerをインストールする yumでdockerをインストール #サービスを開始する sy...

MySQL インデックス失敗の原理

目次1. インデックス失敗の理由2. インデックスの秩序が崩れる状況を見てみましょう。 - インデッ...

MySQLへの外部ネットワークアクセスを許可し、MySQLアカウントのパスワードを変更する方法

mysqlのrootアカウント、普段はlocalhostか127.0.0.1で接続しています。会社の...

CSS 複数 3 列適応レイアウト実装の詳細な説明

序文従来のWEBレイアウトに沿うため、すべてヘッダーとフッターモードの左・中央・右レイアウトで書かれ...

CSSコンテンツ属性の具体的な使用法

コンテンツ属性は通常、::before および ::after 疑似要素で使用され、疑似要素のコンテ...

Vueのトグルボタンをクリックしてボタンを有効にし、無効にします。

実装方法は3つのステップに分かれています。テンプレートに 2 つのボタンを設定し、v-if と v-...

Linuxドライバのプラットフォームバスの詳細説明

目次1. プラットフォームバスの紹介1.1. Linuxドライバの分離と階層化1.1.1. Linu...

ウェブデザイナーのための超便利なツール 50 選

ウェブデザイナーになるのは簡単ではありません。デザインやアーキテクチャを考慮するだけでなく、さまざま...

Docker コンテナ データ ボリュームの名前付きマウントと匿名マウントの問題

目次コンテナデータボリュームとはコンテナ データ ボリュームが必要なのはなぜですか?使用データボリュ...

WeChatアプレットに2048ミニゲームを実装する詳細なプロセス

レンダリング サンプルコード今日は、WeChat アプレットを使用して 2048 ゲームを実装します...

CSS 変数に基づくテーマ切り替えに最適なソリューション (推奨)

この要件を受け取ったとき、Baidu は、CSS リンクの置き換え、className の変更、le...

SecureCRT に基づくリモート Linux ホストへのファイルのアップロードとダウンロードのグラフィカルな手順

wget や curl ツールを使用して、Linux サーバーで大規模なネットワーク ファイルを直接...

momentJs を使用してカウントダウン コンポーネントを作成する (サンプル コード)

今日はvueとmomentで作ったカウントダウンを紹介したいと思います。具体的な内容は以下のとおりで...

MySQL 8.0.16 Win10 zip バージョンのインストールと設定のグラフィック チュートリアル

この記事では、MySQL 8.0.16 Win10 zip版のインストールと設定のグラフィックチュー...