数据库入门到初级 · 第 04/22 篇。实验口径:MySQL 8.4、InnoDB、utf8mb4。案例:小型商城。
数据库里的值看起来都是字符,但“199.00 元”“手机号 13900000001”“2026 年 1 月 10 日”具有不同的业务含义。字段类型决定数据库怎样存储、比较和计算它们。选对类型,后面的查询和约束会省很多力气。
1. 数字不都应该存成整数
| 场景 | 本系列采用的类型 | 理由 |
|---|---|---|
| 用户、订单编号 | BIGINT UNSIGNED |
非负整数,编号空间较大 |
| 库存、购买数量 | INT 加约束 |
需要整数运算,并限制业务范围 |
| 商品价格 | DECIMAL(10,2) |
按十进制精确保存两位小数 |
| 在售标记 | TINYINT 加 0/1 约束 |
明确允许的状态值 |
DECIMAL(10,2) 的 10 是总位数,2 是小数位数,不是“整数 10 位再加 2 位小数”。商品价格还要加 CHECK (price >= 0),因为这个类型本身仍允许负数。
FLOAT 和 DOUBLE 使用近似浮点表示,适合某些科学和测量数据。对需要准确结算的金额,通常使用精确小数,或使用整数表达最小货币单位;后者要清楚货币、单位和溢出范围。
2. 手机号为什么应该是字符串
手机号、邮编和证件号虽然看起来像数字,却通常不需要加减乘除,还可能包含前导零、国家区号或其他字符。把它们当字符串,才能保留原来的业务表示。
CHAR 适合固定长度字符数据,VARCHAR 适合变长字符数据;TEXT 可保存更长文本,但不代表所有字段都应使用它。字段长度是业务边界的一部分,过长、过短都需要解释。
本系列使用 utf8mb4 字符集。还要区分字符集与排序规则:前者决定能表示哪些字符,后者影响字符串比较与排序,例如大小写是否被视为相同。邮箱是否允许大小写差异,需要业务规则和数据库规则一致。

3. 日期时间不要随手存成文字
日期可使用 DATE,日期加时间可使用 DATETIME。MySQL 的 TIMESTAMP 涉及时区转换,并有不同的表示范围;不要仅凭名字选择。
本系列的 created_at 使用 DATETIME,示例中的固定时间统一按业务当地时间理解。DATETIME 本身不携带时区;真实应用可约定保存 UTC,再在展示时转换,但所有写入端和读取端都要遵守约定。
SELECT id, created_at
FROM orders
WHERE created_at >= '2026-01-10 00:00:00'
AND created_at < '2026-01-12 00:00:00';
这里使用左闭右开区间,包含 10 日和 11 日。它比拼接“当天最后一秒”更容易覆盖带小数秒的时间值。
4. NULL、空字符串和 0 完全不同
NULL 表示缺失或未知;'' 是长度为零的字符串;0 是一个确定的数值。比如未知库存不能直接当作库存 0,未知手机号也不应该随意填“0”。
样例里,小林有手机号,小周的手机号是 NULL,小陈的手机号是空字符串。这是为了练习区别,不是在推荐业务同时使用两种“未填写”表示。
SELECT name FROM users WHERE phone IS NULL;
SELECT name FROM users WHERE phone = '';
SELECT COUNT(*) AS all_users, COUNT(phone) AS known_phone_values
FROM users;
前两条分别得到“小周”和“小陈”。最后一条为 3 与 2,因为 COUNT(phone) 忽略 NULL,但会计入空字符串。判断 NULL 使用 IS NULL,不能写 = NULL。
5. 不要只选类型,还要写规则
同样是 INT,库存允许 0,购买数量必须大于 0。类型只解决一部分问题,非空、唯一和检查约束解决另一部分。
实际系统还应明确:金额是否含税、时间属于哪个时区、长度按什么业务上限、是否允许缺失。没有这些定义,类型看起来正确,数据仍然可能被误读。
练习与答案
练习一:订单号 202601100001 应该存整数吗? 先判断它是参与计算的数值还是有固定格式的标识。若要保留前导零和格式,字符串往往更合适。
练习二:能否用 DEFAULT 0 表示“价格还没确定”? 不宜。0 元是明确价格,待定是另一种业务状态,应分别建模。
练习三:NULL + 10 是 10 吗? 不是,结果为 NULL。只有业务明确允许把缺失当作 0 时,才考虑 COALESCE 等处理。
延伸阅读
上一篇:第 03 篇 · 系列目录 · 下一篇:第 05 篇