《前言》
《目录》
(一) Winner2.0 框架基础分析
(二) 短信中心
(三)SSO单点登录
(四)PLSQL报表系统
(五)钱包系统
(六)GPU支付中心
(七)权限系统
(八)监控系统
(九)会员中心
(十)消息中心
(十一)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
也可以扫描博客左侧二维码加群。
来源: http://www.cnblogs.com/demon28/p/7919665.html