thinkphp5+mysql事务案例

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
简介: 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 ;

   }


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
1月前
|
SQL 关系型数据库 MySQL
MySQL锁机制:并发控制与事务隔离
本文深入解析了MySQL的锁机制与事务隔离级别,涵盖锁类型、兼容性、死锁处理及性能优化策略,助你掌握高并发场景下的数据库并发控制核心技巧。
|
2月前
|
存储 监控 Oracle
MySQL事务
MySQL事务具有ACID特性,包括原子性、一致性、隔离性和持久性。其默认隔离级别为可重复读,通过MVCC和间隙锁解决幻读问题,确保事务间数据的一致性和并发性。
MySQL事务
|
3月前
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
29天前
|
SQL 关系型数据库 MySQL
Mysql数据恢复—Mysql数据库delete删除后数据恢复案例
本地服务器,操作系统为windows server。服务器上部署mysql单实例,innodb引擎,独立表空间。未进行数据库备份,未开启binlog。 人为误操作使用Delete命令删除数据时未添加where子句,导致全表数据被删除。删除后未对该表进行任何操作。需要恢复误删除的数据。 在本案例中的mysql数据库未进行备份,也未开启binlog日志,无法直接还原数据库。
|
12天前
|
关系型数据库 MySQL 数据库
【赵渝强老师】MySQL的事务隔离级别
数据库并发访问时易引发数据不一致问题。如客户端读取到未提交的事务数据,可能导致“脏读”。MySQL通过四种事务隔离级别(读未提交、读已提交、可重复读、可序列化)控制并发行为,默认为“可重复读”,以平衡性能与数据一致性。
151 0
|
1月前
|
关系型数据库 MySQL 数据库
MySql事务以及事务的四大特性
事务是数据库操作的基本单元,具有ACID四大特性:原子性、一致性、隔离性、持久性。它确保数据的正确性与完整性。并发事务可能引发脏读、不可重复读、幻读等问题,数据库通过不同隔离级别(如读未提交、读已提交、可重复读、串行化)加以解决。MySQL默认使用可重复读级别。高隔离级别虽能更好处理并发问题,但会降低性能。
|
3月前
|
安全 关系型数据库 MySQL
mysql事务隔离级别
事务隔离级别用于解决脏读、不可重复读和幻读问题。不同级别在安全与性能间权衡,如SERIALIZABLE最安全但性能差,READ_UNCOMMITTED性能高但易导致数据不一致。了解各级别特性有助于合理选择以平衡并发性与数据一致性需求。
158 1
|
6月前
|
关系型数据库 MySQL 大数据
大数据新视界--大数据大厂之MySQL 数据库课程设计:MySQL 数据库 SQL 语句调优的进阶策略与实际案例(2-2)
本文延续前篇,深入探讨 MySQL 数据库 SQL 语句调优进阶策略。包括优化索引使用,介绍多种索引类型及避免索引失效等;调整数据库参数,如缓冲池、连接数和日志参数;还有分区表、垂直拆分等其他优化方法。通过实际案例分析展示调优效果。回顾与数据库课程设计相关文章,强调全面认识 MySQL 数据库重要性。为读者提供综合调优指导,确保数据库高效运行。
|
3月前
|
运维 算法 机器人
阿里云AnalyticDB具身智能方案:破解机器人仿真数据、算力与运维之困
本文将介绍阿里云瑶池旗下的云原生数据仓库AnalyticDB MySQL推出的全托管云上仿真解决方案,方案采用云原生架构,为开发者提供从开发环境、仿真计算到数据管理的全链路支持。
|
1月前
|
存储 人工智能 关系型数据库
阿里云AnalyticDB for PostgreSQL 入选VLDB 2025:统一架构破局HTAP,Beam+Laser引擎赋能Data+AI融合新范式
在数据驱动与人工智能深度融合的时代,企业对数据仓库的需求早已超越“查得快”这一基础能力。面对传统数仓挑战,阿里云瑶池数据库AnalyticDB for PostgreSQL(简称ADB-PG)创新性地构建了统一架构下的Shared-Nothing与Shared-Storage双模融合体系,并自主研发Beam混合存储引擎与Laser向量化执行引擎,全面解决HTAP场景下性能、弹性、成本与实时性的矛盾。 近日,相关研究成果发表于在英国伦敦召开的数据库领域顶级会议 VLDB 2025,标志着中国自研云数仓技术再次登上国际舞台。
224 0

推荐镜像

更多