开门见山说个事:标题里写“MySQL 6”,但你翻遍官方文档也找不到这个版本号。MySQL 从 5.7 跳到 8.0,根本没有 6.x 正式版。所以我更愿意把这个标题理解成“某个系列教程的第 6 章”,主题是数据库约束。如果你也是刚开始学 MySQL,或者写了好几年 SQL 但没系统整理过约束相关细节,这篇文章就是给你准备的——从概念到建表实操,再到常见报错排查,一次性把 MySQL 约束这块讲明白,顺便把 热搜里那些“mysql面试题”“mysql排序”“mysql中int+5”相关的高频问题也串进来说清楚。
1. 数据库约束到底在解决什么问题
1.1 没有约束时,数据会变成什么样
先别急着写建表语句,我们得先理解约束存在的意义。
我见过不少刚入行的开发,建表时就是一梭子字段全怼上去,不设主键、不加唯一、不管非空,结果系统上线跑了两三个月,业务方突然跑过来说“订单数据怎么查出来两万多条一模一样的记录”“用户手机号居然有人填了三个不同的号”。这类问题本质就一个:表结构没有约束,脏数据进入系统的成本为零。
举个最典型的场景:用户表。如果注册接口只是简单 INSERT 一条记录,没有任何约束,那么手机号这一列可能出现空值、重复值、纯字母串,甚至长度只有 5 位。等你做营销通知的时候,一条短信群发出去,发送失败率直接拉满。数据量小的时候这种问题看似无害,等表里积累了几十万条垃圾数据再想清洗,那个成本比当初每条插入多做一次校验要高得多。
约束的本质,就是把“数据必须符合什么规则”这个校验逻辑,下沉到数据库层。每一次 INSERT 或 UPDATE 进数据,都要过一遍数据库的“安检门”,不符合规则的直接拦下,报错给你,根本不给你写进去的机会。
1.2 数据完整性:四个层面的“规矩”
谈到约束,绕不开“数据完整性”这个概念。你可以把它理解成一张表的两条底线,数据库层面的完整性强制性,通常分为四层:
- 实体完整性:表里的每一行都要能被唯一识别。主键约束就是干这个的。
- 域完整性:某一列的数据不能突破规定的取值范围。非空约束、默认值约束、检查约束属于这一类。
- 参照完整性:一张表里写的值,必须能在另一张表里找到对应的存在。外键约束干的活。订单表的 user_id 必须出现在用户表的 id 里,这就是参照完整性的典型例子。
- 用户定义完整性:有些规则既不属于上面三类,却是业务强依赖的。比如状态字段只能取 1/2/3,价格必须大于 0,这可以通过 CHECK 约束或触发器实现。
这四个层面加起来,就构成了数据库完整性的完整闭环。约束是手段,完整性才是目的。学约束不能光背语法,得知道每种约束在守哪个门。
1.3 为什么不能只靠应用层校验
有人会说:我不加约束,自己在代码里判断不行吗?行,但请你先想清楚几个场景。
第一,应用层校验只对当前这一个应用有效。如果你的数据库不止一个入口——后台管理系统、数据维护脚本、运营临时跑一条 UPDATE、还有同事直接连库改数据——那应用层校验就形同虚设。第二,并发场景下,应用层先查再插的“先判断后写入”不是原子的。两个人同时注册一个手机号,两边都查到不存在,然后同时插入,数据库层没有唯一约束的话,两条相同手机号的数据就这样进去了。代码里加锁当然能解决,但数据库约束是最简单、最不会被绕过的那道防线。
当然,应用层校验也有它的意义:减少非法请求打到底层的次数,提升用户体验。但我不建议你拿应用层校验当挡箭牌,从而省掉数据库约束。成熟的系统设计思路是:双层校验,应用层负责体验,数据库层负责兜底。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 六大约束类型的全景拆解
2.1 主键约束与唯一约束:身份和排他性
先看一张非常简单但很常用的表结构:
sql复制CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
phone VARCHAR(20) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE,
username VARCHAR(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
主键约束 PRIMARY KEY 有三个特征,你必须记牢:唯一、非空、一张表只能有一个。它可以建立在单列上,也可以建立在多列组合上——后者叫复合主键。比如订单明细表里,订单号 + 商品序号合成一个主键,就是典型的复合主键用法:
sql复制CREATE TABLE order_items (
order_id INT NOT NULL,
item_no INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY (order_id, item_no)
);
主键和唯一约束很像,但有本质区别:唯一约束允许多个 NULL 值存在,而且一张表里可以有多个唯一约束。MySQL 的 InnoDB 引擎里,主键还会自动生成聚簇索引,表数据物理存储顺序直接挂在主键上。所以一个常见建议是:主键尽量选择单调递增的列,比如自增 ID,而不是随机字符串。因为如果主键不递增,每次插入都要移动已有数据,写入性能会明显下降。
2.2 非空约束与默认值约束:底层防线
非空约束 NOT NULL 很直白,就是不允许这一列为 NULL 值。但这里有个很容易被忽略的坑:空字符串和 NULL 不是一回事。'' 是一个合法的值,它不等于 NULL。
我实际排查过一个问题:用户身份证字段加了 NOT NULL,结果业务方传了空字符串进来,照样插入成功。后来数据统计的时候一堆空字符串身份证号,排除起来特别费劲。所以如果你真的想让某个字段“必须存在”,光靠 NOT NULL 不够,还得配合其他约束或业务逻辑。
默认值约束 DEFAULT 则是在你插入数据时没有指定值的情况下,自动填入一个预设值。这个在控制状态字段时非常好用:
sql复制CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
status TINYINT NOT NULL DEFAULT 1 COMMENT '1-待支付 2-已支付 3-已取消',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
这里的两处默认值值得留意:status 的默认值 1,表示新建订单默认是“待支付”状态;created_at 的默认值 CURRENT_TIMESTAMP,表示插入时自动记录当前时间。这种写法不仅省了应用层代码,还能避免应用层服务器时间不一致的问题——时间统一由数据库服务器生成,对后续跨时区排查更有利。
2.3 外键约束:跨表引用的“担保人”
外键约束 FOREIGN KEY 是约束里最复杂、也最容易引起争议的一个。它要求子表某列的值,必须存在于父表被引用列的值集合中,否则拒绝写入。
sql复制CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id)
);
这里 CONSTRAINT 后面的 fk_orders_user 是这个外键约束的名字,方便后续通过名字去删除或维护。
外键约束支持几种引用动作,理解它们的关系很重要:
- RESTRICT / NO ACTION:默认行为。父表删除或更新时,如果子表有引用,直接拒绝操作。这是最保守的。
- CASCADE:父表删除时,子表引用它的记录也被删掉;父表更新主键时,子表的外键值也跟着更新。
- SET NULL:父表删除时,子表外键列被置为 NULL。前提是外键列允许 NULL。
- SET DEFAULT:MySQL 的 InnoDB 引擎实际上不支持这个动作,即使写了也会被忽略。这里要记住。
实战中的取舍,我在后面实操章节会详细说。这里先明确一点:外键不是越多越好,因为它会在每次写入时额外做一次参照检查,高并发场景下确实有性能损耗。很多互联网公司干脆不在数据库层建外键,而是靠应用层逻辑保证。但我个人建议:财务、订单这类核心系统中,关键路径的外键该加还是得加,损失一点写入性能换数据一致性,值。
2.4 CHECK 约束与 int 类型的显示宽度:两个容易踩坑的点
CHECK 约束用于限制列的取值范围。比如价格必须大于 0,数量不能为负数:
sql复制CREATE TABLE products (
id INT PRIMARY KEY,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL,
CONSTRAINT chk_price_positive CHECK (price > 0),
CONSTRAINT chk_stock_nonnegative CHECK (stock >= 0)
);
这里非常关键的一个现实情况是:MySQL 5.7 之前(包括 5.7),CHECK 约束是“可以写,但会被忽略”的。什么意思?就是你建表时写了 CHECK,MySQL 不会报错,但插入非法数据时也不拦你。真正强制生效是从 MySQL 8.0.16 开始。所以我经常跟人开玩笑说,5.7 里的 CHECK 就是个安慰剂。
再来说热搜词里那个“mysql中int+5”。你大概率看到的是这两个问题之一。
一个是 int(5) 这种写法。很多人误以为 int(5) 表示这个整数最多只能存 5 个数字,这是完全错误的。int(5) 里的 5 只是“显示宽度”,和存储范围没有关系。int 类型在 MySQL 里固定占 4 个字节,无论你写 int、int(5) 还是 int(11),能存储的范围都是 -2147483648 到 2147483647。显示宽度只在配合 ZEROFILL 属性时,会自动补零到指定宽度,比如 int(5) 且开启 ZEROFILL,存 123 会显示成 00123。但对绝大多数业务来说,没有必要用 ZEROFILL。
另一个是“int 类型的字段做 +5 运算会不会溢出”。如果你执行 UPDATE 操作,把 int 列的值加 5,结果超过 int 范围上限,MySQL 的行为取决于当前的 SQL 模式。默认非严格模式下,可能会静默截断成上限值;严格模式下会直接报错“Out of range value”。所以在做数值累加的业务场景里,提前确认列类型是否够用,比事后等报错再处理要省心得多。
3. 实操:从零设计一套带约束的表结构
3.1 完整建表脚本示例
光讲概念太虚,我们直接上一套接近真实业务的建表脚本。场景是一个简化版的电商系统,涉及用户、商品、订单、订单明细四张表:
sql复制CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8mb4;
USE shop;
-- 用户表
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
phone VARCHAR(20) NOT NULL,
email VARCHAR(100),
nickname VARCHAR(50) NOT NULL,
gender TINYINT NOT NULL DEFAULT 0 COMMENT '0-未知 1-男 2-女',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_users_phone UNIQUE (phone),
CONSTRAINT uk_users_email UNIQUE (email)
) ENGINE=InnoDB;
-- 商品表
CREATE TABLE products (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
status TINYINT NOT NULL DEFAULT 1 COMMENT '1-上架 0-下架',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_products_price CHECK (price > 0),
CONSTRAINT chk_products_stock CHECK (stock >= 0)
) ENGINE=InnoDB;
-- 订单表
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
order_no VARCHAR(40) NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
status TINYINT NOT NULL DEFAULT 1 COMMENT '1-待支付 2-已支付 3-已取消 4-已完成',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_orders_order_no UNIQUE (order_no),
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id),
CONSTRAINT chk_orders_total CHECK (total_amount >= 0)
) ENGINE=InnoDB;
-- 订单明细表
CREATE TABLE order_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL COMMENT '下单时商品快照价格',
CONSTRAINT fk_items_order FOREIGN KEY (order_id) REFERENCES orders(id),
CONSTRAINT fk_items_product FOREIGN KEY (product_id) REFERENCES products(id),
CONSTRAINT chk_items_quantity CHECK (quantity > 0)
) ENGINE=InnoDB;
3.2 为什么要这样设计:处处有理由
这套设计里有很多细节,不是随手敲的,每个约束背后都有真实的考量。
users 表里,phone 和 email 都加了唯一约束。phone 是业务强唯一,必须加;email 允许用户不填,NULL 可以重复,但填了的不能重复。这里注意一个细节:唯一约束允许多个 NULL 共存,所以在 email 列上加 UNIQUE 是安全的,不会影响未填写邮箱的用户重复注册。
orders 表里的 order_no 也加了唯一约束。为什么已经有了自增主键 id,还要单独搞一个订单号?因为对外业务里,订单号经常需要给用户看、给客服查、给支付回调验,如果把自增值直接暴露出去,容易出现遍历下单记录的安全隐患。所以用 order_no 做业务主键,用自增 id 做内部主键,两条线互不干扰。
外键关系上,order_items 的 price 字段设计成“快照价格”。我特意加了一行 COMMENT 说明,这是很多人设计表时容易忽略的问题:商品价格是会变的,下单时记录快照价格,后续商品改价也不会影响历史订单的统计。
外键是否要加,这里的选择是加了。为什么?因为这个场景是电商,订单和用户、订单和明细之间的引用关系极其严格。你总不希望出现一笔订单的 user_id 在用户表里找不到记录,或者订单明细挂在一条不存在的订单上。加外键虽然会损失一点点写入性能,但换来来的是查询时敢于放心 join,不用整天担心关联键断裂。
3.3 约束的三种维护方式:建表、ALTER 添加、DROP 删除
实际项目中,约束往往不是一次性建好的。表都上线了,突然发现某个字段需要做唯一限制,这时就得用 ALTER TABLE 来动态添加约束。以下代码我用的是 MySQL 8.0 语法,5.7 里 CHECK 不会真正生效,这一点前面已提过,不再赘述。
sql复制-- 为已有表添加唯一约束
ALTER TABLE users ADD CONSTRAINT uk_users_phone UNIQUE (phone);
-- 为已有表添加外键约束
ALTER TABLE orders ADD CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id);
-- 为已有表添加 CHECK 约束(8.0.16 及以上生效)
ALTER TABLE products ADD CONSTRAINT chk_products_price CHECK (price > 0);
-- 删除约束
ALTER TABLE users DROP INDEX uk_users_phone;
ALTER TABLE orders DROP FOREIGN KEY fk_orders_user;
ALTER TABLE products DROP CHECK chk_products_price;
注意几个语法坑。
删除唯一约束用的是 DROP INDEX,不是 DROP CONSTRAINT。这是因为 MySQL 里唯一约束和唯一索引在底层是同一回事。删除外键约束要用 DROP FOREIGN KEY,但外键列上关联的索引不会自动删除,如果确认没用了,需要手动 DROP INDEX。删除 CHECK 约束用的是 DROP CHECK,紧跟约束名。
另外补充:当外键列上没有索引时,MySQL 会自动创建一个索引。这导致很多人在查看表结构时感到奇怪:“我没给它建索引,怎么多出来一个?”这就是外键的隐式索引机制,InnoDB 在处理外键关联时自动创建的,不要随便删。
3.4 用 Workbench 可视化查看和维护约束
如果你用的 Navicat 或 MySQL Workbench,维护约束比敲命令直观得多。Workbench 里右键表名选择“Alter Table”,可以看到每个字段的属性面板,Not Null、Primary Key、Unique、Default 都可以直接勾选。外键需要切到 Foreign Keys 选项卡,添加引用表和引用列。
如果你是 Windows 上刚装好 MySQL,还没配好图形化工具,我这里说个快速验证约束生效的办法:打开 Workbench,点开表结构,再执行几条明显违反约束的 SQL,看报错信息是不是和命令行完全一致。可视化工具只是方便管理,约束的校验逻辑始终在 MySQL 服务端,不会因为换工具而变化。
我自己在实际操作中发现,用 Workbench 查看外键关系时,有时表的设计面板里“Foreign Keys”是灰的。这种情况大概率是表引擎不是 InnoDB,而是 MyISAM。MyISAM 不支持外键,约束建不上。所以建表时记得指定 ENGINE=InnoDB,或者在 Workbench 的表选项里把引擎改回来。
4. 高频报错与排查技巧实录
4.1 “Duplicate entry”:唯一约束触发
这是新手最常遇到的报错之一。往 users 表插入手机号 13800138000,第二次插入时报错:
code复制ERROR 1062 (23000): Duplicate entry '13800138000' for key 'users.uk_users_phone'
排查思路其实很简单。先确认你插入的数据和表里已有数据是否真的重复了,注意查询时不要忽略空格和零宽字符。我有一次排查了半天,最后发现是应用层传过来的手机号带了一个不可见字符,表结构里看不出来,用 SELECT phone, HEX(phone) FROM users 才定位出来。
4.2 “Cannot add or update a child row”:外键校验失败
code复制ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails
这表示你插入的子表数据在父表里找不到对应记录。比如你往 orders 表插了一条 user_id = 999 的记录,但 users 表里根本没有 id = 999 的用户。
排查时先查父表里是否存在对应数据:
sql复制SELECT id FROM users WHERE id = 999;
如果确实不存在,又确实需要插入,那么要么先补充父表记录,要么检查业务逻辑为什么允许这个非法引用发生。很多时候 1452 报错是代码里先插子表、后插父表的顺序问题导致的。解决顺序问题有两条路:调整业务代码插入顺序;或者先把外键约束删掉,插入后再通过定时任务校验。
但我不推荐第二种做法——约束的价值就在于实时拦截,删掉就失去意义了。
4.3 CHECK 约束不生效:看版本说话
如果你在 5.7 里执行了包含 CHECK 的建表语句,然后插入一条 price = -1 的数据,发现竟然成功了,别慌,这不是你写错了,是 5.7 根本不检查 CHECK。
确认版本:
sql复制SELECT VERSION();
解决方式有三个方向:升级到 8.0.16 及以上;如果没法升级,在 5.7 下改用触发器来实现检查逻辑;或者把校验放到应用层。这里面我最不推荐触发器,因为触发器在批量导入数据时容易被忽略,调试也比较麻烦。
4.4 NULL 值在唯一约束和排序中的特殊待遇
先问一个问题:users.email 列有唯一约束,现在往里面插入 10 行 email 为 NULL 的数据,能不能成功?
答案:能。MySQL 认为每个 NULL 值都是不同的,所以唯一约束不会拦截 NULL 和 NULL 之间的重复。这在 oracle 的旧版行为和 MySQL 中还有细微差别,但在 MySQL 里不必纠结,记住这个结论即可。
另一个容易踩坑的点是排序:NULL 在 ORDER BY 默认升序时排在最前,降序时排在最后。这常常导致分页查询的数据“看起来顺序不对”。如果业务要求 NULL 值固定排到最后,可以这么写:
sql复制SELECT * FROM users ORDER BY ISNULL(email), email;
这里 ISNULL(email) 会把 NULL 的记为 1,排到非 NULL 的 0 后面,然后再按 email 排序,效果就是非 NULL 正常排序,NULL 压底。这是个很实用的小技巧。
4.5 面试题常问:int 类型 +5 运算与存储过程的约束问题
热搜词里出现的“mysql排序”“mysql存储过程”“mysql面试题”我也简单提一下,因为这些确实关联到了约束相关的面试考察点。
面试官问 int 类型 +5 可能溢出吗?答案要分两层说:int 固定 4 字节,范围有限,加 5 可能溢出;在严格模式下,MySQL 会报错“Out of range value for column”,非严格模式下可能静默截断。面试官更想听到你能说出“SQL 模式”这个概念,以及如何在应用层提前校验数值范围。
存储过程里怎么处理约束冲突?存储过程执行 INSERT 时如果违反约束,会直接抛异常,存储过程里可以用 DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' 这类异常处理机制来捕获。但我的实际经验是:不要把业务规则大量塞进存储过程,存储过程可维护性差,调试成本高,能用应用层和数据库约束解决的,就别加中间层。
我整理了一张速查表,方便你做线上排查时直接对照:
| 报错信息 | 常见原因 | 快速定位方向 |
|---|---|---|
| ERROR 1062 Duplicate entry | 唯一约束或主键重复 | 查看报错里的 key 名,查对应列是否重复 |
| ERROR 1452 Cannot add or update a child row | 外键关联值不存在 | 检查子表引用的值在父表是否存在 |
| ERROR 1215 Cannot add foreign key constraint | 外键建不上 | 对比关联列类型、长度、字符集是否一致 |
| ERROR 1364 Field doesn't have a default value | 非空字段未赋值 | 查看报错字段,补充值或修改表结构 |
| ERROR 1264 Out of range value | 数值超出列范围 | 检查列类型是否过小,考虑升级 BIGINT |
| CHECK 不报错但不生效 | MySQL 版本低于 8.0.16 | SELECT VERSION() 确认版本 |
5. 约束命名规范与维护心法
这个主题我单独拎出来说,是因为实际项目里,后期维护的痛感往往比前期建表更强烈。好的约束命名能让你在报错时一眼定位;差的命名会让你在几十条外键约束里翻半天。
我的建议是:主键约束用 pk_表名_字段名 风格,唯一约束用 uk_表名_字段名,外键约束用 fk_表名_父表名,检查约束用 chk_表名_字段名。这套命名规范没什么高深之处,但它能带来的直接好处是:报错信息里出现哪个 key 名,你能立刻对应到表、字段和约束类型。
5.1 约束字段的类型匹配:定时排查隐患
外键建立失败最常见的报错是 ERROR 1215:Cannot add foreign key constraint。我也踩过坑,后来总结起来主要是三个原因:
- 关联字段类型不一致。父表 id 是 BIGINT,子表 user_id 是 INT,直接导致外键无法建立。
- 字符集不一致。父表字段用了 latin1,子表字段用了 utf8mb4,MySQL 会认为两个字段无法关联。
- 父表字段没有索引。InnoDB 要求父表的引用列必须建有索引(主键本来就是索引,但如果是其他列,裸添加外键大概率失败)。
排查这种问题,最快的办法是用 SHOW CREATE TABLE 表名 查看两边的字段定义,逐项对比类型、长度、字符集。但这话说起来简单,实践里我见过最隐蔽的坑:一个表格里某个字段是 INT UNSIGNED,关联表是 INT,二者默认字符集和排序规则也不一致,报错信息一直提示无法添加外键,排查了三个小时才定位到 UNSIGNED 问题。这个经历提醒我,建表的时候统一字段类型和字符集规范,后面的维护成本会低非常多。
5.2 约束和索引的联动关系
主键自动生成聚簇索引,唯一约束自动生成唯一索引,外键会自动在子表上创建普通索引。这些联动机制让新手容易混淆:约束和索引到底是不是一回事?
我的理解是:约束是逻辑层面的规则,索引是物理层面的加速结构。二者经常配套出现,但不能完全画等号。比如你在某个字段上建了一个普通索引,它并不能保证字段值唯一;但你在字段上建了唯一约束,它就一定带着索引。理清这个关系,看 EXPLAIN 执行计划的时候会更有底气。
5.3 约束设计的三条经验
第一,主键尽量不要用业务字段。身份证号、手机号这些看起来唯一的字段,理论上能做主键,但一旦业务规则变化(比如用户注销后重新注册,手机号需要复用),业务主键会带来极强的连锁影响。自增主键还是最省心的默认选项。
第二,唯一约束和外键约束要“按需加”,不要不管三七二十一全堆上去。高并发写入场景下,索引越多,写入越慢。约束不是越多越好,合理评估业务需求的强弱再决定。
第三,上线前做一次约束审查。拿一份最近的生产环境表结构清单,逐个表检查主键是否缺失、唯一约束是否齐备、外键是否合理。这一步虽然不产生新功能,但能避开大量半夜紧急修复数据的问题。
6. 最后再分享两个实操技巧
第一个技巧:建表的时候养成写 COMMENT 注释的习惯。约束本身不会告诉你“为什么要有这个规则”,但注释可以。一年后你再看自己建的表,看到一行注释写着“status 1-待支付 2-已支付”,比对着代码逻辑猜字段含义要省事太多。
第二个技巧:如果你的项目在 Docker 里跑 MySQL,进容器查看表结构一点都不麻烦,命令是 docker exec -it mysql容器名 mysql -uroot -p,进去之后照样执行 SHOW CREATE TABLE。热搜里很多人搜 docker 安装 mysql,核心就是一条命令的问题,但需要注意 mysql 容器默认的字符集不一定是我们业务需要的 utf8mb4,启动时最好显式加上 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci,否则后续加外键、查中文时可能出现字符集不匹配的连锁问题。
我做数据库设计这段时间,最大的体会是:约束不是用来给开发添麻烦的,而是保护业务数据的一道护城河。每次看到报错,先别急着删约束,先想清楚数据为什么违规,是业务规则变了,还是数据本身就该被拦下来。这个思维习惯,往往比任何一种具体的 SQL 写法都重要。
