数据库操作的“事务安全区”

前言

------------------- 

前天突然收到顾客的电话,说系统的单据操作后,找不到了。我查看了日志,发现有表被锁的情况,立刻感觉到是系统升级后,有事务处理的问题。

 

晚上检查代码,发现了原来有事务开启之后,没有关闭的代码存在,导致了数据的丢失。这个简直是超级郁闷。幸好没有造成太大的损失。不过这种情况以后还会出现,怎样才能保证一个事务操作是安全的呢?

 

事务安全区

-------------------- 

事务安全区是我自己想出来的,含义就是:在这个区域里面操作事务是绝对安全的,任何代码上的bug都不会对系统数据造成影响。

一个理想的例子:

ExpandedBlockStart.gif 代码
     class  TransactionBusiness
    {
        
// delcare transaction safe area

        
public   void  TransactionProcess()
        {
            
//  open transaction here.
        }

        
//  check transaction here.
    }

 

当方法体离开之后,有一种机制能够检测事务是否被开启过,如果是,则自动回滚,并记录日志。 

 

从技术角度如何解决这个问题呢?我想到了四个方案。

 

事务安全区技术实现四剑客(伪代码)

---------------------------- 

1. 小白模式 

 ExpandedBlockStart.gif代码

     class  TransactionBusiness
    {
        
public   void  TransactionProcess()
        {
            DbCommand command 
=   new  DbCommand();

            
try
            {
                command.Open();

                command.Execute();

                
// oops!! Forgot commit!
            }
            
finally
            {
                
if  (command.IsOpen)
                    command.Rollback();
            }
        }
    }


    
class  DbCommand
    {
        
bool  isOpen  =   false ;

        
public   void  Open()
        {
            
// open transaction here.

            isOpen 
=   true ;
        }

        
public   bool  IsOpen
        {
            
get
            {
                
return  isOpen;
            }
        }

        
public   void  Execute()
        {
            
// execute command
        }

        
public   void  Rollback()
        {
            
// rollback transaction

            isOpen 
=   false ;
        }

        
public   void  Commit()
        {
            
// commit transaction

            isOpen 
=   false ;
        }
    }

这个嘛。。我就不解释了,是属于事务操作的入门级别规范了。是个写代码的人都应该掌握的。

  

2. 使用using关键字。

ExpandedBlockStart.gif 代码
     class  TransactionBusiness
    {
        
public   void  TransactionProcess()
        {
            
using  (DbCommand command  =   new  DbCommand())
            {
                command.Open();

                command.Execute();

                
// oops!! Forgot to commit here!!
            }
        }
    }

    
class  DbCommand : IDisposable
    {
        
bool  isOpen  =   false ;

        
public   void  Open()
        {
            
// open transaction here.

            isOpen 
=   true ;
        }

        
public   bool  IsOpen
        {
            
get
            {
                
return  isOpen;
            }
        }

        
public   void  Execute()
        {
            
// execute command
        }

        
public   void  Rollback()
        {
            
// rollback transaction

            isOpen 
=   false ;
        }

        
public   void  Commit()
        {
            
// commit transaction

            isOpen 
=   false ;
        }

        
public   void  Dispose()
        {
            
if  ( this .IsOpen)
            {
                
this .Rollback();
            }
        }
    }

 

使用了using关键字后,当离开using的区域,系统自动调用Dispose,这样,就能够检测是否关闭了事务。

个人感觉是一种合格的选择。 

 

3. 使用匿名代理

ExpandedBlockStart.gif 代码
     class  TransactionBusiness
    {
        
public   void  TransactionProcess()
        {
            DbCommand command 
=   new  DbCommand();

            command.OpenSafely(
delegate ()
            {
                command.Execute();

                
// oops!! Forgot to commit here!!
            }
            );
        }
    }

    
class  DbCommand
    {
        
bool  isOpen  =   false ;

        
public   delegate   void  SafeTransactionArea();

        
public   void  OpenSafely(SafeTransactionArea area)
        {
            isOpen 
=   true ;

            area();

            
if  (isOpen)
            {
                Console.WriteLine(
" transaction is not commit. rollback. " );

                Rollback();
            }
        }

        
public   void  Rollback()
        {
            isOpen 
=   false ;
        }

        
public   void  Commit()
        {
            isOpen 
=   false ;
        }

        
internal   void  Execute()
        {
            
// execute to database
        }
    }

 

