1. 约束的核心价值与设计思路先聊一个我亲身踩过的坑。那年我在维护一套订单系统咱们就叫它某订单项目吧有一张订单明细表接单后我把订单状态字段从“待审核”改成“已审核”一天后业务方过来说数据对不上了——历史订单里居然凭空多出几条“已取消”的记录最后查出来是同步程序在跑批时写错了主键关联把一个不存在的订单编号写进了明细表。表结构设计得宽业务写库完全没把关脏数据一进来后面所有统计、对账全部跟着错。那会儿我才真正意识到MySQL里头的表的约束不是考试题它是数据库的最后一道防线。约束看起来是建表时写的几行定义但它的本质是把业务校验规则从业务代码前移到了数据库引擎层让数据库替你挡掉那些“不合逻辑”的写入。你可能觉得业务代码里校验一下就行但如果几百个系统模块都直连这张表约束就是唯一能统一把关的地方。很多人觉得约束就是“非空、主键、外键”其实远不止这些。搞懂约束不仅能保证数据干净也直接决定了你建表语句的规范程度和性能上限——比如主键怎么设计会和整张表的索引树结构绑定唯一约束会隐式生成索引这些都会影响查询效率。所以下面内容不光是“教语法”更是把约束背后的设计逻辑和实战中的取舍讲清楚。2. 为什么需要约束数据完整性不是一个选项2.1 数据完整性的三个层次约束存在的根本目的可以拆成三个层次正好对应数据库教科书里常说的实体完整性、参照完整性、用户定义完整性。实体完整性指的是表和行之间的关系可靠表中每一行都应该能被唯一识别不能出现完全重复的记录。主键约束是实体完整性的直接体现就好比每辆车都有一个唯一的车牌号没有车牌号的车上不了路两张一摸一样的记录会导致查询结果分不清哪条是哪条业务上就乱了。参照完整性解决的是表与表之间的依赖问题。比如订单表和订单明细表明细表里的订单编号必须是真实存在订单表里的订单编号不能凭空造出一个指向空气的外键。外键约束就是为了保证这种“引用合法”。用户定义完整性更像是业务规则的数据库化表达。比如订单金额必须大于0用户性别字段只能是“男”“女”或“未知”库存不能为负数这些都是业务上人为定义的规则。用CHECK约束、默认值约束、非空约束去实现比在程序里到处写if判断要可靠得多。2.2 约束带来的实战优势从我个人的运维经验来看数据库层面做约束有几个程序代码校验收不到的好处。统一性所有客户端、所有接入系统共享同一套校验逻辑不会出现甲系统严格控制、乙系统随便写的情况。原子性约束是数据库自动判定的校验和写入在同一次事务内完成不存在“先检查再写入”之间的时间差。可靠性如果某天上线时不小心禁用了一段业务校验代码表结构上的约束依然默默在工作被动防御比主动校验更抗故障。性能优化主键约束和唯一约束会自动生成索引等于约束和索引一并完成了查询走索引直接受益。2.3 约束不是越多越好但这里也有一个很关键的认知约束不是越多越好加了约束就要付出代价。外键约束会让每次插入、更新额外做一次关联校验在高并发写入场景下这会放大锁的竞争。CHECK约束也会逐行校验如果一张表每分钟写入几十万行复杂的CHECK会变成明显的CPU开销。所以实战中遇到超高并发的核心流水表有些人反而主动放弃外键和复杂约束把校验下沉到服务层——这是一道需要权衡的题目下文我会更详细说。3. 约束的完整类型解析与实操要点MySQL里的约束一共有六类主流用法按我的理解它们各管一摊实际操作时选择依据完全不同。3.1 主键约束PRIMARY KEY主键约束是表的“身份认证”它保证某一列或多列的组合能唯一确定行而且不允许为NULL。主键有两大用途业务身份 物理存储定位。从存储引擎的角度InnoDB的表是聚簇索引组织表主键就是聚簇索引的入口行记录实际上按照主键顺序存储在B树的叶子节点上。这就是为什么建议主键使用自增整数而非随机字符串因为自增整数可以保证插入是顺序追加的减少页分裂而随机主键会让数据散落插入引发大量页分裂写性能会迅速劣化。实操经验里最让我反复给新人强调的是“用业务字段当主键要万分小心”。比如有人用身份证号做主键觉得全国唯一结果后来发现身份证号有升位、少数民族特殊编码、生僻字的问题改又不敢改非常难受。后来我主导过的表设计基本都采用自增整数或雪花ID作为代理主键而业务唯一性单独用唯一约束去保证两件事分开。3.2 唯一约束UNIQUE KEY唯一约束保证某个字段或字段组合不重复但允许NULL而且MySQL里NULL和NULL之间不算冲突——这是一个反直觉的地方好多人踩过坑。之所以默认很多设计会把邮箱、手机号设成唯一约束核心原因是约束会生成唯一索引对这类高频精确查询字段来说既能保证数据不乱又能加速检索一石二鸟。不过需要注意在设计组合唯一约束时NULL的处理经常埋雷。我曾经处理过一个批量导入客户会员卡号的问题数据中卡号存在NULL当时建了唯一约束后多次导入总是报“Duplicate entry”错误但查了数据明明没有重复。最后排查才发现本地库里NULL被默认处理成NULL值不算重复但某条数据从旧系统迁移时被写成了空字符串空字符串之间是会冲突的。所以如果业务明确要求“某些字段要么全部为空要么全部填且不重复”建议把空值统一转换成NULL或者用生成列唯一索引的办法规避。3.3 非空约束NOT NULL非空约束看起来最无脑删除记录后如果一个允许NULL的字段没有被程序填充实际上就会写成NULL后续统计COUNT加不加条件就全变了。比如订单金额字段如果允许NULLSUM统计时不注意会合计出NULL值非常烦人。实战里我的建议相对偏执业务上硬性要求的字段一律NOT NULL并且带默认值。用DEFAULT 0代替NULL用DEFAULT 代替空字符串用DEFAULT 1900-01-01代替出生日期为空。这样做的好处是业务代码里不用到处判null索引效率也更高因为NULL在InnoDB中还需要额外的标志位处理存NULL越多的列索引可压缩性越差。当然“NULL语义”有时也有正价值比如“补充说明”这种字段没有内容就是没有内容用NULL表示“未填写”反而是语义精确的。所以我的原则是允许NULL不是为了缓解校验压力而是为了正确表达缺失语义。3.4 默认值约束DEFAULTDEFAULT约束是为字段指定默认值插入时如果不显式给值就自动填充。直接使用并非所有场景都适合在SQL里写DEFAULT。比如订单创建时间字段通常用CURRENT_TIMESTAMP作为默认值而更新时间字段希望每次更新自动改写那就得配合ON UPDATE CURRENT_TIMESTAMP属性。这两种在数据导流、数据同步时经常给新手带来困惑——有时候程序显式写入了一个时间反而把默认机制跳过了。还有一种情况是默认值的计算时机MySQL 8.0里的DEFAULT支持表达式比如DEFAULT (UUID())在某些分布式场景下可以避免应用层生成ID时长短不一。但也要提醒默认值表达式在8.0之前不支持如果还在用老版本就别在DDL里写复杂的函数默认值不然存储过程迁移会崩。3.5 外键约束FOREIGN KEY外键约束应该是所有约束类型中最有争议性的一个。它保证子表引用的值必须存在父表内或者为NULL是关系型数据库“关系”二字在物理层的真正体现。但外键也有真实的复杂度插入子表时要检查父表存在删除父表时要处理子表数据RESTRICT、CASCADE、SET NULL的选择题高并发下外键检测与行锁的联动让数据库毛刺增多。过去这几年我观察到越来越多中大型业务系统会选择“逻辑外键”——就是表结构里不真正声明FOREIGN KEY而是在业务层保证了关联仅靠普通索引维持查询性能。那外键到底还值得用吗我的态度是分场景。对大部分中小系统的核心关联表、后台管理类业务外键是非常安全的选择数据库通过约束帮你挡住错误关联省掉大量代码。而超大规模写入、大量分库分表的场景外键的跨库关联本身就无法实现物理外键自然退出舞台。使用外键时还要考虑好父表的删除策略改起来代价不小。3.6 检查约束CHECKMySQL从8.0.16版本开始才真正强制支持CHECK约束8.0.16之前CHECK会被语法解析器接受但直接忽略——这事我补一句很多老项目在5.7时代写了CHECK结果没生效所以维护旧库时千万别理所当然认为当年建过CHECK就还有效。CHECK约束可以写出很灵活的表达式比如CREATE TABLE orders ( order_id BIGINT PRIMARY KEY, order_amount DECIMAL(10,2) CHECK (order_amount 0), order_status TINYINT CHECK (order_status IN (0,1,2)) );这样等于在数据库层完成“金额必须正数、状态必须合法”的校验而且简单高效。关于CHECK的现代用途还能配合JSON字段做深度校验比如检查JSON里的数组长度不过这种高级玩法生产环境尽量少用因为报错信息调试起来比较痛苦。下表整理了六类约束的特性对比便于挑选。约束类型作用维度是否自动建索引对写入性能影响使用频率PRIMARY KEY行唯一标识是聚簇较高主键设计差则更高极高UNIQUE KEY字段唯一是二级每次写需查重高NOT NULL非空校验否极小高DEFAULT缺省填充否极小高FOREIGN KEY跨表参照是自动建关联列索引较高关联检查中CHECK自定义业务规则否中逐行表达式校验低-中4. 实操过程从建表到修改约束的完整流程这一节我直接带你们走一遍实操会用一张典型的用户订单模型来演示如何完整地设计、添加、验证约束。建一张项目实战中的用户表、订单表、订单明细表三层关系正好覆盖主键、唯一、非空、默认、外键、检查约束六种使用。4.1 理清表结构再动手动手之前第一件事永远是把表的关系图画清楚。我经常说建表这十分钟决定未来三年的运维体验你比谁都清楚业务字段但哪些字段必须有值哪些字段可以缺省哪些字段不允许重复一定要在设计阶段和产品确认清楚。我们这次模拟一个小型电商系统的三张表用户表 user存储用户基础信息用户需要手机号唯一性别有枚举限制。订单表 orders属于某个用户的订单金额必须大于0订单状态有固定枚举。订单明细表 order_item属于某个订单每行关联商品编号。设计方案时思考几个问题主键类型选BIGINT自增还是UUID手机号要不要单独唯一索引订单金额用十进制定点数保证精度状态字段用TINYINT还是VARCHAR外键要不要建物理外键。经过权衡这里用户表和订单表之间的用户ID关联采用物理外键原因是我们希望从数据库层面直接防止订单挂到不存在的用户头上订单表和明细分表的关联虽然是核心父子关系但明细表写入频率高物理外键在这种场景下拖慢写入稳定性所以只建立普通索引属于逻辑外键。4.2 建表语句的完整演示用户表CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 用户ID自增主键, phone VARCHAR(20) NOT NULL COMMENT 手机号, nickname VARCHAR(50) NOT NULL DEFAULT 未设置 COMMENT 昵称, gender TINYINT NOT NULL DEFAULT 0 COMMENT 性别 0未知 1男 2女, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态 1正常 2禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone), CONSTRAINT ck_user_gender CHECK (gender IN (0,1,2)), CONSTRAINT ck_user_status CHECK (status IN (1,2)) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;订单表CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 订单ID, order_no VARCHAR(32) NOT NULL COMMENT 订单编号, user_id BIGINT NOT NULL COMMENT 下单用户ID, total_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 订单总金额, order_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已取消, pay_time DATETIME DEFAULT NULL COMMENT 支付时间未支付为空, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT ck_orders_amount CHECK (total_amount 0), CONSTRAINT ck_orders_status CHECK (order_status IN (0,1,2)) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;订单明细表CREATE TABLE order_item ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 明细ID, order_id BIGINT NOT NULL COMMENT 订单ID, product_id BIGINT NOT NULL COMMENT 商品ID, product_name VARCHAR(100) NOT NULL COMMENT 商品快照名称, price DECIMAL(10,2) NOT NULL COMMENT 成交单价, quantity INT NOT NULL DEFAULT 1 COMMENT 数量, subtotal DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 小计金额, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_order_id (order_id), CONSTRAINT ck_item_quantity CHECK (quantity 0), CONSTRAINT ck_item_price CHECK (price 0) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;4.3 约束设计背后的决策解释建表语句其实暗含多个刻意为之的决策我逐条说说为什么这样选。用户表用phone放唯一索引是因为手机号天然业务唯一但主键仍然是自增ID。这里体现了代理主键和业务唯一分离的原则手机号是可变业务属性虽然当下唯一但未来可能出现注销复用等状态流转不适合当主键。自增ID当主键则保证插入顺序可控最大程度保证聚簇索引顺序写减少随机I/O。订单表里外键user_id的更新策略我选了ON UPDATE CASCADEON DELETE RESTRICT。很多人不懂这里的差别RESTRICT意味着如果用户表里删除某个ID而订单表里还有这个用户的订单那么删除会被拒绝这就防止了订单变成“无主孤儿”CASCADE则说明如果用户ID被修改订单表的外键字段自动跟着变保持引用链条不断。对绝大多数业务来说这是最稳妥的搭配。反过来说如果选ON DELETE CASCADE用户注销时会把该用户所有订单一起删掉对重要的交易数据来说这种级联删除风险极大我是不会用的。订单明细表选择不建物理外键是因为它是高频插入表每条明细都要前置检查父订单是否存在会放大写入的开销。同时订单明细数据是交易快照业务上本就不允许因为父订单被删而级联删除所以逻辑外键配合普通索引idx_order_id就能真正满足业务。这个取舍本质上是基于“读写比”做的判断——明细表写入量是订单表几倍物理外键的收益不抵成本。4.4 修改与删除约束的实操语法建表时把约束规划好当然最理想但项目迭代总会有需要追加约束的时候。这一块的操作我单独说一下因为坑很多。添加主键、唯一键、外键和普通索引使用ALTER TABLE。-- 添加唯一约束 ALTER TABLE order_item ADD UNIQUE KEY uk_order_product (order_id, product_id); -- 添加外键约束 ALTER TABLE order_item ADD CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE RESTRICT; -- 添加检查约束MySQL 8.0.16 ALTER TABLE order_item ADD CONSTRAINT ck_item_subtotal CHECK (subtotal price * quantity);删除约束语法-- 删除检查约束 ALTER TABLE order_item DROP CHECK ck_item_subtotal; -- 删除唯一键 ALTER TABLE order_item DROP INDEX uk_order_product; -- 删除外键 ALTER TABLE order_item DROP FOREIGN KEY fk_item_order;这里特别提醒三个实操常识。第一ALTER TABLE加约束会锁表在MySQL 8.0里InnoDB的在线DDL支持情况其实因约束类型而异加外键和CHECK在大部分情况下允许DML并发但是在5.7及以下版本部分操作会锁表生产环境务必先估行数和低峰期操作。第二加唯一约束和主键前一定先确认现有数据没有重复值否则执行到一半直接报错回滚大表上这种失败特别浪费时间。第三MySQL里“删除主键”前如果表里还有其他索引与主键有依懒关系很可能报错所以操作前先SHOW CREATE TABLE看清依赖关系。4.5 查看约束的“体检”方法有时项目接手一张老表不知道现在这张表有什么约束怎么快速体检我推荐两个入口。一个是SHOW CREATE TABLE 表名;直接看建表语句最直观所有约束一目了然。另一个是查information_schemaSELECT TABLE_NAME, CONSTRAINT_NAME, CONSTRAINT_TYPE FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA your_database AND TABLE_NAME orders;这样能批量看到哪张表有哪些类型约束做库表梳理的报表时特别好用。information_schema里还能进一步查KEY_COLUMN_USAGE看约束具体落在哪几列上排查问题时效率会高很多。5. 约束与索引的关系一张表里的隐形性能开关这一节本来可以早说但放到这里讲才感觉顺理成章——因为操作约束时你一定会撞上索引问题。我刚开始工作时总是把约束和索引当两个独立概念后来被几次压测教育的明明白白这两者在InnoDB里就是一家人。5.1 隐式索引机制主键约束自动生成聚簇索引唯一约束自动生成二级索引外键约束自动在子表的外键列上生成索引。这个机制很多人觉得理所当然但它带来的功效和花销都需要知道。聚簇索引的主要作用不再重复唯一索引在InnoDB的二级索引结构中同时会在叶子节点中存主键值用于回表而外键自动建索引这一点特别容易被人低估因为如果子表外键列没有索引每次父表更新或删除时数据库为了检查约束就得全表扫子表那性能会是灾难。这条自动索引机制也是为什么有人会无意中发现明明建表时没加索引但user_id这个外键列查询速度还不错——这就是外键约束“顺手”给建了索引。5.2 约束命名规范和索引名冲突约束和索引共用一个命名空间的一部分比如UNIQUE KEY和普通索引名字必须唯一。日常开发最常见的报错场景是先给字段A建了一个普通索引后来想给它加唯一约束结果忘记删除旧索引ALTER加唯一键时报索引名重复。这就要严格管理命名规范业内通常这样主键PRIMARY唯一键uk_字段名普通索引idx_字段名外键fk_表名_字段名检查约束ck_表名_字段名按这个规范命名看名字就知道约束作用和归属避免撞名事故。5.3 约束带来的隐形开销与优化手段每种约束背后都有成本这里我列一个实测经验表供参考。约束类型每次写入额外开销常见性能瓶颈主键聚簇索引排序插入若页满需分裂主键随机写入导致页分裂唯一约束唯一索引查重B树查找高并发下重复值检测锁等待外键父表/子表关联查询关联表锁竞争CHECK表达式逐行求值复杂表达式消耗CPU优化方向上有一个关键技巧如果只追求唯一索引的查询加速能力而不需要“拒绝重复”的约束可以考虑用普通索引替代。比如订单表里的order_no如果业务代码已经保证不会重复数据库再建唯一约束的维护成本就属于纯开销。相反如果担心数据入口不可控那唯一约束带来的查重能力就是安全保障多花点索引维护成本也值得。这个取舍必须结合业务写入入口的可信度来判断没有放之四海而皆准的结论。实践中我还常遇到一个场景夜市高并发秒杀时用户领券记录表有唯一约束(user_id, coupon_id)瞬间并发插入相同组合会相互撞唯一索引表现为大量Duplicate entry报错但同一业务里如果同时存在“领券去重”和“高并发写入”无论怎么优化唯一索引本身撞索引问题都避免不了。这时候真正稳妥的做法是业务层先排队或削峰而不是指望MySQL在冲突时“宽容处理”。6. 常见问题与排查技巧实录这一节我把这几年在实际项目里遇到的高频问题拉个清单配上排查思路和解决方案也算是我的一份避坑笔记。6.1 Duplicate entry报错但查不到重复记录这一幕出现过太多次。明明表中没有重复数据插入时却报“Duplicate entry”。排查套路一般这样第一步确认报错的索引是哪一个报错信息会带索引名例如for key uk_phone。第二步检查是不是大小写或尾部空格问题MySQL的utf8mb4排序规则下通常不区分大小写但有些排序规则区分尾部空格也有讲究比如VARCHAR的a和a 在某些校验规则下会被认为相同。第三步检查迁移过程是否把空字符串和NULL搞混淆空字符串和NULL不冲突但两个空字符串却会冲突。第四步看是否曾经删过数据如果使用过ALTER TABLE ... DROP PRIMARY KEY后重加有时候历史幽灵记录还在得先清数据。6.2 外键插入失败Cannot add or update a child row这个报错意味着你插入子表时外键字段引用了父表里不存在的值。高发场景是分库迁移时数据没同步完子表先跑导致关联缺漏。解决思路是先检查父表对应记录是否存在SELECT id FROM user WHERE id 1056;确认不存在后要么补插父表数据要么修正子表的外键值要么专门定位这批“孤儿数据”SELECT DISTINCT o.user_id FROM orders o LEFT JOIN user u ON o.user_id u.id WHERE u.id IS NULL;找出之后再决定补数据还是修数据而不是盲目删。生产环境上碰到这类问题我特别强调先备份后修改不然待处理的孤儿数据数量大时手忙脚乱更容易出错。6.3 删除父表数据报错怎么合理处理删除父表记录报“Cannot delete or update a parent row”时首先别慌也别用禁用外键检查的方式强行删。正确顺序应该是先评估子表里有哪些关联记录需要处理。在业务层面确认子表数据该保留还是该删除。统一在事务内执行删除或改值。如果真的遇到大批量数据清洗临时禁用外键检查SET FOREIGN_KEY_CHECKS0;我基本不用在生产库上风险在于容易产生漏洞。如果实在要用也要先判断目标表没有被其他表引用否则禁完外键删数据后孤儿数据混进去就再也查不出来了。6.4 主键用完了怎么办BIGINT自增主键理论容量巨大但现实中确实见过主键接近耗尽的情况那通常是设计时用了INT或者主键没有从1递增。遇到主键快耗尽还没法改表结构时我会优先看看是不是可以重置自增值但涉及已有数据的表不能简单重置。稳妥的长期方案是在架构层做分表或者把INT换成BIGINT但这个变更在大表上成本很高。所以最初设计表结构时请务必预留余量——核心数据表直接上BIGINT不要为了省几个字节去用INT。6.5 约束冲突的快速定位SQL最后送一个排查约束问题的三连查询SQL是我自己的工具箱常驻内容。-- 查看一张表的全部约束 SHOW CREATE TABLE orders; -- 查看某个库的约束全貌 SELECT TABLE_NAME, CONSTRAINT_NAME, CONSTRAINT_TYPE FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA your_database; -- 查看某个约束对应的具体列 SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA your_database AND CONSTRAINT_NAME fk_orders_user;这套组合在手无论多复杂的库都能快速梳理清楚约束情况。7. 个人经验与设计心法文章写到这份上我把这十年里关于MySQL表约束最核心的几条心得浓缩一下不算总结就是我在实际项目里摸爬滚打留下的判断标准。第一主键设计是一票决定项。自增整数当主键仍然是70%以上业务场景的最优解那些上来就想用UUID当主键的新手往往会被随机插入导致的页分裂搞得头皮发麻。如果未来明确有分库分表需求可以用雪花算法生成的唯一ID它既有顺序性又不依赖单库自增。第二物理外键和逻辑外键要在设计文档里明确写清楚。表之间有关系但并不是所有关系都要用物理外键。我的惯例是对查询多、写入少、且数据一致性要求极高的核心关系用物理外键对写入量大、路径复杂、存在分库可能的用逻辑外键索引。这个决策一旦确定要写进团队规范否则后来维护的人会在“这张表到底有没有外键约束”上反复困惑。第三约束命名规范要从第一期开始推。我参与过的项目里约束命名混乱的表后面做数据库巡检、数据字典归档的时候痛苦指数翻倍。命名规范看起来是小事但因为后续所有脚本、报表、监控告警都可能围绕约束名做关联查询前期没有标准后期统一改命名的成本远高于你想象。第四CHECK约束千万别过度自信。MySQL的CHECK表达式支持很强但报错信息对业务方非常不友好面对一个乱码一样的报错提示你很难快速定位表达式的逻辑问题。业务规则复杂的时候一部分校验放数据库做常规防线一部分留在程序做友好提示两边配合才是最好的体验。第五也是我总放在最后说的把一个表设计出来只是起点约束始终要跟着业务演进走。上线的第三个月、第五年数据量级不同访问模式不同当年觉得完美的约束设计可能需要调整。所以要养成定时体检的习惯每个月挑一天把最近慢日志里涉及的表拿出来看看索引、约束和真实数据分布还是不是匹配。数据库设计和写代码不一样写代码可以动态部署、随时迭代物理表结构一旦约束设计失配之后每一次修改都像是在给地基换钢筋。谨慎设计、合理取舍这是我对所有正在设计新表的同行们最真诚的忠告。