数据库 UUID使用规范 MeteorCat 2026-04-13 2026-04-13 随着 UUID 规范的日渐成熟, 目前已经可以在应用在大量日志库上面作为 主键(PrimaryKey) 使用
之所以考虑到不采用 自增ID(AUTO_INCREMENT) 就是为了方便分布式系统/多库数据合并等场景,
但因为早期规范存在缺陷现在还有一下问题要处理:
早期 UUID(v1/v4) 版本是随机乱序生成, 配合 InnoDB 主键是聚簇索引会导致频繁页分裂和索引碎片
采用字符串存储(CHAR(36)) 空间浪费巨大, 空间多占 2~3 倍会导致索引体积暴涨
UUIDv1 依赖 MAC + 时间, 可能会导致分布式多库合并出现冲突
只有在 UUIDv7 之后才修复以上问题, 目前 MySQL和MariaDB 之类方案可能因为版本导致没办法采用有序的 UUIDv7
目前不推荐直接采用 MySQL/MariaDB 内置的 UUID 类型, 需要为了考虑到兼容性采用 BINARY(16) 用二进制保存节约空间.
那么设计的表结构可能如下处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 登录日志 # MySQL 版本之后才有 UUID_TO_BIN, 为了保持兼容性需要在代码端自定义 UUID_TO_BIN 和 BIN_TO_UUID 方法 # 注意: 目前很多云数据库内部很多采用自定义魔改版本, 可能不提供这类自带函数 # 标准 UUID 本身 = 128 位(bit)的数字, 而 128 bit = 16 byte(字节), 所以可以直接采用 binary(16) create table if not exists app_login_logs ( id binary(16) not null comment '有序UUID标识', uid bigint not null comment '用户ID', # 创建信息 time bigint not null comment '用户创建时间, 毫秒级别的UTC时间戳', ip varchar(64) not null comment '用户创建IP地址', token varchar(32) not null default '' comment '用户最新生成的登陆凭证', primary key (id) ) comment '应用登录日志, 采用 UUID_TO_BIN(UUID(), 1), 生成有序二进制, 通过 BIN_TO_UUID(id,1) 可以反序列化成 UUID' engine = InnoDB charset = utf8mb4 collate = utf8mb4_unicode_ci
因为内部 binary(16) 是作为 128 位(bit) 的数字, 所以作为主键会将其作为有序保存在内部从而不会导致页分裂和索引碎片
但是目前还没处理 UUIDv7 版本问题, 所以外部程序就需要按照最新版本 UUIDv7 来生成, 这里提供 PHP 版本的生成方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php function uuid_v7 ( ): string { $timestamp = (int )(microtime (true ) * 1000 ); $random = random_bytes (10 ); $timeBin = hex2bin (str_pad (dechex ($timestamp ), 12 , '0' , STR_PAD_LEFT)); $uuidBin = $timeBin . chr (0x70 | (ord ($random [0 ]) & 0x0F )) . chr (0x80 | (ord ($random [1 ]) & 0x3F )) . substr ($random , 2 ); $uuid = bin2hex ($uuidBin ); return vsprintf ('%08s-%04s-%04s-%04s-%012s' , [ substr ($uuid , 0 , 8 ), substr ($uuid , 8 , 4 ), substr ($uuid , 12 , 4 ), substr ($uuid , 16 , 4 ), substr ($uuid , 20 , 12 ) ]); } ?>
注意: 目前虽然生成 UUIDv7 但是还不能直接保存到 binary(16) 需要处理下二进制内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function uuid_to_bin (?string $uuid = null ): string { $uuid = empty ($uuid ) ? uuid_v7 () : $uuid ; $hex = str_replace ('-' , '' , $uuid ); $hex = substr ($hex , 12 , 4 ) . substr ($hex , 8 , 4 ) . substr ($hex , 0 , 8 ) . substr ($hex , 16 ); return hex2bin ($hex ); } function bin_to_uuid (string $bin ): string { $hex = bin2hex ($bin ); return sprintf ( '%s-%s-%s-%s-%s' , substr ($hex , 8 , 8 ), substr ($hex , 4 , 4 ), substr ($hex , 0 , 4 ), substr ($hex , 16 , 4 ), substr ($hex , 20 , 12 ) ); }
大部分情况下很少会对 UUID 做复杂操作, 所以直接采用这种方式就能很好处理大部分日志库主键问题, 用数据模型处理下即可:
1 2 3 4 5 6 $binaryId = uuid_to_bin ();$pdo ->prepare ("INSERT INTO system_log(id, content) VALUES (?, ?)" ) ->execute ([$binaryId , "测试日志" ]);