1. ホーム
  2. Web プログラミング
  3. PHP プログラミング
  4. phpの例

thinkphp6でmysqlの悲観的ロックを使って商品の売れ残り問題を解決するための実装

2022-01-14 14:21:52

ペシミスティック・ロック(wikipedia)の紹介です。

悲観的ロックとは、その名の通り、データが部外者(このシステムで現在行われている他のトランザクションや、外部システムから処理されるトランザクションを含む)によって変更されることを保守的に考え、データ処理中はデータをロック状態にしておくことを指します。悲観的ロックの実装は、データベースが提供するロック機構に依存することが多い(データアクセスの排他性を真に保証できるのは、データベース層が提供するロック機構だけであり、そうでなければ、本システムにロック機構を実装しても、外部システムがデータを修正しない保証はない)。

利用シーン例 MySQL InnoDBを例として

商品商品テーブルで、商品IDが1、購入回数が1、ステータスが1なら棚にある、2なら棚にない、と仮定する。さて、ユーザーがこの商品を購入し、高連続性でないシナリオでの処理ロジックは次のようになります。

  • この商品に関する情報を検索します。
  • 購入した数量以上の在庫があるかどうかを確認します。
  • アイテムの在庫と売上を変更する。

この上記のシナリオは、同時アクセス数が多い場合に問題が発生する可能性が高いです。商品在庫が100の場合、同時アクセス数が多いシナリオでは1000の同時アクセスがあるかもしれませんが、ステップ2に到達するまでにすべて検出され、パスされるでしょう。その結果、商品在庫が-900という状況になります。明らかに需要が満たされていないのです

商品テーブルの構造。

CREATE TABLE `goods` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `status` tinyint(1) NOT NULL DEFAULT '1',
  `total` int(11) NOT NULL DEFAULT '0',
  `sell` int(11) NOT NULL DEFAULT '100',
  `price` decimal(10,2) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
INSERT INTO `test`. `goods`(`id`, `name`, `status`, `total`, `sell`, `price`) VALUES (1, 'goods', 1, 0, 100, 15.00);

オーダーテーブルの構造。

CREATE TABLE `orders` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `uid` int(11) NOT NULL DEFAULT '0',
  `create_time` datetime NOT NULL,
  `status` tinyint(1) NOT NULL DEFAULT '1',
  `goods_id` int(11) NOT NULL DEFAULT '0',
  `order_no` varchar(200) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

悲観的なロック処理を使用する。

グッズ情報を照会した後に現在のデータをロックすると、修正が終わるまでロックが解除されます。そうすると、その過程で、グッズがロックされているので、第三者が変更することができなくなります。

注:悲観的なロックを使うには、mysqlデータベースのオートコミットプロパティをオフにする必要があります。MySQLはデフォルトでオートコミットモードを使用しており、更新操作を行うと、MySQLはすぐに結果をコミットします。thinkphp6では、コミットロールバックを手動で行うためにトランザクションを使用しています。

<?php
 
namespace app\controller;
 
use app\BaseController;
use think\facade\Db;
 
class Test extends BaseController
{
 
    /**
     * without locking
     * @return string|void
     */
    public function test_1()
    {
        $num = 1;
        $goods_id = 1;
        Db::startTrans();
        try {
            $where = [];
            $where['id'] = $goods_id;
            $where['status'] = 1;
            $goods_info = Db::table('goods')->where($where)->find();
            if (empty($goods_info)) {
                return 'Item does not exist';
            }
            $total = $goods_info['total'];
            $sell = $goods_info['sell'];
            if ($total < $num) {
                return 'Out of stock';
            }
            $data['total'] = $total-$num;
            $data['sell'] = $sell+$num;
            $res = Db::table('goods')->where(['id'=>$goods_id])->update($data);
            $order_data = [];
            $order_data['uid'] = rand(1000,9999);
            $order_data['status'] = 1;
            $order_data['create_time'] = date('Y-m-d H:i:s');
            $order_data['goods_id'] = $goods_id;
            $order_data['order_no'] = date('YmdHis').rand(1000,10000);
            $order_res = Db::table('orders')->insert($order_data);
            Db::commit();
        } catch (\Exception $e) {
            // Roll back the transaction
            Db::rollback();
            echo $e->getMessage();
            exit('rollback');
        }
        echo 'Request successful';
    }
 
    /**
     * locking - pessimistic locking
     * @return string|void
     */
    public function test_2()
    {
        $num = 1;
        $goods_id = 1;
        Db::startTrans();
        try {
            $where = [];
            $where['id'] = $goods_id;
            $where['status'] = 1;
            $goods_info = Db::table('goods')->lock(true)->where($where)->find();
            if (empty($goods_info)) {
                return 'Item does not exist';
            }
            $total = $goods_info['total'];
            $sell = $goods_info['sell'];
            if ($total < $num) {
                return 'Out of stock';
            }
            $data['total'] = $total-$num;
            $data['sell'] = $sell+$num;
            $res = Db::table('goods')->where(['id'=>$goods_id])->update($data);
            $order_data = [];
            $order_data['uid'] = rand(1000,9999);
            $order_data['status'] = 1;
            $order_data['goods_id'] = $goods_id;
            $order_data['order_no'] = date('YmdHis').rand(1000,10000);
            $order_data['create_time'] = date('Y-m-d H:i:s');
            $order_res = Db::table('orders')->insert($order_data);
            Db::commit();
        } catch (\Exception $e) {
            // Roll back the transaction
            Db::rollback();
            echo $e->getMessage();
            exit('rollback');
 
        }
        echo 'Request successful';
    }
 
}

jmeter ツールを使用してテストを行い、スレッドテストグループを作成します。

jmeterを使った並行性の高いテストの作成例は、こちらをご覧ください。 JMeterを使った高同時性テスト_左右 ... 's blog - CSDN Blog_jmeter High Concurrency Testing

1秒間に1000人の同時ユーザーアクセスを想定したシミュレーションです。

 httpリクエストを作成します。

結果ツリーの外観を追加します。

 からテストが始まる。

ロック無しで100件分の結果

在庫100点、受注187点、売れ残り87点、これはプロジェクト開発では絶対ダメなことです。

ロック結果が100アイテム

 ロックされ、解決されました。

この記事はthinkphp6が商品の売れ残りの問題を解決するためにmysql悲観的なロックを使用して、ここに導入され、より関連thinkphp6商品の売れ残りの内容は、スクリプトハウスの以前の記事を検索してくださいまたは次の関連記事を閲覧を継続し、より将来のスクリプトハウスをサポートして願っています!.