导读:本期聚焦于小伙伴创作的《如何生成SQL唯一序列号?掌握UUID与序列函数实战应用》,敬请观看详情。订单号重复导致数据混乱是数据库设计中典型的事务隐患。关系型数据库提供了两种主流方案来生成全局唯一序列号:一种是依赖数据库内置序列对象(如PostgreSQL的SERIAL、Oracle的SEQUENCE),另一种是利用UUID算法生成无序但极高概率唯一的字符串。序列函数性能优异且递增有序,适合做主键,但存在单点瓶颈与水平扩展限制;UUID免中心化协调,天然适配分布式,但占用空间大且索引插入易产生碎片。理解二者底层机制与适用边界,才能针对高并发交易、分库分表等场景选出稳妥的编号策略。

在业务系统里,为每条记录分配一个不重复的标识是基础需求。关系型数据库通常给出两条路径:使用序列函数产出递增整数,或使用UUID生成无序唯一字符串。二者在存储、性能与扩展模型上差异明显,选型前需要先弄清原理。

如何生成SQL唯一序列号?掌握UUID与序列函数实战应用

一、序列函数生成唯一序列号

序列(Sequence)是数据库维护的一个独立计数器对象,它通过原子操作保证每次调用都返回唯一且递增的值。以PostgreSQL为例,可以用CREATE SEQUENCE定义一个序列,然后在插入数据时通过nextval函数获取下一个值。这种方式生成的序列号是纯数字,体积小、插入B+树索引时不会产生随机写,因此写入性能很好。

下面是一段PostgreSQL中创建序列并使用的示例:

-- 创建从1开始、步长为1的序列
CREATE SEQUENCE order_seq START 1 INCREMENT 1;

-- 建表时引用序列作为默认值
CREATE TABLE orders (
    id BIGINT DEFAULT nextval('order_seq') PRIMARY KEY,
    order_no TEXT,
    created_at TIMESTAMP
);

-- 插入数据,id自动从序列获取
INSERT INTO orders (order_no, created_at)
VALUES ('A001', NOW());

序列函数的优势在于简单高效,数据库自己保证并发安全,不需要应用层做任何处理。但它也有明显短板:序列通常绑定单个数据库实例,在主从复制或分库分表环境中,不同节点若各自维护序列,就可能生成重复号;另外,业务量暴涨时单一序列可能成为写入瓶颈。

为缓解单点问题,一些团队会采用步长错开策略,比如节点A的序列INCREMENT设为2、起始1,节点B起始2,这样两节点各取奇偶值避免冲突。不过这种办法在节点数变多时配置繁琐,且序列值容易被外部推测,不适合做对外的订单号。

二、UUID生成唯一序列号

UUID(通用唯一识别码)是一个128位的标识符,标准格式以36字符字符串呈现,例如550e8400-e29b-41d4-a716-446655440000。它不依赖中心数据库,而是结合时间戳、网卡MAC、随机数等因子计算,理论上重复概率极低。SQL中多数现代数据库都内置了UUID函数,如MySQL的UUID()、PostgreSQL的gen_random_uuid()。

使用UUID做主键或序列号的写法非常直接:

-- PostgreSQL生成UUID主键
CREATE TABLE user_log (
    log_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    content TEXT,
    ts TIMESTAMP
);

-- 插入时无需指定log_id
INSERT INTO user_log (content, ts)
VALUES ('login success', NOW());

-- MySQL中直接用UUID()
INSERT INTO session (sid, info) VALUES (UUID(), 'test');

UUID最大的好处是去中心化,应用任意节点都能独立生成,天然适配微服务与多数据中心。但它也有代价:字符串占16字节(二进制)或36字节(文本),比 BIGINT 的8字节大不少;并且因为无序,新插入的主键值随机分散在索引各处,会导致InnoDB等聚簇索引频繁页分裂,写入吞吐不如序列。

为降低UUID的索引碎片,业界推出过UUIDv1按时间排序、以及Snowflake式有序UUID。如果必须在SQL层使用,可优先选用数据库提供的有序UUID扩展,或者在应用层用雪花算法算好再写入,避免纯随机UUID拖慢表。

三、两种方案对比与选型

我们把核心差异整理成下表,方便对照:

维度序列函数UUID
唯一性保障单库内绝对唯一,跨库需人工规划全局极高概率唯一
存储空间8字节(BIGINT)16字节二进制或36字节文本
写入性能顺序写,索引友好随机写,易页分裂
分布式支持弱,需步长等妥协方案强,无中心依赖
可读性短数字,便于排查长字符串,不直观

从表中可以看出,如果系统仍是单库且追求极致写入,序列函数是首选;当业务已经分库分表,或需要离线终端也能发号,UUID更稳妥。实践中也有混合做法:数据库用序列做内部主键,对外暴露的订单号则用UUID或雪花算法生成,兼顾性能与安全。

另外要注意,不论用哪种方式,都应在表上建立唯一约束,防止程序bug或手动改数据引入重复。序列本身不防人为指定相同值插入,UUID虽碰撞率低但仍建议留一道约束兜底。

四、应用层结合SQL的注意事项

在代码里调用序列或UUID时,尽量让数据库自己填默认值,而不是先查号再插。例如用MyBatis时可写

<insert id="addOrder">
    INSERT INTO orders (order_no, created_at)
    VALUES (#{orderNo}, #{createdAt})
</insert>

上述写法依赖表的DEFAULT nextval或gen_random_uuid,减少一次往返。若用JDBC手动拿序列,也要放在同一个事务里,避免并发时号被跳过造成浪费。对于UUID,如果数据库版本较老没有内置函数,可以在Java侧用UUID.randomUUID().toString()生成后传入,但需确认字段类型是UUID或CHAR(36)。

最后提醒,序列号不等于业务流水号。序列函数给的1、2、3容易暴露销量,对外下单接口最好映射一层不可推测的编码,而UUID虽不可推测却太长,用户抄写易错,所以面向前端的单号往往要再做格式化,比如取UUID中段加日期前缀,这也是为什么很多系统内部用序列、外部用衍生号。

UUID序列函数唯一序列号修改时间:2026-08-02 12:18:32

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。