php mysql同時ロック防止トランザクション


1、背景:
現在、このようなニーズがあり、データを挿入する際にtestテーブルのusernameが「mraz」であるデータの有無を判断し、無ければ挿入し、ある場合は「挿入済み」と提示し、usernameが「mraz」であるレコードを1つだけ挿入することを目的としている.
2、一般的なプログラムロジックは以下の通りである.
$conn = mysqli_connect('127.0.0.1', 'root', '111111') or die(mysqli_error());  
  
mysqli_select_db($conn, 'mraz');  
  
$rs = mysqli_query($conn, 'SELECT count(*) as total FROM test WHERE username = "mraz" ');  
$row = mysqli_fetch_array($rs);  
if($row['total']>0){  
    exit('exist');  
}  
  
mysqli_query($conn, "insert into test(username) values ('mraz')");  
var_dump('error:'.mysqli_errno($conn));  
$insert_id = mysqli_insert_id($conn);  
  
echo 'insert_id:'.$insert_id.'
'; mysqli_free_result($rs); mysqli_close($conn);

3、一般的に少量の要求の場合、プログラムロジックに問題はありません.しかし、高同時要求が実行されると、プログラムは予想通りに実行されず、複数のusernameが「mraz」であるレコードが挿入される.
4、解決策:mysqlのFOR UPDATE文と取引の隔離性を利用する.FOR UPDATEはInnoDBにのみ適用され、トランザクション(BEGIN/COMMIT)で有効になる必要があります.
コードを調整すると、次のようになります.
$conn = mysqli_connect('127.0.0.1', 'root', '111111') or die(mysqli_error());  
  
mysqli_select_db($conn, 'mraz');  
mysqli_query($conn, 'BEGIN');  
$rs = mysqli_query($conn, 'SELECT count(*) as total FROM test WHERE username = "mraz" FOR UPDATE');  
$row = mysqli_fetch_array($rs);  
if($row['total']>0){  
    exit('exist');  
}  
  
mysqli_query($conn, "insert into test(username) values ('mraz')");  
var_dump('error:'.mysqli_errno($conn));  
$insert_id = mysqli_insert_id($conn);  
  
mysqli_query($conn, 'COMMIT');  
echo 'insert_id:'.$insert_id.'
'; mysqli_free_result($rs); mysqli_close($conn);

5、phpのcurlシミュレーション高同時要求phpスクリプトを再利用し、データベースにusernameが「mraz」のレコードが1つしかないことを確認する.プログラム実行の予想結果に達する
Thinkphp 5使用例:
同じデータが存在するか否かを判断し、存在する場合はそのまま戻り、存在しない場合は実行する
Db::startTrans();
$sql = 'select count(*) as count from xs_reward_order where status <> 2 and user_id='.$user_id.' and task_id='.$data['task_id'].' FOR UPDATE';
$res = Db::query($sql);
if($res[0]['count'] > 0){
   return returnJson('',205, '     !                 ');
}
Db::commit();