MySQL UUID_SHORT() 返回 64 位無符號長整數(基於 server_id、啟動時間、計數器),**全局唯一、單調遞增、無破折號、比標準 UUID 短 50%、極適合 InnoDB 主鍵**(避免頁分裂、索引緊湊)。本文實現 Hibernate 自定義 IdentifierGenerator 調用 UUID_SHORT(),含映射配置、實體定義、事務邊界處理與效能對比。
UUID_SHORT() 特性
- 格式:
BIGINT UNSIGNED (0 ~ 18446744073709551615)
- 組成:
(server_id & 255) << 56 + (startup_time_seconds << 24) + counter
- 單調遞增:同一實例內嚴格遞增,**利於 InnoDB 聚簇索引順序寫入**
- 全局唯一:需保證
server_id (my.cnf) 全局唯一
- 無需網絡協調,比 Snowflake 簡單
Hibernate 自定義生成器實現
1. 生成器類
package com.example.util;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class ShortUUIDIncrementGenerator implements IdentifierGenerator {
private static final Logger log = LoggerFactory.getLogger(ShortUUIDIncrementGenerator.class);
private static final String SQL = "SELECT UUID_SHORT()";
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) {
try (PreparedStatement ps = session.connection().prepareStatement(SQL);
ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
long id = rs.getLong(1);
log.debug("Generated UUID_SHORT ID: {}", id);
return id;
}
throw new HibernateException("UUID_SHORT() returned no result");
} catch (SQLException e) {
log.error("Could not retrieve UUID_SHORT", e);
throw new HibernateException("Could not retrieve UUID_SHORT", e);
}
}
}
2. 實體映射配置
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(generator = "shortUid")
@GenericGenerator(name = "shortUid", strategy = "com.example.util.ShortUUIDIncrementGenerator")
@Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false, columnDefinition = "BIGINT UNSIGNED")
private Long id;
// 其他字段...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
3. XML 映射替代 (hbm.xml)
關鍵細節與避坑
| 問題 | 原因 | 解決 |
| ID 為負數 / 溢出 | Java Long 有符號,UUID_SHORT() 最大值 > Long.MAX_VALUE (9.22E18) | 實際 UUID_SHORT() 當前值 ~ 1.5E18,遠小於 Long.MAX_VALUE,**暫安全**;長期建議用 BigInteger 或字符串存儲 |
| 事務邊界:生成器在事務外執行 | Hibernate 默認在 INSERT 前調用 generate(),若事務回滾 ID 已消耗 | 接受間隙(單調遞增不要求連續),或用增強版:事務提交前生成 |
| 並發性能 | 每次 INSERT 需額外 SELECT UUID_SHORT() | 可接受(單次 RTT),極高併發可改用 HiLo/Sequence/預分配池 |
| server_id 衝突 | 多實例 server_id 相同 → ID 衝突 | 強制配置唯一 server_id (1-255),監控 SHOW VARIABLES LIKE 'server_id' |
| 備庫只讀報錯 | 備庫 read_only=ON,UUID_SHORT() 需寫入計數器 | 生成器僅在主庫調用,備庫不持久化實體或用只讀事務 |
效能對比:UUID_SHORT vs UUID vs Snowflake vs 自增
| 方案 | 長度 | 有序性 | 索引友好 | 生成開銷 | 全局唯一 | 適用場景 |
| 自增 BIGINT | 8B | 嚴格遞增 | ✅ 最佳 | 極低 (內部) | 單實例 | 單庫、低併發 |
| UUID_SHORT | 8B | 單調遞增 | ✅ 極佳 | 低 (1 RTT) | 全局 (需 server_id) | **主從/集群主鍵首選** |
| UUID (RFC4122) | 16B/36字符 | 無序 | ❌ 頁分裂嚴重 | 極低 (本地) | 全局 | 分散式無協調、非主鍵 |
| Snowflake (64bit) | 8B | 趨勢遞增 | ✅ 良好 | 極低 (本地) | 全局 (需 workerId) | 高併發微服務、客戶端生成 |
生產環境增強版:預分配池減少 RTT
@ApplicationScoped
public class UuidShortPool {
private final BlockingQueue pool = new LinkedBlockingQueue<>(10000);
private final AtomicBoolean loading = new AtomicBoolean(false);
@PostConstruct
void start() { refillAsync(); }
public synchronized long nextId() {
Long id = pool.poll();
if (id == null) { // 池空,同步補充 (罕見)
refillSync();
id = pool.poll();
}
if (pool.size() < 1000 && loading.compareAndSet(false, true)) {
refillAsync();
}
return id;
}
private void refillAsync() {
CompletableFuture.runAsync(() -> {
try { refillSync(); } finally { loading.set(false); }
});
}
private void refillSync() {
List batch = jdbcTemplate.queryForList("SELECT UUID_SHORT() FROM DUAL", Long.class, 1000);
pool.addAll(batch);
}
}
// 生成器委託給池
public class PooledShortUUIDGenerator implements IdentifierGenerator {
@Autowired UuidShortPool pool;
public Serializable generate(...) { return pool.nextId(); }
}
遷移檢查表
- ✅ 所有實例
server_id 唯一、非 0、my.cnf 持久化
- ✅ 表主鍵定義
BIGINT UNSIGNED,JPA @Column(columnDefinition = "BIGINT UNSIGNED")
- ✅ 測試主從切換:備庫提升為主後 UUID_SHORT() 正常生成
- ✅ 壓測驗證:QPS、主鍵衝突率、索引頁分裂率
- ✅ 監控
SHOW VARIABLES LIKE 'server_id'、SELECT UUID_SHORT() 連續性