不能直接用 uniqid() 生成订单号,因其仅依赖微秒时间戳、无碰撞校验,高并发下极易重复;须加盐(如 uniqid('', true))、拼接业务标识、数据库唯一约束校验,并结合 redis 原子递增或雪花算法保障全局唯一。

用 uniqid() 生成订单号?别直接用,它不唯一
直接调用 uniqid() 得到的字符串,在高并发或毫秒级重复请求下大概率重复——它只依赖当前微秒时间戳,没做碰撞校验。真实电商下单场景里,哪怕 0.1 秒内两个请求进来,uniqid() 可能返回相同值。
必须加盐 + 后缀校验:
- 加
more_entropy=true参数(如uniqid('', true)),让 PHP 拼上系统随机数,提升熵值 - 拼接业务标识,比如
'ORD_' . date('ymd') . uniqid('', true),避免不同天订单号前缀冲突 - 数据库插入前用
INSERT ... ON DUPLICATE KEY UPDATE或先SELECT校验,不能只靠 PHP 层“觉得不会重”
订单号要带日期和可读性?用 sprintf() + 自增ID组合
纯时间戳或随机串难排查问题,运营、客服需要一眼看出日期和大致顺序。但又不能直接暴露数据库自增ID(有安全和泄露量级风险)。
推荐做法是:用当天已生成订单数(非主键ID)+ 固定前缀 + 时间片段:
立即学习“PHP免费学习笔记(深入)”;
- 查表统计当日订单数:
SELECT COUNT(*) FROM orders WHERE created_at >= CURDATE(),再用sprintf('%s%06d', date('ymd'), $count+1) - 更稳妥是用 Redis 原子递增:
INCR order_seq_20240520,避免 DB 查询延迟和锁竞争 - 注意时区:PHP 的
date()和 MySQL 的CURDATE()必须同属一个时区,否则跨零点会漏计或重复
分布式环境生成全局唯一订单号?绕不开 Redis INCR 或雪花算法
单机 PHP 进程可用文件锁或 DB 自增,但多台机器部署时,必须依赖外部协调服务。MySQL 自增ID 在分库后失效,uniqid() 更不可靠。
两种主流方案对比:
-
Redis INCR:简单快,适合中小流量。用INCR order_id_seq+EXPIRE防堆积,但 Redis 故障会导致下单失败 - 雪花算法(
twitter/snowflake类实现):需自己移植或用扩展如ext-snowflake。注意 PHP 不原生支持 64 位整数运算,bcadd()或gmp_add()处理位移时容易出错 - 别用 UUIDv4:太长(36 字符)、无序、索引效率低,DB 里存
CHAR(36)比CHAR(20)多占近一倍空间
为什么不能用 md5(microtime() . rand())?哈希不是防重手段
看到网上有人拼随机数再哈希,以为“打乱就唯一”,这是典型误解。MD5 输出是固定 32 位十六进制字符串,理论碰撞概率随数量上升指数级增长——生成 10 万订单,碰撞概率已超 1%。
真正起作用的是熵源质量,不是哈希本身:
-
microtime(true)精度仅微秒,PHP 脚本执行快于 1μs 就撞车 -
rand()是伪随机,seed 相同则序列一致;应改用random_int()(PHP 7+) - 如果非要哈希,至少用
spl_object_hash(new stdClass()) . microtime(true) . random_int(0, 9999)这类多维度混合
最常被忽略的一点:订单号一旦生成并返回给客户端,就不能因 DB 写入失败而“重试生成新号”。必须保证“号已发,事必成”,否则支付对账会彻底混乱。











