几个年头里反复踩的坑
建表时随手选类型,上线后不是慢就是错。我整理了团队里最高频的几类字段选型问题,新项目开工前照着过一遍。这些坑每一个都对应过一次线上事故或一次漫长的数据迁移,所以值得写下来反复提醒。
varchar 长度
有人图省事一律 varchar(255),其实 MySQL 在 utf8mb4 下,255 字符要 255×4=1020 字节,配合某些索引前缀会触到 767 字节上限,建索引直接失败。定长用 char,变长且明确上限用合适的 varchar,别无脑 255。我们曾有个手机号字段 varchar(255),其实 11 位足够,浪费不说,还因为它在联合索引里占了大头导致索引选错。
另外 varchar 是变长,但行总长(含所有 varchar 实际长度)不能超过 65535 字节(不含 blob/text),多个大 varchar 拼一起会触发行溢出,落到溢出页,查询变慢。所以不是 varchar 越大越安全。
时间类型
datetime 和 timestamp 选型纠结过很久:
- timestamp 只占 4 字节,但范围只到 2038 年,且受时区影响,存的是 UTC、读出来按会话时区转;
- datetime 占 8 字节,范围到 9999 年,存的是字面值不随时区变;
- MySQL 8.0 还加了 timestamp(6) 支持微秒,对精度要求高的场景有用。
我们统一:对"绝对时刻"(创建时间、支付时间)用 timestamp 或 BigInt 存毫秒,便于范围查询和跨时区;对"生日、营业时间"这类语义用 datetime,因为它不随时区漂。踩过的坑:一个全球业务用 timestamp 存,结果报表按东八区生成,和美东同事对不上,改用 BigInt 毫秒后世界太平。
金额类型
用 double 存金额是大忌,0.1+0.2 不等于 0.3 的浮点误差会在对账时爆雷。一律 DECIMAL(18,2),或更小单位用 BIGINT 分。我们曾有个老表用 double 存余额,跑了一年对账差了 3 分钱,查了半天才定位到浮点累加误差。迁移时写了个脚本把 double×100 四舍五入成 BIGINT 分,校验总和一致才切换。
-- 错误
price DOUBLE;
-- 正确
price DECIMAL(18,2);
-- 或存分
price_cent BIGINT;
字符集
早期库是 latin1,存中文直接乱码,读取出来是问号。新库统一 utf8mb4(注意不是 utf8,后者最多 3 字节,存不了 emoji 和某些生僻字)。我们遇到过用户昵称带 emoji,utf8 表插入直接报错,改成 utf8mb4 才解决。连接串也要显式设 characterEncoding=utf8mb4,否则驱动可能按 latin1 协商。
布尔与枚举
布尔用 TINYINT(1) 还是 BIT?MySQL 里 TINYINT(1) 最通用,ORM 映射也顺。枚举状态不建议用 ENUM 类型——改枚举值要 ALTER TABLE,锁表风险大;我们用 VARCHAR(20) 存状态码字符串,或者 SMALLINT 存数字,含义放代码里。这样加状态不用动表结构。
一个真实的迁移教训
去年把一个核心表从 latin1 转 utf8mb4,因为数据量大(2 亿行),直接 ALTER 锁了 40 分钟,期间写入全堵。后来学乖了,用 gh-ost 做在线 schema 变更,业务零感知。所以字段类型规范要在一开始定好,事后改的代价是指数级的。
索引列的类型也别选错
字段类型还影响索引能不能用上。比如手机号存成 VARCHAR 和 BIGINT 都能查,但 VARCHAR 索引按字符串比,BIGINT 按数字比,前者多一层转换。更严重的是隐式转换:如果字段是 VARCHAR 但查询传了数字,MySQL 会做类型转换导致索引失效,全表扫描。我们曾有个 user_id 字段是 VARCHAR,代码里传了 Long,慢查询排查半天才发现是隐式转换。规范是:数值就 BIGINT/INT,别用字符串存数字。
建表模板
把这些规范写成团队的建表模板,新表照填:
CREATE TABLE t_order (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
price DECIMAL(18,2) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMP NOT NULL,
KEY idx_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CHARSET 写死 utf8mb4,金额 DECIMAL,时间 TIMESTAMP,状态 VARCHAR。新人照模板建表,基本不会踩上面那些坑。规范前置比事后迁移便宜一百倍。
小结
字段类型是数据库的"地基",选错改起来要锁表。varchar 别 255 主义、时间看语义、金额用 DECIMAL 或 BIGINT 分、字符集上 utf8mb4、状态用 VARCHAR/SMALLINT 而非 ENUM。这几条能避开八成坑。规范写进团队的建表模板,比靠人记靠谱。