美文网首页
mysql通过 LAST_INSERT_ID 实现ID生成器

mysql通过 LAST_INSERT_ID 实现ID生成器

作者: 内沐 | 来源:发表于2020-08-16 17:41 被阅读0次

首先了解 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 the SELECT statement (or mysql_insert_id()), without affecting or being affected by other clients that generate their own sequence values.


本文主要使用第二个用法

为了便于行文,使用特地业务场景进行描写.

场景描述: 按天提供趋势递增的唯一id.

  1. 建表:
create table sequence
(
    day varchar(45)      not null comment '日期key建'
        primary key,
    id  bigint default 0 not null comment '序列id'
)
    comment '按天自增序列表';
  1. 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

相关文章

网友评论

      本文标题:mysql通过 LAST_INSERT_ID 实现ID生成器

      本文链接:https://www.haomeiwen.com/subject/lpntjktx.html