约束与主外键:让数据库守护数据完整性 仅定义列与类型还不够——你还需要约束(constraint)来规定「什么样的数据是合法的」。约束是数据库层面的规则,会在每次写入或修改时自动校验,拒绝任何违规操作。这一节是表设计中保障数据质量的关键。 为什么需要约束 假设没有任何约束:邮箱可以重复、金额可以是负数、外键可以指向不存在的记录。那么脏数据会迅速积累,应用层要写大量防御性代码,且依然防不胜防。约束的价值在于把规则下沉到数据库,让所有客户端、所有接口共享同一道防线。一次定义,处处生效。 主键约束(Primary Key) 主键用于唯一标识表中的每一行。一张表只能有一个主键,它由一列或多列组成,要求值唯一且不为空。 主键的两个硬性要求: 唯一:任意两行的主键值不能相同。 非空:主键值不能为空。
仅定义列与类型还不够——你还需要约束(constraint)来规定「什么样的数据是合法的」。约束是数据库层面的规则,会在每次写入或修改时自动校验,拒绝任何违规操作。这一节是表设计中保障数据质量的关键。
假设没有任何约束:邮箱可以重复、金额可以是负数、外键可以指向不存在的记录。那么脏数据会迅速积累,应用层要写大量防御性代码,且依然防不胜防。约束的价值在于把规则下沉到数据库,让所有客户端、所有接口共享同一道防线。一次定义,处处生效。
主键用于唯一标识表中的每一行。一张表只能有一个主键,它由一列或多列组成,要求值唯一且不为空。
主键的两个硬性要求:
最常见的做法是用一个 id 列作为主键,类型为 UUID(自动生成)或自增大整数。主键会自动创建索引,因此按主键查找非常快。
外键用于建立表与表之间的引用关系,并保证引用的有效性。例如订单表中的 user_id 引用用户表的 id,外键约束保证「每个订单对应的用户必须真实存在」。
外键带来的保护包括:
CASCADE:连带删除或更新引用它的行(如删除用户时一并删除其订单)。SET NULL:把外键置为空(适用于「解除关联但不删除」的场景)。RESTRICT / NO ACTION:阻止删除,直到先处理掉引用它的行。外键是关系型数据库表达「关系」并维护一致性的核心机制,是 NoSQL 数据库普遍欠缺的能力。
唯一约束保证某一列(或几列的组合)的值在全表范围内不重复。典型场景:
email 必须唯一,防止重复注册。slug(网址中的标识)必须唯一。唯一约束允许多个空值(NULL 不算重复),这一点在实际使用时要留意。
非空约束要求某列必须有值,不能为空。它常与数据类型配合,进一步收紧合法性。例如订单的 amount 既要是数值,又不能为空。
经验法则:默认给字段加非空约束,只有在确实需要「未知 / 可选」语义时才允许为空。空值会增加查询与逻辑处理的复杂度。
检查约束允许你用任意布尔表达式自定义规则,非常灵活。例如:
amount >= 0:金额不能为负。检查约束是兜底的「业务规则防线」,能拦截那些类型与外键都管不到的逻辑错误。
默认值不是严格意义上的约束,但常与约束配合。当插入数据时若未提供某列的值,则使用默认值。典型用法:
created_at 默认为当前时间(now())。id 默认为自动生成的 UUID。published 默认为「假」。善用默认值,能让插入操作更简洁,并保证关键字段不会因为遗漏而为空。
仍以文章表为例,综合各类约束后,它应满足:
id 为主键(唯一且非空)author_id 为外键,关联用户表,作者被删除时级联处理其文章title 非空(必须有标题)slug 唯一(网址标识不能重复)published 默认为「假」word_count >= 0(字数不能为负,通过检查约束)这套约束共同确保:每篇文章有唯一标识、有合法作者、有标题、网址标识不重复、关键数值合法。无论哪个客户端写入数据,数据库都会强制执行这些规则。
约束会在每次写入时增加校验开销,但这个代价几乎总是值得的——它换来的是数据质量的保障。唯一需要注意的:
对于绝大多数应用,正确使用约束的收益远大于性能损耗。
约束是数据库守护数据完整性的「规则层」:主键保证唯一标识,外键保证关系有效,唯一约束防止重复,非空约束防止遗漏,检查约束表达业务规则,默认值简化写入。把它们用足,你的数据就会自带一道坚实的防线。下一节我们把视角放大,讨论如何用主外键设计表与表之间的关系。