文章目录
-
- 数据类型:存储数据的基础
- 一、前言
- 二、数据类型分类
-
- [2.1 MySQL 数据类型概览](#2.1 MySQL 数据类型概览)
- 三、数值类型
- 四、字符串类型
- 五、日期时间类型
-
- [5.1 DATE 类型](#5.1 DATE 类型)
- [5.2 DATETIME 类型](#5.2 DATETIME 类型)
- [5.3 TIMESTAMP 类型](#5.3 TIMESTAMP 类型)
- [5.4 日期时间类型示例](#5.4 日期时间类型示例)
- [5.5 日期时间类型选择建议](#5.5 日期时间类型选择建议)
- 六、枚举和集合
- 七、其他字符串类型
-
- [7.1 TEXT 类型](#7.1 TEXT 类型)
- [7.2 BLOB 类型](#7.2 BLOB 类型)
- 八、数据类型选择指南
- 九、总结与最佳实践
数据类型:存储数据的基础
一、前言
💬 这一篇讲什么:MySQL 中的各种数据类型
🚀 核心内容:
- 整数、浮点数、字符串、日期等类型有什么区别?
- 如何选择合适的数据类型?
- 数据类型如何影响存储空间和性能?
在前一篇中,我们学会了创建表。表由若干列组成,每列都需要指定数据类型。数据类型决定了列能存储什么样的数据,以及存储的效率。选择正确的数据类型是数据库设计的关键。
二、数据类型分类
2.1 MySQL 数据类型概览
MySQL 支持多种数据类型,按照功能分为以下几类:
| 分类 | 类型 | 用途 |
|---|---|---|
| 数值类型 | TINYINT、SMALLINT、INT、BIGINT、FLOAT、DECIMAL | 存储数字 |
| 字符串类型 | CHAR、VARCHAR、TEXT、BLOB | 存储文本和二进制数据 |
| 日期时间类型 | DATE、TIME、DATETIME、TIMESTAMP | 存储日期和时间 |
| 特殊类型 | ENUM、SET、JSON | 存储枚举值、集合、JSON 数据 |
选择正确的数据类型有以下好处:
- 节省存储空间:合适的类型占用更少磁盘空间。
- 提高查询性能:数据类型影响索引效率和查询速度。
- 数据完整性:正确的类型限制可以防止无效数据。
- 易于维护:类型明确使代码更容易理解。
三、数值类型
3.1 整数类型
语法
sql
TINYINT | SMALLINT | INT | BIGINT [UNSIGNED] [ZEROFILL]
UNSIGNED:表示无符号,即不能为负数。
ZEROFILL:不足指定宽度时用 0 补齐(不常用)。
各整数类型对比
| 类型 | 存储空间 | 有符号范围 | 无符号范围 | 使用场景 |
|---|---|---|---|---|
| TINYINT | 1 字节 | -128 ~ 127 | 0 ~ 255 | 状态标记、布尔值 |
| SMALLINT | 2 字节 | -32768 ~ 32767 | 0 ~ 65535 | 不常用 |
| MEDIUMINT | 3 字节 | -8388608 ~ 8388607 | 0 ~ 16777215 | 不常用 |
| INT | 4 字节 | -2^31 ~ 2^31-1 | 0 ~ 2^32-1 | 通常的整数存储 |
| BIGINT | 8 字节 | -2^63 ~ 2^63-1 | 0 ~ 2^64-1 | 大数值、ID、时间戳 |
TINYINT 示例
有符号 TINYINT:
sql
CREATE TABLE tt1 (num TINYINT);
-- 插入有效值
INSERT INTO tt1 VALUES (1);
INSERT INTO tt1 VALUES (-128);
INSERT INTO tt1 VALUES (127);
-- 尝试超出范围
INSERT INTO tt1 VALUES (128); -- 错误:超出范围
ERROR 1264 (22003): Out of range value for column 'num' at row 1
无符号 TINYINT:
sql
CREATE TABLE tt2 (num TINYINT UNSIGNED);
-- 插入有效值
INSERT INTO tt2 VALUES (0);
INSERT INTO tt2 VALUES (255);
-- 尝试插入负数或超出范围
INSERT INTO tt2 VALUES (-1); -- 错误:无符号不能为负
ERROR 1264 (22003): Out of range value for column 'num' at row 1
INSERT INTO tt2 VALUES (256); -- 错误:超出范围
ERROR 1264 (22003): Out of range value for column 'num' at row 1
INT 示例
sql
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT COMMENT '用户ID',
score INT DEFAULT 0 COMMENT '得分'
);
INSERT INTO users (user_id, score) VALUES (1001, 95);
INSERT INTO users (user_id, score) VALUES (1002, 87);
SELECT * FROM users;
选择整数类型的建议
- 布尔标记 :使用
TINYINT(1)或BOOLEAN(实际上是TINYINT(1)的别名)。 - 用户 ID、数据库 ID :使用
BIGINT,避免未来溢出。 - 普通数字 :通常用
INT。 - 不要使用 UNSIGNED:MySQL 官方建议避免 UNSIGNED。当值溢出时,用更大的类型(如从 INT 升级到 BIGINT)比用 UNSIGNED 更安全。
3.2 BIT 类型
语法
sql
BIT(M)
M 表示位数,范围从 1 到 64。默认为 1。
BIT 的特点
- 存储位(二进制)数据,每位只能是 0 或 1。
BIT(1)只能存储 0 或 1。BIT(8)可以存储 0 ~ 255(8 位二进制数)。- 显示时按 ASCII 码对应的字符显示。
BIT 示例
sql
CREATE TABLE tt4 (id INT, a BIT(8));
-- 插入数值 10
INSERT INTO tt4 VALUES (10, 10);
-- 查询时,10 对应的 ASCII 字符显示
SELECT * FROM tt4;
输出:
bash
+----+------+
| id | a |
+----+------+
| 10 | | -- 10 对应 ASCII 是不可见字符
+----+------+
插入 65(对应字符 'A'):
sql
INSERT INTO tt4 VALUES (20, 65);
SELECT * FROM tt4;
输出:
bash
+----+------+
| id | a |
+----+------+
| 10 | |
| 20 | A | -- 65 对应 ASCII 字符 'A'
+----+------+
BIT(1) 实例
存储 0 或 1 的状态:
sql
CREATE TABLE tt5 (gender BIT(1));
INSERT INTO tt5 VALUES (0); -- 成功
INSERT INTO tt5 VALUES (1); -- 成功
INSERT INTO tt5 VALUES (2); -- 错误:越界
ERROR 1406 (22001): Data too long for column 'gender' at row 1
BIT 的使用建议
- 场景有限 :BIT 类型使用场景不多。通常用
TINYINT(1)代替BIT(1)更清晰。 - 不建议使用:显示困扰(按 ASCII 码显示)且用途有限。
- 如果必须用:BIT(1) 存储 true/false,BIT(8) 存储权限位掩码等。
3.3 浮点数类型
FLOAT 类型
语法:
sql
FLOAT(M, D) [UNSIGNED]
M:指定显示长度(包括整数部分和小数部分)。D:指定小数位数。- 存储空间:4 字节。
- 精度:约 7 位有效数字。
FLOAT(4,2) 的含义:
- 整数部分最多 4 - 2 = 2 位。
- 小数部分固定 2 位。
- 范围:-99.99 ~ 99.99。
- 当插入的值超过小数位数时,MySQL 会四舍五入。
示例:
sql
CREATE TABLE tt6 (id INT, salary FLOAT(4, 2));
-- 正常插入
INSERT INTO tt6 VALUES (100, -99.99); -- 成功
INSERT INTO tt6 VALUES (101, -99.991); -- 多出的小数被四舍五入
SELECT * FROM tt6;
输出:
bash
+-----+--------+
| id | salary |
+-----+--------+
| 100 | -99.99 |
| 101 | -99.99 | -- -99.991 被四舍五入为 -99.99
+-----+--------+
无符号 FLOAT:
sql
CREATE TABLE tt7 (id INT, salary FLOAT(4, 2) UNSIGNED);
-- 无符号:范围是 0 ~ 99.99
INSERT INTO tt7 VALUES (100, -0.1); -- 警告:转换为 0
INSERT INTO tt7 VALUES (100, 99.99); -- 成功
SHOW WARNINGS;
DECIMAL 类型
语法:
sql
DECIMAL(M, D) [UNSIGNED]
M:最大位数,范围 1 ~ 65。D:小数位数,范围 0 ~ 30。- 存储空间:可变,最多 17 字节。
- 精度:精确的定点数,不存在浮点数误差。
DECIMAL 与 FLOAT 的区别:
| 特性 | FLOAT | DECIMAL |
|---|---|---|
| 存储方式 | 浮点数(近似) | 定点数(精确) |
| 精度 | 约 7 位有效数字 | 最多 65 位 |
| 存储空间 | 4 字节 | 可变,通常 5-17 字节 |
| 运算速度 | 快 | 较慢 |
| 使用场景 | 科学计算 | 财务数据、金额 |
DECIMAL 精度示例:
sql
CREATE TABLE tt8 (
id INT,
salary_float FLOAT(10, 8),
salary_decimal DECIMAL(10, 8)
);
INSERT INTO tt8 VALUES (100, 23.12345612, 23.12345612);
SELECT * FROM tt8;
输出:
bash
+-----+--------------+---------------+
| id | salary_float | salary_decimal|
+-----+--------------+---------------+
| 100 | 23.12345695 | 23.12345612 | -- DECIMAL 更精确
+-----+--------------+---------------+
浮点数类型选择建议
- 财务数据(价格、金额) :必须用
DECIMAL。浮点数的舍入误差会在金钱计算中造成严重问题。 - 科学计算 :可以用
FLOAT或DOUBLE。 - 一般数据 :如果有小数需求,优先选
DECIMAL。
四、字符串类型
4.1 CHAR 类型(定长字符串)
语法
sql
CHAR(L)
L:字符长度,范围 0 ~ 255。- 定长:无论实际存储多少字符,都占用指定长度的空间。
- 存储空间:L 字节(UTF-8 编码下,如果字符是汉字,实际占用 3*L 字节)。
CHAR 示例
sql
CREATE TABLE tt9 (id INT, name CHAR(2));
-- 插入英文字符
INSERT INTO tt9 VALUES (100, 'ab');
-- 插入汉字(在 UTF-8 编码下,2 个汉字占用 6 字节)
INSERT INTO tt9 VALUES (101, '中国');
SELECT * FROM tt9;
输出:
bash
+-----+------+
| id | name |
+-----+------+
| 100 | ab |
| 101 | 中国 |
+-----+------+
CHAR 的特点
- 固定空间 :
CHAR(10)永远占用 10 个字符的空间,即使只存 1 个字符。 - 查询快:因为长度固定,数据库能直接定位到数据。
- 空间浪费:如果实际数据远小于定义长度,会浪费空间。
4.2 VARCHAR 类型(变长字符串)
语法
sql
VARCHAR(L)
L:最大字符长度,范围 0 ~ 65535。- 变长:只占用实际数据所需的空间,加上 1-3 字节的长度记录。
- 存储空间:实际数据长度 + 1-3 字节。
VARCHAR 长度的计算
VARCHAR(L) 中的 L 代表字符数,但实际占用的字节数受表字符集影响:
- UTF-8 编码 :1 个字符占 1-3 字节,行长度限制 65535 字节,所以
VARCHAR(L)最大约为 65532 / 3 ≈ 21844。 - GBK 编码 :1 个字符占 1-2 字节,行长度限制 65535 字节,所以
VARCHAR(L)最大约为 65532 / 2 = 32766。
VARCHAR 示例
sql
CREATE TABLE tt10 (id INT, name VARCHAR(6));
-- 插入英文
INSERT INTO tt10 VALUES (100, 'hello');
-- 插入汉字(4 个汉字,1 个标点符号 = 5 个字符)
INSERT INTO tt10 VALUES (101, '我爱,中国');
SELECT * FROM tt10;
输出:
bash
+-----+--------------------+
| id | name |
+-----+--------------------+
| 100 | hello |
| 101 | 我爱,中国 |
+-----+--------------------+
VARCHAR 长度验证
在 UTF-8 编码下,验证 VARCHAR 的最大长度:
sql
-- 创建 VARCHAR(21845),会报错(超过限制)
CREATE TABLE tt11 (name VARCHAR(21845)) CHARSET=utf8;
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type is 65535.
-- 创建 VARCHAR(21844),成功
CREATE TABLE tt11 (name VARCHAR(21844)) CHARSET=utf8;
Query OK, 0 rows affected (0.01 sec)
4.3 CHAR vs VARCHAR
| 特性 | CHAR | VARCHAR |
|---|---|---|
| 长度 | 定长 | 变长 |
| 空间占用 | 固定,浪费 | 按需分配,节省 |
| 查询速度 | 快(直接定位) | 稍慢(需要读取长度) |
| 修改成本 | 高(可能需要重新分配) | 低 |
| 最大长度 | 255 | 65535 (实际受表字符集限制) |
如何选择:
-
长度固定且短 :使用
CHAR。比如:- 性别:
CHAR(1)或ENUM('M', 'F') - 国家代码:
CHAR(2) - MD5 哈希:
CHAR(32) - 身份证:
CHAR(18)
- 性别:
-
长度变化 :使用
VARCHAR。比如:- 用户名:
VARCHAR(50) - 邮箱:
VARCHAR(100) - 地址:
VARCHAR(200) - 备注:
VARCHAR(500)
- 用户名:
五、日期时间类型
5.1 DATE 类型
格式 :YYYY-MM-DD
存储空间:3 字节
范围 :1000-01-01 ~ 9999-12-31
示例 :2024-04-07
5.2 DATETIME 类型
格式 :YYYY-MM-DD HH:MM:SS
存储空间:8 字节
范围 :1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
示例 :2024-04-07 14:30:45
5.3 TIMESTAMP 类型
格式 :YYYY-MM-DD HH:MM:SS(与 DATETIME 相同)
存储空间:4 字节
范围 :1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC
特点:
- 自动记录当前时间戳。
- 创建记录时自动设为当前时间。
- 更新记录时自动更新为当前时间。
5.4 日期时间类型示例
sql
CREATE TABLE birthday (
t1 DATE,
t2 DATETIME,
t3 TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入数据
INSERT INTO birthday (t1, t2) VALUES ('1997-07-01', '2008-08-08 12:01:01');
SELECT * FROM birthday;
输出:
bash
+------------+---------------------+---------------------+
| t1 | t2 | t3 |
+------------+---------------------+---------------------+
| 1997-07-01 | 2008-08-08 12:01:01 | 2024-04-07 14:35:22 | -- t3 自动设为当前时间
+------------+---------------------+---------------------+
更新数据:
sql
UPDATE birthday SET t1 = '2000-01-01';
SELECT * FROM birthday;
输出:
bash
+------------+---------------------+---------------------+
| t1 | t2 | t3 |
+------------+---------------------+---------------------+
| 2000-01-01 | 2008-08-08 12:01:01 | 2024-04-07 14:36:10 | -- t3 自动更新为当前时间
+------------+---------------------+---------------------+
5.5 日期时间类型选择建议
| 类型 | 使用场景 | 说明 |
|---|---|---|
| DATE | 生日、出生日期 | 只需要日期,不需要时间 |
| DATETIME | 订单时间、发布时间 | 需要精确到秒的时间戳 |
| TIMESTAMP | 创建时间、更新时间 | 自动记录操作时间,节省空间 |
实际应用:
sql
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100),
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
published_date DATE COMMENT '发布日期'
);
在这个例子中:
created_at记录文章创建的精确时间。updated_at每次修改文章时自动更新。published_date只记录发布的日期。
六、枚举和集合
6.1 ENUM 类型(单选)
语法
sql
ENUM('value1', 'value2', 'value3', ...)
特点:
- 只能选择一个值。
- 内部用数字存储(1, 2, 3, ...)以节省空间。
- 最多 65535 个选项。
ENUM 示例
sql
CREATE TABLE users (
id INT,
name VARCHAR(30),
gender ENUM('male', 'female', 'other')
);
-- 插入时使用字符串值
INSERT INTO users VALUES (1, 'Alice', 'female');
INSERT INTO users VALUES (2, 'Bob', 'male');
-- 也可以使用数字索引(1-based)
INSERT INTO users VALUES (3, 'Tom', 1); -- 1 对应 'male'
SELECT * FROM users;
输出:
bash
+----+-------+--------+
| id | name | gender |
+----+-------+--------+
| 1 | Alice | female |
| 2 | Bob | male |
| 3 | Tom | male |
+----+-------+--------+
6.2 SET 类型(多选)
语法
sql
SET('value1', 'value2', 'value3', ...)
特点:
- 可以选择多个值,用逗号分隔。
- 内部用位掩码存储(1, 2, 4, 8, 16, 32, ...)。
- 最多 64 个选项。
SET 示例
场景:调查用户的兴趣爱好。
sql
CREATE TABLE votes (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(30),
hobby SET('登山', '游泳', '篮球', '武术'),
gender ENUM('男', '女')
);
-- 插入单个爱好
INSERT INTO votes (username, hobby, gender) VALUES ('李磊', '登山', '男');
-- 插入多个爱好(用逗号分隔)
INSERT INTO votes (username, hobby, gender) VALUES ('雷锋', '登山,武术', '男');
INSERT INTO votes (username, hobby, gender) VALUES ('Juse', '登山,武术', '女');
-- 也可以用数字表示(2的幂):登山=1, 游泳=2, 篮球=4, 武术=8
-- 登山+武术 = 1+8 = 9
INSERT INTO votes (username, hobby, gender) VALUES ('小明', 9, '男');
SELECT * FROM votes;
输出:
bash
+-----------+---------------+--------+
| username | hobby | gender |
+-----------+---------------+--------+
| 李磊 | 登山 | 男 |
| 雷锋 | 登山,武术 | 男 |
| Juse | 登山,武术 | 女 |
| 小明 | 登山,武术 | 男 |
+-----------+---------------+--------+
6.3 SET 的查询
简单查询(不推荐)
直接查询会有问题:
sql
SELECT * FROM votes WHERE hobby = '登山';
输出:
bash
+-----------+-------+--------+
| username | hobby | gender |
+-----------+-------+--------+
| 李磊 | 登山 | 男 |
+-----------+-------+--------+
这个查询只返回恰好等于 '登山' 的记录,多选的记录被漏掉了。
使用 FIND_IN_SET 函数
FIND_IN_SET(sub, str_list) 函数返回子字符串在字符串列表中的位置,如果不存在返回 0。
查询所有喜欢登山的人(包括登山和其他爱好的组合):
sql
SELECT * FROM votes WHERE FIND_IN_SET('登山', hobby);
输出:
bash
+-----------+---------------+--------+
| username | hobby | gender |
+-----------+---------------+--------+
| 李磊 | 登山 | 男 |
| 雷锋 | 登山,武术 | 男 |
| Juse | 登山,武术 | 女 |
| 小明 | 登山,武术 | 男 |
+-----------+---------------+--------+
FIND_IN_SET 详解:
sql
-- 返回 1('a' 在位置 1)
SELECT FIND_IN_SET('a', 'a,b,c');
-- 返回 0('d' 不在列表中)
SELECT FIND_IN_SET('d', 'a,b,c');
输出:
bash
+---------------------------+
| FIND_IN_SET('a', 'a,b,c') |
+---------------------------+
| 1 |
+---------------------------+
+---------------------------+
| FIND_IN_SET('d', 'a,b,c') |
+---------------------------+
| 0 |
+---------------------------+
在 WHERE 子句中使用时,FIND_IN_SET 返回非零值就表示匹配:
sql
SELECT * FROM votes WHERE FIND_IN_SET('篮球', hobby);
输出所有包含篮球爱好的人。
6.4 ENUM 和 SET 的对比
| 特性 | ENUM | SET |
|---|---|---|
| 选择数量 | 单选 | 多选 |
| 存储方式 | 数字索引(1, 2, 3, ...) | 位掩码(1, 2, 4, 8, ...) |
| 最多选项 | 65535 | 64 |
| 查询方式 | 直接相等比较 | 需要 FIND_IN_SET 函数 |
| 空间占用 | 1-2 字节 | 1-8 字节 |
| 使用场景 | 性别、状态、级别 | 权限、标签、兴趣爱好 |
6.5 ENUM 和 SET 的使用建议
使用 ENUM 的场景:
- 用户性别:
ENUM('male', 'female', 'other') - 订单状态:
ENUM('pending', 'paid', 'shipped', 'delivered') - 用户等级:
ENUM('bronze', 'silver', 'gold', 'platinum')
使用 SET 的场景:
- 用户权限:
SET('read', 'write', 'delete', 'admin') - 商品标签:
SET('新品', '热销', '折扣', '推荐') - 课程类型:
SET('视频', '资料', '作业', '讨论')
避免过度使用:
- ENUM 和 SET 虽然节省空间,但查询逻辑复杂。
- 如果需要频繁查询这些字段,考虑用关联表代替。
- 修改选项时(添加或删除选项)需要修改表结构,成本较高。
七、其他字符串类型
7.1 TEXT 类型
语法:
sql
TEXT
存储空间:最多 65535 字节(约 64 KB)
使用场景:
- 文章内容
- 评论文本
- 产品描述
- 任何超过 VARCHAR 能存储的长文本
特点:
- 不能指定长度。
- 不能有默认值。
- 查询性能比 VARCHAR 稍低(不能建立普通索引,只能全文索引)。
7.2 BLOB 类型
语法:
sql
BLOB
存储空间:最多 65535 字节
使用场景:
- 二进制数据(图片、视频、音频的二进制内容)
- 序列化对象
- 加密数据
特点:
- 用于存储二进制数据,而非文本。
- 不涉及字符集或校验规则。
八、数据类型选择指南
8.1 完整的数据类型对比表
| 数据类型 | 存储空间 | 范围 | 使用场景 |
|---|---|---|---|
| TINYINT | 1 字节 | -128 ~ 127 或 0 ~ 255 | 布尔标记、状态 |
| INT | 4 字节 | -2^31 ~ 2^31-1 | 通常整数 |
| BIGINT | 8 字节 | -2^63 ~ 2^63-1 | 用户 ID、时间戳 |
| FLOAT | 4 字节 | 约 ±3.4×10^38 | 不要用,精度不足 |
| DECIMAL(M,D) | 5-17 字节 | 精确小数 | 金额、价格(首选) |
| CHAR(L) | L 字节 | 最多 255 字符 | 固定长度(身份证、MD5) |
| VARCHAR(L) | L+1~3 字节 | 最多 65535 字节 | 可变长度(名字、邮箱) |
| TEXT | 最多 64 KB | 大段文本 | 文章、备注 |
| DATE | 3 字节 | 1000-9999 年 | 日期 |
| DATETIME | 8 字节 | 1000-9999 年 | 日期+时间 |
| TIMESTAMP | 4 字节 | 1970-2038 年 | 自动时间戳 |
| ENUM | 1-2 字节 | 最多 65535 个 | 单选(性别、状态) |
| SET | 1-8 字节 | 最多 64 个 | 多选(权限、标签) |
8.2 常见表设计示例
用户表
sql
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
email VARCHAR(100) NOT NULL COMMENT '邮箱',
password_hash CHAR(32) NOT NULL COMMENT 'MD5密码哈希',
age TINYINT UNSIGNED COMMENT '年龄',
gender ENUM('male', 'female', 'other') COMMENT '性别',
phone CHAR(11) COMMENT '手机号',
status TINYINT DEFAULT 1 COMMENT '状态:1=活跃,0=禁用',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB CHARSET=utf8mb4;
商品表
sql
CREATE TABLE products (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '商品ID',
name VARCHAR(100) NOT NULL COMMENT '商品名称',
description TEXT COMMENT '商品描述',
price DECIMAL(10, 2) NOT NULL COMMENT '价格',
stock INT DEFAULT 0 COMMENT '库存',
category VARCHAR(50) COMMENT '分类',
tags SET('新品', '热销', '折扣', '推荐') COMMENT '标签',
image_url VARCHAR(255) COMMENT '图片URL',
is_active TINYINT(1) DEFAULT 1 COMMENT '是否上架',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARSET=utf8mb4;
订单表
sql
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '订单ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
total_amount DECIMAL(10, 2) NOT NULL COMMENT '订单总金额',
status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending' COMMENT '订单状态',
payment_method ENUM('credit_card', 'paypal', 'wechat', 'alipay') COMMENT '支付方式',
delivery_date DATE COMMENT '配送日期',
remarks VARCHAR(500) COMMENT '备注',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB CHARSET=utf8mb4;
8.3 设计表时的常见错误
❌ 错误做法一:过度使用 UNSIGNED
sql
-- 不推荐
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
);
✅ 正确做法:
sql
-- 推荐
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY
);
理由:UNSIGNED 最多只能存 2^32,而 BIGINT 能存 2^63。BIGINT 不会比 UNSIGNED INT 多占多少空间,但更安全。
❌ 错误做法二:用 FLOAT 存储金额
sql
-- 不推荐
CREATE TABLE products (
price FLOAT
);
✅ 正确做法:
sql
-- 推荐
CREATE TABLE products (
price DECIMAL(10, 2)
);
理由:FLOAT 有精度问题,会导致金钱计算错误。
❌ 错误做法三:VARCHAR 长度设置太小
sql
-- 不推荐
CREATE TABLE users (
email VARCHAR(20) -- 太短,无法存储长邮箱
);
✅ 正确做法:
sql
-- 推荐
CREATE TABLE users (
email VARCHAR(100) -- 足够大,留有余量
);
理由:VARCHAR 是按需分配空间,设置大一点不浪费。
❌ 错误做法四:用 CHAR 存储变长数据
sql
-- 不推荐
CREATE TABLE articles (
title CHAR(100) -- 大多数标题远小于 100,浪费空间
);
✅ 正确做法:
sql
-- 推荐
CREATE TABLE articles (
title VARCHAR(200) -- 变长,节省空间
);
九、总结与最佳实践
现在你已经掌握了:
✅ 数值类型:TINYINT、INT、BIGINT 的范围和使用场景
✅ 浮点数:FLOAT vs DECIMAL 的精度差异,财务数据必用 DECIMAL
✅ 字符串:CHAR 定长快但浪费,VARCHAR 变长省空间但稍慢
✅ 日期时间:DATE、DATETIME、TIMESTAMP 的区别和选择
✅ 特殊类型:ENUM(单选)和 SET(多选)的查询方式
✅ 完整案例:用户表、商品表、订单表的设计示例
核心建议:
- 字符集统一用 UTF-8MB4:支持所有语言和 emoji。
- 整数用 BIGINT:避免 UNSIGNED 导致的溢出风险。
- 金额必用 DECIMAL:FLOAT 精度不足会造成严重问题。
- 字符串合理选择长度:VARCHAR 设置要足够大,CHAR 用于固定长度。
- 日期时间用 TIMESTAMP:自动记录创建和更新时间。
- ENUM/SET 要合理使用:不要过度设计,修改选项时要改表结构。
建议练习:
设计一个博客系统的数据库,需要以下表:
users:用户信息articles:文章内容comments:评论tags:标签categories:分类
为每个表设计合适的字段和数据类型。
下一篇,我们将学习表的约束:PRIMARY KEY(主键)、NOT NULL、UNIQUE、FOREIGN KEY(外键)等。约束是确保数据完整性和一致性的关键机制。