掌握 MySQL:约束、范式与视图详解

复制代码
感谢阅读!❤️
如果这篇文章对你有帮助,欢迎 **点赞** 👍 和 **关注** ⭐,获取更多实用技巧和干货内容!你的支持是我持续创作的动力!
**关注我,不错过每一篇精彩内容!**

目录

    • 一、Constraint约束
      • [1. 约束介绍](#1. 约束介绍)
      • [2. 支持的约束类型(字段级):](#2. 支持的约束类型(字段级):)
      • [3. 非空约束 `not null`](#3. 非空约束 not null)
      • [4. 默认约束 `default`](#4. 默认约束 default)
      • [5. 唯一性约束 `unique`](#5. 唯一性约束 unique)
      • [6. 检查约束 `check`](#6. 检查约束 check)
      • [7. 主键约束 `primary key`](#7. 主键约束 primary key)
      • [8. 外键约束 `foreign key`](#8. 外键约束 foreign key)
    • 二、数据库三范式
      • [1. 第一范式](#1. 第一范式)
      • [2. 第二范式](#2. 第二范式)
      • [3. 第三范式](#3. 第三范式)
      • [4. 表设计口诀](#4. 表设计口诀)
    • 三、视图
      • [1. 视图的核心特点](#1. 视图的核心特点)
      • [2. 创建视图的语法](#2. 创建视图的语法)
      • [3. 视图的主要用途(为什么用视图?)](#3. 视图的主要用途(为什么用视图?))
      • [4. `CHECK OPTION`:防止越权插入](#4. CHECK OPTION:防止越权插入)
      • [5. 视图的优缺点](#5. 视图的优缺点)
      • [6. 管理视图的常用命令](#6. 管理视图的常用命令)
      • [7. 视图使用的最佳实践(避坑指南)](#7. 视图使用的最佳实践(避坑指南))
      • [8. 视图使用频率](#8. 视图使用频率)
      • [9. 视图最常用的 6 大场景(附真实案例)](#9. 视图最常用的 6 大场景(附真实案例))
        • [场景 1:**简化复杂查询(封装逻辑)**](#场景 1:简化复杂查询(封装逻辑))
        • [场景 2:**数据安全与权限控制(行/列级过滤)**](#场景 2:数据安全与权限控制(行/列级过滤))
        • [场景 3:**多租户数据隔离(SaaS 系统)**](#场景 3:多租户数据隔离(SaaS 系统))
        • [场景 4:**兼容旧接口(平滑迁移)**](#场景 4:兼容旧接口(平滑迁移))
        • [场景 5:**BI 与报表层抽象**](#场景 5:BI 与报表层抽象)
        • [场景 6:**物化视图加速查询(高性能读)**](#场景 6:物化视图加速查询(高性能读))
      • [10. 总结](#10. 总结)

一、Constraint约束

1. 约束介绍

创建表时,可以给表的字段添加约束,可以保证数据的完整性有效性

约束通常包括:

  • 非空约束:not null
  • 默认约束:default
  • 唯一性约束:unique
  • 检查约束:check
  • 主键约束:primary key
  • 外键约束:foreign key

约束的使用方式:

  • 字段约束:

    • 定义某一列的同时直接写在该列后面。
    • 只能作用于当前这一列
    sql 复制代码
    -- 写在字段后的称为:列级约束
    
    CREATE TABLE STUDENT(
    	ID INT(8) PRIMARY KEY,
        NAME VARCHAR(255) NOT NULL UNIQUE,
        GENDER CHAR(1) CHECK(GENDER IN ('男', '女')),
        AGE INT(4) CHECK(AGE > 18)
    )
  • 表级约束

    • 所有列定义之后 ,单独用 CONSTRAINT 子句声明。
    • 可以跨多列进行约束(如复合主键(通常不使用)、多列唯一等)
    sql 复制代码
    -- 单独定义的约束称:表级约束
    
    CREATE TABLE STUDENT(
    	ID INT(8),
        NAME VARCHAR(255) NOT NULL,
        GENDER CHAR(1),
        AGE INT(4),
        
        CONSTRAINT PK_ID PRIMARY KEY(ID),
        
        CONSTRANIT UNIQUE_NAME UNIQUE(NAME), 
    )

2. 支持的约束类型(字段级):

约束类型 支持字段级? 支持表级?
DEFAULT ✅ 是 ❌ 否
NOT NULL ✅ 是 ❌ 否
PRIMARY KEY ✅(单列) ✅(多列)
UNIQUE ✅(单列) ✅(多列)
CHECK ✅(单列条件) ✅(跨列条件)
FOREIGN KEY ⚠️ 部分支持 ✅ 推荐方式

3. 非空约束 not null

sql 复制代码
DROP TABLE IF EXISTS T_STUDENT;

CREATE TABLE T_STUDENT(
	ID INT(10),
    NAME VARCHAR(255) NOT NULL
);

-- ❌插入NULL数据将会报错
INSERT INTO T_STUDENT VALUES (1,NULL);

-- ❌插入NULL数据将会报错
INSERT INTO T_STUDENT(ID) VALUES (1);

4. 默认约束 default

sql 复制代码
DROP TABLE IF EXISTS T_STUDENT;

CREATE TABLE T_STUDENT(
	ID INT(4),
    NAME VARCHAR(255),
    GENDER CHAR(1) DEFAULT '男'
);

-- ✅没填写GENDER字段,默认填写'男'
INSERT INTO T_STUDENT (ID,NAME) VALUES(1,'Tom');

5. 唯一性约束 unique

SQL 复制代码
DROP TABLE IF EXISTS T_STUDENT;

CREATE TABLE T_STUDENT(
	ID INT(4),
    NAME VARCHAR(255) UNIQUE
);

-- ✅插入第一条Tom记录
INSERT INTO T_STUDENT (ID,NAME) VALUES(1,'Tom');

-- ❌再插入一条Tom记录将会报错
INSERT INTO T_STUDENT (ID,NAME) VALUES(2,'Tom');

6. 检查约束 check

sql 复制代码
DROP TABLE IF EXISTS T_STUDENT;

CREATE TABLE T_STUDENT(
	ID INT(4),
    NAME VARCHAR(255),
    AGE INT(8) CHECK(AGE > 18)
);

-- ❌插入小于18岁的记录将会报错
INSERT INTO T_STUDENT(ID,NAME,AGE) VALUES(1,'Tom',10);

-- ✅插入大于18岁的记录
INSERT INTO T_STUDENT(ID,NAME,AGE) VALUES(1,'Tom',20);

7. 主键约束 primary key

  • 作用:

    唯一标识表中的每一行

  • 特点:

    • 值不允许为 NULL(隐含 not null)
    • 值不允许重复 (隐含 unique)
    • 一张表都应该有主键,没有主键的表视为无效表
  • 分类:

    • 根据主键字段数量分类:

      • 单一主键(推荐使用)
      • 符合主键
    • 根据业务分类

      • 自然主键:主键和任何业务都无关系,只是一个单纯的自然数据 (推荐使用)
      • 业务主键:主键与业务挂钩,例如:身份证号作为主键

单一主键(推荐使用)

只有某一个字段被设置为主键

sql 复制代码
DROP TABLE IF EXISTS T_STUDENT;

CREATE TABLE T_STUDENT(
    ID INT(10) PRIMARY KEY,
    NAME VARCHAR(255),
    AGE INT(10)
);

-- 正常插入
INSERT INTO T_STUDENT VALUE(1,'Tom',18);
INSERT INTO T_STUDENT VALUE(2,'Jack',20);

-- 报错
INSERT INTO T_STUDENT VALUE(2,'Filip',20);

复合主键(不推荐使用)

多个字段被设置为主键

sql 复制代码
DROP TABLE IF EXISTS T_STUDENT;

CREATE TABLE T_STUDENT(
    ID INT(10),
    NAME VARCHAR(255),
    AGE INT(10),

    CONSTRAINT PK_ID_NAME PRIMARY KEY(ID,NAME)
);

-- 正常插入
INSERT INTO T_STUDENT VALUE(1,'Tom',18);
INSERT INTO T_STUDENT VALUE(2,'Jack',20);

-- 正常插入
INSERT INTO T_STUDENT VALUE(2,'Tom',18);
INSERT INTO T_STUDENT VALUE(1,'Jack',20);

主键自增

既然主键值是一个自然的数字,mysql 为主键值提供了一种自增机制,不需要程序员维护,mysql 自动维护该字段,使用auto_increment

sql 复制代码
DROP TABLE IF EXISTS T_STUDENT;

CREATE TABLE T_STUDENT(
    ID INT(10) PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(255)
);

-- 正常插入,主键自动生成,从1开始
INSERT INTO T_STUDENT(NAME) VALUES('Tom');
INSERT INTO T_STUDENT(NAME) VALUES('Jack');

-- 删除表中数据
DELETE FROM T_STUDENT;
-- 继续插入数据,主键不会自动从1开始,而是在原先基础上继续自增
INSERT INTO T_STUDENT(NAME) VALUES('Tom');
INSERT INTO T_STUDENT(NAME) VALUES('Jack');

8. 外键约束 foreign key

  • 作用:建立子表(从表)对父表(主表)的引用,确保子表中的值必须在父表中存在

  • 特点:

    • 维护数据完整性,正确性
    • 支持级联操作(级联删除 on delete cascade、级联更新 on update cascade 等)
    • 添加外键的字段,其引用的字段必须是主键 或者添加了唯一约束的字段
    • 添加了外键约束的字段中的数据必须来自其他字段,不能随便填写
    • 外键约束可以给单个字段添加,叫单一外键,也可以给多个字段联合添加,叫复合外键,复合外键很少使用。
    • A 表引用 B 表中的数据,可以把 B 表称为父表,A 表称为子表
      • 创建表是,先创建父表,再创建子表
      • 插入数据时,先插入父表,再插入子表
      • 删除数据时,先删除子表,再删除父表
      • 删除表时,先删除子表,再删除父表
  • 示例:

  1. 有一个需求,要求设计一张表,存储学生及其学习信息

第一种方案:一张表

id(主键) name age sno school
1 张三 20 1 第二中学
2 王五 22 1 第二中学
3 周瑜 24 1 第二中学
4 黄盖 25 2 第一中学
5 诸葛 26 2 第一中学
6 江山 27 2 第一中学

这种方式会导致数据冗余,浪费空间,但查询效率高

第二种方案:俩张表

  • School 表

    sid(主键) sname
    1 第二中学
    2 第一中学
  • Student表

    id(主键) name age sid(外键)
    1 张三 20 1
    2 王五 21 1
    3 周瑜 21 1
    4 黄盖 21 2
    5 诸葛 21 2
    6 江山 21 2

如果采用两张表存储数据,那么对于student表的sid字段的值就不能随便填写的。这个sid学校编号,必须要求这个字段的值来自school表的sid。为了达到这一要求,此时就必须给student表的sid字段添加外键约束

sql 复制代码
-- 先删子表,再删父表
DROP TABLE IF EXISTS STUDENT;
DROP TABLE IF EXISTS SCHOOL;


CREATE TABLE SCHOOL(
    SID INT(10) PRIMARY KEY,
    SNAME VARCHAR(255) NOT NULL
);

CREATE TABLE STUDENT(
    ID INT(10) PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(255) NOT NULL,
    AGE INT(10) NOT NULL,
    SID INT(10),

    -- 创建外键
    CONSTRAINT FK_SID FOREIGN KEY(SID) REFERENCES SCHOOL(SID)
);


-- 先插入数据到父表,再插入数据到子表
INSERT INTO SCHOOL VALUES(1,'第二中学'),(2,'第一中学');

-- 成功插入数据
INSERT INTO STUDENT(NAME,AGE,SID) VALUES('Tom', 18, 1);
INSERT INTO STUDENT(NAME,AGE,SID) VALUES('Jack', 18, 2);

-- 报错,因为school表中没有id为3的数据
INSERT INTO STUDENT(NAME,AGE,SID) VALUES('Fabio', 18, 3);

-- 先删除子表数据,再删除父表数据
DELETE FROM STUDENT;

DELETE FROM SCHOOL;

常见的级联操作类型

FOREIGN KEY 约束中,可通过以下两个子句定义级联行为:

  • ON DELETE:当父表记录被删除时,子表如何响应。
  • ON UPDATE:当父表主键/唯一键被更新时,子表如何响应。

每种操作可指定以下 5 种行为:

行为 说明
CASCADE 级联操作(删除/更新子表对应行)
SET NULL 将子表外键字段设为NULL
SET DEFAULT 将子表外键字段设为默认值(需有默认值)
RESTRICT / NO ACTION 拒绝操作(默认行为)
(无动作) 隐式等同于 RESTRICT

级联删除

创建子表时,外键可以添加:on delete cascade。这样在删除父表数据时,子表会级联删除,谨慎使用。

sql 复制代码
DROP TABLE IF EXISTS STUDENT;
DROP TABLE IF EXISTS SCHOOL;

CREATE TABLE SCHOOL(
    SID INT(10) PRIMARY KEY,
    SNAME VARCHAR(255) NOT NULL
);

CREATE TABLE STUDENT(
    ID INT(10) PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(255) NOT NULL,
    AGE INT(10) NOT NULL,
    SID INT(10),

    -- 设置级联删除
    CONSTRAINT FK_SID FOREIGN KEY(SID) REFERENCES SCHOOL(SID) ON DELETE CASCADE
);

INSERT INTO SCHOOL VALUES(1,'第一中学'),(2,'第二中学'),(3,'第三中学');

INSERT INTO STUDENT(NAME,AGE,SID) VALUES
('Tom', 18, 1),
('Sun', 17, 1),
('Jack', 19, 2),
('Toney', 24, 2),
('Jin', 20, 2),
('Mali', 20, 3),
('Fabio', 20, 3);

-- 当直接删除父表中的数据,子表会删除对应的数据
-- 查询未删除之前的数据
SELECT * FROM STUDENT;

-- 删除SID = 3 的school
DELETE FROM SCHOOL WHERE SID = 3;

-- 查询未删除之后的数据
SELECT * FROM SCHOOL;
SELECT * FROM STUDENT;

-- 也可以修改外键约束
-- 先删除原先的外键约束,再重新创建外键约束
ALTER TABLE STUDENT DROP FOREIGN KEY FK_SID;

-- 添加新的外键约束,设置级联删除
ALTER TABLE STUDENT ADD CONSTRAINT NEW_FK_SID FOREIGN KEY(SID) REFERENCES SCHOOL(SID) ON DELETE CASCADE;

级联更新

创建子表时,外键可以添加:on update cascade。这样在更新父表数据时,子表会级联更新。

sql 复制代码
DROP TABLE IF EXISTS STUDENT;
DROP TABLE IF EXISTS SCHOOL;

CREATE TABLE SCHOOL(
    SID INT(10) PRIMARY KEY,
    SNAME VARCHAR(255) NOT NULL
);

CREATE TABLE STUDENT(
    ID INT(10) PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(255) NOT NULL,
    AGE INT(10) NOT NULL,
    SID INT(10),

    -- 设置级联删除
    CONSTRAINT FK_SID FOREIGN KEY(SID) REFERENCES SCHOOL(SID) ON UPDATE CASCADE
);

INSERT INTO SCHOOL VALUES(1,'第一中学'),(2,'第二中学'),(3,'第三中学');

INSERT INTO STUDENT(NAME,AGE,SID) VALUES
('Tom', 18, 1),
('Sun', 17, 1),
('Jack', 19, 2),
('Toney', 24, 2),
('Jin', 20, 2),
('Mali', 20, 3),
('Fabio', 20, 3);

-- 当直接删除父表中的数据,子表会删除对应的数据
-- 查询未删除之前的数据
SELECT * FROM STUDENT;

-- 更新SNAME = '第三中学'的school
UPDATE SCHOOL SET SID = 333 WHERE SNAME = '第三中学';

-- 查询未删除之后的数据
SELECT * FROM SCHOOL;
SELECT * FROM STUDENT;

级联置空

创建子表时,外键可以添加:on delete set null。这样在删除父表数据时,将子表外键字段值置为 NULL

sql 复制代码
DROP TABLE IF EXISTS STUDENT;
DROP TABLE IF EXISTS SCHOOL;

CREATE TABLE SCHOOL(
    SID INT(10) PRIMARY KEY,
    SNAME VARCHAR(255) NOT NULL
);

CREATE TABLE STUDENT(
    ID INT(10) PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(255) NOT NULL,
    AGE INT(10) NOT NULL,
    SID INT(10),

    -- 设置级联删除
    CONSTRAINT FK_SID FOREIGN KEY(SID) REFERENCES SCHOOL(SID) ON DELETE SET NULL
);

INSERT INTO SCHOOL VALUES(1,'第一中学'),(2,'第二中学'),(3,'第三中学');

INSERT INTO STUDENT(NAME,AGE,SID) VALUES
('Tom', 18, 1),
('Sun', 17, 1),
('Jack', 19, 2),
('Toney', 24, 2),
('Jin', 20, 2),
('Mali', 20, 3),
('Fabio', 20, 3);

-- 当直接删除父表中的数据,子表会删除对应的数据
-- 查询未删除之前的数据
SELECT * FROM STUDENT;

-- 删除SID = 3 的school
DELETE FROM SCHOOL WHERE SID = 3;

-- 查询未删除之后的数据
SELECT * FROM SCHOOL;
SELECT * FROM STUDENT;

二、数据库三范式

数据库表设计原则,教你如何设计数据库表有效,并且节省空间。

1. 第一范式

任何一张表都应该有主键,每个字段的是原子性的,不能再拆分。

  • 下列表设计不符合第一范式:没有主键,联系方式还可以拆分
学生编号 学生姓名 联系方式
101 Tom tom@qq.com ,13755555555
102 Jack jack@qq.com ,13755555556
103 Mali mali@qq.com ,13755555557
  • 应该这样设计表
学生编号(PK) 学生姓名 邮箱 电话
101 Tom tom@qq.com 13755555555
102 Jack jack@qq.com 13755555556
103 Mali mali@qq.com 13755555557

2. 第二范式

建立在第一范式之上,非主键字段必须完全依赖于主键字段,不能产生部分依赖

  • 虽然符合第一范式,但违背了第二范式:学生姓名和老师姓名都产生了部分依赖,导致数据冗余。学生姓名依赖,学生编号,但不依赖教师编号;同理,老师姓名依赖教师编号,但不依赖学生编号。导致了学生姓名和老师姓名都产生了部分依赖。

    学生编号(PK) 学生姓名 教师编号(PK) 教师姓名
    101 Tom 1 张三
    102 Jack 2 李四
    103 Mali 3 王五
  • 应该这样设计表(多对多)

    • Student

      学生编号(PK) 学生姓名
      101 Tom
      102 Jack
      103 Mali
    • Teacher

      教师编号(PK) 教师姓名
      1 张三
      2 李四
      3 王五
    • StudentTeacherRelation

      关系编号(PK) 学生编号(FK) 教师编号(FK)
      1 101 1
      2 102 2
      3 103 3

3. 第三范式

建立在第二范式之上,非主键字段对主键字段不能产生传递依赖

  • 下述表设计违背了第三范式,班级名称依赖班级编号,班级编号依赖学生编号,产生了传递依赖,导致班级名称数据冗余了

    学生编号(PK) 学生姓名 班级编号 班级名称
    101 Tom 1001 终极一班
    102 Jack 2001 终极二班
    103 Mali 3001 终极三班
    104 Milk 3001 终极三班
  • 应该这样设计表(一对多)

    • Class

      班级编号(PK) 班级名称
      1001 终极一班
      2001 终极二班
      3001 终极三班
    • Student

      学生编号(PK) 学生姓名 班级编号(FK)
      101 Tom 1001
      102 Jack 2001
      103 Mli 3001

4. 表设计口诀

一对一

  • 方式一:主键共享
  • 丈夫表

    丈夫编号(PK) 姓名
    101 Tom
    102 Jack
  • 妻子表

    妻子编号(PK + 外键) 姓名
    101 Alice
    102 Eden
  • 方式二:外键唯一
  • 丈夫表

    丈夫编号(PK) 姓名
    101 Tom
    102 Jack
  • 妻子表

    妻子编号(PK) 姓名 丈夫编号(FK + UNIQUE)
    101 Alice 101
    102 Eden 102

一对多

一对多,两张表,多的表,加外键

  • Class

    班级编号(PK) 班级名称
    1001 终极一班
    2001 终极二班
    3001 终极三班
  • Student

    学生编号(PK) 学生姓名 班级编号(FK)
    101 Tom 1001
    102 Jack 2001
    103 Mli 3001
    多对多

多对多,三张表,关系表,加外键

  • Student

    学生编号(PK) 学生姓名
    101 Tom
    102 Jack
    103 Mali
  • Teacher

    教师编号(PK) 教师姓名
    1 张三
    2 李四
    3 王五
  • StudentTeacherRelation

    关系编号(PK) 学生编号(FK) 教师编号(FK)
    1 101 1
    2 102 2
    3 103 3

最终的设计

最终以满足客户需求为原则,有时候会拿空间换速度


三、视图

视图(View )是关系型数据库中一种虚拟表 ,它并不实际存储数据(除非是物化视图),而是基于一个或多个基础表(或其它视图)的 SQL 查询结果集 。视图提供了一种逻辑抽象层,用于简化复杂查询、增强安全性、提高可维护性。


1. 视图的核心特点

特性 说明
虚拟表 视图本身不存储数据(普通视图),数据仍来自底层表
动态生成 每次查询视图时,都会执行其定义的 SQL 语句
可像表一样使用 SELECTJOIN、甚至在某些条件下 INSERT/UPDATE/DELETE
依赖基表 视图的结构和数据完全依赖于其引用的表

2. 创建视图的语法

sql 复制代码
CREATE [OR REPLACE] [ALGORITHM = {MERGE | TEMPTABLE | UNDEFINED}] 
VIEW view_name [(column_list)]
AS
    select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION];

** 简化常用形式:**

sql 复制代码
CREATE VIEW view_name AS
SELECT column1, column2, ...
FROM table_name
WHERE condition;

** 示例:**

sql 复制代码
-- 创建一个只显示活跃用户的视图
CREATE VIEW active_users AS
SELECT id, name, email
FROM users
WHERE status = 'active';

现在你可以像查表一样使用它:

sql 复制代码
SELECT * FROM active_users WHERE name LIKE '张%';

3. 视图的主要用途(为什么用视图?)

  • 简化复杂查询
    将多表连接、聚合、子查询等封装成简单视图。
sql 复制代码
-- 复杂查询封装为视图
CREATE VIEW order_summary AS
SELECT 
    o.customer_id,
    c.name AS customer_name,
    COUNT(o.id) AS order_count,
    SUM(o.total) AS total_spent
FROM orders o
JOIN customers c ON o.customer_id = c.id
GROUP BY o.customer_id, c.name;

业务代码只需:SELECT * FROM order_summary;


  • 数据安全与权限控制
    • 向用户仅暴露部分列或行,隐藏敏感字段(如密码、薪资)。
    • DBA 可授予用户对视图的权限,而不给基表权限。
sql 复制代码
-- 隐藏 salary 字段
CREATE VIEW employee_public AS
SELECT id, name, department, hire_date
FROM employees;
-- 授权
GRANT SELECT ON employee_public TO user_role;

  • 提供向后兼容性
    当基表结构变更(如拆分表),可通过视图保持旧接口不变。
sql 复制代码
-- 原来有 users 表,现在拆成 users + profiles
-- 但老应用仍用旧结构
CREATE VIEW users AS
SELECT u.id, u.name, p.email, p.phone
FROM users_core u
JOIN user_profiles p ON u.id = p.user_id;

  • 逻辑数据分区
    通过视图呈现不同维度的数据切片。
sql 复制代码
CREATE VIEW vip_customers AS
SELECT * FROM customers WHERE level = 'VIP';

CREATE VIEW recent_orders AS
SELECT * FROM orders WHERE order_date >= CURDATE() - INTERVAL 30 DAY;

4. CHECK OPTION:防止越权插入

当你通过视图插入/更新数据时,可能写入不符合视图条件 的记录。
WITH CHECK OPTION 可阻止这种行为。

示例:

sql 复制代码
CREATE VIEW high_salary_employees AS
SELECT * FROM employees
WHERE salary > 10000
WITH CHECK OPTION;  -- 关键!

-- 尝试插入低薪员工
INSERT INTO high_salary_employees (name, salary) 
VALUES ('新人', 5000);  -- ❌ 被拒绝!
  • LOCAL:只检查当前视图的条件。
  • CASCADED(默认):检查当前视图及所依赖的所有视图的条件。

5. 视图的优缺点

优点:

  • 简化复杂逻辑,提升开发效率;
  • 增强安全性,实现行/列级访问控制;
  • 提供逻辑抽象,解耦应用与物理表结构;
  • 兼容旧接口,平滑迁移。

缺点:

  • 性能开销:每次查询都执行底层 SQL(尤其多层嵌套视图);
  • 调试困难:错误信息可能不直观;
  • 不可更新性:多数视图只能读;
  • 依赖管理:基表结构变更可能导致视图失效。

6. 管理视图的常用命令

操作 SQL 语句
查看视图定义 SHOW CREATE VIEW view_name;(MySQL) SELECT definition FROM pg_views WHERE viewname = 'xxx';(PG)
修改视图 CREATE OR REPLACE VIEW ...
删除视图 DROP VIEW [IF EXISTS] view_name;
查看所有视图 SELECT table_name FROM information_schema.views;

7. 视图使用的最佳实践(避坑指南)

✅ 1. 命名规范清晰

  • 前缀标识:v_vw_view_(如 v_user_summary
  • 避免与表名冲突

✅ 2. 避免深层嵌套

  • ❌ 不要:视图A → 视图B → 视图C → 表
  • ✅ 建议:最多 1~2 层嵌套,否则性能难排查

✅ 3. 慎用可更新视图

  • 仅在简单单表投影时使用;
  • 复杂逻辑建议用 存储过程 + 事务 替代。

✅ 4. 配合 CHECK OPTION 防越权写入

sql 复制代码
CREATE VIEW high_value_customers AS
SELECT * FROM customers WHERE credit > 100000
WITH CHECK OPTION;  -- 防止 INSERT 低信用客户

✅ 5. 文档化视图用途

在数据库注释或 Wiki 中记录:

  • 视图目的
  • 依赖哪些表
  • 是否可更新
  • 刷新策略(物化视图)

✅ 6. 性能监控

  • 对高频视图执行 EXPLAIN 分析执行计划;
  • 若慢,考虑:
    • 添加基表索引
    • 改用物化视图
    • 拆分逻辑到应用层

✅ 7. 生命周期管理

  • 删除不再使用的视图(避免"僵尸对象");
  • 重构时评估是否仍需保留。

8. 视图使用频率

结论:非常常用,尤其在以下环境中:

场景 使用频率
企业级应用(ERP、CRM、财务系统) ⭐⭐⭐⭐⭐ 高频
数据仓库 & BI 报表 ⭐⭐⭐⭐⭐ 几乎必备
多租户 SaaS 系统 ⭐⭐⭐⭐ 常用于数据隔离
微服务架构(数据库共享或遗留系统对接) ⭐⭐⭐ 常用于兼容层
小型项目 / 快速原型 ⭐⭐ 较少,但仍有价值

💡 根据 DBA 和后端工程师的实践反馈:80% 以上的中大型数据库都会定义至少几个关键视图


9. 视图最常用的 6 大场景(附真实案例)

场景 1:简化复杂查询(封装逻辑)

痛点:业务查询涉及多表 JOIN、聚合、子查询,SQL 冗长易错。

实践示例:

sql 复制代码
-- 原始复杂查询(每次都要写)
SELECT 
  u.id, u.name, 
  COUNT(o.id) AS order_count,
  COALESCE(SUM(o.amount), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2024-01-01'
GROUP BY u.id, u.name;

-- 封装为视图
CREATE VIEW user_order_summary_2024 AS
SELECT 
  u.id, u.name, 
  COUNT(o.id) AS order_count,
  COALESCE(SUM(o.amount), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2024-01-01'
GROUP BY u.id, u.name;

效果

  • 前端/报表只需 SELECT * FROM user_order_summary_2024;
  • 逻辑集中管理,修改只需改视图,无需改所有调用方。

场景 2:数据安全与权限控制(行/列级过滤)

痛点:不同角色用户只能看到部分数据(如销售看不到薪资)。

实践示例:

sql 复制代码
-- 员工表含敏感字段
CREATE TABLE employees (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  salary DECIMAL(10,2),
  department VARCHAR(50),
  manager_id INT
);

-- 创建只读视图给普通员工
CREATE VIEW employee_public AS
SELECT id, name, department
FROM employees;

-- 授权
GRANT SELECT ON employee_public TO sales_team;
-- 撤销对基表的访问
REVOKE SELECT ON employees FROM sales_team;

效果

  • 销售团队只能看到姓名和部门,无法访问薪资
  • 即使 SQL 注入,也无法绕过视图限制(因无基表权限)。

场景 3:多租户数据隔离(SaaS 系统)

痛点:一个数据库服务多个客户,需确保 A 客户看不到 B 客户数据。

实践示例:

sql 复制代码
-- 主表带 tenant_id
CREATE TABLE orders (
  id INT PRIMARY KEY,
  tenant_id VARCHAR(20),
  product VARCHAR(100),
  amount DECIMAL(10,2)
);

-- 为每个租户创建视图(或动态生成)
CREATE VIEW tenant_A_orders AS
SELECT id, product, amount
FROM orders
WHERE tenant_id = 'TENANT_A';

进阶做法

  • 应用层根据当前租户名动态拼接视图名;
  • 或使用 Row-Level Security (RLS)(PostgreSQL/SQL Server 支持),但视图仍是轻量级替代方案。

场景 4:兼容旧接口(平滑迁移)

痛点:重构数据库表结构,但老系统不能停。

实践示例:

sql 复制代码
-- 旧表:users (id, name, email, phone)
-- 新设计:拆分为 users + contact_info

-- 创建兼容视图
CREATE VIEW users AS
SELECT 
  u.id, u.name,
  c.email, c.phone
FROM users_core u
JOIN contact_info c ON u.id = c.user_id;

效果

  • 老应用继续 SELECT * FROM users; 无感知;
  • 新应用直接操作新表,逐步迁移。

场景 5:BI 与报表层抽象

痛点:报表 SQL 复杂,且需统一指标口径(如"活跃用户"定义)。

实践示例:

sql 复制代码
-- 定义统一业务指标
CREATE VIEW dim_active_users AS
SELECT user_id, reg_date, last_login
FROM users
WHERE last_login >= CURRENT_DATE - INTERVAL '30 days';

CREATE VIEW fact_daily_sales AS
SELECT 
  DATE(order_time) AS sale_date,
  SUM(amount) AS revenue,
  COUNT(*) AS orders
FROM orders
WHERE status = 'completed'
GROUP BY DATE(order_time);

效果

  • 数据分析师直接查视图,避免重复造轮子
  • 指标变更只需改视图,全公司报表自动同步。

场景 6:物化视图加速查询(高性能读)

痛点:实时聚合查询太慢(如千万级订单统计)。

实践示例(PostgreSQL):

sql 复制代码
-- 创建物化视图(存储结果)
CREATE MATERIALIZED VIEW mv_monthly_sales AS
SELECT 
  DATE_TRUNC('month', order_date) AS month,
  SUM(total) AS revenue
FROM orders
GROUP BY month;

-- 创建索引加速查询
CREATE INDEX ON mv_monthly_sales (month);

-- 定时刷新(如每天凌晨)
REFRESH MATERIALIZED VIEW mv_monthly_sales;

效果

  • 查询从 10秒 → 10毫秒
  • 适用于准实时场景(不要求秒级更新)。

10. 总结

视图 = SQL 查询的别名 + 安全屏障 + 逻辑抽象层

它不是"银弹",但在简化查询、保护数据、解耦架构方面极具价值。

合理使用视图,能让你的数据库设计更清晰、更安全、更易维护!

相关推荐
盒马coding2 小时前
高性能MySQL到PostgreSQL异构数据库转换工具MySQL2PG
数据库·mysql·postgresql
小Mie不吃饭2 小时前
Spring boot + mybatis-plus + Redis 实现数据多级缓存(模拟生产环境)
java·spring boot·redis·mysql·缓存
摇滚侠2 小时前
RocketMQ 教程丨深度掌握 MQ 消息中间件,RocketMQ 集群,笔记 28-38
数据库·笔记·rocketmq
Gobysec2 小时前
Goby 漏洞安全通告|MongoDB Zlib 信息泄露漏洞(CVE-2025-14847)
数据库·安全·mongodb·漏洞检测工具
醉风塘2 小时前
深入解析与彻底解决:MongoDB“about to fork child process”启动故障
数据库·mongodb
Psycho_MrZhang2 小时前
Web安全之SQL注入-CSRF-XSS
sql·web安全·csrf
大猫会长2 小时前
新手的postgreSQL笔记
数据库·笔记·postgresql
大鱼>2 小时前
按时间删除MongoDB中按时间命名的Collection
数据库·mongodb
机器学习算法与Python实战2 小时前
阿里千问安全审核大模型,本地部署,实测
经验分享