1. MySQL数据类型概述
在数据库设计过程中,数据类型的选择直接影响着数据存储效率、查询性能和系统稳定性。MySQL作为最流行的关系型数据库之一,提供了丰富的数据类型体系,每种类型都有其特定的使用场景和限制条件。
我见过太多因为数据类型选择不当导致的性能问题:一个本该用TINYINT的字段被定义成了INT,结果百万级数据表凭空多占了几百MB空间;VARCHAR长度随意设置成255的"懒人做法",导致内存分配浪费;日期时间存储用字符串而非原生类型,使得所有日期计算都要先做类型转换...
2. 数值类型详解
2.1 整数类型
MySQL提供了五种整数类型,它们的区别主要在于存储空间和取值范围:
| 类型 | 存储空间 | 有符号范围 | 无符号范围 |
|---|---|---|---|
| TINYINT | 1字节 | -128 ~ 127 | 0 ~ 255 |
| SMALLINT | 2字节 | -32768 ~ 32767 | 0 ~ 65535 |
| MEDIUMINT | 3字节 | -8388608 ~ 8388607 | 0 ~ 16777215 |
| INT | 4字节 | -2147483648 ~ 2147483647 | 0 ~ 4294967295 |
| BIGINT | 8字节 | -2^63 ~ 2^63-1 | 0 ~ 2^64-1 |
实际项目中,我强烈建议根据业务数据的实际范围选择最小够用的类型。比如存储年龄用TINYINT足够,用户ID用INT,而订单号这种可能很大的数值才需要用BIGINT。
2.2 浮点与定点数
对于需要小数的场景,MySQL提供:
- FLOAT:单精度浮点,4字节,约7位有效数字
- DOUBLE:双精度浮点,8字节,约15位有效数字
- DECIMAL(M,D):定点数,M是总位数,D是小数位数
金融相关数据必须使用DECIMAL,因为FLOAT/DOUBLE会有精度损失。曾经有个电商项目因为用FLOAT存储金额,导致0.01元的误差累积到月底对账时出现了上千元的差额。
3. 字符串类型
3.1 CHAR与VARCHAR
这对经典类型的区别常被误解:
| 特性 | CHAR | VARCHAR |
|---|---|---|
| 存储方式 | 固定长度 | 可变长度 |
| 空间使用 | 总是占用定义的长度 | 只占用实际数据长度+1~2字节 |
| 适用场景 | 长度固定的数据(如MD5) | 长度变化大的数据(如地址) |
常见误区是把所有字符串字段都定义成VARCHAR(255)。实际上应该根据业务需求设置合理的长度,比如手机号可以设VARCHAR(20),姓名VARCHAR(50)。过大的长度不仅浪费空间,还会影响内存排序的性能。
3.2 文本类型
当需要存储大段文本时:
- TINYTEXT:最大255字节
- TEXT:最大65KB
- MEDIUMTEXT:最大16MB
- LONGTEXT:最大4GB
文本类型不能有默认值,且排序时只使用前1024字节。如果只是存储短文本文案,建议用VARCHAR替代TEXT,因为TEXT字段会使用额外存储空间。
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字节 |
| YEAR | YYYY | 1901 ~ 2155 | 1字节 |
4.2 时区陷阱
TIMESTAMP会转换为UTC存储,检索时再转换回当前时区,而DATETIME则原样存储。曾经有个跨国项目因为没注意这个区别,导致报表时间显示混乱。
如果业务需要记录确切的用户输入时间(如生日),应该用DATETIME;如果是需要自动记录的时间(如创建时间),用TIMESTAMP更方便。
5. 特殊类型
5.1 ENUM和SET
ENUM允许定义最多65535个不同的字符串值,但实际存储的是值的索引(1-2字节)。适合像状态字段这种有限选项的场景。
SET类似ENUM,但允许选择多个值,用位图方式存储。比如存储用户爱好:'游泳,电影,阅读'。
虽然ENUM节省空间,但修改枚举值需要ALTER TABLE,在频繁变更的业务中可能成为瓶颈。我一般只在稳定不变的分类数据上使用ENUM。
5.2 JSON类型
MySQL 5.7+原生支持JSON类型,提供了一系列JSON操作函数。适合存储半结构化数据,如产品属性、配置项等。
sql复制-- 创建包含JSON字段的表
CREATE TABLE products (
id INT AUTO_INCREMENT,
details JSON,
PRIMARY KEY (id)
);
-- 插入JSON数据
INSERT INTO products (details) VALUES
('{"name": "Laptop", "specs": {"cpu": "i7", "ram": "16GB"}}');
-- 查询JSON字段
SELECT details->'$.name' AS product_name,
details->'$.specs.cpu' AS cpu
FROM products;
JSON字段虽然灵活,但无法建立有效索引,复杂查询性能较差。关系型数据还是应该用传统的列存储。
6. 类型选择实战建议
6.1 存储优化原则
- 优先选择占用空间小的类型
- 固定长度数据用CHAR,可变长度用VARCHAR
- 整数类型根据范围选择最小够用的
- 精确小数用DECIMAL,非精确计算可用FLOAT/DOUBLE
- 日期时间根据精度和范围需求选择
6.2 性能影响
- 大字段(如TEXT/BLOB)会使用额外存储空间,影响查询速度
- 使用ENUM代替字符串可提升比较效率
- 不恰当的类型会导致隐式类型转换,使索引失效
- UTF8MB4字符集比Latin1占用更多空间
6.3 常见错误案例
- 用VARCHAR存储IP地址:应该用INT UNSIGNED+INET_ATON()函数
- 用字符串存储日期:无法使用日期函数和索引
- BOOLEAN用TINYINT(1)表示,其实MySQL没有真正的BOOLEAN类型
- 自增ID用BIGINT但实际业务根本达不到INT上限
7. 数据类型与索引效率
正确的数据类型选择对索引效率至关重要:
- 对整数列建立索引效率最高
- 字符串索引应考虑前缀索引(如INDEX(column(10)))
- 不要在TEXT/BLOB列上建完整索引,考虑前缀索引或全文索引
- ENUM/SET的索引效率高于普通字符串
曾经优化过一个查询缓慢的用户表,发现是在VARCHAR(255)的email字段上建立了索引。通过改为VARCHAR(50)并添加前缀索引INDEX(email(20)),查询速度提升了5倍。
8. 数据类型迁移与变更
修改已有表的数据类型需要谨慎:
sql复制-- 安全的变更方式(以扩大VARCHAR长度为例)
ALTER TABLE users MODIFY COLUMN username VARCHAR(100) NOT NULL;
-- 危险操作(缩小字段长度可能导致数据截断)
ALTER TABLE products MODIFY COLUMN sku VARCHAR(10);
大表修改数据类型会导致锁表,建议在低峰期进行,或使用pt-online-schema-change等工具。我曾经因为直接修改一个千万级用户表的字段类型,导致生产环境卡死15分钟。