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

前言

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

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

 

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

 

事务安全区

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

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

一个理想的例子:

代码
    class TransactionBusiness
    {
        
//delcare transaction safe area

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

        
// check transaction here.
    }

 

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

 

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

 

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

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

1. 小白模式 

 代码

    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关键字。

代码
    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. 使用匿名代理

代码
    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. 使用静态编译 + 反射检测

 

代码
    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;
        }
    }

 

 

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

代码
    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. 并且非常希望骨灰级别的您,能共享一下您的源代码解决方案。小弟在此谢过了,哈哈哈。

 

 

posted @ 2010-08-22 14:59    阅读(1145)  评论(6编辑  收藏  举报
IT民工