{T}

数据约束

约束(Constraint)是数据库保证数据完整性的机制,让数据库在写入时自动校验,避免非法数据进入表。它是关系数据库"强约束"价值的核心体现。本文系统讲解六类常用约束:主键、唯一、非空、默认、检查、外键。

约束概述

为什么需要约束

在数据库层面用约束拦截非法数据,而不是完全依赖应用层:

  • 应用层校验可被绕过(直接连库、脚本灌数据等);
  • 约束是声明式的数据契约,多人协作时结构即文档;
  • 数据库能基于约束做查询优化(如唯一索引、主键索引)。

约束分类

约束关键字级别作用
主键PRIMARY KEY表级唯一标识一行,非空且唯一
唯一UNIQUE列/表级列值不重复(允许一个 NULL)
非空NOT NULL列级禁止 NULL
默认DEFAULT列级未提供值时用默认值
检查CHECK表级满足表达式条件
外键FOREIGN KEY表级引用另一表,保证参照完整性

主键 PRIMARY KEY

作用与规则

  • 唯一标识一行,非空 + 唯一,一个表只能有一个主键;
  • 通常作为聚簇索引(InnoDB),是最高效的定位方式;
  • 主键可由一列(单列主键)或多列(联合主键/复合主键)组成。

语法

sql
-- 建表时:列级定义
CREATE TABLE heros (
    id INT PRIMARY KEY,     -- 列级主键
    name VARCHAR(20)
);

-- 建表时:表级定义(支持联合主键)
CREATE TABLE player_game (
    player_id INT NOT NULL,
    game_id   INT NOT NULL,
    score     INT,
    PRIMARY KEY (player_id, game_id)   -- 联合主键
);

-- 事后添加/删除
ALTER TABLE heros ADD PRIMARY KEY (id);
ALTER TABLE heros DROP PRIMARY KEY;

自增主键

sql
CREATE TABLE heros (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    name VARCHAR(20),
    PRIMARY KEY (id)
);

最佳实践:主键尽量用无业务含义的自增整数或分布式 ID,避免用可变长字符串(影响索引大小与性能)。详见《范式设计》中"键"的概念。

唯一 UNIQUE

作用与规则

  • 保证列或列组合的值不重复,但允许多个 NULL(NULL 不参与唯一性比较);
  • 一个表可有多个唯一约束,会自动建立唯一索引。

语法

sql
-- 列级
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(50) UNIQUE
);

-- 表级 + 联合唯一
CREATE TABLE orders (
    order_no  VARCHAR(32),
    user_id   INT,
    UNIQUE KEY uk_order_no (order_no),
    UNIQUE KEY uk_user_date (user_id, order_date)  -- 组合唯一
);

-- 事后添加/删除
ALTER TABLE users ADD UNIQUE KEY uk_email (email);
ALTER TABLE users DROP INDEX uk_email;

UNIQUE vs PRIMARY KEY

特性PRIMARY KEYUNIQUE
是否允许 NULL✅(一个)
每表数量1多个
是否自动建索引
语义实体标识业务唯一性(如邮箱、订单号)

典型应用:业务上"不该重复"的字段(手机号、邮箱、订单号)都应用 UNIQUE,靠数据库兜底防止重复数据。

非空 NOT NULL 与默认 DEFAULT

作用

  • NOT NULL:该列禁止 NULL,必须给值;
  • DEFAULT 值:未显式给值时,数据库填充默认值。

语法

sql
CREATE TABLE users (
    id       INT PRIMARY KEY,
    name     VARCHAR(50) NOT NULL,                    -- 必填
    status   TINYINT NOT NULL DEFAULT 1,              -- 默认 1
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,  -- 默认当前时间
    remark   VARCHAR(200) DEFAULT '无'                 -- 可空但有默认
);

设计建议

sql
-- ✅ 推荐:业务无值用"空串/0/默认值"而非 NULL
CREATE TABLE users (
    nickname VARCHAR(50) NOT NULL DEFAULT '',   -- 而非允许 NULL
    score    INT NOT NULL DEFAULT 0
);

-- ❌ 不推荐:大量可空字段会让查询/索引复杂化
CREATE TABLE users (
    nickname VARCHAR(50) NULL,
    score    INT NULL
);

为什么不建议滥用 NULL:NULL 的三值逻辑(TRUE/FALSE/UNKNOWN)容易导致查询陷阱;NULL 不参与索引的某些优化;NOT NULL + DEFAULT 语义更清晰。详见《数据过滤》中 NULL 的处理。

检查 CHECK

作用

通过表达式约束列的值范围,数据库在写入时校验。注意:MySQL 8.0 才真正强制执行 CHECK(5.7 仅解析不执行);SQL Server/Oracle/PostgreSQL 都支持。

语法

sql
-- MySQL 8.0 起强制生效
CREATE TABLE products (
    id INT PRIMARY KEY,
    price DECIMAL(10,2),
    qty   INT,
    CONSTRAINT chk_price CHECK (price >= 0),          -- 价格非负
    CONSTRAINT chk_qty CHECK (qty >= 0 AND qty <= 10000)
);

-- 列级 CHECK
CREATE TABLE people (
    age INT CHECK (age BETWEEN 0 AND 150),
    gender CHAR(1) CHECK (gender IN ('M', 'F'))
);

