SQL约束实战指南:从数据完整性到防重防脏的完整设计
作为常年跟SQL打交道的人我翻看自己的笔记时发现“约束”这一章被画满了记号。很多初学者觉得约束不过是建表时顺手写的几个单词实际上一旦数据量上来、业务逻辑变复杂约束设计得好不好直接决定你是优雅地维护数据还是天天在深夜补数据、删重复记录、对账对到怀疑人生。这篇笔记我就把自己在实际项目中关于SQL约束的思考、踩坑和调试经验完整梳理一遍从最基础的概念到容易翻车的细节再到实用的运维查询尽量写得让你能直接照着用。先给个整体定位约束是数据库保证数据完整性、一致性的第一道防线也是在应用层代码之外最便宜、最可靠的守门员。它能帮我们挡掉重复数据、非法值、悬空引用等大多数脏数据问题。适合刚学完增删改查想进阶的开发者、正在设计表结构的后端工程师以及那些被线上脏数据折磨到想重构的运维同学参考。1. 约束到底是干什么的数据完整性的四道防线聊约束之前得先统一认知数据库里的脏数据不是靠运气避免的是靠机制。约束就是那套机制它的本质是“规则前置”——在你往表里写入数据之前数据库先帮你把不符合规则的数据拦下来。如果把一张表比作一个小区的门禁系统约束就是每一道门的安保规则你是谁、能进哪个区域、随身携带的东西是否符合规定全部要过检。我在实际项目里最深的体会是约束不只是给数据库看的更是给团队所有人看的“契约文档”。你写下一个NOT NULL等于告诉所有后来者这个字段必须有值业务逻辑里不允许空着你写下一个UNIQUE等于声明这里不允许重复任何试图插入重复值的操作都是非法的。这种契约如果靠开发者在应用层各自判断很容易漏而且口径不统一。数据库层面的约束是集中式的强制规则谁来了都得守。常见的约束体系可以分成四类对应四类最典型的数据完整性问题第一类是非空约束解决的是“字段缺失”的问题。比如用户表的手机号订单表的订单号这些字段一旦为空下游统计、关联查询全都会出乱子。空值在SQL里是个很特殊的存在它不等于0也不等于空字符串它表示“未知”。未知值参与计算时会把结果也变成未知经典的三值逻辑让很多新手栽跟头。非空约束就是直接从源头禁止这种不确定性进入核心字段。第二类是唯一约束解决的是“数据重复”的问题。用户身份证号、商品条码、流水单号这些业务上必须唯一的字段光靠应用层查询再插入是防不住并发的——两个请求同时判断“不存在”然后同时插入就重复了。唯一约束让数据库在索引层面直接拒绝重复键这才是真正可靠的兜底。第三类是主键约束它其实是“非空唯一”的组合但它还有另外一层更重要的使命确立记录的身份标识。主键是每一行数据的身份证号它不仅能防止重复还是外键引用的锚点。没有主键的表就像一个没有门牌号的房间别人想引用你都找不到坐标。第四类是外键约束和检查约束它们守护的是“引用完整性”和“域完整性”。外键保证子表里的引用不会指向不存在的父记录检查约束保证字段取值落在一个合理的范围内比如年龄不能为负数、状态码必须在一个枚举集合里。这两类约束是业务规则最直接的数据库表达。想清楚这四道防线你就明白约束设计不是写代码而是在画业务边界。每一张表、每一个字段都应该问一句这里的合法数据是什么边界在哪里边界全部定清楚表结构才谈得上稳定。2. 六大约束逐一拆解语法、场景与设计心法约束的语法不难难的是知道什么时候该用哪一个、怎么用才不给自己挖坑。我按实际项目中使用频率从高到低把六大约束逐个过一遍每个都会带上具体场景、写法和容易忽视的细节。2.1 非空约束NOT NULL最便宜的数据质量保险写法很简单建表时字段后面跟NOT NULL或者用ALTER TABLE来修改CREATE TABLE users ( id INT NOT NULL, nickname VARCHAR(50) NOT NULL, phone VARCHAR(20) -- 允许为空 ); ALTER TABLE users ALTER COLUMN nickname VARCHAR(50) NOT NULL;这里有个值得注意的设计取舍什么样的字段才应该设为非空我的原则是“凡是在业务逻辑里被当作关键条件、展示主信息、参与计算或关联的字段都必须非空”。比如订单表的order_no、支付表的transaction_id、用户表的account这些字段空了整行数据就没有意义。但也有故意留空的场景。比如用户表的phone字段如果业务允许用户跳过绑定手机号那它就不该是非空。很多人一上来把所有字段都设为非空结果插入数据时处处报错最后不得不填一堆占位符反而制造了另一种脏数据。我的经验是非空约束要跟着“业务必填项”走而不是跟着“字段存在感”走。还有一个小坑NOT NULL和默认值经常搭配使用。如果一个字段既要必填又要简化插入逻辑就可以给它设一个DEFAULT比如状态字段默认0创建时间默认CURRENT_TIMESTAMP。这样保证了非空又不用每次插入都显式赋值。2.2 唯一约束UNIQUE并发防重的唯一可靠手段唯一约束在业务上的常用场景包括身份证号、邮箱、手机号、订单号、商品编码等。写法有两种列级和表级CREATE TABLE employees ( id INT PRIMARY KEY, id_card VARCHAR(18) UNIQUE, -- 列级唯一约束 email VARCHAR(100), CONSTRAINT uk_emp_email UNIQUE (email) -- 表级唯一约束可命名 );如果是多个字段组合起来才要求唯一比如“同一个用户对同一商品只能有一个购物车记录”就需要表级组合唯一约束CREATE TABLE cart ( user_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, CONSTRAINT uk_cart_user_product UNIQUE (user_id, product_id) );这里要特别提醒一个和NULL相关的经典问题在多数关系型数据库中唯一约束允许出现多个NULL值。比如email设了唯一约束但允许用户不填邮箱那么两行的email都为NULL时不会被当作重复。这个行为初看很反直觉但仔细一想也合理——NULL表示未知两个“未知”到底是不是同一个“未知”数据库选择放行是为了不误伤合法数据。如果你需要的是“邮箱不管填没填都不能重复”那就得另想办法比如用生成列加上过滤索引或者在应用层做判断。大多数业务场景其实接受“NULL可以多条”这个行为所以我在设计时一般不折腾但只要业务上明确要求“要么为空要么唯一”就要提前意识到这个细节。唯一约束的性能影响也值得说一句它是通过唯一索引实现的所以插入时会增加索引维护的开销。对高频插入的表索引越多写入越慢这是必然的权衡。但优先保证数据正确性更重要性能优化可以通过批量插入、分区表等手段来对冲。还有一个容易被忽略的点唯一约束要起名字尤其是表级约束。起名规则我一般统一采用uk_表名_字段名例如uk_cart_user_product。别小看命名的事后期排查问题、写迁移脚本时没有名字的约束在数据库系统表里会生成一堆随机ID处理起来非常痛苦。2.3 主键约束PRIMARY KEY表设计的定海神针主键约束是唯一约束加上非空约束的组合但它承担了更重要的角色记录的唯一身份标识。每张表都应该有主键这话我说了很多遍但还是见到不少表设计成“无主键的堆表”后果是数据重复时连精确定位删除都做不到只能靠ROW_NUMBER()窗口函数去重才能收拾残局。主键的写法很简单CREATE TABLE orders ( id BIGINT PRIMARY KEY, order_no VARCHAR(32) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 也可以在表级声明复合主键 CREATE TABLE order_items ( order_id BIGINT NOT NULL, line_no INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, PRIMARY KEY (order_id, line_no) );主键选择上我的建议分两种情况。一种是单表单机场景优先用自增整数或者雪花ID充当代理主键。自增整数简单高效索引紧凑写入性能好分布式场景则用雪花ID之类的应用生成ID避免自增冲突。另一种是业务上确实存在天然唯一标示的组合比如订单明细里的(order_id, line_no)可以设计成复合主键因为这组字段本身就是业务上的记录身份。关于主键的生成策略《阿里巴巴Java开发手册》里有一句话我很赞同在数据表结构设计中主键字段最好与业务字段解耦。什么意思就是不要拿手机号、身份证号这些真实业务标识做主键。因为业务标识是会变的——手机号可能换身份证号也可能因为录入错误被修正。一旦主键值要更新所有引用它的外键都要跟着改那就是一场灾难。所以我的铁律是主键用和业务无关的代理键业务唯一性交给唯一约束去保证。复合主键也不是完全没有代价。它会让所有引用该表的外键都变成复合外键关联查询时条件更繁琐索引也会更大。所以能用单列代理主键解决的就别用复合主键除非复合键确实就是记录不可再分的身份单元。2.4 外键约束FOREIGN KEY关系型数据库的灵魂外键是关系型数据库区别于一般键值存储的关键能力。它的作用是让子表中的字段值必须精确匹配父表中的主键或唯一键值从而保证引用完整性。举个最常见的例子订单明细表里的product_id必须能在商品表里找到否则这张订单里的商品是“幽灵商品”后续库存、账单全对不上。建表时外键的写法CREATE TABLE order_items ( id BIGINT PRIMARY KEY, order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, quantity INT NOT NULL, CONSTRAINT fk_oi_order FOREIGN KEY (order_id) REFERENCES orders(id), CONSTRAINT fk_oi_product FOREIGN KEY (product_id) REFERENCES products(id) );外键约束最考验设计能力的部分是删除时的行为策略有三种常用选项RESTRICT/NO ACTION父表记录被删除时如果子表还有引用则删除失败。这是最保守的方式杜绝孤儿数据。CASCADE父表记录删除时子表相关记录一并删除。适合订单和订单明细这种整体生命周期绑定的关系。SET NULL父表记录删除时子表引用字段置空。适合“商品被删除但订单里的历史信息需要保留”的场景。我自己的实践经验是是否真的要在数据库层面启用外键要看团队规模和系统复杂度。在互联网高并发场景下很多团队会选择在应用层维护“伪外键”——也就是表中存着product_id但不在数据库层面加FOREIGN KEY。原因有几个高频写入时外键检查会带来额外的锁和开销分库分表后外键约束在分布式环境下很难全局生效业务逻辑里已经做了完整性校验再把校验放在数据库里显得重复。但是如果系统并发量并不大、数据准确性要求特别高比如财务、订单、医疗、政务类系统我强烈建议启用外键。它会实实在在阻止一大批数据错误省掉你无数排查脏数据的夜晚。有时候我跟人讨论到这儿对方会说“外键影响性能所以我们不用”但等线上出现了订单关联不存在的商品的脏数据再回头看那点性能开销可能远小于数据修复的人力成本。如果你因为特殊原因没有启用外键那我至少建议你建好索引。外键字段如果没有索引关联查询时父表子表来回扫描性能会差得离谱。即使不加外键约束order_id、product_id上也要建索引这个是底线。2.5 检查约束CHECK把业务规则搬进数据库检查约束用来限定字段的取值范围比应用层if判断更防漏。比如年龄不能为负、折扣不能大于100%、订单状态只能取指定枚举等。写法CREATE TABLE employees ( id INT PRIMARY KEY, age INT NOT NULL, salary DECIMAL(10,2) NOT NULL, status VARCHAR(20) NOT NULL, CONSTRAINT chk_age_range CHECK (age 18 AND age 65), CONSTRAINT chk_salary_positive CHECK (salary 0), CONSTRAINT chk_status_valid CHECK (status IN (ACTIVE, INACTIVE, LEFT)) );检查约束的最大价值在于它把业务规则下沉到了数据库任何入口的非法数据都进不来——不管是管理后台、报表导入还是直接连数据库跑的脚本。这就避免了“历史遗留脏数据”的经典问题老系统没有校验后来接的新系统做了一堆防御还是挡不住脏数据通过旧入口流入。我遇到过的一个典型场景某系统的订单金额字段业务上要求必须大于零但旧代码里没有校验结果半年后报表里出现了一条金额为-50的异常订单。后来排查发现是一个批处理脚本少写了负号判断。加上CHECK (amount 0)之后这种错误从源头就被拦截了。所以我说检查约束是“比你更早发现bug的哨兵”。要注意的是检查约束在MySQL 8.0.16之前的版本中会被解析但实际不生效这一点让很多人踩过坑。如果你在用MySQL要先确认版本别以为加了约束就万事大吉。SQL Server、PostgreSQL、Oracle的检查约束都是正常强制的。还有一个灵活用法检查约束可以用来实现“逻辑上互斥”的校验。比如“如果订单是线下支付则必须有支付时间不能为空如果是线上支付则必须填写支付平台流水号”这种复杂性完全可以用两三个CHECK组合出来把应用层复杂的if判断变成数据库的声明式规则。2.6 默认值约束DEFAULT让字段自己照顾自己严格来说默认值不算标准的“约束”分类但它跟约束的配合非常紧密能减少插入时的遗漏问题也能配合非空约束控制字段合法性。我常常把它当成“兜底约束”来看。常见的默认值写法CREATE TABLE users ( id INT PRIMARY KEY, nickname VARCHAR(50) NOT NULL, points INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP );默认字段最适合放这些值计数类字段积分、次数、库存初始值、状态类字段启用/禁用、时间类字段创建时间、更新时间。有了默认值INSERT语句可以少写一半字段代码也更简洁。这里有一个非常实用的小技巧尤其适用于SQL Server场景用NEWID()或NEWSEQUENTIALID()作为uniqueidentifier类型字段的默认值生成GUID主键。很多人在热词里搜“sql 默认值guid”实际上就是想解决分布式系统中自增主键冲突的问题。在SQL Server里可以这样建CREATE TABLE tokens ( id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(), token_code VARCHAR(64) NOT NULL, created_at DATETIME DEFAULT GETDATE() );NEWID()生成的GUID是完全随机的适合作为令牌、票据这类不需要顺序的标识。但如果你把它当主键随机GUID会让索引页频繁分裂写入性能变差。这时考虑NEWSEQUENTIALID()它在Windows系统上保证新生成的GUID比之前的更大顺序插入对索引更友好。不过注意NEWSEQUENTIALID()只能用作默认值不能在普通查询里直接调用。默认值的另一个坑是默认值只在显式不给字段赋值时生效。如果你明确插入了NULL那默认值不会生效字段仍然是NULL。所以要让“不填时自动用默认值且不出现NULL”还得配合NOT NULL一起使用。尤其对于points INT NOT NULL DEFAULT 0如果插入语句里写了points NULL照样报错或存入NULL这取决于列的约束这个细节最容易让人困惑。3. 约束的保管与运维从系统表查元数据到日常巡检约束建好了不代表一劳永逸。随着业务迭代我们需要时不时查看表里有哪些约束、约束是否生效、某个字段是否被约束保护。不同数据库的查询语法各不相同我把自己常用的几套查询整理出来都是生产环境验证过的。3.1 SQL Server用系统视图管理约束SQL Server里约束的元数据主要藏在sys.objects、sys.columns、sys.key_constraints、sys.foreign_keys、sys.check_constraints这些视图里。最实用的一个查询是把某张表上的所有约束汇总出来SELECT t.name AS table_name, o.name AS constraint_name, o.type_desc AS constraint_type FROM sys.objects o JOIN sys.tables t ON o.parent_object_id t.object_id WHERE t.name orders AND o.type IN (PK, UQ, F, C, D) ORDER BY o.type;这里type字段的取值含义分别是PK主键UQ唯一约束F外键C检查约束D默认约束。日常巡检时我会再聚合一下看每张表的约束数量数量明显偏少的表就要警惕是不是漏加约束了。如果想看某个外键具体关联到哪张表的哪个字段可以用sys.foreign_key_columns关联SELECT fk.name AS fk_name, tp.name AS parent_table, cp.name AS parent_column, tr.name AS referenced_table, cr.name AS referenced_column FROM sys.foreign_keys fk JOIN sys.foreign_key_columns fkc ON fk.object_id fkc.constraint_object_id JOIN sys.tables tp ON fkc.parent_object_id tp.object_id JOIN sys.columns cp ON fkc.parent_object_id cp.object_id AND fkc.parent_column_id cp.column_id JOIN sys.tables tr ON fkc.referenced_object_id tr.object_id JOIN sys.columns cr ON fkc.referenced_object_id cr.object_id AND fkc.referenced_column_id cr.column_id WHERE fk.name fk_oi_order;在SQL Server里删除约束也需要注意比如删除默认约束要先拿到约束的名字ALTER TABLE users DROP CONSTRAINT DF__users__points__1234; -- 名字从sys.objects中查新增约束的标准格式是ALTER TABLE ... ADD CONSTRAINT ...。这里再提一句SQL Server对默认约束的命名很“随意”如果不手动起名系统会自动生成类似DF__users__points__1234这种带数字后缀的名字迁移脚本里引用这种名字非常脆弱所以我建议在创建表时所有的约束都显式命名统一前缀规范主键pk_、外键fk_、唯一uk_、检查chk_、默认df_。3.2 MySQL约束查询和版本差异MySQL里约束的信息可以查询information_schema.table_constraintsSELECT table_name, constraint_name, constraint_type FROM information_schema.table_constraints WHERE table_schema your_db AND table_name orders;通过information_schema.key_column_usage还能进一步拿到约束对应的字段SELECT constraint_name, column_name, referenced_table_name, referenced_column_name FROM information_schema.key_column_usage WHERE table_schema your_db AND table_name order_items;这里要特别再次强调MySQL版本差异8.0.16之前CHECK约束是“只读不检查”的你在建表时写了CHECK它也不报错数据也不会被拦截。很多老项目的MySQL 5.7表面上跑得好好的一但升级到8.0突然开始报检查约束的错误就是因为新版本的检查约束真正开始生效了。这个坑在升级数据库版本时尤其常见需要提前把历史数据全部梳理一遍确保都通过了约束规则再升级。3.3 PostgreSQL约束信息一把梭PostgreSQL的pg_constraint表提供了非常完整的约束信息SELECT conname AS constraint_name, contype AS constraint_type, pg_get_constraintdef(oid) AS constraint_definition FROM pg_constraint WHERE conrelid orders::regclass;contype取值包括p主键、u唯一、f外键、c检查、x排他。最贴心的是pg_get_constraintdef可以直接把约束的定义语句解析出来比如CHECK ((salary 0))一眼就能看清约束的规则省得再去翻建表脚本。我在排查问题时经常开一个这样的查询把所有外键的定义原样打出来辅助判断删除顺序避免因为引用关系导致删表失败。3.4 约束运维的日常巡检清单我给自己列过一个“约束健康检查”清单每次接手一个新数据库都会先跑一遍所有核心业务表是否都有主键没有主键的优先补上。关键业务唯一字段订单号、身份证号、邮箱是否都加了唯一约束所有外键字段是否都有索引尤其那些未启用外键约束的“伪外键”字段。是否存在为空但不该为空的字段通过SELECT COUNT(*) FROM 表 WHERE 字段 IS NULL抽样检查。CHECK约束是否真的在生效在测试环境插入一条非法数据验证。约束命名是否规范随机名字的约束在迁移脚本里是隐患。这些巡检不一定天天做但每次大版本升级、表结构变更、重构前我都会对照这个清单过一遍。很多线上故障的根因其实在建表那一刻就埋下了巡检只是把隐患提前挖出来。4. 约束的常见报错与排查实战约束本身不复杂但实际使用中因为表结构、数据状态、语法差异报错信息五花八门。我把这几年遇到的高频问题整理成了一个速查表附带排查思路方便你直接对照处理。场景典型报错核心原因解决思路插入重复值Duplicate entry xxx for key uk_...违反唯一约束先查是否已有相同数据确认业务上是否可以存在如果允许则去掉唯一约束不允许则需要清洗已有数据插入空值Column xxx cannot be null违反非空约束确认业务上该字段是否真的必填若是则补齐值再插入若否则考虑取消非空约束或加默认值删除父表记录Cannot delete or update a parent row: a foreign key constraint fails子表仍有引用数据根据业务决定是CASCADE删除子表、SET NULL还是先清理子表再删除更新主键Cannot change column id: used in a foreign key constraint主键被外键引用尽量避免改主键确需修改时先更新子表引用或临时禁用外键检查生产库慎用检查约束不生效数据插入成功但应失败MySQL版本低于8.0.16检查版本升级或改用触发器实现校验默认值没生效插入NULL后字段仍为NULL默认值只对未指定字段生效显式NULL会覆盖默认值插入时要么不写该字段要么判定业务上是否可为空SQL Server安全连接报错driver cannot ... SSL...不是约束问题是连接配置问题这类问题要排查JDBC/驱动和服务器加密配置与表结构约束无关别混淆上面表格里的前六项都和约束直接相关最后一项我在热词榜单里看到了顺带提一句报错里带“SSL”字样时先检查客户端连接字符串、驱动版本和数据库实例是否强制加密不要跑到表结构里找原因。它和约束完全是两码事。再分享一个线上事故级的实际排查案例。上个月我接到一个工单说报表里出现了重复的订单记录但订单表明明建了唯一索引理论上不可能重复。我第一反应是索引没有真正生效。后来查了表结构才发现唯一约束建在order_no上但业务上真正应该唯一的是(order_no, tenant_id)——因为系统多租户每个租户的订单号可以相同。当初建表时只看到了单租户模式后来加了租户维度就忘了更新约束。结果就是两个租户各生成一张“单号一样”的订单报表一跨租户聚合就炸了。这个案例说明两个问题一是约束设计必须紧跟业务模型的演进表结构变了约束定义也要同步评审二是排查重复类问题时要把“唯一键字段组合”从头到尾捋一遍不要默认现有约束就是正确的。后来我把约束改成复合唯一(tenant_id, order_no)又写了一次数据清洗脚本把历史脏数据去重才算彻底解决。还有一次同事急着上线往一张已有数据的表上加NOT NULL约束结果ALTER TABLE一直卡住。原因是表里有几千条记录该字段本来就是NULL加非空约束之前需要先处理历史数据。正确顺序应该是先写UPDATE把NULL改成合法的默认值再执行ALTER TABLE加约束。这个顺序问题看着简单但生产环境执行时容易因为数据量太大导致事务超时我建议分批更新比如每1万行提交一次。排查约束相关问题的通用方法论其实就三条先看数据再看约束。报错信息里带着字段名和约束名先把对应数据查出来理解它为什么会被拦。用元数据查询确认约束真的存在且定义正确。我见过有人在测试环境改了约束生产环境没同步结果行为不一致。改约束前先备份或者用事务包裹。尤其是对已有大表的ALTER TABLE先在测试库演练一遍预估耗时。这几条听着朴素但配合上面的速查表能解决绝大多数约束纠纷。5. 约束设计的一些进阶经验和反模式单个约束的使用门槛不高难的是在一整套业务里面把约束设计得合理、可维护、不反弹。我总结了一些进阶心得和反面教训这些用真金白银换来的经验比语法本身更值得留个心眼。第一约束的“度”要合适不能过松也不能过紧。过松比如外键不加、状态值不设检查约束脏数据就源源不断过紧比如所有字段全部非空、所有关系全部CASCADE删除会导致业务扩展极其痛苦想加一个非核心字段都要动表结构。我的原则是“核心链路严辅助字段松”。订单、支付、库存这类核心链路上的表约束要齐全、规则要严格日志、配置、报表临时表则尽量少加约束方便快速写入和迭代。第二外键CASCADE删除要慎用。用户在界面上删除一个订单你希望订单明细一起删吗有些场景确实希望比如购物车的勾选项。但有些场景不希望比如订单和发货单的关系——删订单不代表发货记录要删因为财务可能还要对账。如果设置了CASCADE一旦误删或者批量清理影响面会被无限放大。我更推荐在关联紧密、生命周期完全一致的关系上使用CASCADE比如主订单与订单明细、主表与附件表而对有历史留痕需求的关系用RESTRICT或SET NULL更安全。第三约束名命名规范不是小事。我在前面反复强调过命名因为现实中吃过亏。某次迁移脚本要删除一个默认约束但开发环境里系统自动生成的约束名和预发环境不一样脚本直接执行报错。从那之后我要求所有新表结构里的约束一律显式命名并且把命名规则写进团队的建表规范里。第四警惕“我用应用层校验所以不需要数据库约束”的思路。应用层校验确实重要但它是分散的、容易遗漏的。比如某个后台管理工具、某个数据导入脚本、某个临时修复SQL都可能绕过业务系统的校验逻辑直接写库。只要绕过一次脏数据就进来了。数据库约束恰恰是最后一道不管谁都得遵守的防线。即使在应用层做了校验数据库层面我依然建议加上关键约束这叫“双层保险”。第五约束变更要走完整的审批与验证流程。改约束不是改注释它会影响所有写入路径。在大型表上加唯一约束、加非空约束都需要评估锁表时间。如果在线上业务高峰期直接执行ALTER TABLE ... ADD CONSTRAINT很可能把整个写入链路拖垮。我的做法是把这类变更放到低峰期并且先在一台只读实例上模拟执行看耗时和锁情况再决定方案。这些经验总结成一句话约束设计是数据架构的一部分不是建表时的附属动作。它应该在需求分析阶段就参与进来和字段、索引、分区一起被认真设计。凡是事后补救的约束多多少少都要付出数据清洗或者流程变更的代价。前面聊了这么多其实就是在说一件事约束是我们给数据世界立的规矩规矩立得好后面省下的是无数个通宵排查数据的夜晚。我个人在实际操作中的体会是每次新建一张业务表我都会花十分钟把每个字段从头到尾问一遍这个值能为空吗能重复吗取值范围是什么和别的表是什么关系这十分钟的思考往往能在未来省下十个小时的返工。如果你也想把数据库这关把得更牢不妨从把你手头最关键的几张表打开把约束情况完整地摸一遍底开始。别等到脏数据找上门才想起约束这个老朋友。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →