ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL表约束详解:从NOT NULL到CHECK,守护数据完整性

MySQL表约束详解:从NOT NULL到CHECK,守护数据完整性 1. 为什么我说“表的约束是数据合法性的保险丝”做数据库开发这几年我见过最让人头疼的不是SQL写不出来而是表结构设计得稀烂导致的脏数据。上周帮同事排查一个报表对不上的问题最后发现是用户表里手机号字段既没加唯一约束也没加非空约束光重复的号段就有几百条。MySQL 表的约束就是解决这类数据合法性问题的关键手段。很多刚学 MySQL 的朋友一听到“约束”两个字第一反应是觉得麻烦“字段还要指定能不能为空、要不要唯一这不是给自己找事吗”我当年刚接触数据库时也这么想直到在公司业务库里踩了坑被业务方追着问了好几天才彻底明白约束有多重要。约束的本质是让数据库在写入入口处就把数据质量关而不是等数据脏了再靠人肉清洗。举个生活化的例子你办一张银行卡银行系统是不是强制要求身份证号必填、手机号不能重复、卡号必须全局唯一这些规则落到数据库里就是 NOT NULL、UNIQUE、PRIMARY KEY 这些约束。没有约束的表就像没有安检的机场什么都能往里塞取数的时候才叫痛不欲生。从数据库理论角度讲约束主要保证四类完整性实体完整性每行记录都要能被唯一区分对应主键约束、唯一约束。域完整性字段值必须符合业务规则比如年龄不能为负、性别只能是男或女对应非空约束、检查约束、默认值约束。参照完整性表与表之间的关联不能“断链”比如订单表的用户ID必须在用户表里存在对应外键约束。用户自定义完整性业务自定义的规则比如“折扣价不能大于原价”通常靠 CHECK 约束配合应用层逻辑实现。还有一个重要概念需要先搞清楚MySQL 的约束分为列级约束和表级约束。列级约束直接跟在某个字段定义后面只对这一个字段生效表级约束写在所有字段定义完之后可以约束一个或多个字段的组合。比如联合主键、复合唯一键这种必须用表级约束来写。理解这个区别后面写建表语句时就不会迷茫。另外必须提醒一句MySQL 8.0.16 是 CHECK 约束的分水岭。在这个版本之前MySQL 虽然能解析 CHECK 语法但基本是“存而不验”你写了等于白写从 8.0.16 开始 CHECK 约束才真正被强制执行。后面我会专门讲这个坑现在先记住结论用 CHECK 之前先确认你的 MySQL 版本。这篇文章的目标读者我默认你已经会最基本的 CREATE TABLE 和 INSERT。如果你还在犹豫“约束到底该怎么加”这篇文章会从原理到实操告诉你每个约束的适用场景、语法细节和常见坑。2. 六大常用约束逐个拆解从定义到踩坑2.1 NOT NULL给字段立规矩NOT NULL 是最简单也最容易被忽略的约束它的含义就是“这个字段必须要有值插入时不能省略”。业务上的必填项比如用户的手机号、订单的金额、文章的标题都应该标记为 NOT NULL。语法非常简单在字段类型后面加上NOT NULL即可CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, nickname VARCHAR(50) NULL );这里有个新手特别容易混淆的点空字符串和 NULL 是两回事。NULL 表示“从未赋值”是一个未知的占位符而空字符串是一个确定的值表示“内容为空”。NOT NULL 约束只禁止 NULL并不禁止空字符串。也就是说INSERT INTO user (username, nickname) VALUES (, 小明)是能成功的因为username虽然是空串但并不是 NULL。如果你希望字段既不能为 NULL、也不能填空字符串那得靠 CHECK 约束来做二次校验。还有一点在 MySQL 中某些数据类型自带了“不能为空”的特性比如主键列而 TEXT/BLOB 等大字段类型在旧版本中甚至不允许设置默认值和 NOT NULL 组合的某些行为。实际操作中我习惯把 NOT NULL 和 DEFAULT 配合使用这样既能保证非空又能给业务留一个“默认值兜底”的通道。2.2 UNIQUE唯一性保障和它“网开一面”的 NULLUNIQUE 约束确保该列或列组合的值不重复。典型场景就是手机号、身份证号、邮箱、订单号。语法也不难-- 列级写法 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, phone VARCHAR(20) UNIQUE NOT NULL ); -- 表级写法可命名 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, phone VARCHAR(20) NOT NULL, CONSTRAINT uk_phone UNIQUE (phone) );UNIQUE 有一个特别容易踩的坑MySQL 允许在一个 UNIQUE 约束的列中插入多个 NULL 值。为什么因为 MySQL 用了“NULL ! NULL”这个逻辑NULL 和任何值比较的结果都是 UNKNOWN所以不会触发唯一性冲突。这意味着如果你建了一张用户表手机号列加了 UNIQUE 但没加 NOT NULL理论上你可以插入无数条手机号为 NULL 的记录。这在业务上可能不是你想要的效果——你以为做了唯一保障结果脏数据照样溜进来。解决方式有两个。第一个是“夫妻搭配”UNIQUE 和 NOT NULL 一起用这是最推荐的做法手机号、身份证号这类字段本来就该必填。第二个是用 “哨兵值” 代替 NULL比如业务规定未填写手机号时存0或unknown这样 UNIQUE 依然生效——但这个方法不优雅只适合无法改成必填字段的历史包袱场景。还要注意UNIQUE 约束在 MySQL 里会隐式创建一个唯一索引。所以加唯一约束不仅仅是“加规则”还会带来索引的存储和查询开销。不过这个开销通常是值得的因为唯一索引本身也能加速按该字段的查询属于一箭双雕。复合唯一约束多个字段联合唯一同样创建复合唯一索引比如订单表的(order_id, product_id)每个订单里每种商品只能出现一次。2.3 PRIMARY KEY一张表只能有一个“身份证”主键约束是每张表的灵魂。主键列的值必须非空且唯一而且一张表只能有一个主键。你可以把主键理解为这条记录在数据库里的“身份证号”。CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, product_code VARCHAR(20) NOT NULL, product_name VARCHAR(100) NOT NULL ); -- 或者用表级写法 CREATE TABLE product ( id INT NOT NULL, product_code VARCHAR(20), product_name VARCHAR(100) NOT NULL, PRIMARY KEY (id) );主键最常见的搭配是AUTO_INCREMENT自增列。自增列在插入时不指定值MySQL 会自动生成递增的数字天然保证唯一性和非空性。注意一个规则AUTO_INCREMENT 列必须是索引列通常就是主键列。如果你在其他字段上定义了自增MySQL 会报错ERROR 1075: Incorrect table definition; there can be only one auto column and it must be defined as a key。还有联合主键的概念。比如“订单商品明细表”同一订单下同一商品显然只能出现一行这时可以用(order_id, product_id)作为联合主键。联合主键本质上是一种表级约束要求这个字段组合在全表范围内不重复。实际开发中联合主键用得不算特别多因为大部分表我们都习惯用一个无意义的自增 ID 来做主键业务唯一性用 UNIQUE 约束来保证这样后续修改业务规则时更灵活。一个小建议主键字段尽量不要让业务字段来担任。比如拿身份证号做主键听起来很合理但一旦业务上出现“一个用户可以绑定两个身份证”的需求你就被迫改表结构了。用自增 ID 或者 UUID 作为主键把业务唯一性交给 UNIQUE是更稳妥的设计。2.4 FOREIGN KEY表与表之间的契约外键约束是六大约束里最复杂的一个它负责维护两张表之间的参照完整性。比如订单表里的user_id必须指向用户表里真实存在的用户不能凭空写一个 99999 的 ID否则订单就成了“无主订单”。CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES user(id) );这里有几个硬性要求不满足就会直接报错两张表都必须使用InnoDB 存储引擎。外键列的数据类型必须与引用列一致包括长度、符号、字符集都要对齐。被引用的列必须是索引列通常是主键或唯一键。MySQL 在创建外键时会自动为外键列创建索引但你也可以提前手动建好。外键还有一个重要配置参照动作也就是当父表的主键被更新或删除时子表该怎么办。MySQL 支持RESTRICT、CASCADE、SET NULL、NO ACTION四种SET DEFAULT 在 MySQL 中不支持。动作含义适用场景RESTRICT / NO ACTION父表有子记录时禁止删除/更新默认行为最安全CASCADE父表更新/删除时子表跟着更新/删除级联删除订单明细SET NULL父表删除时子表外键列置为 NULL外键列必须允许为空比如订单主表和订单明细表的场景主表订单删除时明细表数据也没意义了可以设置ON DELETE CASCADE一键清理。而某些业务场景下父表数据删除后希望保留子表记录可以把外键列设为 NULL用ON DELETE SET NULL。我见过很多团队因为“外键影响性能”而全面禁用外键这个观点我只能说部分认同。在生产环境尤其是分库分表后外键确实会带来额外检查和锁开销我也不是所有表都加外键。但如果你是一个中小型系统、单库单表外键的保证能力远大于它带来的性能损耗。我自己在基础设计中倾向于加外键等真正遇到瓶颈再评估是否移除而不是从一开始就放弃这块安全网。2.5 CHECK 约束MySQL 8.0.16 之后才真正听你的话CHECK 约束用来限制字段的取值范围属于域完整性的范畴。比如年龄必须大于等于0、性别只能是 M/F、价格不能为负数。它的语法很直观CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, age INT NOT NULL CHECK (age 0 AND age 150), gender CHAR(1) NOT NULL CHECK (gender IN (M, F)) );但这里就是最大的坑在 MySQL 8.0.16 之前CHECK 约束根本不会生效。你在建表语句里写了CHECK (age 0)MySQL 会用语法解析器收下它然后直接丢进“已读不回”列表你插入 age -1 也不会报错。MySQL 官方文档明确写过5.7 及更早版本中 CHECK 子句会被解析但忽略不强制校验。如果你用的是 8.0.16 之后的版本默认情况下 CHECK 约束会被强制执行。如果想临时“睁一只眼闭一只眼”可以在定义时加NOT ENFORCEDage INT NOT NULL CHECK (age 0) NOT ENFORCED检查约束的表达式还有一些限制不能使用存储函数、不能使用子查询、不能引用其他表的字段MySQL 8.0 支持引用同表的其他列比如CHECK (end_date start_date)这也很有用。我在订单表里就常用这种“跨列检查”比如CHECK (discount_price original_price)把业务规则直接压进数据库应用层防漏数据库兜底。2.6 DEFAULT给字段一个默认的“答案”DEFAULT 不算严格意义上的约束但它常和约束一起出现。它的含义是插入数据时如果不指定这个字段就使用默认值。CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, status TINYINT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP );这里有一个 MySQL 8.0.13 之后的增强DEFAULT 支持表达式默认值而不仅仅是字面量。比如可以给created_at设置DEFAULT (CURRENT_TIMESTAMP)可以给 JSON 字段设置DEFAULT (JSON_ARRAY())。这在 8.0.13 之前是不支持的旧版本里 JSON 字段不能设置默认值只能靠应用层写入。需要特别注意的是DATETIME 和 TIMESTAMP 的默认值。在实际建表时我经常看到有人这样写create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP的意思是这一行数据只要被更新时间字段就自动刷新为当前时间。这在记录“最后修改时间”时非常方便但也容易让人意外——你只想改个昵称结果 update_time 悄悄变了。如果业务上只需要“创建时间”千万别加ON UPDATE CURRENT_TIMESTAMP。DEFAULT 和 NOT NULL 的关系也要理清一个字段可以同时有NOT NULL DEFAULT xxx表示不允许为 NULL但允许你不传值、由默认值顶上。这种组合在业务上是最常见的比如状态字段默认值是 1用户没传状态就自动是 1。3. 实操从建表到修改约束的完整生命周期3.1 建表时定义约束列级写法与表级写法的区别先看一个完整的建表语句把所有约束类型都串一遍CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键ID, username VARCHAR(50) NOT NULL COMMENT 用户名, phone VARCHAR(20) NOT NULL COMMENT 手机号, email VARCHAR(100) COMMENT 邮箱, gender CHAR(1) NOT NULL DEFAULT M COMMENT 性别, age INT NOT NULL DEFAULT 0 COMMENT 年龄, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone), UNIQUE KEY uk_username (username), CONSTRAINT chk_age CHECK (age 0 AND age 150), CONSTRAINT chk_gender CHECK (gender IN (M, F)) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;这段 SQL 里id、username、phone、gender、age、created_at后面跟的NOT NULL、DEFAULT、AUTO_INCREMENT都是列级约束而PRIMARY KEY、UNIQUE KEY、CHECK (chk_age...)写在字段列表之后是表级约束。两者都能实现对字段的约束区别在于列级只影响单个字段书写简单但不能给约束起名字除了少数情况。表级可以给约束起名字支持复合约束更利于后续管理和排查。这里我建议凡是稍微复杂一点的约束都用表级写法并显式命名。比如CONSTRAINT uk_phone UNIQUE (phone)中的uk_phone就是约束名。约束名很重要后面要做 DDL 变更时你要靠它来定位和删除约束。如果用系统自动生成的名字一旦到生产环境找名字都找半天。3.2 用 ALTER TABLE 添加和删除约束建表之后发现约束加漏了不需要重建表用ALTER TABLE就可以动态调整。我整理了一份常用语法速查表操作语法备注添加非空约束ALTER TABLE user MODIFY username VARCHAR(50) NOT NULL;需要重写字段定义删除非空约束ALTER TABLE user MODIFY username VARCHAR(50) NULL;同上添加唯一约束ALTER TABLE user ADD UNIQUE KEY uk_phone (phone);删除唯一约束ALTER TABLE user DROP INDEX uk_phone;唯一约束对应索引添加主键ALTER TABLE user ADD PRIMARY KEY (id);删除主键ALTER TABLE user DROP PRIMARY KEY;注意处理自增添加外键ALTER TABLE orders ADD CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES user(id);删除外键ALTER TABLE orders DROP FOREIGN KEY fk_orders_user;添加检查约束ALTER TABLE user ADD CONSTRAINT chk_age CHECK (age 0);8.0.16 生效删除检查约束ALTER TABLE user DROP CHECK chk_age;有两个细节要特别提醒第一删除主键之前如果主键列是 AUTO_INCREMENT会报错。因为自增列必须依赖索引你得先把自增属性去掉比如ALTER TABLE user MODIFY id INT NOT NULL;然后再DROP PRIMARY KEY。第二MySQL 没有“直接修改约束”的语句。你想把 CHECK 的年龄范围从 150 改成 120只能先DROP CHECK chk_age再ADD CONSTRAINT chk_age CHECK (age 120)。这是一个常见误区别想着像改字段一样一条语句搞定。3.3 约束信息查询与命名规范约束加完之后怎么确认它真的存在很多人只会SHOW CREATE TABLE我个人更推荐直接查系统表既精确又适合脚本排查。查约束基本信息SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA your_db;查唯一约束/主键覆盖了哪些列SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA your_db AND TABLE_NAME user;查外键的参考表和参考字段SELECT * FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA your_db;关于约束命名我建议在团队里定一个统一规范比如主键约束pk_表名_字段唯一约束uk_表名_字段外键约束fk_表名_关联表_id检查约束chk_表名_字段命名规范最大的价值体现在生产环境出问题时。查系统表一看名字就知道这个约束是什么、管什么不用反复解析建表语句。3.4 综合案例一个订单系统的表设计最后用一个接近现实的案例把前面所有知识点串起来。假设我们要设计四张表用户表、商品表、订单主表、订单明细表。-- 用户表 CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL, status TINYINT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_phone (phone), UNIQUE KEY uk_user_username (username), CONSTRAINT chk_user_status CHECK (status IN (1, 2, 3)) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 商品表 CREATE TABLE product ( id INT NOT NULL AUTO_INCREMENT, product_code VARCHAR(30) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_product_code (product_code), CONSTRAINT chk_product_price CHECK (price 0), CONSTRAINT chk_product_stock CHECK (stock 0) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单主表 CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_orders_no (order_no), KEY idx_orders_user (user_id), CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES user(id), CONSTRAINT chk_orders_amount CHECK (total_amount 0) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单明细表 CREATE TABLE order_item ( id INT NOT NULL AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, price DECIMAL(10,2) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_item (order_id, product_id), CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, CONSTRAINT fk_item_product FOREIGN KEY (product_id) REFERENCES product(id), CONSTRAINT chk_item_quantity CHECK (quantity 0), CONSTRAINT chk_item_price CHECK (price 0) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个设计里订单明细表用UNIQUE KEY uk_order_item (order_id, product_id)保证同一个订单里同一商品不出现两次订单主表和明细表用了“强外键”加ON DELETE CASCADE删除订单时明细自动清理金额、库存、数量都加了 CHECK 防负数状态字段用 DEFAULT 加 CHECK 保证只有业务允许的取值。这样的表结构即使前端表单校验漏了、后端代码写崩了数据库这一层也能把大部分非法数据挡在门外。4. 常见问题与排查技巧4.1 外键约束创建失败多半是这几个原因外键是报错频率最高的约束。最常见的两个错误码是ERROR 1215 (HY000): Cannot add foreign key constraint和ERROR 1005 (HY000): Cant create table。真正常见的诱因有五个两张表的存储引擎不一致其中一张是 MyISAM另一张是 InnoDB。外键要求双方都是 InnoDB。字段类型不一致比如子表是BIGINT父表是INT数字范围对不上或者字符集不一致utf8mb4和latin1也是不行的。被引用列不是索引列。外键引用的父表列必须是有索引的主键天然满足。外键列和引用列的 NULL 属性冲突比如父表列是NOT NULL子表外键列却是允许 NULL 的这本身不算报错但如果设置了SET NULL而子表列不允许 NULL就会报错。引用对象不存在比如表名写错、库名写错。排查技巧先SHOW CREATE TABLE 子表名和SHOW CREATE TABLE 父表名对比引擎、字符集、字段类型再确认父表被引用列是否有索引。90% 的外键问题靠这三步就能定位。4.2 唯一约束明明加了怎么还能插入重复数据如果你发现唯一约束“没生效”先别急着骂 MySQL。唯一约束只不允许重复的“非 NULL 值”NULL 永远不算重复。比如phone VARCHAR(20) UNIQUE你可以插入三行 phone 为 NULL 的数据MySQL 一个报错都不会给。如果业务上确实要求手机号要么有值、要么只允许一个空值我一般直接用UNIQUE NOT NULL这是最省事的方案。如果历史数据里已经混入了大量 NULL需要先清洗数据把 NULL 统一改成业务约定的哨兵值比如0再补加NOT NULL约束。还有一种情况值得留意唯一约束和业务规则的时间维度经常不对应。比如用户表里手机号唯一但业务允许手机号被回收后再开放给新用户。这个需求靠普通 UNIQUE 做不了需要设计“手机号状态”的联合唯一或者把回收记录单独归档到历史表。这类问题不是 MySQL 的坑是业务建模的坑。4.3 建表时写了 CHECK为什么插入非法数据照样成功这个问题的答案我在前面已经提到了先确认 MySQL 版本是不是 8.0.16及以上。如果你用的是 5.7CHECK 约束就是废纸一张。即使版本足够新还要检查约束是否被NOT ENFORCED标记为不强制执行。从 MySQL 8.0.16 开始CHECK 约束默认ENFORCED校验生效。查询约束状态SELECT CONSTRAINT_NAME, ENFORCED FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_NAME user;如果返回NO说明这约束被暂停了。你可以在定义时改成ENFORCEDCREATE TABLE user ( age INT, CHECK (age 0) ENFORCED );最后还要注意CHECK 表达式不支持存储函数和子查询也不支持对在其他表中字段的引用。比如你想写CHECK (stock (SELECT AVG(stock) FROM product))MySQL 直接拒绝这类需求必须由应用层或触发器完成。4.4 改了字段类型约束莫名其妙消失了这是 DDL 操作里最容易“阴沟翻船”的一个点。用ALTER TABLE ... MODIFY修改字段时如果你没有把原约束重新写全MySQL 会把你没写的约束悄悄丢掉。举个例子原来字段是phone VARCHAR(20) NOT NULL UNIQUE你只想把长度从 20 改成 30于是写了ALTER TABLE user MODIFY phone VARCHAR(30);结果会怎样NOT NULL和UNIQUE全丢了。因为MODIFY的本质是“用新的字段定义整体替换旧的字段定义”你给出的新定义里没有约束那它就是没有约束。正确的写法是ALTER TABLE user MODIFY phone VARCHAR(30) NOT NULL UNIQUE;这个坑几乎每个人都踩过而且影响很隐蔽——表还在数据还在但约束静悄悄地没了。我建议在做任何MODIFY操作之前先用SHOW CREATE TABLE复制一份完整结构改了字段类型之后把约束补齐再对比一下前后差异。4.5 约束会不会拖慢写入性能会但要看你怎么权衡。NOT NULL 和 DEFAULT 基本没有性能损耗UNIQUE 和 PRIMARY KEY 每插入一行都要检查唯一性同时维护索引外键除了维护索引还要检查父表引用是否存在并可能触发锁等待。压力测试下外键确实会让写入有一定性能下降。我的观点是性能问题的优先级不应该排在数据正确性前面。你可以在读写分离、分库分表之后再考虑去掉部分外键但在单体应用阶段外键带来的数据完整性保障远大于这点性能损耗。退一步讲如果因为去掉外键导致业务出现脏数据后续排查和补偿的成本远超当初省下的那点数据库开销。5. 我的一点实操体会约束这个东西用得规矩它就是业务规则的最后一道防线用得随意它就变成线上事故的隐形入口。这些年我参与过的项目里凡是在建表阶段认真设计过约束的模块后期数据问题就特别少反而是“先建表、后补漏”的表补丁打了一堆逻辑越来越乱。如果让我给一个刚入门的朋友提建议我建议你从今天开始建表时强制自己回答四个问题这张表的主键是什么哪些字段必填哪些字段不允许重复字段的取值范围有没有需要数据库兜底的规则把这四个问题答完你的表结构质量就已经超过了大多数初学者水平。最后分享一个小技巧给约束起名时别嫌麻烦。uk_user_phone和fk_orders_user这种命名在半年后你回来看表结构时会比phone_2这种自动命名友好太多。遇到线上报错比如Duplicate entry xxx for key uk_user_phone你一眼就能知道是手机号重复了而不是再花十分钟去查这到底是个什么索引。数据合法性不是一句口号它就体现在这一条条不起眼的约束里。
返回列表