web后台多⽤户操作同⼀条数据同步问题
场景(问题)描述如下:
0,⽤户A、B同时打开⼀个页⾯,页⾯显⽰,客户表T_CUSTOMER字段(C_NAME、C_AGE)
姓名:张三,年龄:25
1,A 将姓名“张三”改为“张三1”,然后保存
2,B 将年龄“25”改为“30”,然后保存
这样A的操作就被覆盖了,姓名⼜变回“张三”了,⼤家⼀般怎么处处这种情况?
这⾥给出⼀个较易⽤的解决⽅案
给表添加⼀字段:LAST_UPDATE,即最后更新时间
回放场景
0,⽤户A、B同时打开⼀页⾯,⾯页显⽰:
姓名:张三,年龄:25,LAST_UPDATE:2008-10-17 13:45:00
1,A 将姓名“张三”改为“张三1”,然后保存
重点在这⾥:更新数据时WHERE条件⾥多⼀条件:AND LAST_UPDATE = '2008-10-17 13:45:00'
更新成功,此时触发器会将当前时间“2008-10-17 13:46:00”赋值给LAST_UPDATE
2,B 将将年龄“25”改为“30”,然后保存
B更新数据时WHERE条件⾥也有这个条件:AND LAST_UPDATE = '2008-10-17 13:45:00',但此时LAST_UPDATE的值已经在A修改记录时变成2008-10-17 13:46:00
下⾯要做的就是给出提⽰了:喔哟,此信息在你发呆这段时间已被⼈改过啦,所以你需要返⼯。
触发器代码如下:
===================================================
CREATE OR REPLACE TRIGGER T_CUSTOMER
BEFORE UPDATE ON T_CUSTOMER
FOR EACH ROW
/*
记录最后修改时间
*/
BEGIN
:NEW.LAST_UPDATE := SYSDATE;
END;
===================================================
如果触发器不熟悉或者只是不喜欢⽤触发器,完全可以修改记录时同时给LAST_UPDATE字段赋值,以此替代触发器的作⽤。
【并发操作】多⽤户并发操作的解决⽅案
【问题】在以前的系统开发中,经常遇到⼀个同样问题,就是多个⽤户同时并发操作⼀条记录,这次在交易系统开发过程中,⼜出现了这样问题。⽐如交易商A提交单⼦,由审核⼈员B审核,此时A正在修改单位,B也正在查看这条记录,A先修改保存后B再审核保存,导致B审核通过的记录不是他所看到的。
【分析】仔细考虑问题,⼤概分析了三个⽅法, 并确定了⼀个可⾏的⽅案,可能还有不完善的地⽅,但解决现有问题还是绰绰有余的。
最先想的是在交易业务代码中⽤lock对修改⽅法加锁,运⾏时注⼊单例,并且对⽅法加同步锁,保证了业务数据的正确性, 但是效率低下,⽤户在使⽤中不⽅便,背离了系统可以并发操作的原则。
然后考虑使⽤悲观锁,这次运⾏时注⼊原型,并发操作效率提⾼, 但是,在同步处理数据库表时⼜造成锁表,这样并发效率依旧很低。
最后考虑乐观锁, 只是⼀种数据版本校验机制,它不做数据库层次上的锁定,需要在要并发操作的主表中加⼊⼀个"VERSION"字段或
者“LASTUPDATETIME”,如果研究过oracle的SCN应该有异曲同⼯之妙,它的原理是这样:运⾏时注⼊原型,这时数据表并不锁住,只是每次update或insert时将VERSION更新, 当下次update,insert时,
会校验VERSION,如果不⼀致,此时提⽰信息已经修改过了,并且在事务控制下rollback,这样,保证了数据的正确性,并且也不影响并发操作的效率。但是出现的问题是:如果A读了数据,未来及更新,B先更新了数据, 那么他将提交不上数据,因为VERSION已经失效,这样就造成了A重新读取数据,重新填写单据信息。 这也是乐观锁⼀个缺点,可以在更新前临时保存A填写的数据,再在跳转页中加载这些数据,保证了交易商不⽤⿇烦再次输⼊这些数据,更新成功则清空这些临时数据
【结果】
乐观锁在并发操作问题上,保证了业务效率和数据的正确性,基本可以采⽤这种⽅案解决交易中并发问题。
●悲观锁:指在应⽤程序中显式地为数据资源加锁。悲观锁假定当前事务操
纵数据资源时,肯定还会有其他事务同时访问该数据资源,为了避免当前
事务的操作受到⼲扰,先锁定资源。尽管悲观锁能够防⽌丢失更新和不可
重复读这类并发问题,但是它会影响并发性能,因此应该很谨慎地使⽤悲
观锁。
●乐观锁:乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问
该数据资源,因此完全依靠数据库的隔离级别来⾃动管理锁的⼯作。应⽤
程序采⽤版本控制⼿段来避免可能出现的并发问题。
----------------------------------------------------------------------------
.Net中的事务处理(多⽤户同时操作⼀条信息时是⽤-并发) [Web Applicaion in C#]
SqlConnection myConnection = new SqlConnection("Data Source=localhost;Initial Catalog=Northwind;Integrated
Security=SSPI;");
myConnection.Open();
SqlTransaction myTrans = myConnection.BeginTransaction(); //使⽤New新⽣成⼀个事务
SqlCommand myCommand = new SqlCommand();
myCommand.Transaction = myTrans;writeline用什么替代
try
{
myCommand.CommandText = "Update Address set location='23 rain street' where userid='0001'";
myCommand.ExecuteNonQuery();
myTrans.Commit();
Console.WriteLine("Record is udated.");
}
catch(Exception e)
{
myTrans.Rollback();
Console.WriteLine(e.ToString());
Console.WriteLine("Sorry, Record can not be updated.");
}
finally
{
myConnection.Close();
}
需要注意的是,如果使⽤OleDb类⽽不是Sqlclient类来定义SQL命令和连接,我们就必须使⽤OleTransation来定义事务。
数据库系统程序员需要⽐⼀般应⽤软件程序员懂得更多。⼀般程序员对事务处理的理解不够全⾯。事务处理的关键是在提交事务或者取消事务时,万⼀系统崩溃了,数据库在再次启动时,仍然需要保持数据可逻辑⼀致性。
最简单的事务处理过程如下:
1. 开始⼀个事务。进⼊“事务待命”状态。
2. 在“事务待命”状态,记录事务中改变的数据库记录。此改变不能直接改变数据库中的值,必须先⽤⼀个顺序的“事务⽇志”记录在⼀边。同时,对于要改变的原始记录加锁,让其它⽤户⽆法读和写。(考虑银⾏取款问题,可以发现如果此时两个⽤户都在对同⼀帐号取数,然后减去⼀定⾦额,在先后写回数据库,银⾏就亏钱了)。如果记录已经被其它事务加锁,则报错;同⼀事务中却可以重复加锁。
3. 在“事务待命”,如果⽤户给出commit transaction命令,则进⼊“事务拷贝”状态,拷贝所有加锁的记录成备份。
4. 上⾯3执⾏完,则进⼊“事务更新”状态,⽤“事务⽇志”中记录⼀⼀更新实际的数据库记录。
5. 上⾯4执⾏完,则进⼊“事务结束”状态,释放所有的记录锁,然后抛弃“事务⽇志”和备份的原数据库记录。
6. 上⾯5做完后,事务被删除。
但是,最为关键的是,事务系统必须执⾏以下过程:⼀但数据库由于软件、硬件问题发⽣故障,重启动后,⼀旦有事务没正常删除,则:
7. 如果在“事务待命”、“事务结束”状态,则重新从5中结束事务的动作开始执⾏。
8. 如果在“事务更新”状态,则重新从4开始更新记录,并继续想下执⾏。结果,虽然系统崩溃过,但事务仍然能正常提交。
Informix、Oracle、DB2等数据库的实际事务处理流程更复杂,⽬的是具有更好的抵抗系统错误性质,因为事务保护是业务系统安全稳定的最后⼀道防线(⽐⽤2个CPU、热备份等更重要。因为那些⽅法对已经混乱错误的数据照样保护,结果经常造成更多问题)。由于事务处理的流程⽐⼀般程序想像得复杂,因此可能会感到⽤起来⽐较繁琐。但是了解事务在保护数据库⽅⾯的良苦⽤⼼,就可以更好地设计出更灵活的业务流程。
如果“⼀个完整的事务可能包括a,b,c,d四个⼩事务”就⽐较奇怪。既然是个过程构成⼀个事务,没必要中间在划分为4个⼩事务,问为中间任何操作出错都需要整个“回滚”到a之前。在执⾏完a后⽤⼀个if语句判断要不要再执⾏“b,c,d”就⾏,end if之后提交事务。
应⽤中包含的事务应当尽量让它“瞬间”完成,避免在⽐较忙时造成⽤户进程的互锁。事务⽐较频繁的系统,⼀秒钟有⼏个⽤户互锁就可能造成严重问题,因为事务是以⼀个稳定的频率来的,⽽服务器上互锁的进程越积越多,⼏个⼩时后,可能有上百台机器都死掉了,只能⼀台⼀台地重新开机,花费很多时间、⾦钱,还会被⽤户骂死!
但是并不是说事务之间就不能有⽤户交互。可以在⽤户3分钟还没确认后,就⾃动报告错误,并且取消
事务。不过我从不冒这个险,⽽是使⽤下⾯⽅法。
将事务拆分成两段,需要⾮常深⼊地研究⽤户业务流程,研究⽤户在发现业务数据不⼀致时是如何纠正的,并且继承⽤户⼿⼯操作流程。这样,软件分析的⼯作量相应加⼤了。有时,这是必然的,应当说服⽤户接受这种现象,并且与开发者⼀起逐步解决。
⽐如,卖的交易,⽤户输⼊号码,此时在POS机打印并且记账。这并不需要在卖时与后台服务器实时联⽹,只要每隔10分钟上传⼀次数据就⾏了。只有这样,才能在现有的硬件和⽹络条件下使得销售开展起来。如果开发者固守者号码录⼊、服务器记账、前台打印合并为⼀个事务的天真想法,其产品⼀定会在激烈的市场中败阵。
当然,随着硬件、软件、⽹络的不断变化,如何灵活应⽤事务的⽅法会不断变化,没有⼀定的规矩,关键要看软件的效果。虽然⼤的事务可能不断拆分成⼩的事务,中间⽤业务流程联系起来;同时,合并⼀些⼩的事务或者由计算机⾃动处理业务数据不⼀致问题,从⽽给⽤户提供⽹络上的“傻⽠相机”⼀样的易⽤、稳定的产品仍然是今天最有挑战意义的软件⼯程⽬标。 --------------------------------------------------------------------------------------
这个问题不是C#处理的,⽽是你调⽤的存储过程或批查询需要处理的,如果你⽤C#执⾏⼀系列的更新语句的话,你可以使⽤ADO .NET SqlClient的事务来控制并发时的数据完整性!
SqlConnection conn=new SqlConnection(connectionstr);
SqlTransaction mytran=conn.BeginTransaction();
SqlCommand cmd=new SqlCommand();
cmd.Connection=conn;
cmd.Transaction=mytran;
cmd.CommandText=.....;
int rc=cmd.ExecuteNoQuery();
mytran.Commit();
....
你可以通过错误处理机制来控制事务提交还是会滚...
-------------------------------------------------------------------------------------------- 我在程序中使⽤⼀种原始的⽅法处理需要锁定的内容。
⽐如我的表中存储有订单的⾏项⽬,每次只允许⼀个⽤户对⾏项⽬进⾏编辑。
建⽴“锁定表”,每当⽤户编辑订单时,在"锁定表"中加⼊订单号。当加⼊失败时则说明已有⽤户在编辑订单,当⽤户退出订单或锁定时间超过⼀个阈值时则删除锁定记录,允许其他⽤户编辑并锁定订单。
这样处理适合锁定多⾏的订单类数据,锁定表中可以保存其他附加信息。
还有⼀种⽅法在数据库中使⽤事务。使⽤事务更新数据库,这样可以保证同⼀时间只有⼀个⽤户可以对⾏更新。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论