建视图就一句 CREATE VIEW 名字 AS SELECT ...,没有别的必填项。
IF NOT EXISTS 是「已存在就跳过」,不是「覆盖」,这个名字容易误会。
OR REPLACE、ALGORITHM、WITH CHECK OPTION 三个选项沙箱全没有,但都得知道。
12.2 讲三件事:权限、怎么建、注意事项。这一篇按「能跑的先跑,跑不了的贴原码」走。
上一篇证明了视图是「存下来的 SELECT」,这一篇就是怎么把这句话存进去。
重点在两个地方:ALGORITHM 影响性能,WITH CHECK OPTION 影响写入安全。
一、最小写法
一句就够,没有必填选项:
CREATE VIEW v1 AS SELECT id, name, price FROM tb_goods WHERE stock < 50;
跑出来四行:电钻、角磨机、交换机、测线仪,库存都低于 50。
WHERE 写在视图里,以后查它就不用再写条件。
IF NOT EXISTS 可以加,但它的意思是「已存在就算了」。
重名时不报错,也不改原视图——它不是「覆盖」,是「跳过」。
二、列名要不要唯一:两边不一样
教材的注意事项里有一条:视图的列名不能重复。 这句话在 MySQL 里是对的,在沙箱里不成立。
| 环境 | 两个 id 的视图 |
|---|---|
| 沙箱 | 能建,第二列自动改名 id:1 |
| MySQL | 报 Duplicate column name 'id' |
沙箱不但不拦,还帮你把第二列改名成 id:1,好让两列区分开。
看起来贴心,其实是在埋雷:按 id 取值的地方只能拿到第一列。
所以这条规矩仍然要守:两个同名列各起一个别名。
视图的列名就是它的接口,接口别留重名。
三、改视图:OR REPLACE 沙箱没有
MySQL 可以一句替换掉旧定义:
CREATE OR REPLACE VIEW v1 AS
SELECT id, name, price, stock FROM tb_goods;
沙箱报 near "OR": syntax error——SQLite 没有这个关键字。
改视图只能两步:先 DROP VIEW,再 CREATE VIEW。
两步之间有一个空档,这时候查这张视图会报「没有这张表」。
MySQL 那一句是原子的,中间不出现空档,这就是它存在的理由。
线上改视图这件事,原子和不原子差别很大:前者用户察觉不到,后者可能有一瞬间的报错。
四、ALGORITHM:合并还是临时表
MySQL 建视图时可以指定算法,这个选项影响性能,教材讲得比较简。
| 取值 | 意思 |
|---|---|
MERGE |
视图定义展开进外层 SQL |
TEMPTABLE |
先算进临时表 |
UNDEFINED |
让优化器自己选 |
MERGE 等于「把视图那句话原样拼到外层」,所以外层的条件能推进去。
TEMPTABLE 等于「先算完再说」,外层的条件只能筛临时表。
这个选项的价值只有一条:外层条件能不能推进去,决定它到底要读多少行。
沙箱没有这个选项,但它实际上按 MERGE 走,证据就是计划一模一样。
反例也能造出来:子查询里带 ORDER BY 时,计划里多出一步 USE TEMP B-TREE。
SCAN tb_goods
USE TEMP B-TREE FOR ORDER BY
这两行就是「临时表」的影子:优化器没法把带排序的子查询完全展开,得先排一遍。
所以想让 MERGE 生效,视图里别写 ORDER BY——顺序留给外层决定。
这也是教材那条「视图里不要用 ORDER BY」的真正原因。
五、WITH CHECK OPTION:管住能不能改
先想一个问题:往 WHERE price >= 100 的视图里插一条 price = 5 的记录,会怎样?
插得进去,但插完在视图里看不见——因为它不满足条件,等于凭空消失。
CREATE VIEW v3 AS SELECT id, price FROM tb_goods WHERE price >= 100
WITH CHECK OPTION;
加上这个选项,这种插入会被直接拒绝。
它保证「写进去的东西一定还看得见」,视图的可见范围和可写范围就对齐了。
沙箱报 near "WITH": syntax error,没有这个能力。
而且沙箱连视图都不许改,所以这个问题在沙箱里根本不会出现。
要记住的是概念:视图的「可见范围」和「可写范围」是两件可以不一致的事。
六、权限:沙箱没有这一层
建视图本身是一项权限,查视图还要两个权限。
| 要做什么 | MySQL 要的权限 |
|---|---|
| 建视图 | CREATE VIEW |
| 查视图 | 视图的 SELECT + 基表的 SELECT |
| 改视图数据 | 基表的 INSERT/UPDATE/DELETE |
| 删视图 | 视图的 DROP |
第二行值得琢磨:查视图要两个权限。
因为视图只是个壳子,真正的数据在基表里,两边都得放行。
还有一个 DEFINER:视图记着「是谁建的」,默认按建的人的身份去查基表。
所以建视图的账号一被删,视图可能整个失效——这是运维上真实会踩的坑。
沙箱没有账号体系,这一整节都只能贴原码。
能演示的只有一句:没有权限这一层,视图就纯粹是「查询的别名」。
七、动手 Demo:一句建视图,三个沙箱没有的选项
这一段从最小写法开始,依次试 IF NOT EXISTS、重名列、OR REPLACE、
ALGORITHM 与 WITH CHECK OPTION。凡是沙箱没有的,都把报错原文打出来,再贴 MySQL 原码。
import sqlite3
c = sqlite3.connect(u":memory:")
# ① 最小写法,加 IF NOT EXISTS 不报错也不改原视图
c.execute(u"CREATE VIEW v1 AS SELECT id, name, price "
u"FROM tb_goods WHERE stock < 50")
c.execute(u"CREATE VIEW IF NOT EXISTS v1 AS SELECT id FROM tb_goods")
# ② 两列同名:沙箱不拦,自动改名
c.execute(u"CREATE VIEW v_join AS SELECT g.id, c.id, g.name "
u"FROM tb_goods g JOIN tb_cat c ON c.id=g.cat_id")
print(c.execute(u"PRAGMA table_info(v_join)").fetchall())
# ③ CREATE OR REPLACE:沙箱没有
try:
c.execute(u"CREATE OR REPLACE VIEW v1 AS SELECT id FROM tb_goods")
except sqlite3.OperationalError as e:
print(e)
# ④ 带 ORDER BY 的子查询会多出一步临时表
print(c.execute(u"EXPLAIN QUERY PLAN SELECT * FROM "
u"(SELECT id FROM tb_goods ORDER BY price)")
.fetchall())
# ⑤ WITH CHECK OPTION:沙箱没有
try:
c.execute(u"CREATE VIEW v3 AS SELECT id, price FROM tb_goods "
u"WHERE price >= 100 WITH CHECK OPTION")
except sqlite3.OperationalError as e:
print(e)
实跑输出:
--------------------------------------------------------------------
第五十五课:建视图的规矩,和三个沙箱没有的选项
--------------------------------------------------------------------
**教材 12.2 讲三件事:权限、怎么建、注意事项。**
**这一课按「能跑的先跑,跑不了的贴原码」的顺序走。**
**中间三个选项(`OR REPLACE`、`ALGORITHM`、`WITH CHECK OPTION`)沙箱全不支持,**
**但它们是教材的重点,所以逐个说清「它解决什么问题」。**
一、最小写法
--------------------------------------------------------------------
mysql> CREATE VIEW v1 AS SELECT id, name, price FROM tb_goods WHERE stock < 50
mysql> SELECT * FROM v1
id name price
---------------
2 电钻 380
3 角磨机 260
9 交换机 680
10 测线仪 150
4 rows in set
**一句 `CREATE VIEW 名字 AS SELECT ...` 就完了,没有别的必填项。**
**`IF NOT EXISTS` 可以加,作用是不让重名报错:**
mysql> CREATE VIEW IF NOT EXISTS v1 AS SELECT id FROM tb_goods
**加上之后不报错,也不改原视图——它只是「已存在就算了」。**
**这个名字容易误会:它不是「覆盖」,是「跳过」。**
二、列名要不要唯一:两边不一样
--------------------------------------------------------------------
**两张表都有 `id`。教材说这样会报「列名重复」,沙箱却建成功了:**
CREATE VIEW v_join AS
SELECT g.id, c.id, g.name
FROM tb_goods g JOIN tb_cat c ON c.id=g.cat_id
mysql> SELECT * FROM v_join LIMIT 3
id id:1 name
--------------
1 1 螺丝刀
2 1 电钻
3 1 角磨机
3 rows in set
mysql> PRAGMA table_info(v_join)
cid name type notnull dflt_value pk
--------------------------------------
0 id INTEGER 0 NULL 0
1 id:1 INTEGER 0 NULL 0
2 name TEXT 0 NULL 0
3 rows in set
**不但建成了,还能查——而且它自动把第二列改名成 `id:1`,好让两列区分开。**
**`PRAGMA table_info` 里也是 `id` 和 `id:1` 两行,一样没拦。**
[教材原码 · 本沙箱不执行]
| CREATE VIEW v_join AS
| SELECT g.id, c.id, g.name FROM tb_goods g
| JOIN tb_cat c ON c.id = g.cat_id;
教材原码,MySQL 到这一句直接报 Duplicate column name 'id'
**所以这条「注意事项」在沙箱里根本不会触发,但它仍然值得守。**
**因为「按列名取值」的地方遇到重名一定出问题:应用代码取 `row['id']` 只能拿到一个。**
**解法是给其中一个起别名,或者干脆两个都起:**
mysql> DROP VIEW v_join
CREATE VIEW v_join AS
SELECT g.id AS gid, c.id AS cid, g.name
FROM tb_goods g JOIN tb_cat c ON c.id=g.cat_id
mysql> SELECT * FROM v_join LIMIT 3
gid cid name
--------------
1 1 螺丝刀
2 1 电钻
3 1 角磨机
3 rows in set
**视图的列名就是它的接口,接口别留重名。**
**沙箱不拦你,是在帮你埋一个到上层才爆的雷。**
三、改视图:OR REPLACE 沙箱没有
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| CREATE OR REPLACE VIEW v1 AS
| SELECT id, name, price, stock FROM tb_goods;
教材原码,MySQL 用一句就替换掉旧定义
**沙箱报的是 `near "OR": syntax error`。**
**SQLite 没有 `OR REPLACE`,改视图只能两步:先 `DROP VIEW`,再 `CREATE VIEW`。**
**两步之间有一个空档,这时候查这张视图会报「没有这个视图」。**
**MySQL 那一句是原子的,中间不会出现空档,这就是它存在的理由。**
**教材把它放在「注意事项」里,其实它是个挺实用的差别。**
四、ALGORITHM:合并还是临时表
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| CREATE ALGORITHM = MERGE VIEW v2 AS
| SELECT id, name FROM tb_goods WHERE price >= 100;
教材原码,MERGE 表示把视图展开进外层查询
取值 意思
---------------------------------------
`MERGE` 把视图的定义展开进外层 SQL
`TEMPTABLE` 先把结果算进一张临时表
`UNDEFINED` 让优化器自己选(默认)
**`MERGE` 等于「把视图那句话原样拼到外层」,所以外层的条件能推进去。**
**`TEMPTABLE` 等于「先算完再说」,外层条件只能筛临时表,推不进去。**
**沙箱没有这个选项,但它实际上总是按 `MERGE` 走的。**
**证据就是上一课那张计划表:查视图和直接查基表,计划一模一样。**
计划里的两步
----------------------------
SCAN tb_goods
USE TEMP B-TREE FOR ORDER BY
**第二行 `USE TEMP B-TREE FOR ORDER BY` 就是「临时表」的影子。**
**只要子查询里带了 `ORDER BY`,优化器就没法把它完全展开,得先排一遍。**
**所以想让 `MERGE` 生效,视图里别写 `ORDER BY`——顺序留给外层决定。**
**这也是教材「注意事项」里那条「视图里不要用 ORDER BY」的真正原因。**
五、WITH CHECK OPTION:管住能不能改
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| CREATE VIEW v3 AS SELECT id, price FROM tb_goods WHERE price >= 100
| WITH CHECK OPTION;
教材原码,MySQL 保证改完的数据还在视图范围内
**沙箱报的是 `near "WITH": syntax error`。**
**这个选项解决的问题是:往视图里插一条 `price = 5` 的记录,插进去之后**
**它在视图里看不见(不满足 `price >= 100`),等于凭空消失。**
**加上这个选项,这种插入会被直接拒绝。**
**沙箱连视图都不让改,所以这个问题在沙箱里根本不会出现。**
**这一条要记住的是概念:视图的「可见范围」和「可写范围」是两件事。**
六、权限:沙箱没有这一层
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| GRANT CREATE VIEW ON db.* TO 'dev'@'localhost';
教材原码,建视图本身是一项权限
要做什么 MySQL 要的权限
-----------------------------------------------
建视图 `CREATE VIEW`
查视图 视图的 `SELECT` + 基表的 `SELECT`
改视图数据 基表的 `INSERT`/`UPDATE`/`DELETE`
删视图 视图的 `DROP`
**第二行值得琢磨:查视图要两个权限,视图自己的和基表的。**
**因为视图只是个壳子,真正的数据在基表里。**
**还有一个 `DEFINER` 的概念:视图记着「是谁建的」,默认按建的人的身份去查基表。**
**所以建视图的账号一旦被删,视图可能整个失效——这是运维上真实会踩的坑。**
**沙箱没有账号体系,这些一条都演示不了。**
**能演示的只有一句:没有权限这一层,视图就纯粹是「查询的别名」。**
第一组两条语句都不报错,但 v1 还是原来那句——IF NOT EXISTS 是跳过,不是覆盖。
第二组 PRAGMA table_info 打出三行:id、id:1、name。
重名不但没被拦,还被引擎自动改了个名字。
第三组和第五组都是语法错误:near "OR" 和 near "WITH"。
这两条说明沙箱缺的是关键字,不是能力——它连这个概念都没有。
第四组的计划里有 USE TEMP B-TREE FOR ORDER BY,这就是「临时表」的样子。
ALGORITHM 三种取值的差别,在沙箱里就靠这一行体现。
30 秒实验
- 把
IF NOT EXISTS去掉再跑一次,看重名报什么错。 - 把
v_join里两个id都起别名,再看PRAGMA table_info的输出。 - 把子查询的
ORDER BY去掉,看USE TEMP B-TREE那一行还在不在。
常见误区
- 把
IF NOT EXISTS当成OR REPLACE用,改了半天发现视图根本没变。 - 在视图里写两个同名列,沙箱能建就以为没问题,上层取值只拿到一个。
- 视图里写
ORDER BY想让结果有序,结果多了一次排序、外层还不一定保序。
自测三问(点开对答案)
1. `CREATE VIEW IF NOT EXISTS` 和 `CREATE OR REPLACE VIEW` 差在哪? —— 前者是「已存在就跳过」,原视图一个字不改;后者是「存在就替换」。 沙箱只有前者,要替换得先 `DROP` 再 `CREATE`。 2. `ALGORITHM=MERGE` 和 `TEMPTABLE` 的实际差别是什么? —— `MERGE` 把视图定义展开进外层 SQL,外层的条件能推进去; `TEMPTABLE` 先把结果算进临时表,外层条件只能筛临时表,通常更慢。 3. `WITH CHECK OPTION` 解决什么问题? —— 防止把「写进去之后在视图里看不见」的行插进去。 它让视图的可写范围和可见范围保持一致,越界的写入直接拒绝。视图建好了,下一篇讲怎么查定义、怎么改、怎么删,以及为什么沙箱里一个视图都改不了数据。
- ☐ (本篇完,下一篇 12.3 视图操作)