thinkphp5+mysql事务案例

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: mysql的表存储引擎必须是innodb,事务就是多条sql其中一个执行失败就回滚,都执行成功才一起提交。保证多条sql要么都执行成功,要么都不成功。但是事务并不能避免高并发带来的数据错乱问题。如何解决高并发带来的数据错乱问题会单独写一篇文章详细阐述。

mysql的表存储引擎必须是innodb,事务就是多条sql其中一个执行失败就回滚,都执行成功才一起提交。保证多条sql要么都执行成功,要么都不成功。但是事务并不能避免高并发带来的数据错乱问题。如何解决高并发带来的数据错乱问题会单独写一篇文章详细阐述。


如下是一个下单事务案例:1.一定要注意update语句返回受影响的行记录,如果受影响行为0,一定要手动抛出异常,在catch里面统一处理。2.商品库存字段一定要设置成无符号,一单更新成负数sql也会自动抛出异常。3.update语句的where条件一定要增加上库存大于0,等类似这样的条件,这样做的好处就是如果之前更新过,那么这次更新返回受影响行就是0,在结合手动抛出异常,程序也回滚。


  /**

   * 下单方法

   */

    public   function   add_order  () {

        //sleep(20);die; //模拟超时

        //获取平台客户和活动信息

        $clientkeynum  =  $this -> clientkeynum ;

        $activity_info  =  $this -> activity_info ;

        $bianhao  =  $activity_info [ 'activity_code' ];

        $start_time = $activity_info [ "start_time" ];

        $end_time = $activity_info [ "end_time" ];

        //如果小于开始时间内

        if ( time ()< $start_time ){

            $rst [ 'sta' ] =  "0" ;

            $rst [ 'msg' ] =  '活动还未到开始时间!请耐心等待!' ;

            echo   json_encode (  $rst  ); die ;

       }

        //如果大于结束时间

        if ( time ()> $end_time ){

            $rst [ 'sta' ] =  "0" ;

            $rst [ 'msg' ] =  '活动已经结束,期待您下次参与!' ;

            echo   json_encode (  $rst  ); die ;

       }

        //如果活动不可用

        $status = $activity_info [ "status" ];

        if ( $status != '1' ){

            $rst [ 'sta' ] =  "0" ;

            $rst [ 'msg' ] =  '活动已被禁用!' ;

            echo   json_encode (  $rst  ); die ;

       }

        //活动是否归档

        $is_over = $activity_info [ "is_over" ];

        if ( $is_over == '1' ){

            $rst [ 'sta' ] =  "0" ;

            $rst [ 'msg' ] =  '活动已归档!' ;

            echo   json_encode (  $rst ); die ;

       }  

        $request  =  Request :: instance ();

        $param  =  $request -> param ();

        $name  =  trim (  $param [ 'name' ] );

        $phone  =  trim (  $param [ 'phone' ] );

        $province  =  trim (  $param [ 'province' ] );

        $city  =  trim (  $param [ 'city' ] );

        $area  =  trim (  $param [ 'area' ] );

        $address  =  trim (  $_REQUEST [ 'address' ] );

        //常规业务逻辑

        if  (  $name  ==  ''  ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '对不起,收货人姓名不能为空!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        if  (  $phone  ==  ''  ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '对不起,收货人手机号不能为空!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        //php手机号正则校验

        if  ( ! preg_match (  '/ ^ 0?(1|1|1|1|1)[0-9]{10} $ /' ,  $phone  ) ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '对不起,您输入的手机号格式不正确!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        if  (  $province  ==  '请选择'  ||  $city  ==  '请选择'  ||  $area  ==  '请选择'  ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '对不起,请选择收货人地址!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        if  (  $address  ==  ''  ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '对不起,收货人详细地址不能为空!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        //档次手机号 和选择商品信息

        $activity_grade_phone_info  =  session (  'icbc_activity_grade_phone_info'  );

        $cart_good_info  =  session (  'icbc_cart_good_info'  );

        if  (  empty (  $activity_grade_phone_info  ) ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '对不起,档次里面手机号信息丢失!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        if  (  empty (  $cart_good_info  ) ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '对不起,请去重新选择商品!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        $daoru_phone  =  $activity_grade_phone_info [ 'phone' ];

        $table_name  =  'client_activity_grade_phone_' . $bianhao ;

        $goodid  =  $cart_good_info [ 'id' ];

        //校验是否可以兑换

        //名单表

        $activity_grade_phone_info  =  Db :: table (  $table_name  )-> where (  'phone' ,  $daoru_phone  )-> where (  'clientkeynum' ,  $clientkeynum  )-> find ();

        if  (  $activity_grade_phone_info [ 'is_order' ] !=  '0'  ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '您已经兑换过礼品了!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        //兑换的产品必须在当前档次里面

        $grade_id = $activity_grade_phone_info [ 'grade_id' ];

        $grade_good_arr =  Db :: table (  'client_activity_grade_good'  )-> where (  'clientkeynum' ,  $clientkeynum  )-> where (  'grade_id' ,  $grade_id  )-> column ( "good_id" );

        if (! in_array ( $goodid , $grade_good_arr )){

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '对不起产品范围异常,请重新兑换!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        //同一个活动同一个人只能兑换一次

        $orderinfo_count  =  Db :: table (  'client_order_info'  )-> where (  'clientkeynum' ,  $clientkeynum  )-> where (  'daoru_phone' ,  $daoru_phone  )-> where (  'activity_id' ,  $activity_grade_phone_info [ 'activity_id' ] )-> count ();

        if  (  $orderinfo_count > 1  ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '同一个手机号同一个活动只能兑换一次!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        //库存

        $client_good_info  =  Db :: table (  'client_good'  )-> where (  'id' ,  $goodid  )-> where (  'clientkeynum' ,  $clientkeynum  )-> find ();

        if  (  $client_good_info [ 'stock' ]< 1  ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '对不起该产品已经没有了库存!' ;

            echo   json_encode (  $rt  );

            die ;

       }

        //获取活动详情

        $activity_id = $activity_grade_phone_info [ 'activity_id' ];

        $activity_info = Db :: table (  'client_activity'  )-> where (  'id' ,  $activity_id  )-> where (  'clientkeynum' ,  $clientkeynum  )-> find ();

        $activity_name = $activity_info [ 'activity_name' ];

        $project_id = $activity_info [ 'project_id' ];

        //订单唯一标识

        $order_keynum = create_guid ();

        // 启动事务

        $trans_result  =  true ;

        Db :: startTrans ();

        try  {

            //订单表

            $order_sn  =  'D'   . create_order_sn ();

            $order_info [ 'order_sn' ] =  $order_sn ;

            $order_info [ 'name' ] =  $name ;

            $order_info [ 'phone' ] =  $phone ;

            $order_info [ 'province' ] =  $province ;

            $order_info [ 'city' ] =  $city ;

            $order_info [ 'area' ] =  $area ;

            $order_info [ 'address' ] =  $address ;

            $order_info [ 'add_time' ] =  time ();

            $order_info [ 'order_status' ] =  '0' ;

            $order_info [ 'add_time' ] =  time ();

            $order_info [ 'goodid' ] =  $cart_good_info [ 'id' ];

            $order_info [ 'goodimg' ] =  $cart_good_info [ 'goods_thumb' ];

            $order_info [ 'goodsku' ] =  $cart_good_info [ 'goodssku' ];

            $order_info [ 'goodname' ] =  $cart_good_info [ 'goodsname' ];

            $order_info [ 'goodsintegral' ] =  $cart_good_info [ 'goodsintegral' ];

            $order_info [ 'market_integral' ] =  $cart_good_info [ 'market_integral' ];

            $order_info [ 'keynum' ] =  $order_keynum ;

            $order_info [ 'clientkeynum' ] =  $clientkeynum ;

            $order_info [ 'activity_id' ] =  $activity_grade_phone_info [ 'activity_id' ];

            $order_info [ 'activity_name' ] =  $activity_name ;

            $order_info [ 'project_id' ] =  $project_id ;

            $order_info [ 'grade_id' ] =  $activity_grade_phone_info [ 'grade_id' ];

            $order_info [ 'referer' ] =   $_SERVER [ 'HTTP_USER_AGENT' ];

            $order_info [ 'daoru_phone' ] =  $daoru_phone ;

            //同一个活动同一个达标手机号只能有一个订单,数据库达标手机号daoru_phone和活动activity_id两个字段一起做unique索引,一旦重复了,错误也会自动到catch里面

            $order_id  =  Db :: table (  'client_order_info'  )-> insertGetId (  $order_info  );

            //手动抛出异常,如果insert的sql出错也会把异常抛出到catch里面

            if  ( ! $order_id  ) {

                throw   new   \Exception (  'insert,client_order_info失败!'  );

           }

            //修改档次下面名单表, Affected rows: 0 也会成功, 所以手动抛出异常,在catch里面记录异常信息日志

            $grade_phone [ 'order_sn' ] =  $order_sn ;

            $grade_phone [ 'order_keynum' ] =  $order_keynum ;

            $grade_phone [ 'order_id' ] = $order_id ;

            $grade_phone [ 'is_order' ] =  "1" ;

            $grade_phone [ 'order_time' ] =  time ();

            //update语句,where条件要千万注意,增加上is_order='0',这样如果这条记录更新过,那么update返回影响的记录行就是0,就能进入下面的手动抛出异常

            $flag  =  Db :: table (  $table_name  )-> where ( 'is_order' , '1' )-> where (  'phone' ,  $daoru_phone  )-> where (  'clientkeynum' ,  $clientkeynum  )-> update (  $grade_phone  );

            if  ( ! $flag  ) {

                logRes (  Db :: table (  $table_name  )-> getLastSql (),  'order'  );

                throw   new   \Exception (  'update' . $table_name . '失败!'  );

           }

            //订单日志

            $order_log [ 'order_sn' ] =  $order_sn ;

            $order_log [ 'action_user' ] =  '前台客户' ;

            $order_log [ 'action_note' ] =  '前台客户下单' ;

            $order_log [ 'add_time' ] =  time ();

            $order_log [ 'clientkeynum' ] =  $clientkeynum ;

            $log_id  =  Db :: table (  'client_order_log'  )-> insertGetId (  $order_log  );

            if  ( ! $order_id  ) {

                throw   new   \Exception (  'insert,client_order_log失败!'  );

           }

            //减去产品库存 where条件要注意增加stock>0,其次数据库也要把库存字段stock类型改为无符号UNSIGNED,一旦更新成负数,也能在catch中自动捕获异常,从而回滚,保证库存别卖超

            $sql  =  "update client_good set stock=stock-1 where  stock>0 and  clientkeynum=' $clientkeynum '  and id=' $goodid '" ;

            $stock_flag  =  Db :: execute (  $sql  );

            if  ( ! $stock_flag  ) {

                throw   new   \Exception (  'update,client_good的库存失败!'  );

           }

            Db :: commit ();

       }  catch  ( \ Exception   $e  ) {

            // 回滚事务

            Db :: rollback ();

            $trans_result  =  false ;

            $msg  =  $e -> getMessage ();

            logRes (  '订单提交失败!--》' . $msg ,  'order'  );

       }

        //如果失败

        if  ( ! $trans_result  ) {

            $rt [ 'sta' ] =  '0' ;

            $rt [ 'msg' ] =  '兑换失败!' . $msg ;

            echo   json_encode (  $rt  );

            die ;

       }

        //清除session信息,保存订单信息

        $order_info  =  Db :: table (  'client_order_info'  )-> where (  "order_id=' $order_id '"  )-> find ();

        session (  'icbc_order_info' ,  $order_info  );

        //存入session

        Session :: delete (  'icbc_activity_grade_phone_info'  );

        Session :: delete (  'icbc_cart_good_info'  );

        $rt [ 'sta' ] =  '1' ;

        $rt [ 'msg' ] =  '兑换成功' ;

        echo   json_encode (  $rt  );

        die ;

   }


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
18天前
|
SQL 存储 缓存
【MySQL】事务
【MySQL】事务
23 0
|
18天前
|
SQL 存储 关系型数据库
MySQL索引及事务
MySQL索引及事务
32 2
|
2天前
|
存储 关系型数据库 MySQL
mysql 事务基本介绍
mysql 事务基本介绍
|
5天前
|
算法 关系型数据库 MySQL
MySQL (索引 & 事务)
MySQL (索引 & 事务)
22 3
|
6天前
|
存储 SQL 关系型数据库
MySQL事务
MySQL事务
16 0
|
6天前
|
Oracle 关系型数据库 MySQL
【MySQL】事务四大特性ACID、并发事务问题、事务隔离级别
【MySQL】事务四大特性ACID、并发事务问题、事务隔离级别
26 4
|
9天前
|
SQL 存储 关系型数据库
MySQL的事务(看看也许有帮助呢)
【5月更文挑战第18天】MySQL的事务
21 3
|
10天前
|
存储 关系型数据库 MySQL
mysql手动事务
mysql手动事务
20 0
|
10天前
|
存储 关系型数据库 MySQL
MySQL索引事务
MySQL索引事务
20 0
|
18天前
|
前端开发 easyexcel 关系型数据库
EasyExcel处理Mysql百万数据的导入导出案例,秒级效率,拿来即用!
【5月更文挑战第11天】EasyExcel处理Mysql百万数据的导入导出案例,秒级效率,拿来即用!
30 1