最近看到一串 MySQL 相关的热搜词,不少人在搜“mysql中int+5”、“mysql的or能去重吗”、“mysql自动忽略大小写”这类问题。说实话,这些问题单独拿出来都很简单,但连在一起看就很有意思——它们背后指向的都是同一个根基:对表本身的理解。表是 MySQL 里真正的核心容器,库只是目录,索引、约束、数据最终都落在表上。建表、看表、改表、删表,再加上对表内数据的增删改查,这些“基本操作”如果只是背语法,遇到实际问题一样会翻车。
这篇文章我就用一套“学生选课成绩”的模拟场景,把 MySQL 表的基本操作完整走一遍。不光是 CREATE TABLE 和 ALTER TABLE 的语法,还会解释每一步为什么这么选、哪些细节容易踩坑,顺便把热搜里那些零散问题都串起来。适合刚学完 SQL 基础、准备正经做数据建模的初学者,也适合写过一段时间 SQL 但没系统整理过表设计细节的同学。
1. 建表之前:先想清楚业务实体,再动手写 CREATE TABLE
很多新人最大的问题是拿到需求就敲键盘,表结构下意识列几个字段就完事。但表是业务的投影,字段类型、约束、索引设计得不好,后面每一次查询都在为当时的草率买单。所以建表之前,先把业务里有哪些“实体”、实体之间什么关系搞清楚,这一步比 SQL 语法重要十倍。
1.1 从一张学生成绩表的需求说起
假设要做一个最简单的学生选课成绩库,需求大概是这样:学校有学生,学生有学号、姓名、性别、出生日期、入学日期;学校开设课程,课程有课程号、课程名、学分、任课教师;学生选了某门课之后,会产生一个成绩,还要记录考试日期。
如果只图省事,很可能会建一张大宽表,把所有东西塞进去:
sql复制CREATE TABLE student_score (
student_no VARCHAR(20),
student_name VARCHAR(64),
course_no VARCHAR(20),
course_name VARCHAR(100),
credit DECIMAL(3,1),
teacher VARCHAR(64),
score DECIMAL(5,2),
exam_date DATE
);
这种表在 demo 阶段没毛病,查起来甚至很爽,一个 SELECT 什么都出来了。但它经不起真实业务推敲:一个学生选了 10 门课,学生姓名、性别这些信息就要在这张表里重复出现 10 次,这就是数据冗余;哪天学生改了个名字,你得 UPDATE 这张表的每一行,漏掉一行就出现数据不一致。于是就有了关系数据库最基本的设计思路:把不同类型的实体拆成不同的表,用主键唯一标识一行,用外键或逻辑关联把表连起来。
同样的需求拆成三张表,就会清爽很多:
- student:学生基本信息,一行代表一个学生。
- course:课程基本信息,一行代表一门课程。
- score:学生与课程的成绩关系表,一行代表“某个学生考了某门课的成绩”。
这就是学生选课场景里最经典的模型,也是热搜里“学生课程成绩信息实体表设计”那个词背后真正的答案。表不是越少越好,也不是越多越好,而是每一张表都只表达一个清晰的主题。
1.2 存储引擎与字符集:一开始选错,后面迁移很痛苦
表结构设计里有两个最容易被忽略的全局选择,一个是存储引擎,一个是字符集。
存储引擎强烈建议用 InnoDB,理由很实际:支持事务、支持行级锁、支持崩溃恢复。MyISAM 在很多老教程里还会出现,但它的表级锁在并发写入场景下非常吃亏,而且不支持事务,数据一致性没有保障。现在 MySQL 8.0 里 InnoDB 已经是默认引擎,但建表时明确写出来是个好习惯,至少别人看你的建表语句时不会被当前环境的默认配置误导。
字符集这一块,我见过太多历史包袱。早年建库用 utf8,结果插入 emoji 或者一些生僻字直接报错,因为 MySQL 的 utf8 最多只支持 3 字节,真正的四字节字符存不进去。正确选择是 utf8mb4,它才是完整的 UTF-8 实现。如果是 MySQL 8.0,排序规则可以直接用默认的 utf8mb4_0900_ai_ci,这个 collation 支持 Unicode 9.0,比较规则也比较合理;如果是 5.7 或更早,建议用 utf8mb4_general_ci 或 utf8mb4_unicode_ci。
这里的 _ci 后缀代表 case insensitive,也就是比较时忽略大小写。这会导致一个很多人困惑的现象:为什么 WHERE name = 'zhang' 能查出 ZHANG?因为 utf8mb4_0900_ai_ci 本身就不区分大小写。如果你需要严格区分,就得用 utf8mb4_bin 或 utf8mb4_0900_as_cs 这类排序规则。这就是热搜里“mysql自动忽略大小写”的根源,不是 MySQL 抽风,是你在建库建表的时候选了个不区分大小写的 collation。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. CREATE TABLE 落地:字段类型与约束决定表的性能上限
设计完模型,接下来就是把模型翻译成 DDL。这一阶段最常见的误区,集中在数值类型、字符类型和约束三个方面。
2.1 int(5) 到底是什么意思:一个热搜词的典型误解
热搜里有“mysql中int+5”,我猜很多人想问的是 int(5) 这个写法。先说结论:int(5) 里的 5 不是存储长度限制,也不会限制你存多大的数字。
MySQL 整数类型占用的字节数是固定的,INT 永远占 4 字节,取值范围是 -2147483648 到 2147483647,无符号的话是 0 到 4294967295。括号里的数字只是“显示宽度”,主要配合 ZEROFILL 使用。比如 INT(5) ZEROFILL 存 42,查询出来显示 00042,但存储的还是整数 42。这个显示宽度在 modern MySQL 8.0 里已经逐渐被废弃,新代码里我建议直接写 INT,不要写 INT(5)。
对应到热搜“int+5”还有一个方向理解:更新语句 UPDATE student SET age = age + 5 WHERE id = 1,这种情况下要考虑的是整数溢出。比如一个 TINYINT 字段最大值 127,你给它加 5 就可能报 Out of range value 错误。所以选择整数类型时,要根据业务数据的量级预留足够余量:状态码用 TINYINT,年龄、数量用 INT,如果可能超过 21 亿,比如评论数、流水号,就要直接上 BIGINT。
我把常用的整数类型列个表,方便你一眼看清边界:
| 类型 | 字节数 | 有符号范围 | 无符号范围 | 典型用途 |
|---|---|---|---|---|
| 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 | -9223372036854775808 ~ 9223372036854775807 | 0 ~ 18446744073709551615 | 大流量主键/流水号 |
2.2 字符类型与小数精度:VARCHAR、CHAR、DECIMAL 怎么选
字符类型上,最常用的就是 VARCHAR 和 CHAR。VARCHAR 是变长的,存多少用多少,额外加 1 到 2 字节记录长度,适合姓名、邮箱、地址这类长度参差不齐的字段;CHAR 是定长的,适合长度固定且短的字段,比如手机号、MD5 后的哈希值。这里要纠正一个旧观念:VARCHAR 不会因为“预留了 100 个字符”就固定占用 100 个字符的空间,它只按实际内容占空间,所以不用太抠门,但也别一上来就 VARCHAR(5000)——索引是有长度上限的,InnoDB 的单列索引在 utf8mb4 下最大不超过 3072 字节,大字段做索引会非常占空间。
小数类型更需要谨慎。FLOAT 和 DOUBLE 是浮点数,会有精度误差,不
