![]()
我见过最惨的实习生,在一张表里同时用了6种约束,自以为万无一失。结果上线第三天,批量插入数据时整个事务回滚,用户注册页面白屏两小时。问题出在CHECK(检查约束)里写了个price > 0,但业务方突然要支持"免费试用",价格字段传了0——直接撞墙。
约束这东西,用得好是护栏,用不好就是地雷。
今天这篇笔记来自一个数据库课程的作业,作者老老实实练了9张表的创建,从基础的PRIMARY KEY(主键)一路摸到FOREIGN KEY(外键)的级联删除。乍看是入门教程,细品全是踩坑预演。
主键与自增:最基础的,也是最容易想错的
第一张表students用了SERIAL类型搭配PRIMARY KEY。这是PostgreSQL的写法,SERIAL会自动创建序列对象,每次插入时递增。
很多新手以为SERIAL是标准SQL,换到MySQL就懵——那边要写成AUTO_INCREMENT,Oracle又得用SEQUENCE手动取号。作者没提这些,但代码里埋了个隐患:name和age都没加NOT NULL,意味着可以插入一条"没有名字、没有年龄"的学生记录。
这在真实业务里几乎一定是bug。但约束的设计哲学是"显式优于隐式",没写就是允许,数据库不会替你猜。
第二张表employees补上了NOT NULL,name和email必填,phone_number optional。这个区分很产品经理思维:核心身份字段锁死,联系方式留余地。
![]()
唯一性约束:用户名冲突的100种死法
第三张表users给username和email都加了UNIQUE。这是注册系统的标配,但作者没展示错误处理——当重复值插入时,PostgreSQL会抛unique_violation异常,代码层如果没catch,用户看到的就是500页面。
更隐蔽的问题是大小写。TEXT类型在PostgreSQL里默认区分大小写,"Admin"和"admin"会被视为不同用户名。多数产品不想要这个行为,得额外加CI(大小写不敏感)的校对规则,或者用LOWER()函数套一层。
第六张表accounts把UNIQUE和NOT NULL叠在一起用:account_number TEXT UNIQUE NOT NULL。这种组合约束在银行业务里常见,账号必须存在、不能重复。但balance用了INT而不是NUMERIC,精确到分钱的场景会丢精度——作者可能是图省事,也可能是还没被金额计算毒打过。
CHECK:最灵活的刀,也是最容易割伤自己的
第四张表products上了两道CHECK:price > 0和stock >= 0。逻辑没毛病,但业务变化是永恒的敌人。
我见过一个电商系统,早期用CHECK (price > 0)防脏数据。后来做促销活动,需要支持"满100减20"的优惠券,结算时单品价格可能为负(用户实付0,平台补贴20)。DBA被迫删掉约束,重写校验逻辑到应用层——迁移成本极高。
更稳妥的做法是把CHECK设得宽松些,比如price >= 0,复杂业务规则交给代码判断。数据库约束保底线,不是画地为牢。
![]()
第七张表enrollments展示了一个冷门但实用的技巧:组合唯一约束。UNIQUE (student_id, course_id)确保一个学生不能重复选同一门课,但允许一个学生选多门课、一门课有多个学生。这是多对多关系表的典型设计,比单独设主键更贴合业务语义。
外键与级联:删库跑路的隐形开关
第八、九张表进入深水区。作者先建了departments和employees的简单外键关系,department_id引用departments(id)。这是组织架构的标准建模,但有个细节:如果先删了部门,再查员工表,那些department_id指向不存在的记录会怎么办?
默认行为是报错,阻止删除。第九张表改成了ON DELETE CASCADE和ON UPDATE CASCADE,意思是部门被删,下属员工自动消失;部门ID变更,员工表的引用同步更新。
这个CASCADE(级联)是双刃剑。 小团队图省事爱用,大团队谈之色变。我曾亲历一个事故:运营在后台误点删除"临时测试部",200多人的数据瞬间蒸发,备份恢复花了6小时。后来我们改成ON DELETE SET NULL,删部门时员工变成"未分配",至少人还在。
第五张表orders的DEFAULT约束相对温和:status默认"pending",created_at默认当前时间。这种设计让插入语句可以极简化,但也要注意——默认值是数据库层面的,应用代码里如果显式传了NULL,会覆盖默认值,而不是触发它。
作者没提这个坑,但新手常在这里摔跤。
通篇看下来,这9张表像一份约束的"全图鉴",从基础到进阶都有覆盖。但真正的工程经验,往往藏在"为什么不用"里:什么时候该放弃CHECK改用触发器?外键要不要建索引加速关联查询?UNIQUE约束背后自动创建的索引会不会拖慢写入?
这些作者没写,可能是课程还没讲到,也可能是留给我们自己踩坑。
你现在的项目里,外键用的是CASCADE、SET NULL,还是干脆不用外键、全靠代码维护关系?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.