原创

MySQL VIEW 视图完整教程:创建、修改、可更新规则与 CHECK OPTION

几张报表都要重复写“只统计已支付订单、按天求和”,可以把这段查询保存成视图。以后查询视图,拿到的是统一口径的结果;但它不是自动保存一份统计数据,也不意味着报表突然拥有了缓存。

这篇用一个完整练习走完创建、查询、修改和删除,再分别更新聚合视图与简单过滤视图,看哪些写入真正允许。以下SQL在独立MySQL8.4.11实例运行。

先把原始订单准备好

CREATE DATABASE view_practice CHARACTER SET utf8mb4;
USE view_practice;
CREATE TABLE sales_order (
    order_id BIGINT NOT NULL,
    customer_id BIGINT NOT NULL,
    paid_amount DECIMAL(10,2) NOT NULL,
    state VARCHAR(20) NOT NULL,
    created_at DATETIME NOT NULL,
    PRIMARY KEY(order_id),
    KEY idx_view_state_time(state,created_at),
    CONSTRAINT chk_view_amount CHECK(paid_amount>=0)
) ENGINE=InnoDB;
INSERT INTO sales_order VALUES
(1,101,100.00,'paid','2026-10-20 09:00:00'),
(2,102,50.00,'paid','2026-10-20 10:00:00'),
(3,103,40.00,'canceled','2026-10-20 11:00:00'),
(4,104,80.00,'paid','2026-10-21 09:00:00');

已支付记录只认state等于paid,取消订单不会贡献支付笔数或金额。后面的日期表达式直接按这张DATETIME中的业务日期解释,不在查询里额外转换时区。

保存日报查询

CREATE SQL SECURITY INVOKER VIEW daily_sale AS
SELECT DATE(created_at) AS sale_day,
       COUNT(*) AS paid_orders,
       SUM(paid_amount) AS paid_amount
FROM sales_order
WHERE state='paid'
GROUP BY DATE(created_at);

SELECT sale_day,paid_orders,paid_amount
FROM daily_sale
ORDER BY sale_day;

返回两天:10月20日两笔、150.00;10月21日一笔、80.00。视图保存的是查询定义,调用方仍应在自己的查询里明确ORDER BY,不能把某次看到的顺序当作持久属性。

SQL SECURITY INVOKER明确使用调用者权限检查底层对象。应用账号需要具备这条读取链路所需的权限,不能只看到“有权SELECT视图”就假定必然可以读取基表。采用DEFINER模式属于另一种权限安排,应检查实际定义及定义者账号,而不是为了消除权限错误随意切换。

SHOW CREATE VIEW可以查看当前保存的定义:

SHOW CREATE VIEW daily_sale;

不要只根据视图名字猜它统计了哪些状态。线上若经历过多次改动,实际WHERE、列名与开发文档可能已经不同。

改基表,视图是否要刷新

本练习连接采用自动提交。直接修改第一笔订单,再查询日报:

UPDATE sales_order SET paid_amount=120.00 WHERE order_id=1;
SELECT sale_day,paid_orders,paid_amount FROM daily_sale ORDER BY sale_day;

10月20日现在是170.00,不需要执行刷新命令。视图没有另外保留一份150.00的历史结果。

这仍然遵守普通事务的可见性。如果另一个连接在REPEATABLE READ事务里已经建立了读视图,继续查询可能看到原来的版本;那是事务隔离,不是视图缓存失效。先确认连接的事务与提交状态,再判断是否真的读到了错误数据。

同样,视图不能单独替查询结果建立普通索引。本例的过滤与读取仍落在sales_order上,索引也定义在基表。需要优化时应查看实际执行计划;把一条很慢的GROUP BY包进VIEW,并没有提前替你完成计算。执行时即使使用临时结果,也不等于拥有可跨查询复用的物化汇总。

聚合结果不能随意反向更新

尝试把日报金额改成0:

UPDATE daily_sale SET paid_amount=0 WHERE sale_day='2026-10-20';

这条报1288,目标视图不可更新。一天170.00来自多条原始记录,数据库不能根据“总额改成0”猜出应该怎样修改每笔订单。