在匿名代理里面,操作command,是安全的。我个人认为这种写法没有using漂亮,但是也能够解决问题。

他唯一的优点是,当用户需要使用事务的时候,强迫了一种安全的代码格式,而using如果忘记标识,同样会造成事务处理异常。

 

4. 使用静态编译 + 反射检测

 

ExpandedBlockStart.gif 代码
     class  TransactionBusiness
    {
        
public   void  TransactionProcess()
        {
            DbCommand command 
=   new  DbCommand();

            command.Open();

            command.Execute();

            
//  oops!! forgot to commit!!
        }
    }


    
class  DbCommand
    {
        
bool  isOpen  =   false ;

        
public   void  Open()
        {
            
// open transaction here.

            isOpen 
=   true ;
        }

        
public   bool  IsOpen
        {
            
get
            {
                
return  isOpen;
            }
        }

        
public   void  Execute()
        {
            
// execute command
        }

        
public   void  Rollback()
        {
            
// rollback transaction

            isOpen 
=   false ;
        }

        
public   void  Commit()
        {
            
// commit transaction

            isOpen 
=   false ;
        }
    }

 

 

代码部分来看,和普通的写法没有任何不同,关键部分在:

ExpandedBlockStart.gif 代码
     class  SafeTransactionVerification
    {
        
public   void Verify(Assembly assembly)
        {
            
foreach  (Type type  in  assembly.GetTypes())
            {
                
foreach  (MethodInfo info  in  type.GetMethods(BindingFlags.Public  |  BindingFlags.NonPublic  |  BindingFlags.Instance  |  BindingFlags.Static))
                {
                    
bool  hasOpen  =   false ;

                    
bool  hasClose  =   false ;

                    MethodBodyReflector reflector 
=   new  MethodBodyReflector(info);

                    
foreach  (ILInstruction il  in  reflector.Instructions)
                    {
                        
if  (il.GetMethodInfo().DeclaringType  ==   typeof (DbCommand)  &&  il.GetMethodInfo().Name.Equals( " OPEN " , StringComparison.OrdinalIgnoreCase))
                        {
                            hasOpen 
=   true ;

                            
continue ;
                        }

                        
if  (il.GetMethodInfo().DeclaringType  ==   typeof (DbCommand)  &&  il.GetMethodInfo().Name.Equals( " COMMIT " , StringComparison.OrdinalIgnoreCase))
                        {
                            hasClose 
=   true ;

                            
continue ;
                        }
                    }

                    
if  (hasOpen  &&  hasClose)
                        
continue ;

                    
throw   new  Exception( " Transaction is not COMMITTED safely!! " );
                }
            }
        }
    }

    
