Skip to content

外键 / 检查 / 默认值约束

外键约束(FOREIGN KEY)

外键用于建立两张表之间的引用完整性——子表中的某个列,必须引用父表中已存在的值。

外键的基本概念

sql
-- 父表(被引用)
CREATE TABLE class (
    id   INT PRIMARY KEY,
    name VARCHAR(50)
);

-- 子表(引用父表)
CREATE TABLE student (
    id       BIGINT PRIMARY KEY AUTO_INCREMENT,
    name     VARCHAR(50),
    class_id INT,
    FOREIGN KEY (class_id) REFERENCES class(id)  -- 外键约束
);

现在插入数据时:

sql
-- ✅ 可以插入:class_id 在 class 表中存在
INSERT INTO student (name, class_id) VALUES ('张三', 1);

-- ❌ 不可以插入:class_id=99 在 class 表中不存在
INSERT INTO student (name, class_id) VALUES ('李四', 99);
-- ERROR 1452: Cannot add or update a child row: a foreign key constraint fails

外键的级联操作

父表记录被删除/更新时,子表相关记录怎么处理?

sql
CREATE TABLE student (
    id       BIGINT PRIMARY KEY AUTO_INCREMENT,
    name     VARCHAR(50),
    class_id INT,
    FOREIGN KEY (class_id) REFERENCES class(id)
        ON DELETE CASCADE   -- 父表删除,子表对应行自动删除
        ON UPDATE CASCADE   -- 父表更新,子表对应行自动更新
);
选项说明
ON DELETE CASCADE父表删除,子表对应行级联删除
ON DELETE SET NULL父表删除,子表对应行设为 NULL(需要列允许 NULL)
ON DELETE SET DEFAULT父表删除,子表对应行设为默认值
ON DELETE RESTRICT阻止删除(默认)
ON DELETE NO ACTION同 RESTRICT
ON UPDATE CASCADE父表更新,子表对应行自动更新
ON UPDATE SET NULL父表更新,子表对应行设为 NULL
ON UPDATE RESTRICT阻止更新(默认)

级联操作的危险

sql
-- 一个极端例子
FOREIGN KEY (parent_id) REFERENCES parent(id)
ON DELETE CASCADE
ON UPDATE CASCADE

-- 如果 parent 表有 3 层嵌套的级联删除/更新
-- 删除顶层父记录 → 一次性删掉整棵树
-- 如果不小心删错了……完蛋

外键的使用场景与争议

场景建议
小型项目、表少✅ 可以用外键,让数据库帮你维护一致性
高并发系统❌ 不推荐外键,锁竞争严重,影响性能
微服务架构❌ 通常不用外键,每个服务只管理自己的表
核心业务数据✅ 用外键(如订单必须关联已存在的用户)

阿里规范(高并发互联网公司):禁止使用外键。数据一致性在应用层保证。

命名外键约束

sql
ALTER TABLE student
ADD CONSTRAINT fk_student_class
FOREIGN KEY (class_id) REFERENCES class(id)
ON DELETE SET NULL ON UPDATE CASCADE;

CHECK 约束

CHECK 约束限制列值必须满足条件(MySQL 8.0.16+)。

sql
CREATE TABLE student (
    id    BIGINT PRIMARY KEY AUTO_INCREMENT,
    name  VARCHAR(50) NOT NULL,
    age   INT,
    score DECIMAL(5,2),
    -- 年龄必须在 0~150 之间
    CONSTRAINT chk_age   CHECK (age >= 0 AND age <= 150),
    -- 成绩必须在 0~100 之间
    CONSTRAINT chk_score CHECK (score >= 0 AND score <= 100)
);

INSERT INTO student (name, age, score) VALUES ('张三', -5, 85);   -- ERROR
INSERT INTO student (name, age, score) VALUES ('张三', 20, 105);  -- ERROR
INSERT INTO student (name, age, score) VALUES ('张三', 20, 85);   -- OK

MySQL 8.0 之前的 CHECK

MySQL 8.0.16 之前 CHECK 语法存在但被忽略。绕过方式:用触发器模拟:

sql
-- 在插入/更新前检查
CREATE TRIGGER check_age_insert
BEFORE INSERT ON student
FOR EACH ROW
BEGIN
    IF NEW.age < 0 OR NEW.age > 150 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'Age must be between 0 and 150';
    END IF;
END;

DEFAULT 约束(默认值)

sql
CREATE TABLE student (
    id         BIGINT PRIMARY KEY AUTO_INCREMENT,
    name       VARCHAR(50) NOT NULL,
    age        INT DEFAULT 18,           -- 默认 18 岁
    score      DECIMAL(5,2) DEFAULT 0,  -- 默认 0 分
    is_deleted TINYINT(1) DEFAULT 0,    -- 默认未删除
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

DEFAULT 支持的默认值类型

类型示例
数值DEFAULT 0
字符串DEFAULT 'unknown'
表达式DEFAULT (NOW()) — MySQL 8.0+ 支持表达式默认值
函数DEFAULT CURRENT_TIMESTAMP

表达式默认值(MySQL 8.0+)

sql
CREATE TABLE order_info (
    id            BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no      VARCHAR(32) NOT NULL,
    total_amount  DECIMAL(12,2) NOT NULL DEFAULT 0,
    created_at    DATETIME NOT NULL DEFAULT (NOW()),  -- 表达式默认值
    updated_at    DATETIME NOT NULL DEFAULT (NOW()) ON UPDATE (NOW())
);

约束的管理

查看约束

sql
SHOW CREATE TABLE student;

删除约束

sql
-- 删除外键约束
ALTER TABLE student DROP FOREIGN KEY fk_student_class;

-- 删除唯一约束(需要知道约束名)
ALTER TABLE user DROP INDEX uk_email;

-- 删除主键约束
ALTER TABLE student DROP PRIMARY KEY;

约束综合设计示例

sql
CREATE TABLE `order` (
    id            BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no      VARCHAR(32) NOT NULL UNIQUE COMMENT '订单号',
    user_id       BIGINT NOT NULL COMMENT '用户ID',
    total_amount  DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '订单总额',
    status        TINYINT NOT NULL DEFAULT 1 COMMENT '状态 1-待支付 2-已支付 3-已取消',
    created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    CONSTRAINT chk_status CHECK (status IN (1, 2, 3)),
    CONSTRAINT chk_amount CHECK (total_amount >= 0)
);

CREATE TABLE order_item (
    id         BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id   BIGINT NOT NULL COMMENT '订单ID',
    product_id BIGINT NOT NULL COMMENT '商品ID',
    quantity   INT NOT NULL DEFAULT 1 COMMENT '数量',
    price      DECIMAL(10,2) NOT NULL COMMENT '单价',

    CONSTRAINT fk_order_item_order   FOREIGN KEY (order_id)   REFERENCES `order`(id) ON DELETE CASCADE,
    CONSTRAINT fk_order_item_product FOREIGN KEY (product_id) REFERENCES product(id) ON DELETE RESTRICT,
    CONSTRAINT chk_quantity CHECK (quantity > 0),
    CONSTRAINT chk_price    CHECK (price >= 0)
);

下一步

约束学完了,接下来学习 视图(创建/查看/更新/删除)

基于 VitePress 构建