非主键自增问题小记

问题

现有一数据库表tableA,设计中有一个非主键的int型字段(下统称:dbId),需要实现递增需求。

分析

获取最大值加1

获取最大值.

1
2
-- 伪代码
int newDbId = select max(dbId) from tableA;

然后加1

1
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操作呢?还有…

后续会写一些结合工作的《并发的那些事儿》专题。敬请期待!

Alan Zhang wechat
欢迎您扫一扫上面的微信公众号“补愚者说”,订阅我的博客!