您的位置:知识库 » 数据库

不用锁表,没有异常:在高并发网络中高效的更新数据库数据的方式

作者: 亚历山大同志  来源: 博客园  发布时间: 2009-08-10 15:23  阅读: 2780 次  推荐: 0   原文链接   [收藏]  

很多Web系统的瓶颈在网络IO,所以很多系统都采用多Web服务器负载均衡,双DB做双机热备(其实就是只有一个DB,两台只有一台真正工作,死掉一台另一台顶上)的方式部署,在这个时候很多原本不是问题的系统也会产生很多的问题。

这里我们假设有表Product,其定义如下:

列明

类型

说明

Id

Int

自增字段,实例的ID

ProductName

Varchar(100)

商品的名称

StoreCount

int

库存数量

。。。

。。。

。。。

 

假设很不凑巧的,3个管理员P1,P2,P3同时操作了这个表,且P1 update StoreCount=50,P2 update StoreCount=49,P3 update StoreCount=48。这个时候问题就来了,如果是让他们都同时提交进去,当然没问题,但是如果这个时候NWeb程序在读的时候就会产生每台服务器上读出来的数据都可能不一样,A服务器读出来是48B服务器读出来是50C服务器读出来是49

 

如果我们采用数据库锁可以避免这个问题,但是随之而来的是系统效率降低和无可避免的异常,而hibernate等实现的乐观锁呢,呵呵,对不起了,在多Web服务器的时候还能起作用吗?

 

由此产生了以下的解决方案:

和乐观锁的实现相反,我们不反对任何一个客户端的提交,乐观锁对读取的数据增加版本号,那么这个解决方案中对提交的数据增加“版本号”其实也就是时间戳。针对上面的Product表作为例子,为了实现无锁的提交,我们需要增加一个表Product_Dirty,以后我们将称其为脏表,Product表我们称之为主表。脏表的结构和主表几乎完全一致,只是增加了一个时间戳字段用于记录详细的插入时间:

列明

类型

说明

Timespan

Int

时间戳,精确到毫秒(能到纳秒更好)

Id

Int

实例的ID(这里就不是自增字段了)

ProductName

Varchar(100)

商品的名称

StoreCount

int

库存数量

。。。

。。。

。。。

 

在发生任何update的时候都将数据直接插入这个表,不要犹疑,没锁,所以可以快速的,尽情的插入数据。这里还是保持最初的假设,P1P2P3同时修改,所以插入了三条数据。所谓的同时插入其实在毫秒这个级别还是有差距的,所以三条记录的时间戳是不同的。好了这个时候数据进来了,但是主表的列数据还是没有改变,先在假设A服务器和BC服务器都同时开始读数据了。在主表的时候,如果发现脏表有数据则表明主表数据为脏(已经修改过了)这个时候我们就开始合并数据,当然这个操作是需要在一个事务里实现。合并的操作其实很简单,就是取时间戳最大的(也就是最近一次修改)更新主表的数据,同时删掉脏表里的和主表ID相等的所有数据。如果发现主表关联的脏表没数据,那么就说明主表数据正常,就直接读取主表的内容。

 

此解决方案来自电信营帐系统的设计,由于省电信众多系统都是由分布很广的地市州电信业务人员操作,所以修改的时候经常存在本文要解决的问题,由于操作的人多,锁表的话会造成严重的拥塞,故产生了这个解决方案,由于电信的业务需要后台跑了一个服务来合并数据,并且每秒定时运行,故每秒为一个业务周期。我将其修改成在读取的时候合并,更加灵活一些。

 

好处:不用锁表,乐观锁也不用,可以在N多服务器操作的时候使用,且大家都不会报错,简化了异常处理。

 

坏处:增加了表,结构复杂,如果是用于修改原有业务如果只是几个关键表的话还好,全部都采用这个方式工作量巨大(好在电信不缺钱)。

 

弱点:和乐观锁类似,在某些场景下仍然可能脏读,所以如果对这方面有很高的要求,还是用悲观锁吧。

 

0
0

数据库热门文章

    数据库最新文章

      最新新闻

        热门新闻