CREATE TABLE `auto_id` (
`id_name` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '業務標識,如 order_id, user_id',
`id` BIGINT(20) NOT NULL DEFAULT 0 COMMENT '當前最大 ID',
PRIMARY KEY (`id_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='全局自增 ID 生成器';
DELIMITER //
DROP PROCEDURE IF EXISTS `get_increment_id`//
CREATE PROCEDURE `get_increment_id`(
IN `p_id_name` VARCHAR(20),
IN `p_start_val` BIGINT, -- 初始值/步長
OUT `p_out_id` BIGINT
)
BEGIN
DECLARE v_old_id BIGINT DEFAULT 0;
START TRANSACTION;
-- 核心:SELECT ... FOR UPDATE 獲取排他鎖 (X鎖)
-- 阻塞其他事務讀寫此行,保證並發安全
SELECT `id` INTO v_old_id FROM `auto_id` WHERE `id_name` = p_id_name FOR UPDATE;
IF v_old_id IS NULL THEN
-- 首次初始化
INSERT INTO `auto_id` (`id_name`, `id`) VALUES (p_id_name, p_start_val);
SET p_out_id = p_start_val;
ELSE
-- 遞增更新
UPDATE `auto_id` SET `id` = `id` + 1 WHERE `id_name` = p_id_name;
SET p_out_id = v_old_id + 1;
END IF;
COMMIT;
END//
DELIMITER ;
$key = 'order_id';
$start = 1000000; // 起始 ID
// 1. 調用存儲過程
$sql = "CALL get_increment_id('{$key}', {$start}, @id)";
$db->execute($sql);
// 2. 獲取返回值
$ret = $db->query("SELECT @id AS new_id");
$newId = $ret[0]['new_id'];
echo "生成全局 ID: {$newId}";
CREATE TABLE `seq_order_id` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=MyISAM; -- MyISAM 表級鎖下 LAST_INSERT_ID 也安全,InnoDB 同樣適用
-- 1. 更新觸發自增,並將新值設置到會話上下文
UPDATE `seq_order_id` SET `id` = LAST_INSERT_ID(`id` + 1);
-- 2. 直接讀取會話上下文中的新 ID (無需查表,極快)
SELECT LAST_INSERT_ID();
| 維度 | 方案一:行鎖+存儲過程 | 方案二:AUTO_INCREMENT+LAST_INSERT_ID |
|---|---|---|
| 實現複雜度 | 中 (需存儲過程) | 低 (兩條 SQL) |
| 元數據表數量 | 1 表存所有業務 ID | 每業務 1 表 |
| 性能 (高並發) | 行鎖競爭,單行熱點極限 ~5k QPS | 表鎖/行鎖更新,極限更高,無額外邏輯 |
| ID 連續性 | 嚴格連續 (無間隙) | 嚴格連續 |
| 擴展性 | 易擴展 (新增 id_name) | 需建表 |
| 依賴 | InnoDB 事務 | MyISAM/InnoDB 均可 |
純 MySQL 實現全局自增 ID,**方案二 (LAST_INSERT_ID) 代碼最少、性能最強、原理最簡**,推薦優先使用。方案一適合需集中管理所有業務 ID、或已有存儲過程規範的系統。關鍵在於利用數據庫**原子操作與會話隔離特性**,避免應用層鎖與外部依賴。