首先了解 LAST_INSERT_ID
- LAST_INSERT_ID 有自己的存储空间,能存一个数字
- LAST_INSERT_ID两种用法
- 不带参数: 不带参数时返回最近insert的那行记录的自增字段值(与AUTO_INCREMENT配合使用)
- 带参数: 带参数时会将自己存储的数字刷成参数给定的值
重点是这个特性: 它是多用户安全的,
mysql手册原文:
It is multi-user safe because multiple clients can issue the
UPDATE
statement and get their own sequence value with theSELECT
statement (ormysql_insert_id()
), without affecting or being affected by other clients that generate their own sequence values.
本文主要使用第二个用法
为了便于行文,使用特地业务场景进行描写.
场景描述: 按天提供趋势递增的唯一id.
- 建表:
create table sequence
(
day varchar(45) not null comment '日期key建'
primary key,
id bigint default 0 not null comment '序列id'
)
comment '按天自增序列表';
- sql:
首先初始化指定日期的数据.例为日期为: 2020-08-01
insert into sequence values('2020-08-01',1);
select * from sequence;
day | id |
---|---|
2020-08-01 | 1 |
查看
select last_insert_id();
result: 0
此时来了一个申请id的请求.我们通过如下逻辑进行处理
update sequence set id=LAST_INSERT_ID(id +1) where day = '2020-08-01';
select last_insert_id();
result: 2
可以看到返回结果为2.
select * from sequence;
day | id |
---|---|
2020-08-01 | 2 |
此时又有新的请求过来,则继续调用该逻辑
update sequence set id=LAST_INSERT_ID(id +1) where day = '2020-08-01';
select last_insert_id();
result: 3
这样便可以生成当日的一个趋势递增的唯一 id .
上面说完了核心思想,下面说下在java环境中的具体实现
@Service
public class SequenceServiceImpl implements SequenceService {
private static final Logger log = LoggerFactory.getLogger(SequenceServiceImpl.class);
private final Map<String,List<Long>> cache = new HashMap<String, List<Long>>();
@Resource(name = "jdbcTemplate_sequence")
private JdbcTemplate jdbcTemplate;
@Override
@Transactional(value = "sequence")
public long getSequenceId(String key,int step) {
//通过synchronized保障同一时刻只能有一个线程操作cache,保证了并发安全
synchronized (cache){
List<Long> sequences = cache.get(key);
if(sequences == null){
//先判断sequence表里是否有key对应的记录,如果没有则插入一条
try {
checkKeyExist(key);
}catch (Exception e){
log.error("insert sequence key occur exception",e);
throw new RuntimeException("insert sequence key occur exception,"+e.getMessage());
}
cache.put(key,new ArrayList<Long>());
sequences = cache.get(key);
}
//如果sequence集合里没有数据,则从数据库取一个lastInsertId生成
try {
if (sequences.size() == 0) {
long maxId = getLastInsertId(key, step);
sequences = new ArrayList<Long>();
for (long i = maxId - step + 1; i <= maxId; i++) {
sequences.add(i);
}
cache.put(key, sequences);
}
}catch (Exception e){
log.error("getLastInsertId occur exception",e);
cache.put(key,null);
throw new RuntimeException("getLastInsertId occur exception",e);
}
return sequences.remove(0);;
}
}
private void checkKeyExist(String key) {
int n = jdbcTemplate.queryForInt("select count(1) from sequence where day='" +key+"'");
if (n<1){
try{
jdbcTemplate.update("insert into sequence (day,id) VALUES (?,?)",key,0);
}catch (DuplicateKeyException e){
log.error("insert sequence key DuplicateKeyException",e);
}catch (Exception e){
log.error("insert sequence key 出现异常,key="+key,e);
throw new RuntimeException("insert sequence key 出现异常,key="+key,e);
}
}
}
@Override
@Transactional(value = "sequence")
public long getLastInsertId(String key, int step) {
jdbcTemplate.update("update sequence set id=LAST_INSERT_ID(id+"+step+") where day='" +key+"'");
Long id = jdbcTemplate.queryForLong("SELECT LAST_INSERT_ID()");
return id;
}
}
上面代码通过 mysql的 LAST_INSERT_ID (value + step)获取一个全局唯一并且自增的sequnce , 一次获取一个号段,然后通过Map本地cahce 一下,这样操作的目的就是为了避免多次操作数据库,提升了分配id的性能.
结语: 上面主要是针对特定业务场景中LAST_INSERT_ID的使用,重要的是要理解AST_INSERT_ID的特性,这样就可触类旁通.在遇到相关场景时不妨考虑使用AST_INSERT_ID.在笔者所在的部门,有一个全局id生成器,是一个单独的服务,其存在的意义就是为其它应用提供全局唯一的id.而其核心逻辑便是通过LAST_INSERT_ID实现的.
笔者能力有限,文中如有不严谨之处,还望各位及时指正.共勉之.
文章构思及写作用时: 4h
参考:
- https://dev.mysql.com/doc/refman/5.6/en/information-functions.html#function_last-insert-id
网友评论