分表(水平拆分)後,各分表若仍使用 AUTO_INCREMENT,主鍵 ID 會重複,無法全局唯一標識記錄。本文介紹一種利用 MySQL LAST_INSERT_ID(expr) 特性,通過單張序列表實現高性能、全局唯一 ID 分配的方案。
id INT AUTO_INCREMENTCREATE TABLE ticket_mutex (
name VARCHAR(32) NOT NULL PRIMARY KEY COMMENT '業務標識,如 USER、POST',
value BIGINT(20) UNSIGNED NOT NULL COMMENT '當前最大ID值'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '分表ID序列表';
INSERT INTO ticket_mutex(name, value) VALUES ('USER', 0), ('POST', 0);
UPDATE ticket_mutex
SET value = LAST_INSERT_ID(value + 1)
WHERE name = 'USER';
SELECT LAST_INSERT_ID();
| 操作 | 返回值 | 表中 USER 行 value |
|---|---|---|
| 首次執行 | 1 | 1 |
| 第二次 | 2 | 2 |
| 第 N 次 | N | N |
關鍵優勢:單條原子語句完成「遞增 + 取值」,無需事務、無鎖競爭、無額外表鎖,極高併發下仍能保證 ID 單調遞增且無重複。
| 用法 | 語法 | 返回值來源 | 適用場景 |
|---|---|---|---|
| 無參數 | LAST_INSERT_ID() | 最近一次 INSERT 產生的 AUTO_INCREMENT 值 | 普通自增表插入後取 ID |
| 帶表達式 | LAST_INSERT_ID(expr) | 表達式 expr 的計算結果 | 本方案:序列表無自增列,手動維護 value |
官方文檔明確:LAST_INSERT_ID(expr) 會將表達式結果存入會話上下文,後續 LAST_INSERT_ID() 直接返回該值,不受並發影響,會話隔離。
-- 1. 應用層決定分表邏輯(如 user_id % 10)
-- 2. 獲取全局唯一 ID
UPDATE ticket_mutex SET value = LAST_INSERT_ID(value + 1) WHERE name = 'USER';
SET @new_id = LAST_INSERT_ID();
-- 3. 寫入目標分表
INSERT INTO user_@shard_id (id, username, email)
VALUES (@new_id, 'zhangsan', 'zhangsan@example.com');
name 字段區分業務域:USER、POST、ORDER、COMMENT 等互不干擾,單表支撐無限業務線。
| 方案 | 性能 | 實現複雜度 | 單點風險 | ID 連續性 |
|---|---|---|---|---|
| UUID | 低(索引碎片、空間大) | 極簡 | 無 | 無序 |
| Snowflake 算法 | 高 | 中(需部署服務/客戶端庫) | 無 | 趨勢遞增 |
| 數據庫自增 + 步長 | 中 | 中(多實例配置步長) | 有 | 段連續 |
| 本方案 | 高(單條 UPDATE) | 低(純 SQL) | 有(主庫單點) | 完全連續 |
rpl_semi_sync_master=ON)防丟 IDvalue + 100 一次取段,應用層本地消費適用場景:中小規模分表系統、強依賴數字 ID 連續性的業務(如訂單號)、不想引入額外中間件的團隊。核心優勢:零中間件、純 SQL、原子性強、實現極簡。