有些框架直接在 ORM 里面封装了这种 update or insert 的操作,分析源码,可能是直接用 mysql 的 ON DUPLICATE KEY UPDATE 或者就是先查询判断存不存在,再插入。而今天我们就来讨论后者。

BUG举例

因为这个bug是公司的实际业务逻辑,所以我下面不贴出具体的代码,以伪代码说明。一开始写的时候大家都在赶工,没考虑到各种情况,业务都稳定的情况下,突然有一天,有人告诉我,线上的公司表出现两条一某一样的公司信息,申请人也是同一个,什么都一样。我仔细回来检查,具体的业务代码是这样的:

1
2
3
4
5
6
7
$company = db->where('根据输入的信息')->select();
if(!empty($company){
db->update('根据输入的信息');
}
else{
db->insert('根据输入的信息');
}

我一开始看这样的代码感觉没有bug,自己拿号不断去试,结果自己怎么提交最多存进去一条啊,有记录就会更新。我没考虑到并发!!!于是我就开始去用apache自带的ab程序去模拟并发。至于这个工具具体怎么用,我也只会基础的,这里就不继续展开讲了。我就用基础的模拟

1
ab -c 3 -n 3 -p post.txt application/x-www-form-urlencoded http://xxx.xxx.xxx/xx

目前我的解决办法

其实这完全是可以避免的 bug,因为既然我们有这样的业务逻辑,但是表结构没这么设计,然后因为有历史遗留数据的问题,所以很蛋疼。先来说说第一种解决办法吧

  • 修改表结构

既然业务这么设计,我们只要给表增加唯一索引,这样子,来再多的并发,同样的数据只能有一条。其实大部分情况下,一般的处理办法就是给这个表建立一个或者多个字段的唯一索引。这样数据库就能帮我们保证数据的唯一性。其实我一开始说的 mysql 自带的 ON DUPLICATE KEY UPDATE 也是需要有唯一索引才能用的。

  • 使用锁

虽然mysql自带有表锁,但是性能不高。这里可以使用redis的锁,再次一点可以用文件锁。因为php-fpm并不是常驻内存的, 所以只能借助其它东西来代替锁。锁住了之后,只能依次操作, 就避免了并发带来的问题。

  • 使用异步队列

这样场景下,并发就像一条直线的数据进来就像这样
队列