多租户电商系统实现按店铺递增的唯一订单号方案
多租户电商应用实现店铺独立连续订单号方案
需求明确:为每个店铺生成连续递增、无重复的订单号,比如示例中店铺1的订单号依次为1、2、3、4,店铺2独立从1开始计数。
示例场景
初始订单数据:
| shopId | orderNumber | items |
|---|---|---|
| 1 | 1 | hamburger |
| 1 | 2 | hamburger |
| 1 | 3 | hamburger |
| 2 | 1 | hamburger |
当店铺1新增订单时,orderNumber应自动递增为4,最终数据:
| shopId | orderNumber | items |
|---|---|---|
| 1 | 1 | hamburger |
| 1 | 2 | hamburger |
| 1 | 3 | hamburger |
| 2 | 1 | hamburger |
| 1 | 4 | hamburger |
下面是几种可靠的实现方案:
1. 单独维护店铺订单序列表(推荐高并发场景)
创建一张专门存储每个店铺当前订单序号的表,利用数据库的原子更新操作保证序号唯一:
序列表结构
CREATE TABLE shop_order_sequence ( shop_id INT PRIMARY KEY COMMENT '店铺ID', current_order_num INT NOT NULL DEFAULT 0 COMMENT '当前已用最大订单号' );
生成订单号逻辑
每次插入订单前,先原子更新序列表获取新序号:
-- 处理首次新增店铺的情况:不存在则插入初始值1,存在则递增 INSERT INTO shop_order_sequence (shop_id, current_order_num) VALUES (?, 1) ON DUPLICATE KEY UPDATE current_order_num = current_order_num + 1; -- 获取递增后的订单号 SELECT current_order_num FROM shop_order_sequence WHERE shop_id = ?;
拿到这个序号后,直接插入订单表即可。这种方式完全依赖数据库原子操作,并发下不会出现重复,性能稳定。
2. 订单表内置逻辑(适合低并发场景)
如果不想额外建表,可以直接基于订单表查询最大序号后插入,但必须配合事务和行锁避免并发冲突:
事务+行锁实现
BEGIN TRANSACTION; -- 锁定当前店铺的所有订单行,防止其他事务同时查询最大序号 SELECT COALESCE(MAX(orderNumber), 0) INTO @max_order_num FROM orders WHERE shopId = ? FOR UPDATE; -- 插入新订单,订单号为最大序号+1 INSERT INTO orders (shopId, orderNumber, items) VALUES (?, @max_order_num + 1, ?); COMMIT;
注意:这种方式在高并发下会因为行锁导致性能瓶颈,仅适合订单量不大的场景。
3. 分布式锁+缓存(适合分布式多实例应用)
如果应用是多实例部署的分布式系统,用Redis分布式锁配合缓存存储序号,避免数据库压力:
伪代码示例(Python)
import redis import mysql.connector redis_client = redis.Redis(host='localhost', port=6379, db=0) db_conn = mysql.connector.connect(host='localhost', user='user', password='pass', database='ecommerce') def get_next_order_number(shop_id): lock_key = f"shop_order_lock:{shop_id}" seq_key = f"shop_order_seq:{shop_id}" # 获取分布式锁,超时时间设为5秒,防止死锁 with redis_client.lock(lock_key, timeout=5): # 先从缓存取序号,缓存不存在则从数据库同步 current_num = redis_client.get(seq_key) if not current_num: cursor = db_conn.cursor() cursor.execute("SELECT COALESCE(MAX(orderNumber), 0) FROM orders WHERE shopId = %s", (shop_id,)) current_num = cursor.fetchone()[0] redis_client.set(seq_key, current_num) # 递增序号并更新缓存 new_num = int(current_num) + 1 redis_client.set(seq_key, new_num) return new_num
生成序号后插入订单表即可,分布式锁保证同一时刻只有一个实例能修改该店铺的序号。
最后一道防线:唯一索引
无论用哪种方案,都要给订单表的(shopId, orderNumber)加唯一索引,防止极端异常情况导致重复:
CREATE UNIQUE INDEX idx_shop_order_unique ON orders(shopId, orderNumber);
如果真的出现重复插入,数据库会抛出唯一约束异常,应用层捕获后重试即可。
内容的提问来源于stack exchange,提问作者Matias
相关产品推荐
相关产品推荐