包含聚合、GROUP BY等结构的这个日报视图不具备逐行对应的更新关系。不能据此说所有视图都只读,也不能把它改成SELECT *就默认所有插入、更新都一定可用;可更新与可插入还有各自要求。

简单过滤视图可以写,但过滤条件需要保护

再建一个逐笔视图,保留所有必需字段,并要求从这个入口写入后仍满足paid条件:

CREATE SQL SECURITY INVOKER VIEW paid_order AS
SELECT order_id,customer_id,paid_amount,state,created_at
FROM sales_order
WHERE state='paid'
WITH CHECK OPTION;

UPDATE paid_order SET paid_amount=55.00 WHERE order_id=2;
SELECT order_id,paid_amount,state FROM sales_order WHERE order_id=2;

这次更新成功,基表第二笔金额也是55.00。数据没有被写进另一个“视图表”,修改最终仍落在原始订单上。

如果通过这个视图把订单移出paid集合:

UPDATE paid_order SET state='canceled' WHERE order_id=2;

本例报1369,CHECK OPTION拒绝让更新后的行违反视图过滤条件。它解决的是这个写入入口的可见范围,不是给基表永久追加同一条约束。

为确认这一点,直接操作基表:

UPDATE sales_order SET state='canceled' WHERE order_id=2;
SELECT order_id FROM paid_order WHERE order_id=2;
SELECT sale_day,paid_orders,paid_amount FROM daily_sale ORDER BY sale_day;

基表修改成功,paid_order查不到第二笔了。10月20日日报剩一笔、120.00;10月21日仍一笔、80.00。直接更新基表不会被这个视图的CHECK OPTION拦截。若某条规则要求所有写入口都遵守,需要在真正的数据约束与业务流程中落实,不能只给一个视图加条件。

过滤视图的写入也应尊重事务、唯一键和权限规则。本文验证的是两种视图的具体行为,没有提供允许应用绕过订单状态机随意改已支付金额的业务接口。

修改定义,和修改数据不是同一件事

假设日报页面现在只显示支付金额达到100的日期,替换定义:

CREATE OR REPLACE SQL SECURITY INVOKER VIEW daily_sale AS
SELECT DATE(created_at) AS sale_day,
       COUNT(*) AS paid_orders,
       SUM(paid_amount) AS paid_amount
FROM sales_order
WHERE state='paid'
GROUP BY DATE(created_at)
HAVING SUM(paid_amount)>=100;

SELECT sale_day,paid_orders,paid_amount FROM daily_sale ORDER BY sale_day;
SELECT COUNT(*) AS source_orders FROM sales_order;

视图现在只返回10月20日的一笔、120.00,原始订单仍有四条。被HAVING过滤的10月21日没有被删除,只是不再属于这个定义的结果。

CREATE OR REPLACE替换已存在的定义,ALTER VIEW也可用于修改现有视图。发布前要检查调用方:修改列名、类型、结果粒度或过滤范围,都可能改变报表与接口含义。这里重复写出SQL SECURITY INVOKER,避免只关注SELECT正文而忽略访问模式。

可以直接看元数据核对两个视图:

SELECT TABLE_NAME,IS_UPDATABLE,CHECK_OPTION,SECURITY_TYPE
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA=DATABASE()
  AND TABLE_NAME IN ('daily_sale','paid_order')
ORDER BY TABLE_NAME;

daily_sale不可更新,paid_order可更新且有CHECK OPTION;两者都是INVOKER模式。元数据有助于排查配置,但实际写入仍要受具体操作与字段限制,不能仅凭一个YES就认定任意INSERT都能成功。

最后删除练习视图:

DROP VIEW paid_order,daily_sale;
SELECT COUNT(*) AS source_orders FROM sales_order;

基表依然四条。DROP VIEW删除查询对象,不是删除原始订单;CREATE、替换或删除视图属于DDL,也不应该依赖一段ROLLBACK把这些定义操作恢复。维护视图时保留前一版定义及调用方验证结果,比把它误当成可随时回滚的数据修改更可靠。

正文到此结束
评论插件初始化中...
Loading...