MySQL中UUID字段应使用CHAR(36),因定长提升索引与JOIN性能,且5.7+严格模式下UUID()不能直接作默认值;PostgreSQL优先用UUID类型以节省空间并优化查询;SQLite无原生支持,需TEXT加CHECK约束并由应用层生成。
MySQL中添加UUID字段必须用CHAR(36),不能用VARCHAR
mysql的uuid()函数返回固定36字符的字符串(8-4-4-4-12格式),比如"550e8400-e29b-41d4-a716-446655440000"。用varchar(36)看似省空间,但实际在innodb里反而更慢——因为变长字段会触发额外的行格式开销和比较逻辑;而char(36)定长,索引查找、排序、join都更稳定。
常见错误是直接写VARCHAR(36)还加了DEFAULT UUID(),结果插入时报错:Invalid default value for 'id'——这是因为MySQL 5.7+默认严格模式下,UUID()不能直接作列默认值(仅函数如NOW()支持)。
- 建表时用
CHAR(36) NOT NULL DEFAULT (UUID())(MySQL 8.0.13+才支持函数作为默认值) - 老版本(如5.7)必须靠触发器或应用层生成,不能依赖
DEFAULT - 如果已有表,用
ALTER TABLE t ADD COLUMN id CHAR(36) NOT NULL DEFAULT '',再用UPDATE t SET id = UUID()补全,最后加NOT NULL约束
PostgreSQL里UUID字段优先选UUID类型,不是TEXT或CHAR
PostgreSQL原生支持UUID数据类型,它比TEXT或CHAR(36)小一半内存(16字节 vs 36+字节),且内置校验、索引优化和函数支持(如gen_random_uuid())。用TEXT存UUID等于放弃所有类型优势,还会让WHERE id = 'xxx'走不到高效索引。
典型翻车场景:从MySQL迁移过来,习惯性建id TEXT DEFAULT 'uuid_generate_v4()',却忘了装pgcrypto扩展,导致插入时报错:function uuid_generate_v4() does not exist。
- 先执行
CREATE EXTENSION IF NOT EXISTS "pgcrypto" - 字段定义用
id UUID PRIMARY KEY DEFAULT gen_random_uuid() - 别用
uuid_generate_v4()(需要uuid-ossp扩展),gen_random_uuid()更轻量、无需额外权限 - 如果已用
TEXT,改类型要小心:ALTER COLUMN id TYPE UUID USING id::UUID,但前提是所有值都合法
SQLite中没原生UUID类型,得靠BLOB或TEXT+约束
SQLite连UUID关键字都不认,也没内置生成函数。硬塞TEXT最常用,但容易存进非法格式(比如少横线、含空格),后续查起来全是隐患。有人图省事用BLOB存16字节二进制UUID,结果应用层读写全得自己编解码,出错率飙升。
错误示范:id TEXT DEFAULT 'uuid()'——SQLite根本没这个函数,插入时id就真变成字符串"uuid()"。
- 生成必须靠应用层(Python用
uuid.uuid4().hex,Go用uuid.NewString()) - 建表时加CHECK约束:
id TEXT CHECK(length(id) == 36 AND id GLOB '[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]') - 别信“SQLite 3.35+支持UUID”这种二手消息——它只是加了
uuid函数返回文本,不是数据类型
默认值陷阱:ORM里设default=uuid.uuid4只生效一次
Django的models.UUIDField(default=uuid.uuid4)看着没问题,但注意:这个uuid4是函数对象,不是调用结果。每次实例化Model时才调用,所以新记录能拿到不同值;但如果你写成default=uuid.uuid4()(带括号),那就糟了——模块加载时就执行一次,所有记录共享同一个UUID。
SQLAlchemy也类似:Column(UUID, default=uuid.uuid4)正确,default=uuid.uuid4()错误。更隐蔽的是Flask-SQLAlchemy里有人把server_default配成text("uuid_generate_v4()"),结果换到SQLite直接崩。
- Python端生成一律用函数引用,不加
() - 想靠数据库生成,得按数据库类型配
server_default:MySQL用text("UUID()")(8.0.13+),PostgreSQL用text("gen_random_uuid()") - 别在迁移脚本里用
op.alter_column(..., server_default=...)直接改默认值,可能锁表或丢失历史数据
UUID字段看着简单,但跨数据库时连“怎么存”都要重新判断——MySQL认CHAR(36),PostgreSQL认UUID,SQLite只能靠TEXT + CHECK硬扛。最容易被跳过的其实是默认值生成时机:函数引用还是函数调用,服务端生成还是客户端生成,差一个括号,整张表就废了。

















