基本的数据库并发控制

原创 2006年06月02日 09:27:00

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

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

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

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

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

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

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

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

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

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

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

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

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

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

数据库--事务(定义、基本特征、并发问题)

(1)定义: 事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些 操作要么都执行,要么都不执行,它是一个不可分割的工作单位。所以,事务是数据库 维护数据一致性的单位...

基本数据库并发操作

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

oracle数据库基本语句

高性能MySQL -MySQL架构,MVCC多版本并发控制和一些基本概念

内容源于《高性能MySQL》一、MySQL逻辑架构架构图: 最上层不是Mysql独有的, 比如连接处理,授权认证, 安全 等等 第二层核心服务功能,包括查询解析,分析,优化,缓存以及所有内置函数...

Java并发编程实践之线程的基本控制

线程创建后,可以执行start()方法启动线程,根据线程任务的特性和线程之间的协调性要求,需要对线程进行控制。对线程的控制通常是通过调用Thread对象的方法实现的,主要有sleep()、suspen...

DB2数据库基本语法

  • 2015-02-28 15:49
  • 2.69MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)