感谢阅读!❤️
如果这篇文章对你有帮助,欢迎 **点赞** 👍 和 **关注** ⭐,获取更多实用技巧和干货内容!你的支持是我持续创作的动力!
**关注我,不错过每一篇精彩内容!**
目录
-
- 一、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 表称为子表
- 创建表是,先创建父表,再创建子表
- 插入数据时,先插入父表,再插入子表
- 删除数据时,先删除子表,再删除父表
- 删除表时,先删除子表,再删除父表
-
示例:
- 有一个需求,要求设计一张表,存储学生及其学习信息
第一种方案:一张表
| 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 语句 |
| 可像表一样使用 | 可 SELECT、JOIN、甚至在某些条件下 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 查询的别名 + 安全屏障 + 逻辑抽象层
它不是"银弹",但在简化查询、保护数据、解耦架构方面极具价值。
合理使用视图,能让你的数据库设计更清晰、更安全、更易维护!