背景:序列号是业务区分标识,一般情况下就是区分一只交易和另一支交易的标识,实现序列号可以用程序实现,也可以用Oracle的序列来实现,Oracle的序列就是一个原子力度的累加器,通过调用它来实现自增并生成业务序号。但是序列号可能会重复引起其他问题。在实际开发过程中,Java程序实现或者Oracle数据库实现生成序列号/流水号的规则,用于区分每条数据的唯一性,然后用于业务逻辑调用/查询等
-
1.实际开发中遇到的问题:
问题1原因分析:
系统设计问题,经办交易在调用工作流组件生成工作流序号时,会给数据库表加行锁执行update序号+1的操作,因瞬时一笔交易该操作比较慢,未及时释放锁,导致交易未在一分钟完成并返回,导致后续经办交易均在等待此操作,导致部分交易未及时返回报超时。
临时措施:无人工处理,排队交易处理完成系统自动恢复正常。
后续措施:1.与DBA讨论,修改工作流序号生成方式,提高并发;2.优化目前经办交易流程,及时提交数据库操作事务;
问题2原因分析:
对系统交易限额表进行单条数据插入,限额表主键sequence采用默认的缓存步长20.因系统访问次数过多,引起数据库sequence分配发生争用而产生线程阻塞,待sequence缓存重新分配完成,阻塞自动消失,交易成功。
解决措施:系统限额表对应的sequence缓存重新分配完成,阻塞自动消失,交易成功,未进行人工处理。
后续措施:
a)参考生产交易量,调整扩大sequence的缓存步长
b)在系统库表设计时,对于使用序列问题,考虑sequence使用频率,合理设计缓存步长。
-
2.序列号常用生成方式及其特点
方式一:
简述:使用数据库系统序列sequence来生成序号,然后使用特定标识(字母或数字)+ 序号组成流水号。
性能:NOCACHE:效率很低;设置为CACHE:效率有一定的提高,并发特别大时依然有IO效率问题。
序号连续性:存在序号跳号使用可能。
序号重复性:Oracle数据库Sequence本身可以保证所产生序号的唯一性。
使用场景:适用于没有并发或者并发不高/无序号连续性要求的应用场景。
方式二:
简述:使用数据库配置表记录当前序号(不使用缓存)“update序号=序号+1”。
性能:效率很低,并发时会产生锁等待,导致超时的概率很高。
序号连续性:可以保证序号的连续性。
序号重复性:将序号字段设置为主键或唯一索引。
使用场景:适用于没有并发的应用场景,并能满足对序号连续性的要求。
方式三:
简述:使用数据库配置表和应用缓存记录当前序号,并使用序号管理模块统一管理。
性能:效率最高,每次取序号从本地缓存读取,不需要去访问数据库。
序号连续性:存在序号跳号使用可能。
序号重复性:将序号字段设置为主键或唯一索引;通过数据库锁机制。
使用场景:适用于高并发的应用场景。
-
3.序号使用规范及相关应用设计过程要求
1.序列号循环使用或日终清零,完善序号用尽后的处理逻辑和序号(流水号)防重复处理逻辑
2.Oracle序列的创建应避免使用数据库辅助工具plsql手工操作,应通过SQL语句创建并对各个属性进行明确赋值,避免使用默认属性和属性默认值。
3.加强测试的全面性,及早发现缺陷
a)单元测试和sit测试时要增加序号的边缘值测试案例
b)对于存在资源(序号/额度等)竞争的交易,要在性能测试补充场景的测试案例
c)序列大小满足业务增长需求,预估5年业务增长情况;
4.对于高并发应用系统,序号的获取应使用缓存,缓存值参数在上线或调整时需性能测试验证.(序列缓存步长建议为最大并发量的10倍)
若系统部分数据库序列无缓存,频发获取缓存导致导致数据库资源竞争,进而影响sql的执行效率,造成恶性循环。
-
4.序列生产问题分类
1.系统并发大,序列步长不合理,序列生成效率低,引起阻塞或锁等待;
2.序列没有设计循环使用或日终清零;
3.序列号重复,不能插入以序列为唯一索引的登记簿或流水号;
4.序列号超过接口和字段长度,引起本系统或其它系统插入失败而超时;
5.特殊场景,预约业务:日期+序列号,引起预约/日间/批量流水号冲突或重复
-
5.序列具体案例分析
案例1:客户在xxx系统做转账交易时报错
事件原因分析:
事件原因是授权通知表的存量数据清洗机制不合理。仅清理客户已读消息,不清理未读信息,导致该表中存在少量未读数据。该表主键为数据库自增序列,范围为1-99,999,999,达到最大值后重新开始。当新生成的序列与已有数据序列相同时,数据库报错主键冲突,交易报错。
事件解决措施:
将数据库序列范围调整为100,000,000-9,999,999,999。系统恢复正常。
事件后续改进措施及防范措施:
1.优化该表数据清理机制。经沟通,确定又系统自动清理一个月前的数据(含未读和已读)
2.优化该表主键生成规则。调整为业务日期+原有数据自增序列
3.优化转账交易流程。将转账成功后发给授权员的通知操作,改为异步方式。
结论:
当以数据库自增循环序列作为主键时,要辅以其它动态信息生成复合主键。并对历史数据简历定期清理机制,以降低资源冲突风险。
案例2:xx系统流水号重复
事件描述:
一.个别理财产品到期后,出现给部分客户重复计付收益的情况。
二.购买理财产品后,扣款后未看到委托交易记录
事件原因分析:
一.预约交易的流水号占用了未来预约业务日期流水号导致流水号冲突
二.流水号冲突导致收益分配处理异常处理,因清理临时表使用truncate,事务不能回退,已完成计算的产品收益金额未成功回退。
三.流水号冲突导致购买成功的交易流水入库失败,客户委托交易记录查不到。
事件解决措施:
一.通过函件提供客户收益入账有误的信息,追回款项
二.通过临时升级改造(使用delete语句清理临时表,在异常情况下事务完整回退)完成生产修复,避免重复给客户计算收益的事件再次发生。
三.系统完成交易流水等紧急处理,并完成存量被占用流水号、新增预约交易流水号的影响性评估。并于理财发售后完成对存量被占用流水号的处理。
四.系统完成调整预约交易、日间交易、日末清算等流水号生成机制,流水号记录在交易发生日,确保流水号冲突问题不再出现。
事件后续改进措施及防范措施:
梳理各系统使用truncate等不可回退语句的业务场景,分析设计和使用合理性,制定整改方案及计划。
结论:
1.系统设计加强对关联场景、异常场景分析,测试加强组合场景的案例设计
2.系统流水哈生成规则统一合理,避免当前业务占用未来日期流水号。
在重复计付收益事件,除流水号冲突原因以外,还存在在事务中使用Truncate(DDL语句,隐式提交)的情况,导致在异常情况下事务不能回滚,已完成计算的产品收益金额未成功回退。
在事务中使用Truncate等DDL语句
DDL(Data Definition Language)常见语句包括:Create/Alter/Drop/Truncate/Comment/Rename。(使用DDL语句会隐式提交,不能回滚)
DDL语句,建议只用于不涉及事务回滚的一次性脚本里,比如首次建表和索引、清理表历史数据等。
mark学习。