基本数据库并发操作

 

对于多用户系统,数据库操作的并发问题很常见,造成的错误如,数据丢失,读取错误数据等。究其本质原因其实是数据不一致:一个进程读入内存中的数据和数据库中的“同一批”数据在某一时刻已经不一样了(可能数据库中的数据被另外一个进程修改了),但程序并不知道,于是造成各种错误。

 

 

主要要解决的是离线并发问题,其他并发问题通常可以通过系统事务和简单逻辑解决。离线并发通常都与业务逻辑有关,当不可避免时,我们期望的是业务事务能象系统事务一样工作。但长的业务事务有时不能放在一个系统事务中(不灵活,降低了并发性),于是就要自己控制这种跨系统事务的业务事务的并发问题。当业务逻辑比较复杂时,要在正确性和并发性之间权衡一下,这两方面有时是矛盾的,没法得到完美的解决。比如,有的处理可能会使某种操作不正确,但它影响很小,不会带来损失,但能提高系统的并发性,这样的正确性可以牺牲。

 

 

既然本质原因是数据不一致,要解决问题一般要从两方面入手:

 

 

1.       禁止出现数据不一致的情况。

 

 

也就是要使用锁,把数据库中相关数据锁起来,不允许其它进程修改,当业务事务完成后再释放锁。把资源变成独占式的,能避免数据不一致。系统事务也就是这个原理,其锁是数据库底层维护的。这种方式会造成不灵活,降低并发性,因为当一个进程执行某个业务事务时,其他进程都不能做相关操作。

 

 

最简单的方法是把整个业务事务都放在一个系统事务内执行(这就不是离线并发问题了),带来的问题是,当一个进程执行业务事务时,其它进程做相关操作就会进入无响应的等待状态,如果业务事务持续一天,其它进程可能就会等待一天,还可能会造成死锁。

 

 

既然使用了锁,就要防止死锁。想一想,死锁要具有两个必要条件:一,当持有对某些资源的锁时又去请求新的资源;二,如果请求的资源已经被别人锁住,就进入等待状态。这两个条件缺一个都不会出现死锁。

 

 

比较好的方式就是自己控制锁。可以在应用服务器端的内存或数据库中开辟一个区域专门维护锁。例如,在数据库中建一个锁表或给每条数据加一个锁字段,要锁住某条数据时,就标志一下,当其它进程要做相关操作时先检查数据是否已经被锁住,如果已经被锁住,就不再继续执行。当然,还可以根据业务逻辑,给一组数据设定一个锁。这与系统事务不同的是,当请求的资源被锁住时,不是进入等待状态,而是放弃继续执行,提高了灵活性(用户可以进行其它操作),还能有效降低死锁的可能性(使之不满足死锁的第二个条件)。但这种控制方式是最麻烦的,开发者要弄清楚数据的业务逻辑,就是各个业务事务以及相互的关系,还要涉及锁管理和系统事务。还会涉及维护会话的问题,因为用户可能会非正常结束业务的执行(死机,网络中断,停电,地球不转等),会留下没有释放的锁。服务器必须做到,如果某个用户已经非正常停止了会话,就释放会话过程中的锁。

 

 

另外,要防止死锁,在开发过程中应该注意,当执行某个操作时,尽量开始时就一次性的锁住所有需要的资源,在执行过程中不再请求其它资源(使之不满足死锁的第一个条件),操作结束后释放锁。如果要更谨慎一些,有时还可以给锁加上时间限制,当一个锁超过一定时间后就自动失效,在锁过程中所做的操作也都变得无效,这其实是使之不满足死锁的第二个条件。

 

 

2.       允许数据不一致,但数据更新时要进行一致性检查。

 

 

从数据库中读出一批初始数据,依据这些数据做了一系列的操作,当把操作结果更新到数据库时,先检查数据库中那“同一批”初始数据是否已经发生了变化,如果发生了变化,根据业务需要,进行一定的处理。这种方式并发性强,比较灵活,而且是不加锁的,这就避免了不少麻烦问题,实现起来要简单一些。

 

 

简单考虑,检查数据一致性时,可以检查数据库中数据和内存中数据的值是否相同。但如果数据量较大,这种检查恐怕效率很低。其实一致性检查要考虑粒度问题,根据实际情况选择合适的粒度。检查具体数据的值是否变化,可以说是最细的粒度。可以通过数据的版本控制来实现更粗粒度的检查。以数据记录为单位进行版本管理,可以在数据表中加一个整型字段作为版本。当insert一条数据时,设置一个初始版本值。当update数据时,先检查数据库中的版本值是否发生了变化,如果没有变化才执行update,并将被更新的数据版本值加1。根据业务逻辑,有时要把一组数据使用一个版本来管理(更粗的粒度),这可以使用一个专门存储版本的表来实现。要注意的是,一致性检查和数据更新一定要在同一个系统事务中进行,才能保证一致性。

 

 

没有完美的世界,这种方式的缺点是,当提交更新时才会发现不一致,以至于提交之前的所有操作可能都变得无效。例如,用户先从数据库中读出一些初始数据,然后做了大量的修改或录入工作,弄了两三个小时,最后提交时系统检查到数据不一致问题,结果提交失败,工作都白做了,于是用户就会很郁闷,于是就会开始不想使用这样的系统,这是我们不希望看到的。一个缓解的办法是,除了在最后提交时检查一致性外,可以在业务事务进行过程中,时不时的就检查一下,越早发现不一致就可以越早的处理。这也只是缓解,不能从根本上解决。

 

 

第一类方法被称为乐观并发控制方法,第二类方法被称为悲观并发控制方法(“大家们”都喜欢弄出一些有趣的名词来)。总之,离线并发问题不止是技术问题,首先要深入分析系统的业务逻辑,弄清楚系统在何时何地可能会出现什么样的并发问题,再根据具体情况和实际需要,权衡利弊,选择合适的解决方法。应该先考虑长系统事务,如果能接受把整个业务事务都放在一个系统事务中,就避免了离线并发问题。如果不能接受,再考虑乐观控制方法,毕竟这个实现起来要相对简单一些,但只当并发冲突发生的频率较低,可能性较小时,才适合使用乐观方法(如果用户总是提交失败,就更加对这样的系统深恶痛绝了)。然后再考虑悲观控制方法,虽然麻烦,但可用于并发冲突发生频率较高的情况,实际上是限制了系统的并发性。

 

 

Msdn

·             悲观并发控制

一个锁定系统,可以阻止用户以影响其他用户的方式修改数据。如果用户执行的操作导致应用了某个锁,只有这个锁的所有者释放该锁,其他用户才能执行与该锁冲突的操作。这种方法之所以称为悲观并发控制,是因为它主要用于数据争用激烈的环境中,以及发生并发冲突时用锁保护数据的成本低于回滚事务的成本的环境中。

·             乐观并发控制

在乐观并发控制中,用户读取数据时不锁定数据。当一个用户更新数据时,系统将进行检查,查看该用户读取数据后其他用户是否又更改了该数据。如果其他用户更新了数据,将产生一个错误。一般情况下,收到错误信息的用户将回滚事务并重新开始。这种方法之所以称为乐观并发控制,是由于它主要在以下环境中使用:数据争用不大且偶尔回滚事务的成本低于读取数据时锁定数据的成本。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值