Skip to content

Node系列 · 数据库:视图

视图(View)是"虚拟表"——查询时按定义动态生成结果,不存储实际数据。视图能简化复杂 JOIN、控制数据访问权限、抽象业务规则——但性能与可维护性有 trade-off,需要明确何时该用、何时该拆。

一、什么是视图

视图是包装好的 SELECT 语句,对外表现像一张表:

sql
-- 创建一个视图:学生 + 班级 + 综合成绩
CREATE VIEW `v_student_score` AS
SELECT
  s.id          AS `student_id`,
  s.name        AS `student_name`,
  s.class_id    AS `class_id`,
  c.name        AS `class_name`,
  sc.subject,
  sc.score
FROM `student` s
JOIN `class` c ON c.id = s.class_id
JOIN `score` sc ON sc.student_id = s.id;

之后可以像查表一样查视图:

sql
SELECT * FROM `v_student_score` WHERE `class_name` = '高三一班';

INFO

视图不存储数据。每次查视图,MySQL 都会执行它背后的 SELECT 语句。数据始终在原表里。

二、视图的核心作用

2.1 简化复杂查询

把多表 JOIN、聚合、过滤封装成视图,业务代码直接 SELECT * FROM v_xxx

sql
-- 没有视图:每次都要写完整 JOIN
SELECT s.name, SUM(sc.score) AS total
FROM student s
JOIN score sc ON sc.student_id = s.id
WHERE s.class_id = 3
GROUP BY s.id;

-- 有视图:业务代码只关心简单查询
SELECT `student_name`, SUM(`score`) AS total
FROM `v_student_score`
WHERE `class_id` = 3
GROUP BY `student_id`;

2.2 数据权限隔离

让不同角色看到不同字段:

sql
-- 给运营看的视图:脱敏手机号
CREATE VIEW `v_student_for_ops` AS
SELECT
  s.id,
  s.name,
  s.class_id,
  CONCAT(LEFT(s.`phone`, 3), '****', RIGHT(s.`phone`, 4)) AS `phone_masked`
FROM `student` s;

-- 运营账号只有 v_student_for_ops 的权限,看不到真实手机号

2.3 抽象业务规则

"近 30 天活跃用户" 这种业务口径经常变。封装成视图,调用方不感知:

sql
CREATE VIEW `v_active_users_30d` AS
SELECT DISTINCT `user_id`
FROM `login_log`
WHERE `created_at` >= DATE_SUB(NOW(), INTERVAL 30 DAY);

三、创建与管理视图

3.1 创建

sql
CREATE VIEW `v_xxx` AS
SELECT ...;

3.2 替换已有视图

sql
CREATE OR REPLACE VIEW `v_xxx` AS
SELECT ...;

3.3 删除

sql
DROP VIEW IF EXISTS `v_xxx`;

3.4 查看视图定义

sql
SHOW CREATE VIEW `v_xxx`;

四、视图的限制

4.1 视图不一定能更新

视图背后是 SELECT,有些视图是只读的,不能 INSERT / UPDATE / DELETE

视图定义是否可更新
单表、无聚合、无 DISTINCT、无 GROUP BY✅ 可更新
多表 JOIN❌ 不可更新
含聚合函数(COUNT / SUM❌ 不可更新
DISTINCT / GROUP BY / HAVING❌ 不可更新
UNION❌ 不可更新
子查询引用 FROM 的表❌ 不可更新

WARNING

MySQL 允许"看似可更新"的视图背后静默失败——比如 INSERT INTO v_xxx 没报错但实际没生效。生产环境对视图的写操作要谨慎,最好绕过视图直接改原表。

4.2 视图性能不一定好

每次查视图都执行一次底层 SELECT。如果视图背后 JOIN 了 5 张表又没建索引,比直接写 JOIN 更慢。

sql
EXPLAIN SELECT * FROM `v_student_score` WHERE `class_name` = '高三一班';
-- type=ALL + rows=100 万 → 全表扫描,性能差

优化方法

  • 给底层表的关键字段建索引
  • 视图里加 WHERE 条件过滤
  • 物化视图(MySQL 不支持,但可以定时 INSERT INTO 一张真实表)

4.3 视图嵌套有上限

MySQL 视图嵌套层数有上限(默认不高),复杂业务不要套太多层。

五、视图 vs 临时表 vs 子查询

维度视图临时表子查询
存储不存储(虚拟)存储在内存 / 磁盘不存储
复用多查询可复用会话内复用单查询内
索引不能加索引能加索引不能
适用稳定抽象层复杂中间计算一次性查询
sql
-- 视图:稳定抽象,全局可复用
CREATE VIEW v_top_students AS
SELECT student_id, AVG(score) AS avg_score
FROM score
GROUP BY student_id
HAVING avg_score > 90;

-- 临时表:会话内的中间结果,可建索引
CREATE TEMPORARY TABLE tmp_score AS
SELECT student_id, AVG(score) AS avg_score
FROM score
GROUP BY student_id;
ALTER TABLE tmp_score ADD INDEX idx_avg (avg_score);

-- 子查询:一次性
SELECT * FROM (
  SELECT student_id, AVG(score) AS avg_score
  FROM score
  GROUP BY student_id
) t WHERE avg_score > 90;

六、Node 端使用视图

视图对应用层是透明的——当表用:

javascript
const mysql = require('mysql2/promise');

// 视图对 Node 来说就是一张普通表
const [rows] = await pool.execute(
  'SELECT * FROM v_student_score WHERE class_id = ? ORDER BY score DESC LIMIT 10',
  [3]
);

七、何时用视图

适合用

  • 复杂 JOIN 的复用(不同业务都要查同一份数据)
  • 权限隔离(不同角色看不同字段)
  • 业务口径封装("活跃用户"等)

不适合

  • 性能关键的查询(视图不能加索引,复杂视图性能差)
  • 频繁变更的业务逻辑(改视图定义比改应用代码麻烦)
  • 多层嵌套(层数多难调试)

八、小结

  • 视图 = 包装好的 SELECT,对外是虚拟表;不存储数据
  • 三大作用:简化查询 / 权限隔离 / 抽象业务规则
  • 多表 JOIN、聚合、DISTINCT 视图不能更新
  • 视图性能 = 底层 SELECT 性能;复杂视图要靠索引优化
  • 与临时表 / 子查询的取舍:视图是稳定抽象层,临时表是会话中间计算,子查询是一次性