-- 事后添加(MySQL 8.0)
ALTER TABLE products ADD CONSTRAINT chk_price CHECK (price >= 0);
ALTER TABLE products DROP CHECK chk_price;

CHECK 技巧

sql
-- 校验枚举/格式
CREATE TABLE orders (
    status VARCHAR(10) CHECK (status IN ('pending', 'paid', 'cancelled')),
    email  VARCHAR(100) CHECK (email LIKE '%@%')
);

MySQL 8.0 注意:8.0.16+ 才强制 CHECK,且要求"每列值满足 CHECK",REPLACE/INSERT ... ON DUPLICATE KEY UPDATE 等也会校验。

外键 FOREIGN KEY

作用

保证参照完整性:子表的外键列值必须存在于父表主键/唯一列中,防止"悬空引用"。

语法

sql
CREATE TABLE team (
    team_id INT PRIMARY KEY,
    team_name VARCHAR(50)
);

-- 建表时定义外键
CREATE TABLE player (
    player_id INT PRIMARY KEY,
    player_name VARCHAR(50),
    team_id INT,
    CONSTRAINT fk_team FOREIGN KEY (team_id) REFERENCES team(team_id)
);

-- 事后添加(MySQL 需指定约束名)
ALTER TABLE player
  ADD CONSTRAINT fk_team FOREIGN KEY (team_id) REFERENCES team(team_id);

外键的删除/更新动作(ON DELETE / ON UPDATE)

动作父表删除/更新时子表行为
RESTRICT(默认)拒绝删除/更新(若子表有引用)
NO ACTION同 RESTRICT
CASCADE级联删除/更新子表对应行
SET NULL子表外键置 NULL
SET DEFAULT子表外键置默认值
sql
-- 父表删球队时,级联删除该队球员
CREATE TABLE player (
    player_id INT PRIMARY KEY,
    team_id INT,
    FOREIGN KEY (team_id) REFERENCES team(team_id)
      ON DELETE CASCADE ON UPDATE CASCADE
);

⚠️ 外键的使用争议

sql
-- 物理外键:数据库强制
FOREIGN KEY (team_id) REFERENCES team(team_id)

-- 逻辑外键:应用层维护,字段上只建普通索引
KEY idx_team_id (team_id)
方式优点缺点
物理外键数据库保证一致性、自动级联影响写入性能、分布式/分库分表不适用、耦合高
逻辑外键灵活、性能好需应用层保证一致性,可能产生脏数据

本节从约束视角概览外键;外键的完整专题分析(多数据库差异、级联场景、逻辑外键 vs 物理外键取舍、最佳实践)见《21-数据库外键》。

约束综合示例

把六类约束用在一个订单系统上:

sql
CREATE TABLE users (
    id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    email       VARCHAR(100) NOT NULL,
    phone       VARCHAR(20),
    status      TINYINT NOT NULL DEFAULT 1,
    created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY uk_email (email),
    CONSTRAINT chk_status CHECK (status IN (0, 1, 2))
) ENGINE=InnoDB;

CREATE TABLE orders (
    id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    user_id     BIGINT UNSIGNED NOT NULL,
    amount      DECIMAL(12,2) NOT NULL,
    status      VARCHAR(10) NOT NULL DEFAULT 'pending',
    paid_at     DATETIME NULL,
    PRIMARY KEY (id),
    KEY idx_user (user_id),
    CONSTRAINT chk_amount CHECK (amount >= 0),
    CONSTRAINT chk_order_status CHECK (status IN ('pending','paid','cancelled')),
    CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;

约束与数据完整性

四类完整性

完整性机制约束
实体完整性每行有唯一标识PRIMARY KEY、UNIQUE
域完整性列值符合规定NOT NULL、DEFAULT、CHECK、数据类型
参照完整性外键引用有效FOREIGN KEY
用户定义完整性业务规则CHECK + 应用逻辑

约束设计建议

  1. 必填字段一律 NOT NULL + DEFAULT,避免 NULL 泛滥;
  2. 业务唯一字段用 UNIQUE 兜底,防止应用层漏判;
  3. CHECK 适合静态、简单的取值范围;复杂业务规则交给应用层;
  4. 物理外键按需:强一致单机可用,高并发/分库慎用(用逻辑外键);
  5. 约束在建模阶段设计,事后补约束成本高(需扫描/重建数据)。

总结

约束速查

约束关键字作用MySQL 8.0
主键PRIMARY KEY非空唯一标识
唯一UNIQUE值不重复(允许一个 NULL)
非空NOT NULL禁止 NULL
默认DEFAULT缺省值
检查CHECK值满足表达式✅(8.0.16+ 强制)
外键FOREIGN KEY参照完整性

关键点

  1. 约束是数据库层的数据契约,比应用层校验更可靠;
  2. 主键唯一标识行、唯一保证业务不重复、非空+默认收敛 NULL、CHECK 限定取值范围、外键保证引用有效;
  3. NOT NULL + DEFAULT 是消除 NULL 陷阱的推荐组合;
  4. 物理外键能保证一致性但有性能/耦合代价,互联网架构常用逻辑外键替代;
  5. 约束尽量在建表时设计好,配合《13-数据定义语言DDL》《21-数据库外键》阅读更完整。