2013年6月17日星期一

数据库并发解决方案

在如今分布式、高并发、各种负载纵横天下的时代,支持高访问量成为检验一个系统合不合格的重要标准,然而我们除了在运算过程中要求系统更加效率外,在最终的数据存储过程中也希望其能够准确。

针对如何解决多线程并发产生的脏数据问题,本文简单列举一些常见案例及应对措施。

案例一:
本地起10个线程,分别执行10次,对数据库的一条记录的sum字段(初始值为0)+1操作,中间的业务逻辑我们忽略掉,如何保证执行完毕后sum的值为100?
表结构:

字段名字段类型可空字段描述使用备注
IDBIGINT(20)N主键ID无业务含义
SUMNUMBER(20)N金额初始值为0

解决措施:乐观锁机制,利用数据库自身的事务来解决问题,update 表 set sum=sum+#increment#   where id=#id#,适用于一些只更新数量、金额的场景。
尽量不要采用在后台计算一个最终的sum值,然后通过 update 表 set sum=#sum#  where id=#id#,因为此时在读与写的时间间隔里,很有可能其它的线程已经读过或操作过

案例二:
买家操作一笔订单,执行确认收货,假如同一笔订单打开了两个窗口,开始时在一个窗口确认成功,后来在另一个窗口又点了一次,此时应如何解决?

解决措施:在执行“买家确认收货”操作时,我们通常会首先查出这笔订单,判断当前操作用户是否有执行权限,同时判断当前订单的状态是否是“等待买家确认收货”,。。。,如果满足这些前置条件,才允许后面的业务操作,更新数据库。
当然,存在另一种可能,如果是通过自动化脚本操作呢?两次操作几乎同时执行,也就是说,两次的前置校验都能顺利通过(因此那时,数据库记录还没来的及更新),此时一个好的解决方案,操作时增加前置条件,比如确认收货的前置条件是“等待买家确认收货”,如果此时订单的状态变成了成功就无法操作。
update 订单表 set  status="交易成功"  where id=#orderId# and status="等待买家确认收货"
这样,第二次操作sq条件不满足,也就避免执行两次买家确认收货操作。

案例三:
增加前置条件是一个不错的解决方案,但是,不是每个业务都会有前置条件,或者说前置条件不明确,无规则,此时就如何解决?

字段名字段类型可空字段描述使用备注
IDBIGINT(20)N主键ID无业务含义
SUMNUMBER(20)N金额初始值为0
attribute_ccINT(11)N用于为attribute加锁 
解决措施:可以借助memcache用到的一种同步机制(CAS),比较并交换,在数据库表增加一个冗余字段,每次操作都会自动+1
执行业务时,首先会从数据库读取该字段信息,更新业务数据时,会自动比较attribute_cc的值是否有变化,如果有变化,表示刚才读的信息已变化过,需要重新操作。

没有评论:

发表评论