1. MySQL数据类型概述
作为关系型数据库的基石,MySQL的数据类型系统直接影响着数据存储效率、查询性能和系统稳定性。我在实际项目中见过太多因为数据类型选择不当导致的性能问题:一个本该用TINYINT的字段被定义成INT,导致百万级数据表体积膨胀30%;用VARCHAR(255)存储固定长度的MD5值,白白浪费了20%存储空间...
MySQL的数据类型主要分为三大类:
- 数值类型:包括整数和浮点数
- 字符串类型:包含文本和二进制数据
- 日期时间类型:处理各种时间格式
每种类型都有其特定的存储需求和适用场景。比如同样是存储年龄,TINYINT UNSIGNED就比INT更适合,因为人类年龄不可能超过255岁,更不可能是负数。
关键原则:选择能满足需求的最小数据类型。这不仅节省存储空间,更能提升索引效率。
2. 数值类型深度解析
2.1 整数类型实战选择
MySQL提供5种整数类型,它们的区别主要体现在存储空间和取值范围上:
| 类型 | 字节 | 有符号范围 | 无符号范围 |
|---|---|---|---|
| TINYINT | 1 | -128 ~ 127 | 0 ~ 255 |
| SMALLINT | 2 | -32768 ~ 32767 | 0 ~ 65535 |
| MEDIUMINT | 3 | -8388608 ~ 8388607 | 0 ~ 16777215 |
| INT/INTEGER | 4 | -2147483648 ~ 2147483647 | 0 ~ 4294967295 |
| BIGINT | 8 | -2^63 ~ 2^63-1 | 0 ~ 2^64-1 |
实际项目中的经验法则:
- 状态字段用TINYINT:比如订单状态(0未支付,1已支付)
- 外键ID用INT足够:除非是超大型系统
- 自增主键建议用UNSIGNED:避免负数浪费一半空间
-- 典型错误示例:用BIGINT存储用户年龄 CREATE TABLE user ( age BIGINT -- 浪费7个字节 ); -- 正确做法 CREATE TABLE user ( age TINYINT UNSIGNED -- 只需1字节 );2.2 浮点数精准陷阱
FLOAT和DOUBLE作为近似值类型,在进行等值比较时会出现精度问题:
-- 会产生意想不到的结果 SELECT 0.1 + 0.2 = 0.3; -- 返回0(false)金融类数据必须使用DECIMAL:
CREATE TABLE account ( balance DECIMAL(10,2) -- 10位精度,2位小数 );血泪教训:曾经有个电商项目因为用FLOAT存储金额,导致对账时出现0.01元的差额,排查了整整两天!
3. 字符串类型实战指南
3.1 CHAR与VARCHAR的抉择
| 特性 | CHAR | VARCHAR |
|---|---|---|
| 存储方式 | 固定长度 | 可变长度 |
| 空格处理 | 自动补足空格 | 保留原样 |
| 适用场景 | 定长数据(如MD5) | 变长数据(如地址) |
实测对比:存储100万个MD5值(固定32字符)
- CHAR(32):占用32MB
- VARCHAR(32):占用约38MB(有额外长度标识)
3.2 文本类型使用场景
- TEXT系列:存储大段文本,分TINYTEXT(255B)、TEXT(64KB)、MEDIUMTEXT(16MB)、LONGTEXT(4GB)
- BLOB系列:存储二进制数据,分类与TEXT对应
重要限制:TEXT/BLOB列不能有默认值,也不能用作索引的全部内容
4. 时间类型的精妙运用
4.1 各时间类型对比
| 类型 | 格式 | 范围 | 存储需求 |
|---|---|---|---|
| DATE | 'YYYY-MM-DD' | 1000-01-01~9999-12-31 | 3字节 |
| TIME | 'HH:MM:SS' | -838:59:59~838:59:59 | 3字节 |
| DATETIME | 'YYYY-MM-DD HH:MM:SS' | 1000-01-01 00:00:00~9999-12-31 23:59:59 | 8字节 |
| TIMESTAMP | 'YYYY-MM-DD HH:MM:SS' | 1970-01-01 00:00:01~2038-01-19 03:14:07 | 4字节 |
4.2 时区陷阱与解决方案
TIMESTAMP会转换为UTC存储,检索时再转回当前时区,而DATETIME不会:
-- 假设服务器时区为UTC+8 CREATE TABLE events ( dt DATETIME, ts TIMESTAMP ); INSERT INTO events VALUES ('2023-01-01 08:00:00', '2023-01-01 08:00:00'); -- 修改时区后查询 SET time_zone = '+00:00'; SELECT * FROM events; -- 结果:dt显示08:00:00,ts显示00:00:00跨时区系统建议统一使用DATETIME存储,前端负责时区转换。
5. 类型选择性能优化实战
5.1 索引效率对比测试
在100万数据的用户表上测试:
-- 方案1:手机号存为VARCHAR(20) ALTER TABLE users ADD INDEX idx_phone(phone); -- 查询耗时:约120ms -- 方案2:手机号存为CHAR(11) ALTER TABLE users ADD INDEX idx_phone(phone); -- 查询耗时:约85ms定长字段的索引效率通常更高,但需权衡存储空间。
5.2 隐式类型转换陷阱
-- 假设mobile字段是VARCHAR EXPLAIN SELECT * FROM users WHERE mobile = 13800138000; -- 会发现使用了全表扫描而不是索引必须保持查询条件与字段类型一致,这是最常见的性能杀手之一。
6. 特殊类型与应用场景
6.1 ENUM与SET类型
ENUM适合固定选项:
-- 节省存储空间 CREATE TABLE shirts ( size ENUM('x-small', 'small', 'medium', 'large', 'x-large') );SET适合多选场景:
CREATE TABLE permissions ( flags SET('read', 'write', 'delete', 'admin') );6.2 JSON类型实战
MySQL 5.7+支持原生JSON类型:
CREATE TABLE products ( attributes JSON, INDEX idx_attrs ((CAST(attributes->'$.color' AS CHAR(20)))) ); -- 查询红色商品 SELECT * FROM products WHERE JSON_EXTRACT(attributes, '$.color') = 'red';JSON类型的索引需要通过生成列实现,这是NoSQL特性在关系型数据库中的巧妙融合。