Laravel+MySQLの排他制御のテスト

※自分用メモなので理解しがたい部分あり

PHP ver 7.1.8
Laravel ver5.8
MySQL ver5.7.18

トランザクション分離レベル：REPEATABLE-READ
select @@global.tx_isolation, @@session.tx_isolation;

↓my.cnf内↓
[mysqld]
autocommit=1

■排他制御のテストするきっかけ
排他制御を見直す場面があり、laravelの排他制御を組み込んではいたが実際に動作確認をしたことがなかった。
実際に動作確認をするとなると並行して多数の同時アクセスを再現しなくてはいけない。


■テスト準備
　・composer.jsonを開く

　　↓guzzlehttp/guzzleがインストールされているのか確認↓

　　"require": {
　　　"guzzlehttp/guzzle": "^6.3",
　　},

　・laravelのユニットテストコマンドでテスト用クラス作成
　　1. プロジェクトのホームディレクトリへ移動
　　2. php artisan make:test tests/Unit/ExclusiveControlTest.php --unit
　　3. ExclusiveControlTest.phpを開き、下記をuseする
　　　　use GuzzleHttp\Client;
　　　　use GuzzleHttp\Pool;
　　4.多重アクセスさせたいところに下記を書く

```ExclusiveControlTest.php 
　　　//排他制御の処理をしているURL
　　　$base_url = "アクセスしたいURL";

　　　$urls = [];

　　　//何番目のURLがアクセスしたのかわかるように&access_num=".$iを付加させています
　　　for($i=0;$i<20;$i++){
　　　　$urls[] = $base_url."&access_num=".$i;
　　　}

　　　$client = new Client();
　　　$requests = function ($urls) use ($client) {
　　　　　foreach ($urls as $url) {
　　　　　　　yield function () use ($client, $url) {
　　　　　　　　　return $client->getAsync($url);
　　　　　　　};
　　　　　}
　　　};

　　　$pool = new Pool($client, $requests($urls), [
　　　　　//並行させる件数！？を指定
　　　　　'concurrency' => 20,
　　　　　'fulfilled' => function ($response, $index) use ($urls) {
//　　　　　　echo 'アクセス成功 url:' . $urls[$index] . "\n";
　　　　　　},
　　　　　'rejected' => function ($reason, $index) use ($urls) {
//　　　　　　echo 'アクセス失敗 url:' . $urls[$index] . "\n";
　　　　　}
　　　]);

　　　$promise = $pool->promise();
　　　$promise->wait();
```


排他制御の処理を行っているプログラム側
※プログラム例

```ExclusiveControlController.php
public function sendUpdate(Request $request)
{
   $listResponse = [];
   $listResponse = $request->all();

   try {
//  $dbh = new PDO('mysql:host=172.16.0.36;dbname=example_db;port=3306;charset=utf8','abcdef','abcdef5963');
      $dbh = DB::connection('mysql')->getPdo();
      $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);			//エラーの場合、例外を投げる設定
      $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);		//結果の行を連想配列で取得
      $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);					//SQLインジェクション対策

   } catch (PDOException $e) {
//error_log("pdo error\n",3,"/data/www/example/storage/logs/nishi_log.txt");
      return false;
   }

   try{ 
      //トランザクション開始
      $dbh->beginTransaction();
      $stmt = $dbh->prepare("select * from payment_log where sendid = :sendid and status != :status for update");
      $stmt->bindValue(":sendid ", $listResponse['sendid']);
      $stmt->bindValue(":status", 3);
      $stmt->execute();

      $listUserInfo = [];
      while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ){
         $listUserInfo['mail_address']	 = $row['mail_address'];
         $listUserInfo['add_point']	 = $row['add_point']+100;
         $listUserInfo['login_id']	 = $row['login_id'];
      }

      //データ存在しないとき
      if( empty($listUserInfo) ){
//error_log("nodata\n",3,"/data/www/example/storage/logs/nishi_log.txt");
         return false;
      }

　    $stmt = $dbh->prepare("update payment_log set pay_amount = pay_amount + :pay_amount, pay_count = pay_count + 1, point = point + :point, pay_datetime = :pay_datetime, status = :status where login_id = :login_id");
　    $stmt->bindValue(":pay_amount", (int)$listResponse['money'], PDO::PARAM_INT);
　    $stmt->bindValue(":point", (int)$listUserInfo['add_point'], PDO::PARAM_INT);
　    $stmt->bindValue(":login_id", $listUserInfo['login_id']);
　    $stmt->bindValue(":pay_datetime", $now_date);
      $stmt->bindValue(":status", 3);
　    $stmt->execute();

　    $dbh->commit();
　    return true;

   //例外エラー
   }catch(\Exception $e){
//error_log($listUserInfo['pay_count']."rollback1\n",3,"/data/www/jray/storage/logs/nishi_log.txt");
      $dbh->rollback();

      return false;
   }
}

```


■排他制御のテストをして気づいた点
・ネットの技術ブログやドキュメントに掲載されているlaravelの排他制御は排他制御にならない・・！？

・laravelの下記のメソッドを組み合わせて使用したが書き方がいけないのかトランザクション処理はされるが排他制御にならなかった。

```サンプルプログラム.php
　DB::transaction(function () use ($listResponse) {
　　　//更新対象のデータをロック
　　　Payment_log::query()->join('users', 'payment_logs.login_id', '=', 'users.login_id')
	　->select('users.login_id', 'users.mail_address', 'users.pay_count', 'users.point', DB::raw('sum(payment_logs.point) as add_point'), DB::raw('sum(payment_logs.money) as amount'))
	　->where('payment_logs.sendid', $listResponse['sendid'])
	　->where('payment_logs.status', '!=', 3)
	　->lockForUpdate()
	　->first();
　    // 更新処理
　});
```