圣泉山人 后端开发工程师

事务的陷阱

2015-02-15
PHP

在业务中,为了保证数据的一致性、准确性,常常采用了事务处理,但是这往往还不够,因为在网络应用中还存在了并发问题,因此很多时候还需要加上锁机制。而在这两者一起使用的时候,就可能会掉进一个陷阱里面。

USAGE

以我们公司的web应用为例,应用使用了TP框架,并利用其标签位功能实现了申明式事务处理机制。只需要在配置文件中配置好哪些控制器方法需要开启事务即可,当这些控制器方法被访问时,会自动执行

M()->startTrans();

然后在方法结束时触发的标签位方法中检测是要

M()->rollback();

还是

M()->commit();

核心部分时序图如下

img

其中锁机制是利用redis实现的,这里用用户id做了键来进行防止用户连续、快速点击造成的并发请求。

问题

没有事务的时候

  • 第一个请求获得锁
  • 读取表中的旧数据
  • 对表中旧数据做相应判断后,修改表中数据
  • 释放锁
  • 第二个请求活动锁
  • 读取表中的数据,此时获得的是第一个请求修改后的数据
  • 剩余业务处理
  • 释放锁

有事务的时候

  • 开启事务
  • 第一个请求获得锁
  • 读取表中的旧数据
  • 对表中旧数据做相应判断后,修改表中数据
  • 释放锁
  • 第二个请求获得锁
  • 读取表中的数据,此时获得的是仍是旧数据,因为第一个请求的修改还在事务日志当中
  • 执行后续业务代码,会产生和第一次请求相同的结果
  • 释放锁
  • 第3、4、5…次请求…
  • 第一次提交事务
  • 第二次提交事务…

相信已经能很明显的看到问题所在了,比如领取优惠券,如果采用上诉方式的话,用户连续点击领取(当然,前端的限制很容易绕过),就会同时插入多条领取成功的记录,即是领取了多张一样的优惠券,这就是一个八阿哥了。

流程修改

虽然申明式事务用起来确实很方便,但是除了会多少降低效率外,还会导致上面的问题,因此对此进行修改,对核心的业务操作代码使用手动控制事务的开启和关闭,并把这段代码加锁处理,这样,每次请求在提交事务后,才会释放锁,保证:每次获得锁的请求,从表中获取的数据都是最新的数据

img


上一篇 Session

下一篇 ThinkPHP

Comments

Content