public   class  MethodBodyReflector
    {
        
private  List < ILInstruction >  instructions  =   new  List < ILInstruction > ();

        ILGlobals 
global   =   new  ILGlobals();

        
public  MethodBodyReflector(MethodBase info)
        {
            
global .LoadOpCodes();

            
if  (info.GetMethodBody()  !=   null )
            {
                ConstructInstructions(info);
            }
        }

        
///   <summary>
        
///  所有指令集合
        
///   </summary>
         public  List < ILInstruction >  Instructions
        {
            
get
            {
                
return  instructions;
            }
        }

        
private   void  ConstructInstructions(MethodBase info)
        {
            
byte [] il  =  info.GetMethodBody().GetILAsByteArray();

            
int  position  =   0 ;

            
while  (position  <  il.Length)
            {
                ILInstruction instruction 
=  ConstructInstruction(info.Module, info, il,  ref  position);

                instructions.Add(instruction);
            }
        }

        
private  ILInstruction ConstructInstruction(Module module, MethodBase mi,  byte [] il,  ref   int  position)
        {
            ILInstruction instruction 
=   new  ILInstruction();

            
//  get the operation code of the current instruction
            OpCode code  =  OpCodes.Nop;
            
ushort  value  =  il[position ++ ];
            
if  (value  !=   0xfe )
            {
                code 
=   global .SingleByteOpCodes[( int )value];
            }
            
else
            {
                value 
=  il[position ++ ];
                code 
=   global .MultiByteOpCodes[( int )value];
                value 
=  ( ushort )(value  |   0xfe00 );
            }
            instruction.Code 
=  code;
            instruction.Offset 
=  position  -   1 ;
            
int  metadataToken  =   0 ;
            
//  get the operand of the current operation
             switch  (code.OperandType)
            {
                
case  OperandType.InlineBrTarget:
                    metadataToken 
=  ReadInt32(il,  ref  position);
                    metadataToken 
+=  position;
                    instruction.Operand 
=  metadataToken;
                    
break ;
                
case  OperandType.InlineField:
                    metadataToken 
=  ReadInt32(il,  ref  position);
                    
if  (mi  is  ConstructorInfo)
                    {
                        instruction.Operand 
=  module.ResolveField(metadataToken,
                            mi.DeclaringType.GetGenericArguments(), 
null );
                    }
                    
else
                    {
                        instruction.Operand 
=  module.ResolveField(metadataToken,
                            mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                    }
                    
break ;
                
case  OperandType.InlineMethod:
                    metadataToken 
=  ReadInt32(il,  ref  position);
                    
try
                    {
                        
if  (mi  is  ConstructorInfo)
                        {
                            instruction.Operand 
=  module.ResolveMethod(metadataToken,
                                mi.DeclaringType.GetGenericArguments(), 
null );
                        }
                        
else
                        {
                            instruction.Operand 
=  module.ResolveMethod(metadataToken,
                                mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                    }
                    
catch
                    {
                        
if  (mi  is  ConstructorInfo)
                        {
                            instruction.Operand 
=  module.ResolveMember(metadataToken,
                                mi.DeclaringType.GetGenericArguments(), 
null );
                        }
                        
else
                        {
                            instruction.Operand 
=  module.ResolveMember(metadataToken,
                                mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                    }
                    
break ;
                
case  OperandType.InlineSig:
                    metadataToken 
=  ReadInt32(il,  ref  position);
                    instruction.Operand 
=  module.ResolveSignature(metadataToken);
                    
break ;
                
case  OperandType.InlineTok:
                    metadataToken 
=  ReadInt32(il,  ref  position);
                    
try
                    {
                        
if  (mi  is  ConstructorInfo)
                        {
                            instruction.Operand 
=  module.ResolveType(metadataToken,
                                mi.DeclaringType.GetGenericArguments(), 
null );
                        }
                        
else
                        {
                            instruction.Operand 
=  module.ResolveType(metadataToken,
                                mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                    }
                    
catch
                    {
                    }
                    
break ;
                
case  OperandType.InlineType:
                    metadataToken 
=  ReadInt32(il,  ref  position);
                    
if  (mi  is  MethodInfo)
                    {
                        instruction.Operand 
=  module.ResolveType(metadataToken,
                        mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                    }
                    
else   if  (mi  is  ConstructorInfo)
                    {
                        instruction.Operand 
=  module.ResolveType(metadataToken,
                           mi.DeclaringType.GetGenericArguments(), 
null );
                    }
                    
else
                    {
                        instruction.Operand 
=  module.ResolveType(metadataToken);
                    }
                    
break ;
                
case  OperandType.InlineI:
                    {
                        instruction.Operand 
=  ReadInt32(il,  ref  position);
                        
break ;
                    }
                
case  OperandType.InlineI8:
                    {
                        instruction.Operand 
=  ReadInt64(il,  ref  position);
                        
break ;
                    }
                
case  OperandType.InlineNone:
                    {
                        instruction.Operand 
=   null ;
                        
break ;
                    }
                
case  OperandType.InlineR:
                    {
                        instruction.Operand 
=  ReadDouble(il,  ref  position);
                        
break ;
                    }
                
case  OperandType.InlineString:
                    {
                        metadataToken 
=  ReadInt32(il,  ref  position);
                        instruction.Operand 
=  module.ResolveString(metadataToken);
                        
break ;
                    }
                
case  OperandType.InlineSwitch:
                    {
                        
int  count  =  ReadInt32(il,  ref  position);
                        
int [] casesAddresses  =   new   int [count];
                        
for  ( int  i  =   0 ; i  <  count; i ++ )
                        {
                            casesAddresses[i] 
=  ReadInt32(il,  ref  position);
                        }
                        
int [] cases  =   new   int [count];
                        
for  ( int  i  =   0 ; i  <  count; i ++ )
                        {
                            cases[i] 
=  position  +  casesAddresses[i];
                        }
                        
break ;
                    }
                
case  OperandType.InlineVar:
                    {
                        instruction.Operand 
=  ReadUInt16(il,  ref  position);
                        
break ;
                    }
                
case  OperandType.ShortInlineBrTarget:
                    {
                        instruction.Operand 
=  ReadSByte(il,  ref  position)  +  position;
                        
break ;
                    }
                
case  OperandType.ShortInlineI:
                    {
                        instruction.Operand 
=  ReadSByte(il,  ref  position);
                        
break ;
                    }
                
case  OperandType.ShortInlineR:
                    {
                        instruction.Operand 
=  ReadSingle(il,  ref  position);
                        
break ;
                    }
                
case  OperandType.ShortInlineVar:
                    {
                        instruction.Operand 
=  ReadByte(il,  ref  position);
                        
break ;
                    }
                
default :
                    {
                        
throw   new  Exception( " Unknown operand type. " );
                    }
            }
            
return  instruction;
        }

        
private   int  ReadInt16( byte [] _il,  ref   int  position)
        {
            
return  ((_il[position ++ |  (_il[position ++ <<   8 )));
        }

        
private   ushort  ReadUInt16( byte [] _il,  ref   int  position)
        {
            
return  ( ushort )((_il[position ++ |  (_il[position ++ <<   8 )));
        }

        
private   int  ReadInt32( byte [] _il,  ref   int  position)
        {
            
return  (((_il[position ++ |  (_il[position ++ <<   8 ))  |  (_il[position ++ <<   0x10 ))  |  (_il[position ++ <<   0x18 ));
        }

        
private   ulong  ReadInt64( byte [] _il,  ref   int  position)
        {
            
return  ( ulong )(((_il[position ++ |  (_il[position ++ <<   8 ))  |  (_il[position ++ <<   0x10 ))  |  (_il[position ++ <<   0x18 |  (_il[position ++ <<   0x20 |  (_il[position ++ <<   0x28 |  (_il[position ++ <<   0x30 |  (_il[position ++ <<   0x38 ));
        }

        
private   double  ReadDouble( byte [] _il,  ref   int  position)
        {
            
return  (((_il[position ++ |  (_il[position ++ <<   8 ))  |  (_il[position ++ <<   0x10 ))  |  (_il[position ++ <<   0x18 |  (_il[position ++ <<   0x20 |  (_il[position ++ <<   0x28 |  (_il[position ++ <<   0x30 |  (_il[position ++ <<   0x38 ));
        }

        
private   sbyte  ReadSByte( byte [] _il,  ref   int  position)
        {
            
return  ( sbyte )_il[position ++ ];
        }

        
private   byte  ReadByte( byte [] _il,  ref   int  position)
        {
            
return  ( byte )_il[position ++ ];
        }

        
private  Single ReadSingle( byte [] _il,  ref   int  position)
        {
            
return  (Single)(((_il[position ++ |  (_il[position ++ <<   8 ))  |  (_il[position ++ <<   0x10 ))  |  (_il[position ++ <<   0x18 ));
        }
    }

    
public   class  ILGlobals
    {
        
private  OpCode[] multiByteOpCodes;

        
private  OpCode[] singleByteOpCodes;

        
///   <summary>
        
///  Loads the OpCodes for later use.
        
///   </summary>
         public   void  LoadOpCodes()
        {
            singleByteOpCodes 
=   new  OpCode[ 0x100 ];
            multiByteOpCodes 
=   new  OpCode[ 0x100 ];
            FieldInfo[] infoArray1 
=   typeof (OpCodes).GetFields();
            
for  ( int  num1  =   0 ; num1  <  infoArray1.Length; num1 ++ )
            {
                FieldInfo info1 
=  infoArray1[num1];
                
if  (info1.FieldType  ==   typeof (OpCode))
                {
                    OpCode code1 
=  (OpCode)info1.GetValue( null );
                    
ushort  num2  =  ( ushort )code1.Value;
                    
if  (num2  <   0x100 )
                    {
                        singleByteOpCodes[(
int )num2]  =  code1;
                    }
                    
else
                    {
                        
if  ((num2  &   0xff00 !=   0xfe00 )
                        {
                            
throw   new  Exception( " Invalid OpCode. " );
                        }
                        multiByteOpCodes[num2 
&   0xff =  code1;
                    }
                }
            }
        }

        
///   <summary>
        
///  Retrieve the friendly name of a type
        
///   </summary>
        
///   <param name="typeName">
        
///  The complete name to the type
        
///   </param>
        
///   <returns>
        
///  The simplified name of the type (i.e. "int" instead f System.Int32)
        
///   </returns>
         public   static   string  ProcessSpecialTypes( string  typeName)
        {
            
string  result  =  typeName;
            
switch  (typeName)
            {
                
case   " System.string " :
                
case   " System.String " :
                
case   " String " :
                    result 
=   " string " break ;
                
case   " System.Int32 " :
                
case   " Int " :
                
case   " Int32 " :
                    result 
=   " int " break ;
            }
            
return  result;
        }


        
public  OpCode[] MultiByteOpCodes
        {
            
get  {  return  multiByteOpCodes; }
        }

        
public  OpCode[] SingleByteOpCodes
        {
            
get  {  return  singleByteOpCodes; }
        }
    }

    
public   class  ILInstruction
    {
        
private  OpCode code;
        
private   object  operand;
        
private   byte [] operandData;
        
private   int  offset;

        
public  ILInstruction()
        {
        }


        
///   <summary>
        
///  获取指令的类型
        
///   </summary>
         public  ILInstructionType InstructionType
        {
            
get
            {
                
if  (operand  ==   null )
                    
return  ILInstructionType.Unknown;


                
switch  (code.OperandType)
                {
                    
case  OperandType.InlineField:
                        
return  ILInstructionType.Field;

                    
case  OperandType.InlineMethod:
                        {
                            
if  (operand  is  System.Reflection.MethodInfo)
                            {
                                
return  ILInstructionType.Method;
                            }
                            
else   if  (operand  is  System.Reflection.ConstructorInfo)
                            {
                                
return  ILInstructionType.Ctor;
                            }
                            
else
                            {
                                
return  ILInstructionType.Unknown;
                            }
                        }

                    
case  OperandType.ShortInlineBrTarget:
                    
case  OperandType.InlineBrTarget:
                        
return  ILInstructionType.Unknown;

                    
case  OperandType.InlineType:
                        
return  ILInstructionType.Unknown;

                    
case  OperandType.InlineString:
                        
return  ILInstructionType.String;

                    
case  OperandType.ShortInlineVar:
                        
return  ILInstructionType.Variable;

                    
case  OperandType.InlineI:
                    
case  OperandType.InlineI8:
                    
case  OperandType.InlineR:
                    
case  OperandType.ShortInlineI:
                    
case  OperandType.ShortInlineR:
                        
return  ILInstructionType.Number;

                    
case  OperandType.InlineTok:
                        
return  ILInstructionType.Reference;
                }


                
return  ILInstructionType.Unknown;
            }
        }

        
///   <summary>
        
///  获取对应的方法
        
///   </summary>
        
///   <returns></returns>
         public  MethodInfo GetMethodInfo()
        {
            
if  (operand  ==   null )
                
return   null ;

            
if  (InstructionType  ==  ILInstructionType.Method)
                
return  (System.Reflection.MethodInfo)operand;

            
return   null ;
        }

        
///   <summary>
        
///  获取构造函数
        
///   </summary>
        
///   <returns></returns>
         public  ConstructorInfo GetConstructorInfo()
        {
            
if  (operand  ==   null )
                
return   null ;

            
if  (InstructionType  ==  ILInstructionType.Ctor)
                
return  (System.Reflection.ConstructorInfo)operand;

            
return   null ;
        }

        
///   <summary>
        
///  获取域反射
        
///   </summary>
        
///   <returns></returns>
         public  FieldInfo GetFieldInfo()
        {
            
if  (operand  ==   null )
                
return   null ;

            
if  (InstructionType  ==  ILInstructionType.Field)
                
return  ((System.Reflection.FieldInfo)operand);

            
return   null ;
        }

        
///   <summary>
        
///  获取属性
        
///   </summary>
        
///   <returns></returns>
         public   string  GetString()
        {
            
return  operand.ToString();
        }


        
///   <summary>
        
///  Add enough zeros to a number as to be represented on 4 characters
        
///   </summary>
        
///   <param name="offset">
        
///  The number that must be represented on 4 characters
        
///   </param>
        
///   <returns>
        
///   </returns>
         private   string  GetExpandedOffset( long  offset)
        {
            
string  result  =  offset.ToString();
            
for  ( int  i  =   0 ; result.Length  <   4 ; i ++ )
            {
                result 
=   " 0 "   +  result;
            }
            
return  result;
        }


        
public  OpCode Code
        {
            
get  {  return  code; }
            
set  { code  =  value; }
        }

        
public   object  Operand
        {
            
get  {  return  operand; }
            
set  { operand  =  value; }
        }

        
public   byte [] OperandData
        {
            
get  {  return  operandData; }
            
set  { operandData  =  value; }
        }

        
public   int  Offset
        {
            
get  {  return  offset; }
            
set  { offset  =  value; }
        }

        
public   override   string  ToString()
        {
            
return   string .Format( " {0} {1} {2} " , offset, code.ToString(), operand);
        }
    }

    
public   enum  ILInstructionType
    {
        Unknown,

        
///   <summary>
        
///  内部调用的对象
        
///   </summary>
        Field,
        
///   <summary>
        
///  内部调用的方法
        
///   </summary>
        Method,
        
///   <summary>
        
///  内部调用的构造函数方法
        
///   </summary>
        Ctor,
        
///   <summary>
        
///  内部调用的字符串
        
///   </summary>
        String,
        
///   <summary>
        
///  [没有确定]内部调用的值对象
        
///   </summary>
        Number,
        
///   <summary>
        
///  [没有确定]内部调用的变量
        
///   </summary>
        Variable,
        
///   <summary>
        
///  [没有确定]内部调用的引用
        
///   </summary>
        Reference,


        
///   <summary>
        
///  集合初始化
        
///   </summary>
        ArrayCtor,
        
///   <summary>
        
///  对比表达式
        
///   </summary>
        Compare,
        
///   <summary>
        
///  加载值对象
        
///   </summary>
        LoadValue,
        
///   <summary>
        
///  加载数组
        
///   </summary>
        LoadArray,
        
///   <summary>
        
///  根据位置 加载本地变量
        
///   </summary>
        LoadLocalVariable,
    }

 

 

这一大片代码,实际上就干了一件事情:

1. 加载需要检测事务的Assembly

2. 对这个Assembly的所有方法进行方法体的遍历

3. 如果方法体内出现了开启事务,但是没有关闭事务,则抛出异常。 

当然,这个方法使用了DotNet传说中的潘多拉魔盒——IL语言。 

 

本人认为,这个是最佳选择, 也是最高级别的方式。当代码编译完成后,首先整个DLL交给这个检测器进行静态反射检测,如果发现了异常,则表示有事务没有完成的地方。

 

后续

------------------------

本文提供了四种方法,保证事务操作的绝对安全。

 

如果是入门级别的,使用1、2即可。 

 

如果是架构师级别的,使用3.

 

如果是骨灰级别的,使用4. 并且非常希望骨灰级别的您,能共享一下您的源代码解决方案。小弟在此谢过了,哈哈哈。

 

 

转载于:https://www.cnblogs.com/zc22/archive/2010/08/22/1805774.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值