1.有种业务场景比如微信会员注册,我们首先判断openid在数据库表中是否存在如果存在则提示已经存在,否则注册一条会员记录。thinkphp5代码如下
$openid = '1111111' ;
//查询openid是否存在
$info = Db :: table ( 'test' )-> where ( 'openid' , $openid )-> find ();
if (! empty ( $info )) {
echo 'openid已经存在!' ;
die ;
}
sleep ( 2 ); //模拟其他复杂业务逻辑处理增加耗时
//insert插入
$data [ 'openid' ] = $openid ;
$data [ 'add_time' ] = date ( 'Y-m-d H:i:s' , time () );
$rs = Db :: table ( 'test' )-> insert ( $data );
print_r ( $rs );
die ;
我相信这样的场景有很多,而且大家也都写过类似的代码,而且看上去也在正常不过了,但是这样的代码在高并发的情况下会插入数据库表多条记录。
我用apache jmeter压测工具模拟50个并发请求,然后发现数据库进来25条记录。问题出现了。
上面代码sleep(2);是为了模拟注册过程中其他的一些业务逻辑处理。如果没有sleep(2),我用apache jmeter工具并发请求,也没有发现出来多条记录。那么如果解决上面的问题呢?引入mysql锁的概念。锁必须与事务配合使用下面是改进的代码 :
$openid = '1111111' ;
// 启动事务
$trans_result = true ;
Db :: startTrans ();
try {
//查询openid是否存在 ,//查询之后锁住该条记录让其他程序无法修改,直到事务提交释放锁
$info = Db :: table ( 'test' )-> lock ( true )-> where ( 'openid' , $openid )-> find ();
if ( ! empty ( $info ) ) {
//手动抛出异常
throw new \Exception ( 'openid已经存在!' );
}
sleep ( 2 );
//模拟其他复杂业务逻辑处理增加耗时
//insert插入
$data [ 'openid' ] = $openid ;
$data [ 'add_time' ] = date ( 'Y-m-d H:i:s' , time () );
$rs = Db :: table ( 'test' )-> insert ( $data );
if ( ! $rs ) {
//手动抛出异常
throw new \Exception ( 'insert 失败!' );
}
Db :: commit ();
} catch ( \ Exception $e ) {
// 回滚事务
Db :: rollback ();
$trans_result = false ;
echo $msg = $e -> getMessage ();
}
//如果失败
if ( ! $trans_result ) {
echo '执行失败0' . $msg ;
} else {
echo '执行成功1' ;
}
总结,把查询语句和插入语句放到一个事务里面,解决不了并发插入多条记录的问题,还需要配置使用锁,锁的意思就是当n个请求同时过来只有一个人能获取到锁,并且锁住那条记录,其他程序无法获取到就会报错,也就是这么多并发请求,最终只有一个能够执行成功。当然此案例也可以使用mysql表openid字段设置唯一索引,50个并发最终也会只有一个能成功,其他的同样会抛出sql异常。