方案:
我有一个表,其中有一个名为“ Membership_ID ”的字段,这不是表的主键,也不是自动增量。 基本上是一个六位数的UNSIGNED_ZEROFILL INT类型,需要为每个新记录增加。
所以我正在做的事情,我通过created_at获取表中的最后一条记录,并增加其membership_id:
$last_member = Model::orderBy('created_at', 'desc')->first();
$new_id = $last_member->membership_id++;
奇怪的是,一些记录得到了重复的membership_id。在检查时,我注意到这些记录的created_at时间与重复的membership_id几乎完全相同。
现在我已将其更改为:
$last_member = Model::all()->last();
$new_id = $last_member->membership_id++;
彻底测试了它,到目前为止没有重复Membership_ID。
问题是为什么membership_id在第一种情况下重复了。在created_at时间戳中,该值不完全相似,有几秒钟的差异,或者在某些情况下差异是几分钟?
答案 0 :(得分:2)
Model::orderBy('created_at', 'desc')->first()
做了类似的事情:
select * from Models order by 'created_at' desc limit 1
而Model::all()->last()
执行此操作
select * from Models
即。它从数据库中获取所有模型(没有显式排序),最后一个模型由Laravel(在php中)返回。这是非常低效和脆弱的,因为返回的实际Model
依赖于默认的数据库排序。
membership_id由于两个插入之间的race condition而重复。当同时创建两个模型时,两个php进程同时处理两个请求。这是可能发生的事情(或者不是,这就是为什么你有时只能重现它):
除非您使用数据库事务和正确的事务隔离级别,否则步骤2和3可以像这样(重复ID)或互换(没有重复的ID)。
我看到了2个解决方案:
对您所做的2个查询使用Repeatable reads isolation level or higher的数据库事务(获取最后一个membership_id并插入)。
使用数据库SEQUENCE
或table to emulate it。
你现在所做的不是解决方案!除非你采取适当的步骤,否则它与你的第一次尝试一样容易受到竞争条件的影响。根据定义的竞争条件可以发生但不保证会发生。很难最终测试他们的缺席。