thinkphp5+mysql事务案例

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 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 ;

   }


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2天前
|
SQL 安全 关系型数据库
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
事务是MySQL中一组不可分割的操作集合,确保所有操作要么全部成功,要么全部失败。本文利用SQL演示并总结了事务操作、事务四大特性、并发事务问题、事务隔离级别。
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
|
4天前
|
存储 关系型数据库 MySQL
10个案例告诉你mysql不使用子查询的原因
大家好,我是V哥。上周与朋友讨论数据库子查询问题,深受启发。为此,我整理了10个案例,详细说明如何通过优化子查询提升MySQL性能。主要问题包括性能瓶颈、索引失效、查询优化器复杂度及数据传输开销等。解决方案涵盖使用EXISTS、JOIN、IN操作符、窗口函数、临时表及索引优化等。希望通过这些案例,帮助大家在实际开发中选择更高效的查询方式,提升系统性能。关注V哥,一起探讨技术,欢迎点赞支持!
|
7天前
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(04)事务隔离级别、AICD、CAP、BASE原则一直搞不懂? | 看这篇就够了
本文详细介绍了数据库事务的四大特性(AICD原则),包括原子性、隔离性、一致性和持久性,并深入探讨了事务并发问题与隔离级别。同时,文章还讲解了分布式系统中的CAP理论及其不可能三角关系,以及BASE原则在分布式系统设计中的应用。通过具体案例和图解,帮助读者理解事务处理的核心概念和最佳实践,为应对相关技术面试提供了全面的知识准备。
|
18天前
|
关系型数据库 MySQL 数据库
数据库数据恢复—MYSQL数据库文件损坏的数据恢复案例
mysql数据库文件ibdata1、MYI、MYD损坏。 故障表现:1、数据库无法进行查询等操作;2、使用mysqlcheck和myisamchk无法修复数据库。
|
2月前
|
存储 SQL 关系型数据库
MySQL的事务隔离级别
【10月更文挑战第17天】MySQL的事务隔离级别
128 43
|
1月前
|
关系型数据库 MySQL
mysql事务特性
原子性:一个事务内的操作统一成功或失败 一致性:事务前后的数据总量不变 隔离性:事务与事务之间相互不影响 持久性:事务一旦提交发生的改变不可逆
|
28天前
|
关系型数据库 MySQL 数据库
MySQL事务隔离级别及默认隔离级别的设置
在数据库系统中,事务隔离级别是一个关键的概念,它决定了事务在并发执行时如何相互隔离。MySQL提供了四种事务隔离级别,每种级别都解决了不同的并发问题。本文将详细介绍这些隔离级别以及MySQL的默认隔离级别。
|
2月前
|
关系型数据库 MySQL 数据库
一个 MySQL 数据库死锁的案例和解决方案
本文介绍了一个 MySQL 数据库死锁的案例和解决方案。
171 3
|
2月前
|
存储 关系型数据库 MySQL
基于案例分析 MySQL 权限认证中的具体优先原则
【10月更文挑战第26天】本文通过具体案例分析了MySQL权限认证中的优先原则,包括全局权限、数据库级别权限和表级别权限的设置与优先级。全局权限优先于数据库级别权限,后者又优先于表级别权限。在权限冲突时,更严格的权限将被优先执行,确保数据库的安全性与资源合理分配。
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?