您的位置:首页 > 数据库

SQL Server 2000+ADO.NET实现并发控制...(三)

2011-11-09 11:49 597 查看
2.3.3 Serializable
Serializable隔离级别是最高的事务隔离级别,在此隔离级别下,不会出现读脏数据、不可重复读和幻影读的问题。在详细说明为什么之前首先让我们看看什么是幻影读。
所谓幻影读是指:事务1按一定条件从数据库中读取某些数据记录后,事务2插入了一些符合事务1检索条件的新记录,当事务1再次按相同条件读取数据时,发现多了一些记录。让我们通过以下案例来重现幻影读的问题:
第一步,将事务1和事务2均设为RepeatableRead隔离级别,并同时开启事务。
private static void Setup(){conn1 = new SqlConnection(connectionString);conn1.Open();tx1 = conn1.BeginTransaction(IsolationLevel.RepeatableRead);conn2 = new SqlConnection(connectionString);conn2.Open();tx2 = conn2.BeginTransaction(IsolationLevel.RepeatableRead);}
第二步,事务1读取学号为1的学生的平均成绩以及所学课程的门数。此时读到学生1学了3门课程,平均成绩为73.67。注意,此时事务1并未提交。
private static double ReadAverageMarksByTransaction1(){return (double)ExecuteScalar("SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)");}private static int ReadTotalCoursesByTransaction1(){return (int)ExecuteScalar("SELECT COUNT(*) AS num FROM SC WHERE (id = 1)");}private static object ExecuteScalar(string command){Console.WriteLine("-- Execute command: {0}", command);SqlCommand cmd = new SqlCommand(command, conn1);cmd.Transaction = tx1;return cmd.ExecuteScalar();}
第三步,事务2向数据库插入一条新记录,让学号为1的同学再学1门课程,成绩是80。然后提交修改到数据库。
private static void InsertRecordByTransaction2(){string command = "INSERT INTO SC VALUES(1, 5, 80)";Console.WriteLine("-- Insert to table SC by transaction 2");Console.WriteLine("-- Command:{0}\n", command);SqlCommand cmd = new SqlCommand(command, conn2);cmd.Transaction = tx2;try{cmd.ExecuteNonQuery();tx2.Commit();}catch(Exception e){Console.WriteLine(e.Message);tx2.Rollback();}}
第四步,事务1再次读取学号为1的学生的平均成绩以及所学课程的门数。此时读到确是4门课程,平均成绩为75.25。与第一次读取的不一样!居然多出了一门课程,多出的这门课程就像幻影一样出现在我们的面前。测试用主程序如下:
public static void Main(){Setup();try{Console.WriteLine(">>>> Step 1");double avg = ReadAverageMarksByTransaction1();int total = ReadTotalCoursesByTransaction1();Console.WriteLine("avg={0,5:F2}, total={1}\n", avg, total);Console.WriteLine(">>>> Step 2");InsertRecordByTransaction2();Console.WriteLine(">>>> Step 3");avg = ReadAverageMarksByTransaction1();total = ReadTotalCoursesByTransaction1();Console.WriteLine("avg={0,5:F2}, total={1}\n", avg, total);}catch(Exception e){Console.WriteLine("Got an error! " + e.Message);}finally{CleanUp();}}
程序执行结果如下:
Step 1-- Execute command: SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)-- Execute command: SELECT COUNT(*) AS num FROM SC WHERE (id = 1)avg=73.67, total=3>>>> Step 2-- Insert to table SC by transaction 2-- Command:INSERT INTO SC VALUES(1, 5, 80)>>>> Step 3-- Execute command: SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)-- Execute command: SELECT COUNT(*) AS num FROM SC WHERE (id = 1)avg=75.25, total=4
大家可以思考一下,为什么RepeatableRead隔离模式并不能使得两次读取的平均值一样呢?(可以从锁的角度来解释这一现象)。
仍然象前面的做法一样,我们看看究竟发生了什么事情。在探察之前,先将Setup方法中事务1的隔离级别设置为Serializable,再次运行程序,当发现程序运行暂停时,查看数据库当前活动快照,你会发现如图 2-14和图 2-15所示的锁定问题:

图 2-14 Serializable隔离模式对符合检索条件的数据添加了RangeS-S锁

图 2-15 当试图插入符合RangeIn条件的记录时,只能处于等待状态
从图中我们可以看出,在Serializalbe隔离模式下,数据库在检索数据时,对所有满足检索条件的记录均加上了RangeS-S共享锁。事务2试图去插入一满足RangeIn条件的记录时,必须等待这些RangS-S锁释放,否则就只能处于等待状态。在等待超时后,事务2就会被SQL Server强制回滚。
修改后的程序运行结果如下:
>>>> Step 1-- Execute command: SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)-- Execute command: SELECT COUNT(*) AS num FROM SC WHERE (id = 1)avg=73.67, total=3>>>> Step 2-- Insert to table SC by transaction 2-- Command:INSERT INTO SC VALUES(1, 5, 80)超时时间已到。在操作完成之前超时时间已过或服务器未响应。>>>> Step 3-- Execute command: SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)-- Execute command: SELECT COUNT(*) AS num FROM SC WHERE (id = 1)avg=73.67, total=3
事务2的运行失败确保了事务1不会出现幻影读的问题。这里应当注意的是,1、2、3级封锁协议都不能保证有效解决幻影读的问题。
2.3 建议
通过上面的几个例子,我们更深入的了解了数据库在解决并发一致性问题时所采取的措施。锁机制属于最底层的保证机制,但很难直接使用。我们可以通过不同的事务隔离模式来间接利用锁定机制确保我们数据的完整一致性。在使用不同级别的隔离模式时,我们也应当注意以下一些问题:
· 一般情况下ReadCommitted隔离级别就足够了。过高的隔离级别将会锁定过多的资源,影响数据的共享效率。
· 你所选择的隔离级别依赖于你的系统和商务逻辑。
· 尽量避免直接使用锁,除非在万不得已的情况下。
我们可以通过控制WHERE短语中的字段实现不同的更新策略,防止出现丢失的修改问题。但不必要的更新策略可能造成SQL命令执行效率低下。所以要慎用时间戳和过多的保护字段作为更新依据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数据库 休闲 ADO.NET
相关文章推荐