程序员的自我救赎---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 

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

 

 

 

 

 

 

  

 

posted @ 2017-11-29 20:23  Near_wen  阅读(1164)  评论(1编辑  收藏  举报