業務中無處不在的「唯一標識」:訂單號、邀請碼、API Token、分佈式主鍵、幂等鍵、追蹤 ID。PHP 提供從內建函數到擴展庫、從單機到分佈式的完整生成方案。本文按安全性、有序性、長度、生成速度、碰撞概率五大維度對比主流方案,給出選型決策樹與生產級代碼。
| 方案 | 長度 | 有序性 | 安全性 | 碰撞率 | 適用場景 |
|---|---|---|---|---|---|
uniqid() | 13/23 字符 | 微秒級遞增 | 低 (可預測) | 高併發極高 | 臨時文件名、非關鍵業務 |
md5(uniqid()) | 32 字符 | 無序 | 低 | 極低 | 一般唯一鍵、緩存 Key |
random_bytes(16) | 32/24 字符 | 無序 | 高 (CSPRNG) | 理論零 | Token、Session ID、API Key |
| UUID v4 (隨機) | 36 字符 | 無序 | 高 | 理論零 | 分佈式主鍵、跨系統標識 |
| UUID v1 (時間+MAC) | 36 字符 | 粗略有序 | 中 (洩露 MAC) | 極低 | 需粗略排序的分佈式 ID |
| ULID | 26 字符 | 毫秒級有序 | 高 | 極低 | 現代替代 UUID v1,索引友好 |
| 雪花算法 (Snowflake) | 19 位數字 | 毫秒級有序 | 中 (需保護機器 ID) | 零 (單調遞增) | 高併發分佈式主鍵、MySQL 主鍵 |
| NanoID | 21 字符 (可配置) | 無序 | 高 | 可調 | 短 ID、URL 友好、前端傳遞 |
// uniqid(): 微秒時間戳 + 微秒計數器
echo uniqid(); // 65f8a3b2c1d4e (13 字符)
echo uniqid('ord_'); // ord_65f8a3b2c1d4e (帶前綴)
echo uniqid('', true); // 65f8a3b2c1d4e.12345678 (23 字符, 更多熵)
// 組合增強 (仍非安全)
echo md5(uniqid('', true)); // 32 字符十六進制
echo bin2hex(random_bytes(16)); // 32 字符,**推薦替代上行**
echo base64_encode(random_bytes(16)); // 24 字符 URL-safe (需替換 +/=)
function generateSecureToken(int $bytes = 32): string
{
// PHP 7+ random_bytes() 使用系統 CSPRNG (Linux: getrandom, Windows: BCryptGenRandom)
return bin2hex(random_bytes($bytes)); // 64 字符十六進制
}
// URL-safe Base64 (無 + / =)
function generateUrlSafeToken(int $bytes = 24): string
{
return rtrim(strtr(base64_encode(random_bytes($bytes)), '+/', '-_'), '=');
}
// 使用示例
$apiKey = 'sk_' . generateUrlSafeToken(32); // sk_xYz7... (43 字符)
$csrfToken = generateSecureToken(32);
$sessionId = generateUrlSafeToken(16);
function uuid_v4(): string
{
$bytes = random_bytes(16);
// 設置版本位 (4) 和變體位 (10xx)
$bytes[6] = chr((ord($bytes[6]) & 0x0F) | 0x40); // version 4
$bytes[8] = chr((ord($bytes[8]) & 0x3F) | 0x80); // variant 10
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4));
}
// 使用 ramsey/uuid 庫 (生產推薦,Composer: ramsey/uuid)
use Ramsey\Uuid\Uuid;
$uuid = Uuid::uuid4()->toString(); // 標準 UUID v4
$uuid1 = Uuid::uuid1()->toString(); // 基於時間+MAC (需開啟 MAC 獲取)
$uuid6 = Uuid::uuid6()->toString(); // 新標準:時間有序 + 隨機
$uuid7 = Uuid::uuid7()->toString(); // Unix 毫秒時間戳 + 隨機 (最推薦用於 DB 主鍵)
26 字符、Base32 編碼、48 位毫秒時間戳 + 80 位隨機、天然按字典序排序 = 時間序,完美適配資料庫聚簇索引。
// 純 PHP 實現 (需 PHP 7+)
function ulid(): string
{
$time = (int)(microtime(true) * 1000); // 毫秒時間戳
$timeBytes = pack('J', $time); // 6 字節大端 (僅取低 48 位)
$timeBytes = substr($timeBytes, -6); // 保證 6 字節
$random = random_bytes(10); // 10 字節 = 80 位
$binary = $timeBytes . $random;
// Crockford's Base32 編碼 (0-9 A-Z 除 I L O U)
$encode = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
$ulid = '';
for ($i = 0; $i < 16; $i++) {
$byte = ord($binary[$i]);
$ulid .= $encode[$byte >> 3 & 0x1F];
$ulid .= $encode[($byte << 2 & 0x1C) | (ord($binary[$i+1] ?? 0) >> 6 & 0x03)];
}
return substr($ulid, 0, 26);
}
// 生產推薦:robinvdvleuten/ulid-php (Composer)
use Ulid\Ulid;
$ulid = Ulid::generate()->toString(); // 01ARZ3NDEKTSV4RRFFQ69G5FAV
64 位長整數:1bit 符號位 | 41bit 毫秒時間戳 (69年) | 10bit 機器/工作進程 ID (1024) | 12bit 序列號 (4096/ms)。
優勢:**單調遞增、無碰撞、極短 (19 位數字)、MySQL BIGINT 完美適配、索引性能極佳**。
// 純 PHP 實現 (單進程/單機演示,分佈式需引入 Redis/ZK 分配 workerId)
class Snowflake
{
private int $workerId;
private int $sequence = 0;
private int $lastTimestamp = -1;
private const EPOCH = 1609459200000; // 2021-01-01 00:00:00 UTC
private const WORKER_ID_BITS = 10;
private const SEQUENCE_BITS = 12;
private const MAX_WORKER_ID = -1 ^ (-1 << self::WORKER_ID_BITS); // 1023
private const MAX_SEQUENCE = -1 ^ (-1 << self::SEQUENCE_BITS); // 4095
public function __construct(int $workerId)
{
if ($workerId < 0 || $workerId > self::MAX_WORKER_ID) {
throw new InvalidArgumentException("WorkerId must be 0-1023");
}
$this->workerId = $workerId;
}
public function nextId(): int
{
$timestamp = $this->timeGen();
if ($timestamp < $this->lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if ($timestamp === $this->lastTimestamp) {
$this->sequence = ($this->sequence + 1) & self::MAX_SEQUENCE;
if ($this->sequence === 0) {
$timestamp = $this->tilNextMillis($this->lastTimestamp);
}
} else {
$this->sequence = 0;
}
$this->lastTimestamp = $timestamp;
return (($timestamp - self::EPOCH) << (self::WORKER_ID_BITS + self::SEQUENCE_BITS))
| ($this->workerId << self::SEQUENCE_BITS)
| $this->sequence;
}
private function timeGen(): int { return (int)(microtime(true) * 1000); }
private function tilNextMillis(int $last): int {
$ts = $this->timeGen();
while ($ts <= $last) $ts = $this->timeGen();
return $ts;
}
}
// 使用
$sf = new Snowflake(1); // workerId=1
echo $sf->nextId(); // 1348274923847654401 (19 位數字)
> 🏭 生產級部署:使用 baidu/uid-generator (Java) 或 go-snowflake (Go) 作為獨立 ID 服務,PHP 通過 gRPC/HTTP 獲取 ID 批次,避免 PHP 進程間 workerId 衝突、時鐘回撥、單點故障。
// 隨機字母表 (默認 URL-safe)
function nanoid(int $size = 21, string $alphabet = '_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string
{
$bytes = random_bytes($size);
$mask = (1 << (int)ceil(log(strlen($alphabet), 2))) - 1;
$id = '';
for ($i = 0; $i < $size; $i++) {
$byte = ord($bytes[$i]) & $mask;
if ($byte < strlen($alphabet)) {
$id .= $alphabet[$byte];
} else {
$i--; // 重試
}
}
return $id;
}
// 生產推薦:hidehalo/nanoid-php
use Hidehalo\Nanoid\Client;
$nanoid = (new Client())->generateId(); // V1StGXR8_Z5jdHi6B-myT (21 字符)
random_bytes() / generateSecureToken()base64_encode(random_bytes(12))uniqid('', true) / md5(uniqid())md5(uniqid()) 生成 Token:uniqid() 可預測,MD5 無加密強度,彩虹表可逆向。mt_rand()/rand() 生成任何安全標識:非 CSPRNG,種子可推導。CHAR(36) 或 BINARY(16):BINARY(16) 節省 50% 空間且索引更快 (需應用層 pack/unpack)。BIGINT UNSIGNED:MySQL 8.0+ 支援隱式主鍵優化。唯一標識生成看似簡單,實則關繫安全性、資料庫性能、分佈式一致性、業務可追溯性。根據業務屬性選擇合適方案,並在架構層面統一規範 (如「所有新表主鍵採用 Snowflake BIGINT」、「所有對外 Token 採用 URL-safe Base64」),才能避免未來遷移重構的巨大代價。