zoukankan      html  css  js  c++  java
  • 程序员的自我救赎---1.3:事务的使用

    《前言》

    (一) Winner2.0 框架基础分析

    (二)PLSQL报表系统

    (三)SSO单点登录

    (四) 短信中心与消息中心

    (五)钱包系统

    (六)GPU支付中心

    (七)权限系统

    (八)监控系统

    (九)会员中心

    (十) APP版本控制系统

    (十一)Winner前端框架与RPC接口规范讲解

    (十二)上层应用案例

    (十三)总结

    《事务的使用》

    关于事务,我今天要把自己放在一个初学者的心态来写这篇文章。之前几篇文章大多讲的是对于Winner的应用,今天要从根本上来讲

    一下“事务”,以及事务在Winner中的应用。

    首先从基础讲起,什么是“事务”?事务能帮我们解决哪些问题? 摘录百度上的一段话教科书式的文字:

    “数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。”

    其实很好理解,比如说我们的商品购物流程中支付成功之后要做的几步操作: 

    1,修改订单表该状态;

    2,修改库存表库存数量;

    3,添加物流表发货信息;

    三个操作必须一气呵成,这时候就需要串联事务,当一个操作失败之后,事务就回滚整个业务失败。当操作成功之后,所有操作才最终持久化执行。

    假设我们没有事务的话,会怎么样?

    还是上面三个流程,没有联事务就有可能出现以下情况:

    步骤一: 修改订单表该状态 (完成)

    步骤二:修改库存表库存数量 (完成)

    步骤三:添加物流发货信息(失败)

    当步骤三失败,由于没有事务回滚,程序中就必须得通过程序判断步骤三得到失败后,再操作“步骤二”中库存订单数量回到未修改前的值。

    同事还要还原“步骤一”中订单表的订单状态。

    而且,如果数据库健壮性不够,有可能导致二次修改步骤一,步骤二失败,造出数据库一片混乱。

    这就是为什么我们要使用事务,事务有四大特性(百度摘录):

    原子性:事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。

    一致性:事务在完成时,必须使所有的数据都保持一致状态。

    隔离性:由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。

    持久性:事务完成之后,它对于系统的影响是永久性的。

    这些其实我也早忘了,毕竟工作多年以后也不会有这样的考试,读书那会还是记得挺清楚的,只是那会不能感受到

    事务的重要性,那时候的老师也不管那么多就照本宣科的讲,那些是以后工作生涯的重点,有时候老师自己都不知道

    造成了我们可能花很多时间去理解“游标”,“函数” 这些压根用不着几次的东西。

    开始事物:begin transaction

    提交事物:commit  transaction

    回滚事物:rollback transaction

    begin transaction  
     
    declare @errorSum int      --定义局部变量  
     
    set @errorSum=0  --初始化临时变量  
     
    update bank set currentMoneycurrentMoney= currentMoney-1000 where customerName='张三' 
     
    set @errorSum=@errorSum+@@error    --累计是否有错误  
     
    update bank set currentMoneycurrentMoney= currentMoney+1000 where customerName='李四' 
     
    set @errorSum=@errorSum+@@error    --累计是否有错误  
     
    if @errorSum<>0     --如果有错误  
     
    begin  
     
    rollback transaction  
     
    end  
     
    else  
     
    begin  
     
    commit  transaction  
     
    end  
     
    go 

    这里我就偷个懒,不自己去写个事务的案例,直接从网络上摘录,出处与:http://database.51cto.com/art/201108/283348.htm。

    每个数据库语法略又有不同,大整体差不到哪去我这里就不详细解释每个关键字上面意思了,自行百度吧! 再说这还是比较基础的知识,我就一笔带过。

    最后在事务基础知识再补充一点,使用事务时一定要谨慎,事务必须 “一开一关”,开启了一个事务必须要关闭这个事务,无论是提交(commit ) 还是 回滚(roolback)。

    必须要有关闭操作,如果没有关闭事务,则会造成事务挂起。数据库就会被锁,一旦数据数据被锁,轻则导致该表不能操作,重则导致整个数据库不能操作,致使整个

    程序奔溃不能运行。 这里一定要谨慎,在我工作了8年后我任然很多次看到我们系统会出现锁表的情况,都是有个别程序员对事务应用不当,导致事务挂起,数据库死锁。

    Winner一直使用的是Oracle数据库,这里贴一个我们常用的Sql工具:"锁表侦探"

    SELECT ROOT, L.SID_BLOCKED, L.TYPE, L.LMODE, SINFO.*
      FROM (SELECT ROWNUM ORDERNO, CONNECT_BY_ROOT SID_WAITING ROOT, T.*
              FROM (SELECT B.SID SID_BLOCKED, W.SID SID_WAITING, W.TYPE, W.LMODE
                      FROM V$LOCK B, V$LOCK W
                     WHERE B.ID1 = W.ID1
                       AND B.ID2 = W.ID2
                       AND B.BLOCK = 1
                       AND W.REQUEST > 0
                    UNION ALL
                    SELECT NULL, SID, TYPE, LMODE
                      FROM V$LOCK B
                     WHERE B.BLOCK = 1
                       AND SID NOT IN (SELECT DISTINCT W.SID SID_WAITING
                                         FROM V$LOCK B, V$LOCK W
                                        WHERE B.ID1 = W.ID1
                                          AND B.ID2 = W.ID2
                                          AND B.BLOCK = 1
                                          AND W.REQUEST > 0)) T
             START WITH SID_BLOCKED IS NULL
            CONNECT BY SID_BLOCKED = PRIOR SID_WAITING) L
      LEFT JOIN (SELECT S.SID,
                        SERIAL#,
                        O.OBJECT_NAMES,
                        T.START_TIME,
                        S.STATUS,
                        ST.SQL_TEXT,
                        S.MACHINE,
                        
                        S.PROGRAM,
                        S.USERNAME,
                        S.LOGON_TIME
                   FROM V$SESSION S
                   JOIN (SELECT SESSION_ID,
                               SUBSTR(SYS_CONNECT_BY_PATH(OBJECT_NAME, ','), 2) OBJECT_NAMES
                          FROM (SELECT ROW_NUMBER() OVER(PARTITION BY SESSION_ID ORDER BY OBJECT_NAME) AS RN,
                                       LO.*,
                                       O.*
                                  FROM V$LOCKED_OBJECT LO
                                  LEFT JOIN DBA_OBJECTS O
                                    ON LO.OBJECT_ID = O.OBJECT_ID) T
                         WHERE CONNECT_BY_ISLEAF = 1
                         START WITH RN = 1
                        CONNECT BY SESSION_ID = PRIOR SESSION_ID
                               AND RN =
                                  
                                   PRIOR RN + 1) O
                     ON S.SID = O.SESSION_ID
                   LEFT JOIN V$TRANSACTION T
                     ON S.TADDR = T.ADDR
                   LEFT JOIN V$SQLAREA ST
                     ON ST.ADDRESS = S.SQL_ADDRESS) SINFO
        ON SINFO.SID = L.SID_WAITING
     ORDER BY ORDERNO;

    在后面的篇章中会讲到“报表系统”,我习惯把锁表侦探添加到报表系统中,每次遇到锁表情况的时候就上报表系统查看是哪个项目锁表。

    这里有人就会问了,锁表侦探能查出具体哪张表所了,那怎么监控数据库有没有锁表的迹象? 这里要推荐第二个工具:“Spotligth”

    翻译过来叫“聚光灯”, Spotligth有很多版本,有监控 服务器的,有监控数据库的(主流都支持)

    spotlight on Oracle

    spotlight on Mysql

    spotlight on Windows

    我上班的时候 是两台电脑,一台办公,另外一台则挂着Spotligth 实时监控着我们的数据库,一飘红里面上报表系统查“锁表侦探”

    然后通知到相应的技术员,当然有时候还免不了要对犯错误的技术员 “指点”几句。

     

     =======================华丽的分割线=======================

    基础知识就到这里了,下面就是Winner的干货了。 在整个Winner中,我觉得最牛逼的当属“事务”这一块,能想出这种方式并开发出来了的真的很厉害。

    最初我在上家公司任职时,我的老大(William )他跟我讲事务的时候我就觉得太屌了,而这个事务就是由他开发的。

    其他的不多说,贴一张图就知道Winner中的事务有多好用:

              

    真的超级好用,一来不用写一句Sql,二来业务流程清晰,尤其是当程序需要调试的时候,这种方式能让程序员清晰的看到业务逻辑的每一个流程。

    这里运用到一个“职责分离”的思想,我们设定数据库的职责就是:“持久化存储数据”  复杂的业务逻辑由程序去处理。

    我刚参加工作那会任职过的几家公司,就没有这种思想(可能也是因为去的都是单一的项目型公司)。 最常见的就是 一旦涉及业务流程处理的他们

    就习惯性的以“存储过程”去处理,这样就使得开发变得繁琐,一会要写C#代码,一会有要去写sql代码,最重要的是数据库的不同又造成程序员要熟悉

    各种数据库的sql语法来写存储过程、事务、函数等等。

    “职责分离”的思想跟设计模式六大原则中的“单一职责”有点类似,但是“单一职责”更多的是指在程序中一个类只负责一项职责。“职责分离” 相当于“单一职责”

    的抽象版,程序做程序的负责业务逻辑,数据库做数据库的数据存储。   

    我曾经也见过,有的公司一开始用的sqlserver数据库,然后开发方式还是当时特牛气的 Html + Ajax + C# + 存储过程,后来因为业务关系更换到MySql,大量的存储

    过程写在了数据库里面,特别是有些关键字Mysql是没有或不支持的,致使他们痛苦不堪。

    ==============================华丽的分割线========================

    我们来看看Winner是如何实现的,首先Winner的业务类对象都基础了 FacadeBase 这个基类。 (关于Winner解决方案不清楚的可以《解决方案命名规范》

    using System;
    using Winner.Framework.Core.DataAccess;
    using Winner.Framework.Core.Interface;
    using Winner.Framework.Utils;
    
    namespace Winner.Framework.Core.Facade
    {
        public class FacadeBase : IDisposable, IPromptInfo
        {
            public IChangePage ChangePage;
    
            public FacadeBase();
    
            public PromptInfo PromptInfo { get; }
            public Transaction Transaction { get; }
    
            public virtual void Dispose();
            public void ReferenceTransactionFrom(Transaction transaction);
            protected void Alert(ResultType restulType);
            protected void Alert(PromptInfo result);
            protected void Alert(string msg);
            protected void Alert(ResultType restulType, PromptInfo result);
            protected void Alert(ResultType restulType, string msg);
            protected void Alert(string msg, PromptInfo result);
            protected void Alert(ResultType restulType, string msg, PromptInfo result);
            protected void BeginTransaction();
            protected void Commit();
            protected void RealRollback();
            protected void Rollback();
        }
    }

      FacadeBase在Winner.Framework.Core 程序集中,关于 FacadeBase其他的方法后面的篇章中再详细讲,今天重点讲事务这一块。

    public Transaction Transaction { get; } 定义事务对象; 对象由
    public void ReferenceTransactionFrom(Transaction transaction); 串联事务;

    protected void BeginTransaction(); 开启事务;
    protected void Rollback();  回滚事务;
    protected void Commit(); 提交事务;
    protected void RealRollback(); 强制回滚事务;


     
    using System;
    using Winner.Framework.Core.DataAccess;
    using Winner.Framework.Core.Interface;
    using Winner.Framework.Utils;
    namespace Winner.Framework.Core.Facade
    {
        /// <summary>
        /// 通用三层架构的业务处理层(BLL)基类
        /// </summary>
        public class FacadeBase : IDisposable, IPromptInfo
        {
          
            #region 事务
            /// <summary>
            /// 事物对象
            /// </summary>
            public Transaction Transaction { get; private set; }
    
            /// <summary>
            /// 开启事务
            /// </summary>
            protected void BeginTransaction()
            {
                if (this.Transaction == null)
                {
                    this.Transaction = new Winner.Framework.Core.DataAccess.Transaction();
                }
                this.Transaction.BeginTransaction();
            }
    
            /// <summary>
            /// 提交事务
            /// </summary>
            protected void Commit()
            {
                this.Transaction.Commit();
            }
    
            /// <summary>
            /// 强制回滚事物
            /// </summary>
            protected void RealRollback()
            {
                this.Transaction.RealRollback();
            }
    
            /// <summary>
            /// 事物串联
            /// </summary>
            /// <param name="transaction">事物对象</param>
            public void ReferenceTransactionFrom(Transaction transaction)
            {
                this.Transaction = transaction;
            }
    
            /// <summary>
            /// 回滚事物
            /// </summary>
            protected void Rollback()
            {
                this.Transaction.Rollback();
            }
            #endregion
    
        
    
        }
    }
     

    为了更清楚的单一讲清楚事务,FacadeBase我精剪掉了其他方法,只剩下事务有关的方法,会看到FacadeBase作为调用实现几个基本的操作

    整个Winner事务的核心在Winner.Framework.Core.DataAccess.Transaction  这个对象中。

    下面贴一些阿杰开发的Winner2.0 的事务对象,写的非常漂亮。

    using System;
    using System.Data;
    using System.Data.Common;
    using System.Diagnostics;
    using Winner.Framework.Core.CustomException;
    using Winner.Framework.Core.Delegate;
    using Winner.Framework.Utils;
    namespace Winner.Framework.Core.DataAccess
    {
        /// <summary>
        /// 数据库事务机制
        /// </summary>
        /// <remarks>
        /// <![CDATA[
        /// 四大特性:原子性、一致性、隔离性、持久性
        /// ]]>
        /// </remarks>
        [DebuggerDisplay("事务状态={Status},计数器={Counter}")]
        public class Transaction
        {
            #region Event
    
            /// <summary>
            /// 开启事务时触发
            /// </summary>
            public event BeginTransaction BeginTransactionEvent;
    
            /// <summary>
            /// 提交事务时触发
            /// </summary>
            public event CommitTransaction CommitEvent;
    
            /// <summary>
            /// 强制回滚时触发
            /// </summary>
            public event RealRollbackTransaction RealRollbackEvent;
    
            /// <summary>
            /// 回滚事务时触发
            /// </summary>
            public event RollbackTransaction RollbackEvent;
    
            #endregion
    
            #region Property
    
            /// <summary>
            /// 是否已开启事务
            /// </summary>
            public bool IsBegin { get; private set; }
            /// <summary>
            /// 是否提交
            /// </summary>
            public bool IsCommit { get; private set; }
            /// <summary>
            /// 是否强制回滚
            /// </summary>
            public bool IsRealRollback { get; private set; }
            /// <summary>
            /// 是否回滚
            /// </summary>
            public bool IsRollback { get; private set; }
    
            /// <summary>
            /// 事务计数器
            /// </summary>
            public int Counter { get; private set; }
    
    
            /// <summary>
            /// 事务状态
            /// </summary>
            public TransactionStatus Status { get; internal set; }
    
            /// <summary>
            /// 获取或设置连接数据库对象
            /// </summary>
            internal IDbConnection DbConnection { get; set; }
    
            /// <summary>
            /// 获取或设置事务机制对象
            /// </summary>
            internal IDbTransaction DBTransaction { get; set; }
    
            #endregion
    
            #region Member
    
            /// <summary>
            /// 开启事务(此处不真正开事务,会导致性能问题,所以在使用ADO.NET对象时才开启事务)
            /// </summary>
            public void BeginTransaction()
            {
                try
                {
                    if (this.Counter == 0)
                    {
                        this.Status = TransactionStatus.已启动事务;
                        this.IsBegin = true;
                        this.IsCommit = this.IsRollback = this.IsRealRollback = false;
                        if (this.BeginTransactionEvent != null)
                        {
                            this.BeginTransactionEvent(this);
                        }
                    }
                    this.Counter++;
                    OutupRunLog("BeginTransaction() Counter: " + this.Counter);
                }
                catch (Exception e)
                {
                    if (!Debuger.IsDebug)
                    {
                        Log.Error("开启事务时出现异常", e);
                    }
                    throw new TransactionException(e);
                }
            }
    
            /// <summary>
            /// 提交事务
            /// </summary>
            public void Commit()
            {
                try
                {
                    if (this.IsRealRollback)
                    {
                        return;
                    }
                    switch (this.Counter)
                    {
                        case 0:
                            throw new TransactionException(this.Status.ToString());
    
                        case 1:
                            if (!this.DBTransaction.IsNull())
                            {
                                this.DBTransaction.Commit();
                            }
                            if (!this.DbConnection.IsNull())
                            {
                                this.DbConnection.Close();
                            }
                            this.Status = TransactionStatus.事务已提交;
                            this.IsCommit = true;
                            this.IsBegin = this.IsRollback = this.IsRealRollback = false;
                            if (this.CommitEvent != null)
                            {
                                this.CommitEvent(this);
                            }
                            break;
                    }
                    OutupRunLog("Commit() Counter: " + this.Counter);
                    this.Counter--;
                }
                catch (Exception e)
                {
                    if (!Debuger.IsDebug)
                    {
                        Log.Error("提交事务时出现异常", e);
                    }
                    throw e;
                }
            }
    
            /// <summary>
            /// 强制回滚事务
            /// </summary>
            public void RealRollback()
            {
                try
                {
                    if (this.IsRollback)
                    {
                        return;
                    }
                    if (!this.DBTransaction.IsNull())
                    {
                        this.DBTransaction.Rollback();
                    }
                    if (!this.DbConnection.IsNull())
                    {
                        this.DbConnection.Close();
                    }
    
                    this.Status = TransactionStatus.事务已强制回滚;
                    this.IsRollback = this.IsRealRollback = true;
                    this.IsBegin = this.IsCommit = false;
                    if (this.RealRollbackEvent != null)
                    {
                        this.RealRollbackEvent(this);
                    }
                    OutupRunLog("RealRollback()");
                }
                catch (Exception ex)
                {
                    if (!Debuger.IsDebug)
                    {
                        Log.Error("强制回滚事务出现异常", ex);
                    }
                    throw new TransactionException(ex);
                }
                finally
                {
                    this.IsRealRollback = true;
                }
            }
    
            /// <summary>
            /// 回滚事务
            /// </summary>
            public void Rollback()
            {
                try
                {
                    if (this.IsRealRollback)
                    {
                        return;
                    }
                    switch (this.Counter)
                    {
                        case 0:
                            throw new TransactionException(this.Status.ToString());
    
                        case 1:
                            if (!this.DBTransaction.IsNull())
                            {
                                this.DBTransaction.Rollback();
                            }
                            if (!this.DbConnection.IsNull())
                            {
                                this.DbConnection.Close();
                            }
                            this.Status = TransactionStatus.事务已回滚;
                            this.IsRollback = true;
                            this.IsBegin = this.IsCommit = this.IsRealRollback = false;
                            if (this.RollbackEvent != null)
                            {
                                this.RollbackEvent(this);
                            }
                            break;
                    }
    
                    OutupRunLog("Rollback() Counter: " + this.Counter);
                    this.Counter--;
                }
                catch (Exception e)
                {
                    if (!Debuger.IsDebug)
                    {
                        Log.Error("回滚事务时出现异常", e);
                    }
                    throw e;
                }
            }
            #endregion
    
            /// <summary>
            /// 输出运行日志
            /// </summary>
            /// <param name="msg"></param>
            private void OutupRunLog(string msg)
            {
                Debug.WriteLine(msg);
                Console.WriteLine(msg);
            }
        }
    
        /// <summary>
        /// 事务状态
        /// </summary>
        public enum TransactionStatus
        {
            /// <summary>
            /// 未开启事务
            /// </summary>
            未启动事务 = 0,
    
            /// <summary>
            /// 已开启事务,但未操作数据库
            /// </summary>
            已启动事务 = 1,
    
            /// <summary>
            /// 已开启事务,并有数据库事务被挂起
            /// </summary>
            事务已挂起 = 2,
    
            /// <summary>
            /// 
            /// </summary>
            事务已提交 = 3,
            /// <summary>
            /// 事务已回滚
            /// </summary>
            事务已回滚 = 4,
            /// <summary>
            /// 事务已强制回滚
            /// </summary>
            事务已强制回滚 = 5,
        }
    }

      原理不复杂,都是调用System.Data 的 IDbTransaction 去完成的,经典的地方在于这个 Counter 事务计数器累加事务。

    而且阿杰的代码有个很有限的地方,并不是调用 BeginTransaction(),就去开启事务,这里是一个非常巧妙巧妙的设计,有效的

    避免了个别马虎的技术员开启了事务却有在代币流程中忘记提交or回滚造成的数据库死锁。

    下午阿杰过来和我聊了会,我说我在写关于事务的博客,阿杰说了很多。包括他当初为什么这样设计,以及他综合了动软基础框架的

    事务,还有微软的分布式事务。只是我还没办法转换成自己的语言写成博客,同时也要我经历像阿杰这样的创作过程,才能

    像他那样富含底蕴的讲述他的思考逻辑。

    今天我就写到这里,本来想跟深入的剖析一下Transaction对象,但是仔细看了一下,有些地方我也不是特别明白。哈哈,有点尴尬!

    关于对事务这一块的理解,如果有机会我希望阿杰也能写一篇博客,比我更详细的阐述Winner事务,尤其是他在对比多个框架的事务后,

    他对Winner创作过程的思考。他一定写的比我出彩,毕竟我只是一个使用者,而他是创这者。

    关于Winner,我新建了一个QQ群,有兴趣的可以加我们QQ群,阿杰,jason都在群中。我们可以一起探讨Winner,群号:261083244 

    也可以扫描博客左侧二维码加群。

      

  • 相关阅读:
    windows设置文件夹显示缩略图
    windows合并文件夹窗口
    crm高速开发之Entity
    在Eclipse中搭建Dagger和Dagger2使用环境
    CCEditBox/CCEditBoxImpl
    failed to sync branch You might need to open a shell and debug the state of this repo.
    五个方法:让站点用心服务于每一位客户
    Mobile first! Wijmo 5 + Ionic Framework之:Hello World!
    ST Nucleo mbed套件开发 一 MBED环境使用 以Nucleo-F401为例 (二)
    关于Windows通过远程桌面訪问Ubuntu
  • 原文地址:https://www.cnblogs.com/demon28/p/7919665.html
Copyright © 2011-2022 走看看