问题
现有一数据库表tableA,设计中有一个非主键的int型字段(下统称:dbId),需要实现递增需求。
分析
获取最大值加1
获取最大值.1
2-- 伪代码
int newDbId = select max(dbId) from tableA;
然后加11
2-- 伪代码
insert into tableA (dbId) values (newDbId + 1)
这是最容易想到的。
问题:由于不是原子操作,会导致在多线程下出现幻读问题。导致出现相同的dbId出现(不能保证唯一性)。
使用唯一性索引
为了实现规避脏数据的产生,数据的唯一性。这里会想到唯一性索引。
所以,可以将dbId设置为tableA的非主键唯一性索引。利用索引的唯一性。避免相同dbId的产生。
1 | alter table tableA add unique index uidx_dBId (dbId); |
此方式,依据数据库的实现唯一性。违反约束直接报错。
使用乐观锁
对共享dbId,在向数据库表tableA插叙新的记录的时候,增加条件检测。1
insert into tableA (dbId) values (newDbID) where dbId < newDbID
这样,在出现线程ThreadA,ThreadB并发读取到同一个oldDbId。ThreadA先提交,ThreadB后提交,,ThreadB的sql就会不满足where条件,导致插入失败。
方式2和方式3都是依赖数据库底层实现原理。
优点:简单,方便。
缺点:失去一次提交结果(违反约束,报错,提示用户重新提交)。
使用分布式锁
使用分布式锁实现(Redis)。
将max(dbId)放入Redis中,使用Redis的Incr操作的单线程的特性。完成dbId的自增需求。
优点:保证没有重复的数据提交过程。保证流程的完整性(不会出现,报错,提交失败。)
缺点:增加了系统系统的复杂度和维护成本。
后续
并发下的原子操作问题,是否有人会想到CAS操作呢?还有…
后续会写一些结合工作的《并发的那些事儿》专题。敬请期待!