高併發插入場景下出現 Duplicate entry 'xxx' for key 'PRIMARY' 是自增鎖機制配置不當的典型表現。本文深度解析三種鎖模式原理、適用場景與配置策略。
id BIGINT AUTO_INCREMENT PRIMARY KEYDuplicate entry '12345' for key 'PRIMARY'| 模式值 | 名稱 | 鎖粒度 | 分配策略 | 併發性能 | 主從安全性 | 適用場景 |
|---|---|---|---|---|---|---|
| 0 | Traditional | 表級鎖 (AUTO-INC 鎖) | 逐個分配,持有鎖到語句結束 | 最低 | ✅ Statement/Row 皆安全 | 舊版本兼容、Statement 複製、極低併發 |
| 1 | Consecutive (默認 5.1-8.0) | 語句級鎖 | 簡單 INSERT 預分配連續塊;複雜 INSERT 逐個 | 中等 | ⚠️ Statement 不安全 (需 Row 格式) | 通用 OLTP、Row 格式 Binlog |
| 2 | Interleaved | 輕量級互斥鎖 | 來一個分配一個,無預分配 | 最高 | ❌ Statement 不安全 (必須 Row 格式) | 高併發插入、Row 格式 Binlog、無 Statement 複製 |
模式 0 (Traditional):
Thread A: INSERT ... VALUES (NULL), (NULL), (NULL)
┌─────────────────────────────────────┐
│ LOCK TABLE (AUTO-INC) │
│ 分配 100,101,102 │
│ UNLOCK │
└─────────────────────────────────────┘
Thread B: 等待鎖釋放 → 分配 103,104,105
模式 1 (Consecutive) - 簡單 INSERT:
Thread A: INSERT ... VALUES (NULL), (NULL)
┌──────────────┐
│ 分配 100,101 │ (預分配連續塊)
└──────────────┘
Thread B: 並行分配 102,103 (無鎖等待)
--- 但複雜 INSERT (INSERT...SELECT, LOAD DATA) 退化為模式 0 行為 ---
模式 2 (Interleaved):
Thread A: INSERT ... VALUES (NULL)
┌─────┐
│ 100 │ (輕量鎖,即分配即釋放)
└─────┘
Thread B: 並行分配 101, 102...
--- 完全交錯分配,主從 Statement 格式會導致從庫分配順序不一致 ---
| 方案 | 配置 | 優點 | 缺點 | 建議場景 |
|---|---|---|---|---|
| 強制傳統模式 | innodb_autoinc_lock_mode=0 | 絕對安全、兼容 Statement 複製 | 性能最差、表鎖爭用嚴重 | 壓測驗證、舊系統兼容、極低併發 |
| 保持默認模式 | innodb_autoinc_lock_mode=1 | 平衡性能安全、Row 複製安全 | Statement 複製不安全 | ✅ 生產環境推薦 (配合 Row 格式) |
| 高性能交錯模式 | innodb_autoinc_lock_mode=2 | 併發性能最強 | 僅支持 Row 格式、Statement 不安全 | 高併發純插入、Row 格式、無 Statement 依賴 |
| 應用層雪花算法 | 去中心化 ID 生成 | 無數據庫協調、極高性能、全局唯一 | ID 無序、需應用層改造、長度較長 | 分庫分表、微服務、極高併發 |
# my.cnf 生產推薦配置
[mysqld]
# 核心:Row 格式 + 模式 1 (默認) = 安全 + 性能平衡
binlog_format = ROW
innodb_autoinc_lock_mode = 1
# 高併發寫入優化 (8.0.3+ 可考慮模式 2)
# innodb_autoinc_lock_mode = 2
# 連接池配合 (防止連接耗盡加劇鎖競爭)
max_connections = 2000
thread_cache_size = 200
#!/bin/bash
# autoinc_stress_test.sh
THREADS=50
INSERTS_PER_THREAD=1000
DB="test_db"
TABLE="autoinc_test"
mysql -e "CREATE DATABASE IF NOT EXISTS $DB;"
mysql -e "CREATE TABLE $DB.$TABLE (id BIGINT AUTO_INCREMENT PRIMARY KEY, val VARCHAR(20)) ENGINE=InnoDB;"
for i in $(seq 1 $THREADS); do
(
for j in $(seq 1 $INSERTS_PER_THREAD); do
mysql -e "INSERT INTO $DB.$TABLE (val) VALUES ('thread_${i}_${j}');" 2>&1 | grep -E "Duplicate|Error" &
done
wait
) &
done
wait
echo "Duplicate errors: $(mysql -e "SELECT COUNT(*) FROM $DB.$TABLE;" -sN 2>/dev/null || echo 'N/A')"
mysql -e "DROP DATABASE $DB;"
innodb_autoinc_lock_mode=0binlog_format=ROW 可安全使用自增主鍵並發衝突本質是「順序分配與並發執行的矛盾」。生產環境首選:binlog_format=ROW + innodb_autoinc_lock_mode=1 (默認),兼顧性能與主從一致性。極高併發場景考慮應用層分布式 ID 生成器 (雪花算法/UidGenerator),從架構層面消除數據庫協調瓶頸。