带过几次新同学、也面试过不少候选人之后,我发现一个挺有意思的现象:问起数据表的基本操作,十个人里有九个能背出 CREATE TABLE 的语法结构,但真让他们在编辑器里创建一张带完整约束设置的表,再现场完成增删改查和结构变更,多半会卡壳。不是不知道命令,而是没想清楚命令背后的设计逻辑。这篇文章就是一次关于数据表操作的完整复习梳理,覆盖创建表与约束设置、查看表结构、修改表结构、删除表这四块核心内容,既适合正在准备数据库基础考试的同学,也适合刚入行、想系统补一补表操作基本功的开发者。
1. 建表之前:别急着敲CREATE TABLE,先想清楚这几点
1.1 表是业务的映射,不是字段的堆砌
很多人建表的第一反应是“业务要存什么就加什么字段”,这是最大的误区。数据的存储本质是把业务对象的状态固化下来,你建的每一张表,背后都应该对应一个明确的主体:学生表对应学生实体,订单表对应订单实体,库存表对应库存快照。字段是这个主体的属性,而不是临时想法的堆砌。
复习数据表操作时,最值得练的思维习惯是:先写出一段业务描述,再翻译成表结构。比如“每个学生有唯一学号,姓名不能为空,性别默认为未知,出生日期可选,邮箱唯一”。这段描述翻译过来就是:
- 学生表需要一个学号字段,且要加唯一约束
- 姓名字段必须加非空约束
- 性别字段要加默认值
- 出生日期字段允许为空
- 邮箱字段要加唯一约束
你会发现,建表这件事本质上是把业务规则翻译成数据库规则的过程。约束不是数据库在找茬,而是数据库在帮你执行业务规则。
1.2 字段类型选错,后面全是泪
字段类型的选择直接影响存储空间、查询效率和后续维护成本,复习阶段最容易忽略的就是这里。
整数类型要从实际业务量级出发:年龄用 TINYINT 就够了,用户量级可能破千万的用 INT,订单号、流水号这类未来可能突破几十亿的要直接上 BIGINT。金额字段一定要用 DECIMAL,别用 FLOAT 和 DOUBLE,因为浮点数在二进制里无法精确表示十进制小数,累计对账时会出现莫名其妙的差异。字符串类型里 VARCHAR 和 CHAR 的区别也常考:VARCHAR 是变长的,适合姓名、地址这类长度不固定的内容;CHAR 是定长的,适合身份证号、手机号、固定编码这类长度恒定的内容,查询时更快但会浪费空间。
日期类型同样有讲究。DATETIME 和 TIMESTAMP 都能存日期时间,但 TIMESTAMP 有 2038 年问题,范围只到 2038-01-19,而且会受时区影响;DATETIME 范围更大,存什么就是什么。新业务建表我一般默认用 DATETIME,除非有明确的时区换算需求。
1.3 约束到底在约束谁:数据质量的准入规则
可以把约束理解成数据进入表之前的安检规则。没有安检的通道,什么数据都能进,短期看省事,长期看全是脏数据。五大基础约束要形成条件反射:
- 主键约束(PRIMARY KEY):唯一标识一行,一张表只能有一个主键
- 非空约束(NOT NULL):字段不允许为空
- 唯一约束(UNIQUE):字段值不能重复,但可以有一个 NULL
- 默认值约束(DEFAULT):不显式赋值时用预设值
- 外键约束(FOREIGN KEY):引用其他表的主键或唯一键,保证引用完整性
除了这五种,MySQL 8.0.16 之后 CHECK 约束开始真正生效,这一点后面细说。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. CREATE TABLE实战:字段定义与约束设置的组合拳
2.1 最小建表语句长什么样
复习阶段先写最小的可运行语句:
sql复制CREATE TABLE student (
id INT PRIMARY KEY,
name VARCHAR(50)
);
这张表能跑起来,但没有自增、没有非空、没有默认值,连最基本的业务规则都没体现。真正要实现上面的业务需求,需要把约束一项项加进去。我建议你从最小语句开始逐步演进,每加一个约束都想一下“这个约束对应哪条业务规则”,这样才是有效的复习而非抄写。
2.2 主键与自增:选业务主键还是代理主键
主键的选择是一个经典的复习考点。业务主键是业务里天然存在的唯一标识,比如学号、身份证号。代理主键是跟业务无关的整数自增字段,比如 id BIGINT AUTO_INCREMENT。实际项目里绝大多数表用的都是代理主键,原因很简单:业务主键一旦规则调整,牵扯面非常大。
自增主键在使用时有一个反直觉的点:删除行之后自增计数不会回退。你今天插入了 5 行,删掉最后 3 行,再插入一条新记录,id 是 6 而不是 3。这是正常的,习惯了就好,但不要在业务逻辑里依赖“id 必须连续”这个假设。
写自增列还有一个习惯要养成:用 UNSIGNED。INT 默认最大到 21 亿多,加上 UNSIGNED 能到 42 亿,成本几乎为零,收益是直接翻倍的空间。
sql复制id INT UNSIGNED NOT NULL AUTO_INCREMENT
2.3 非空、唯一、默认值:组合起来才叫约束
这三个约束单独看都简单,组合起来就考验理解了。非空约束和默认值约束常常一起出现:一个字段既 NOT NULL 又有 DEFAULT,表示“必须给值,但不给时用默认值顶上”。唯一约束和默认值一起用就要小心了,如果默认值是空字符串,而唯一约束又在同一个字段上,那么两条数据不显式赋值时,会因为“都是空字符串”而触发唯一冲突。
邮箱字段就是典型例子。业务上一个人可以没有邮箱,但如果有,邮箱不能重复。这个需求用“允许 NULL 的唯一约束”来实现:
sql复制email VARCHAR(100) DEFAULT NULL,
UNIQUE KEY uk_email (email)
这样多个用户都可以是 NULL,不会冲突,但只要有实际邮箱值,就必须唯一。
2.4 CHECK约束在MySQL里的真实情况
很多人复习 CHECK 约束时都学到过“MySQL 不支持 CHECK”,这个说法在 8.0.16 之前基本成立——语法解析会通过,但实际不生效,写入非法数据也不会报错。8.0.16 之后,MySQL 开始真正执行 CHECK 约束。
所以在当前主流版本里,性别字段可以直接写:
sql复制gender TINYINT NOT NULL DEFAULT 1,
CONSTRAINT chk_gender CHECK (gender IN (0, 1, 2))
加约束时给约束起一个有意义的名称很重要,否则报错时你会看到一堆系统自动生成的奇怪名字,根本不知道是哪条规则触发的。命名习惯一般用 chk_ 前缀加表名加字段名,比如 chk_gender、chk_age_range。
2.5 外键:用还是不用,这是个问题
外键是约束设置里争议最大的一个点。学校教材和考试题几乎无条件支持外键,因为外键能保证数据引用的完整性;但互联网大厂的实践里,物理外键用得越来越少,原因有三个:
- 高并发写入时,外键检查会带来额外的锁开销
- 分库分表之后,跨库外键根本无法实现
- 业务快速迭代时,外键约束会限制表的拆分和重构
但这不是说外键没有价值。内部管理系统、传统企业应用、数据一致性要求极高的核心表,外键仍然是一个非常可靠的兜底机制。复习阶段必须会写、会理解,实际项目里再根据场景权衡用不用。
外键的级联规则也要记牢:
- CASCADE:删除/更新父表记录时,子表关联记录一起删/改
- SET NULL:父表记录删除时,子表外键字段置为 NULL,前提是子表该字段允许为空
- RESTRICT 和 NO ACTION:默认行为,子表有关联记录时,禁止删除父表记录
一个复习时很容易踩的坑:外键引用的列,类型必须完全一致,包括 UNSIGNED。父表 id 是 INT UNSIGNED,子表 student_id 写成 INT,建表时会报错,提示类型不匹配加外键失败。
2.6 一个带完整约束的建表示例
把前面这些知识点拼成一张相对完整的学生表:
sql复制CREATE TABLE student (
id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
student_no VARCHAR(20) NOT NULL COMMENT '学号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
gender TINYINT NOT NULL DEFAULT 1 COMMENT '性别:0女,1男,2未知',
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
birth_date DATE DEFAULT NULL COMMENT '出生日期',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1正常,0禁用',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_student_no (student_no),
UNIQUE KEY uk_email (email),
KEY idx_birth_date (birth_date),
CONSTRAINT chk_gender CHECK (gender IN (0, 1, 2))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表';
这个示例涵盖了大部分核心考点:自增主键、非空、默认值、唯一约束、检查约束、普通索引、时间字段自动管理、存储引擎和字符集设置。复习时能独立写出这样一段建表语句,创建表这一关就算过了。
注意:这里的 UNIQUE KEY uk_email 允许 NULL 多次出现,因为 MySQL 的 B+ 树默认不限制 NULL 重复,这恰好符合“没邮箱的人可以有多个”的业务场景。
3. 查看表结构:DESC、SHOW CREATE TABLE与information_schema
3.1 DESC:秒出结果,但信息有限
DESC 是最常用的查看表结构命令:
sql复制DESC student;
它会展示字段名、类型、是否为空、默认值、是否主键、额外信息。优点是快捷直观,缺点也很明显:看不到表注释、字段注释、索引名称、外键定义、约束名称。复习时如果只靠 DESC 确认表结构,很多信息会被漏掉。
3.2 SHOW CREATE TABLE:逆向工程的利器
想看完整定义,用这个:
sql复制SHOW CREATE TABLE student;
它会把建表语句原样输出。好处是所有细节都在:存储引擎、字符集、注释、约束、索引、外键、自增偏移量,一目了然。日常工作中如果一个表不知道是谁建的、建了什么,SHOW CREATE TABLE 几乎是第一选择。
数据库迁移、备份恢复的脚本也经常来自这条命令的输出。复习阶段建议养成习惯:建完表后用 SHOW CREATE TABLE 检查一遍,确认自己写的约束都正确落到了表上。
3.3 information_schema:用SQL查数据字典
DESC 和 SHOW CREATE TABLE 都是面向人的,程序化地查询表结构信息则要查数据字典:
sql复制SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '你的数据库名'
AND TABLE_NAME = 'student'
ORDER BY ORDINAL_POSITION;
information_schema 库里的 COLUMNS 表记录了所有库所有表的字段级信息。这个查询在做敏感数据发现、字段规范校验、自动生成代码时非常有用。复习阶段不用背太深,但要知道有这样一个入口,它比 DESC 灵活得多。
3.4 复习自测:用查看命令检验自己建的表
一个很实用的自测方法是:建完表之后,用三种查看方式分别输出结果,比对是否一致。如果 SHOW CREATE TABLE 输出里的约束比你自己预期的多或少了,说明某个约束没写对或漏写了。特别是外键和 CHECK 约束,DESC 里根本显示不全,必须用 SHOW CREATE TABLE 才能确认。
有框架基础的同学可以顺便延伸一个场景:很多工作流引擎,比如 Flowable,第一次启动时会自动创建大量表结构。这些表的建表脚本其实就内嵌在引擎的 jar 包或配置里,这就是“建表脚本被程序化管理”的典型案例。你自己的业务表同样应该把建表 SQL 存成文件管理起来,而不是只留在图形界面的操作历史里。
4. 修改表结构:ALTER TABLE的高频用法和风险控制
4.1 ADD、MODIFY、CHANGE、DROP:四种操作要分清
ALTER TABLE 的核心操作可以分成四类,复习时最容易混淆的是 MODIFY 和 CHANGE。
- ADD:加新字段或新约束
sql复制ALTER TABLE student ADD COLUMN phone VARCHAR(20) DEFAULT NULL COMMENT '手机号';
ALTER TABLE student ADD UNIQUE KEY uk_phone (phone);
- MODIFY:修改字段类型或默认值,不改字段名
sql复制ALTER TABLE student MODIFY COLUMN name VARCHAR(100) NOT NULL COMMENT '姓名';
- CHANGE:同时修改字段名和定义,字段名是必填参数
sql复制ALTER TABLE student CHANGE COLUMN name student_name VARCHAR(100) NOT NULL COMMENT '姓名';
- DROP:删字段或删除约束
sql复制ALTER TABLE student DROP COLUMN phone;
ALTER TABLE student DROP INDEX uk_phone;
ALTER TABLE student DROP CONSTRAINT chk_gender;
CHANGE 是初学者的重灾区。它的语法要求把旧字段名重复两次,第一个是新字段名,第二个是完整字段定义,少写一个都会报错。如果不改字段名,只想改类型,用 MODIFY 更安全。
4.2 改约束的常见场景
实际业务里改约束的需求通常是这几种:
给已有字段加唯一约束,比如给手机号加唯一:
sql复制ALTER TABLE student ADD UNIQUE KEY uk_phone (phone);
要给已有字段加非空约束,需要先确认现有数据没有 NULL,否则直接执行会失败:
sql复制UPDATE student SET phone = '' WHERE phone IS NULL;
ALTER TABLE student MODIFY COLUMN phone VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号';
外键约束的修改更麻烦一些,MySQL 没有直接的 ALTER CONSTRAINT 语法,通常的做法是先删外键再加外键:
sql复制ALTER TABLE student DROP FOREIGN KEY fk_class_id;
ALTER TABLE student ADD CONSTRAINT fk_class_id FOREIGN KEY (class_id) REFERENCES class(id);
复习时有一个常见的迷惑点:为什么有的 ALTER 语句要写 COLUMN 关键字,有的不写?这个问题不用较真,MySQL 对这两个语法都接受,关键是 MODIFY 和 CHANGE 的参数位置不能乱,CHANGE 永远要把新旧字段名都写上。
4.3 为什么生产环境改表要格外谨慎
靠背命令应付考试容易,但在真实项目里,ALTER TABLE 是最容易出事的一类操作。原因在于大表结构变更的代价远超想象。
MySQL 的 DDL 操作虽然从 5.6 开始支持在线变更,但每个具体操作的开销不同。比如直接往一张千万行级别的表上加一个带非空默认值的字段,会触发全表 COPY 重建,期间需要额外的磁盘空间,写入负载过高时依然会阻塞 DML。不夸张地说,生产环境的线上大表加字段,操作不当足以让核心链路雪崩。
不只是新增字段,修改字段类型、修改字符集、加索引这类操作同样要评估。很多团队给大表做结构变更时会用第三方工具,比如 gh-ost、pt-online-schema-change,核心思路都是通过临时表 + 触发器或 binlog 同步的方式,在不停机的情况下完成结构变更。
复习阶段理解到“ALTER TABLE 不是随便点的,是有性能代价的”这一点就够了。开发环境随便操作,上生产前先找 DBA 或经验丰富的人评估一遍变更脚本。
5. 删除数据表:DROP、TRUNCATE、DELETE三兄弟的恩怨
5.1 先分清DDL和DML
删除相关的操作有三个容易混在一起的家伙,要先用一个本质概念把它们区分开:DDL 和 DML。
DROP TABLE 和 TRUNCATE TABLE 都是 DDL,操作完了隐式提交,没得回滚。DELETE FROM 是 DML,在 InnoDB 下可以放在事务里,想撤销还能 ROLLBACK。
区分 DDL 和 DML 看起来是基础概念,但在删除操作上,这个区别直接决定“能不能后悔”,怎么强调都不过分。
5.2 三个操作的核心区别对照
| 对比项 | DROP TABLE | TRUNCATE TABLE | DELETE FROM |
|---|---|---|---|
| 操作类型 | DDL | DDL | DML |
| 删除内容 | 表结构 + 数据 + 索引 + 约束 | 只清数据,保留表结构 | 按条件删数据,保留表结构 |
| 是否可回滚 | 不可 | 不可 | 事务内可回滚 |
| 是否可加 WHERE | 不能 | 不能 | 能 |
| 自增计数 | 表没了 | 重置 | 不重置 |
| 速度 | 最快 | 快 | 慢,逐行删 |
| 触发器 | 不触发 | 不触发 | 触发 |
DBA 界流传着一个说法,“删表速度:DROP > TRUNCATE > DELETE”,但实际选择顺序取决于业务意图。彻底不要这张表了用 DROP;想保留表结构、快速清空所有数据用 TRUNCATE;只删一部分数据或者希望保留回滚能力用 DELETE。
5.3 删除前必须做好的几道保险
复习阶段删的是测试表,随便删无所谓。在真实项目里,删表结构前的检查清单必须走一遍:
- 确认这张表有没有被其他表外键引用,先处理引用关系
- 确认有没有下游任务依赖这张表的数据,比如报表、接口、定时任务
- 确认备份是否已经拉到本地或对象存储
- 确认删除窗口是否在低峰期,以及是否已经通知到相关同事
一条非常实用的保命技巧:删表之前先执行一次 SHOW CREATE TABLE,把输出存成 .sql 文件。一旦发现删错了,至少可以原样重建表结构。数据层面的恢复则需要依赖备份,如果没有备份却直接删了表,谁也没办法。
5.4 外键关系下的删除顺序
删除有关联的表时,顺序很重要。外键约束默认是 RESTRICT 语义,有子表引用时直接删父表会报错:
sql复制DROP TABLE parent_table;
错误信息一般是“Cannot delete or update a parent row: a foreign key constraint fails”。这时候要么先删子表,要么临时禁用外键检查:
sql复制SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE parent_table;
SET FOREIGN_KEY_CHECKS = 1;
注意,SET FOREIGN_KEY_CHECKS = 0 只对当前会话有效,不会影响其他连接。用完记得恢复为 1,不然当前会话后续的写入都不会做外键校验,容易产生脏数据。
提醒:TRUNCATE 在子表存在外键引用时也会失败,即使子表是空的。MySQL 不允许对外键约束的表执行 TRUNCATE,这是它与 DELETE 的一个显著差异,复习时特别容易忽略。
6. 复习实操:在Workbench里用命令行把表操作练熟
6.1 一套完整的复习操练流程
前面讲的知识点,最终要通过一套完整的实操流程串起来。我建议按这个顺序练:
- 先写业务描述:比如“一个班级有多个学生,学生的学号唯一,姓名必填,成绩在 0 到 100 之间”
- 根据描述写 CREATE TABLE,建两张表(class 表和 student 表),student 表用外键关联 class 表
- 用 DESC、SHOW CREATE TABLE、information_schema 三种方式核对表结构
- 插入正常数据、插入违反约束的数据,观察报错信息
- 用 ALTER TABLE 完成加字段、改类型、改字段名、删字段四个操作
- 分别使用 DELETE、TRUNCATE、DROP 操作,观察自增计数和表结构的变化
这套流程全程在 MySQL 命令行或 Workbench 的 SQL 编辑器里完成,不碰图形化建表工具。
6.2 为什么建议你用命令行而不是点点点
MySQL Workbench 的图形化建表界面很友好,拖拖拽拽就能建表,但它有一个问题:操作过程不会自动给你留下一个可复用的脚本文件。图形界面点出来的表,想复制到测试环境、想纳入版本管理,都得再反向生成一遍 SQL。
命令行或者 SQL 脚本方式则完全不同。你写的每一句 CREATE TABLE、ALTER TABLE 都能保存成文件,下次执行就是批量操作。实际项目里,数据库结构变更发起的评审,评审的就是这些 SQL 脚本,而不是某个人在客户端里操作了哪些步骤。
对复习阶段的人来说,用命令行的好处是强迫自己把语法从“看到能认识”变成“闭上眼能写出来”。图形界面点选永远代替不了自己敲一遍的肌肉记忆。
6.3 建表脚本的版本管理习惯
最后补一个很多人前期意识不到、后期一定会后悔的习惯:建表脚本一定要纳入版本管理,跟代码一起提交。
项目里常见的做法是建一个 sql/ 目录,按时间或版本号组织:
code复制sql/
V1.0__create-class-and-student.sql
V1.1__add-phone-to-student.sql
V1.2__add-uk-phone.sql
有人会问,表结构已经有 information_schema 了,为什么还要单独存脚本?因为 information_schema 记录的是当前状态,而脚本记录的是变更历史。当前状态只能回答“现在长什么样”,版本脚本能回答“每个版本到底是哪一步改的、为什么这么改”。
我之前接过一个老项目,数据库里加字段没有留任何脚本记录,结果排查某个字段是什么需求加的、加的时候有没有处理存量数据,全靠翻 git 提交记录和群聊记录,非常痛苦。从那之后,我带团队的第一条数据库规范就是“建表和改表的 SQL 必须随代码一起 Review、一起合入”。
复习阶段就可以开始养成这个习惯,哪怕只是在本地随便建个 sql 目录,把练习用的建表语句按日期存一份,也是在为未来真实项目的开发方式做预演。
我个人练这一套的时候,最大的体会是:数据表的操作本身不难,难的是理解每一个语法选择背后的代价规则。建表时一个 UNSIGNED 的遗漏、一个外键类型不匹配、一个 MODIFY 和 CHANGE 的混淆,单拎出来都是小问题,但在真实的项目环境里,这些都是能让人折腾到半夜的排查线索。所以复习别只盯着语法背,多动手敲、多故意写错几条语句看报错、多对比几种查看方式的差异,基础会比背十遍书牢固得多。
