防跳号的两种方式

数据库连续编号解决方案:锁机制与伪列应用

在财务上有一种需求,就是单据号必须连续不能中断.
这种需求用Oracle的序列不能实现,MySQL的auto_increment也不能实现.
因为事务回滚,或者实例重启,都可能造成单据号不连续的情况,就是跳号.

防止跳号有两种方式,但是前提都是不能删除数据.
第一种方式,用锁限制并发.
一个表有两个字段,一个是模块名称,一个是当前模块的序列值
create table tab_lock
(
    module_name varchar(10) primary key,
    current_value int
)engine=innodb;

insert into tab_lock values('HR',1),('IT',1);
commit;

使用的时候,用悲观锁锁住这行数据,然后使用这个值填充主键,最后让这个序列自增.
select * from tab_lock
where module_name='IT' for update;

update tab_lock
set current_value=current_value+1
where module_name='IT';

commit;

这种方式限制了并发.
一旦回滚,这个号码没有任何变化.这样可以保证单号的连续性.


-------------------------------------------------------------------------

还有一种方式,就是使用一个伪列作为单号.
主键不连续,而伪列单号可以连续.
create table order_log
(
    id int primary key
)engine=innodb;

insert into order_log
values
(1),(2),(4),(5),(7),(10),(13),(17),(20);
commit;

虽然主键不连续,但是可以通过下面的方式使单据号连续.

延迟关联实现分页,每页5条
第一页
select
concat('TX',lpad(b.rn,10,'0')),a.*
from
order_log a
,
(
    select * from
    (
        select
        id,
        @a := @a + 1 AS rn
        from order_log,
        (SELECT @a := 0) t order by id limit 5
    ) c  where rn>0
) b
where a.id=b.id;


第二页
select
concat('TX',lpad(b.rn,10,'0')),a.*
from
order_log a
,
(
    select * from
    (
        select
        id,
        @a := @a + 1 AS rn
        from order_log,
        (SELECT @a := 0) t order by id limit 10
    ) c  where rn>5
) b
where a.id=b.id;


第二种方式显然更好.
两种方式的前提都是不能删除表中数据.

第二种方式其实是
select
concat('TX',lpad(b.rn,10,'0')),a.*
from
order_log a
,
(
    select * from
    (
        select
        id,
        @a := @a + 1 AS rn
        from order_log,
        (SELECT @a := 0) t order by id limit ${pageSize}*${pageNum}
    ) c  where rn>${pageSize}*(${pageNum}-1)
) b
where a.id=b.id;

 

SpringMVC+mybatis HTML5 全新高大尚后台框架_集成代码生成器

public String generateNum() { String dateStr = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); int retryCount = 0; while (retryCount++ <= 10) { try (Connection conn = dataSource.getConnection()) { conn.setAutoCommit(false); // 锁定并获取流水 PreparedStatement lockStmt = conn.prepareStatement( "SELECT CURRENT_SERIAL FROM ebs_serial_number " + "WHERE BIZ_DATE = CURDATE() FOR UPDATE"); ResultSet rs = lockStmt.executeQuery(); boolean hasRecord = rs.next(); // 只调用一次next() // 计算新流水 int newSerial = hasRecord ? rs.getInt(1) + 1 : 1; System.out.println("rs.next() = " + rs.next()); if (!hasRecord) { // 初始化记录 PreparedStatement initStmt = conn.prepareStatement( "INSERT INTO ebs_serial_number VALUES (?, CURDATE(), 1)"); initStmt.setString(1, IdUtil.simpleUUID()); initStmt.executeUpdate(); } // 生成编并检查是否已存在 String bizNum = String.format("QT%s%03d", dateStr, newSerial); PreparedStatement checkStmt = conn.prepareStatement( "SELECT 1 FROM t_lc94_ext WHERE FLD_T_00288 = ? LIMIT 1"); checkStmt.setString(1, bizNum); boolean exists = checkStmt.executeQuery().next(); if (!exists) { // 正式占用流水 PreparedStatement updateStmt = conn.prepareStatement( "UPDATE ebs_serial_number SET CURRENT_SERIAL = ? " + "WHERE BIZ_DATE = CURDATE()"); updateStmt.setInt(1, newSerial); updateStmt.executeUpdate(); conn.commit(); return bizNum; // 成功返回 } conn.rollback(); // 编冲突则回滚 } catch (SQLException e) { if (e.getErrorCode() != 1062) throw new RuntimeException("系统错误", e); } } throw new RuntimeException("生成商机编失败:超过最大重试次数"); // 循环结束的返回值 } 这个逻辑是不是有点问题,我如果直接去调这个接口,那他返回的商机编会一直+1,因为我另外一个表根本没进行保存操作
最新发布
08-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值