视图是 SQL 中最优雅的抽象工具------隐藏复杂性、增强安全性、复用逻辑。而物化视图则更进一步,用存储换性能。
1. 什么是视图?
视图(View)本质上是一个虚拟表,它不存储数据本身,而是保存了一条 SELECT 查询。每次访问视图时,数据库都会重新执行这条查询。
创建视图
sql
-- 基础语法
CREATE VIEW view_name AS
SELECT column1, column2, ...
FROM table_name
WHERE condition;
-- 示例:为员工创建一个简化视图
CREATE VIEW active_employees AS
SELECT e.emp_id, e.name, d.dept_name, e.salary
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
WHERE e.status = 'active';
使用视图
sql
-- 像查普通表一样查视图
SELECT * FROM active_employees WHERE salary > 10000;
-- 查询结果
-- emp_id | name | dept_name | salary
-- -------|---------|-----------|-------
-- 101 | 张三 | 技术部 | 15000
-- 102 | 李四 | 市场部 | 12000
2. 视图的完整 CRUD
sql
-- 创建或替换视图(标准做法)
CREATE OR REPLACE VIEW active_employees AS
SELECT e.emp_id, e.name, d.dept_name, e.salary, e.hire_date
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
WHERE e.status = 'active';
-- 修改视图(MySQL 语法)
ALTER VIEW active_employees AS
SELECT e.emp_id, e.name, d.dept_name, e.email
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id;
-- 删除视图
DROP VIEW IF EXISTS active_employees;
3. 可更新视图
在某些条件下,可以通过视图修改底层数据:
sql
-- 创建可更新视图(必须基于单张表,不包含聚合、DISTINCT、GROUP BY 等)
CREATE VIEW dept_30 AS
SELECT emp_id, name, salary, dept_id
FROM employees
WHERE dept_id = 30;
-- 通过视图更新数据(会直接影响基表)
UPDATE dept_30 SET salary = 5500 WHERE emp_id = 101;
-- 通过视图插入
INSERT INTO dept_30 (emp_id, name, salary, dept_id)
VALUES (110, '王五', 6000, 30);
-- 通过视图删除
DELETE FROM dept_30 WHERE emp_id = 110;
⚠️ 限制 :含 JOIN、聚合函数、GROUP BY、DISTINCT、UNION 的视图通常是不可更新的。MySQL 还额外要求视图没有子查询。
4. 视图的核心价值
4.1 隐藏复杂性
sql
-- 没有视图的情况下,报表查询冗长且容易出错
SELECT
c.cust_name,
o.order_date,
p.prod_name,
oi.quantity,
oi.unit_price * oi.quantity AS line_total
FROM customers c
JOIN orders o ON c.cust_id = o.cust_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.prod_id = p.prod_id
WHERE o.status = 'completed';
-- 创建视图
CREATE VIEW order_details AS
SELECT
c.cust_name,
o.order_date,
p.prod_name,
oi.quantity,
oi.unit_price * oi.quantity AS line_total
FROM customers c
JOIN orders o ON c.cust_id = o.cust_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.prod_id = p.prod_id
WHERE o.status = 'completed';
-- 后续只需简单查询
SELECT * FROM order_details WHERE cust_name = 'ABC Corp';
4.2 增强安全性
sql
-- 只暴露必要字段,隐藏敏感数据
CREATE VIEW employee_public AS
SELECT emp_id, name, dept_name, email
FROM employees;
-- 普通用户只看到视图,永远接触不到 salary、ssn 等敏感信息
GRANT SELECT ON employee_public TO reporting_user;
4.3 逻辑复用与解耦
视图表结构变更时,修改视图定义即可向后兼容:
sql
-- 原表 departments 拆分为 dept_info 和 dept_budget
-- 创建视图保持旧接口兼容
CREATE OR REPLACE VIEW departments AS
SELECT d.dept_id, d.dept_name, d.manager_id,
b.budget AS dept_budget
FROM dept_info d
LEFT JOIN dept_budget b ON d.dept_id = b.dept_id;
5. 物化视图(Materialized View)
物化视图是视图的"物理版本"------它真正存储查询结果,而不是每次重新计算。
核心区别
| 特性 | 普通视图 | 物化视图 |
|---|---|---|
| 数据存储 | 不存储 | 存储结果 |
| 查询速度 | 每次重新执行 | 直接读取快照 |
| 数据实时性 | 实时 | 可能有延迟 |
| 占用空间 | 几乎不占用 | 占用磁盘 |
| 支持索引 | 不继承 | 可建索引 |
创建物化视图
sql
-- PostgreSQL / Oracle
CREATE MATERIALIZED VIEW sales_summary AS
SELECT
p.category,
DATE_TRUNC('month', s.sale_date) AS sale_month,
SUM(s.amount) AS total_sales,
COUNT(*) AS sale_count
FROM sales s
JOIN products p ON s.prod_id = p.prod_id
GROUP BY p.category, DATE_TRUNC('month', s.sale_date);
-- MySQL 不支持物化视图(使用触发器 + 表模拟)
刷新物化视图
sql
-- 完全刷新(重建整个快照)
REFRESH MATERIALIZED VIEW sales_summary;
-- 增量刷新(仅更新变更,更快,需物化日志支持)
REFRESH MATERIALIZED VIEW CONCURRENTLY sales_summary;
-- 定时刷新(借助 pg_cron 或外部调度)
-- 每天凌晨 2 点自动刷新
为物化视图建索引
sql
-- 物化视图支持索引,显著加速查询
CREATE INDEX idx_sales_month ON sales_summary(sale_month);
CREATE INDEX idx_sales_category ON sales_summary(category);
-- 查询速度接近基表索引查询
SELECT * FROM sales_summary
WHERE sale_month >= '2026-01-01'
AND category = 'Electronics';
6. 实战场景对比
场景:统计报表
sql
-- ❌ 普通视图:每次查询都扫描全表,数据量大时几秒甚至几十秒
CREATE VIEW daily_sales AS
SELECT sale_date, SUM(amount) AS total
FROM sales GROUP BY sale_date;
-- ✅ 物化视图:预计算、可索引,毫秒级返回
CREATE MATERIALIZED VIEW daily_sales_mv AS
SELECT sale_date, SUM(amount) AS total
FROM sales GROUP BY sale_date;
-- 大量并发查询报表
SELECT * FROM daily_sales_mv WHERE sale_date = '2026-07-29';
场景:权限控制
sql
-- 视图天然适合做行级权限控制
CREATE VIEW my_dept_employees AS
SELECT * FROM employees
WHERE dept_id = (SELECT dept_id FROM users WHERE username = CURRENT_USER);
-- 用户查到的只能是本部门的员工数据
SELECT * FROM my_dept_employees;
7. 注意事项与最佳实践
- 慎用多层视图嵌套:视图套视图会极大降低可读性和性能。建议最多嵌套 1-2 层。
- 物化视图不是银弹:占用额外存储,且需要维护刷新策略。适合低频更新、高频查询的场景。
- WITH CHECK OPTION:可更新视图加此选项,防止插入/更新数据"消失"在视图外:
sql
CREATE VIEW high_salary AS
SELECT * FROM employees WHERE salary > 10000
WITH CHECK OPTION;
-- 插入 salary=8000 会报错
INSERT INTO high_salary VALUES (200, '测试', 8000);
-- > ERROR: CHECK OPTION failed
- 定期审查视图:随着业务演进,视图引用的基表可能发生变化,定期清理无用视图。
- MySQL 替代方案:MySQL 5.7+ 不支持物化视图,可使用触发器+汇总表模拟,或使用 MySQL 8.0 的函数索引加速查询。
总结
视图用得好,代码质量高一个档次------它让查询更简洁、安全、可维护。物化视图则是数据分析场景的利器,理解它们的适用边界,才能做出正确的架构选择。明天我们继续学习存储过程与函数!