数据库操作的“事务安全区”
前言
-------------------
前天突然收到顾客的电话,说系统的单据操作后,找不到了。我查看了日志,发现有表被锁的情况,立刻感觉到是系统升级后,有事务处理的问题。
晚上检查代码,发现了原来有事务开启之后,没有关闭的代码存在,导致了数据的丢失。这个简直是超级郁闷。幸好没有造成太大的损失。不过这种情况以后还会出现,怎样才能保证一个事务操作是安全的呢?
事务安全区
--------------------
事务安全区是我自己想出来的,含义就是:在这个区域里面操作事务是绝对安全的,任何代码上的bug都不会对系统数据造成影响。
一个理想的例子:
{
//delcare transaction safe area
public void TransactionProcess()
{
// open transaction here.
}
// check transaction here.
}
当方法体离开之后,有一种机制能够检测事务是否被开启过,如果是,则自动回滚,并记录日志。
从技术角度如何解决这个问题呢?我想到了四个方案。
事务安全区技术实现四剑客(伪代码)
----------------------------
1. 小白模式
代码
{
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关键字。
{
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. 使用匿名代理
{
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. 使用静态编译 + 反射检测
{
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;
}
}
代码部分来看,和普通的写法没有任何不同,关键部分在:
{
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. 并且非常希望骨灰级别的您,能共享一下您的源代码解决方案。小弟在此谢过了,哈哈哈。