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


    前言

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

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

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

    事务安全区

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

    事务安全区是我自己想出来的,含义就是:在这个区域里面操作事务是绝对安全的,任何代码上的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. 并且非常希望骨灰级别的您,能共享一下您的源代码解决方案。小弟在此谢过了,哈哈哈。

  • 相关阅读:
    辅助随笔:因知识点不足暂时错过的题目
    NOIP2019翻车前写(and 抄)过的代码
    NOIP2019翻车前计划以及日记
    Luogu P3706 [SDOI2017]硬币游戏
    Luogu P5296 [北京省选集训2019]生成树计数
    Luogu P3307 [SDOI2013]项链
    Gaussian整数
    Problem. S
    LOJ6696 复读机 加强版
    数据库约束
  • 原文地址:https://www.cnblogs.com/zc22/p/1805774.html
Copyright © 2020-2023  润新知