<p>SELECT * 创建视图会导致运行时报错而非创建时报错,因视图仅做一次性快照,不校验字段存在性;底层表删列或改类型后,首次查询即报错,且字段顺序突变、敏感字段自动暴露、嵌套视图风险放大、契约不可追溯,必须显式列出字段以保障稳定性与安全性。</p>

SELECT * 创建视图会导致运行时报错而非创建时报错
视图创建时 SELECT * 不校验字段是否存在,只做一次性快照。底层表后续执行 ALTER TABLE DROP COLUMN email,视图本身仍能成功创建,但首次 SELECT * FROM v_users 就直接报错:Unknown column 'email' in field list。错误被推迟到运行时,且常发生在关键业务路径上,排查成本高。
更隐蔽的是字段类型变更:比如把 status VARCHAR(10) 改成 status TEXT,某些 JDBC 驱动可能截断或转换失败,却无明确提示——你得靠日志里偶发的 Data truncation 或应用层 NullPointerException 倒推问题。
- MySQL/PostgreSQL/SQL Server 全部存在该行为,不是某一家的 bug,而是设计使然
- 存储过程调用该视图再用
SELECT *查结果集,错位会穿透到应用层对象属性 - CI/CD 流水线里建视图成功 ≠ 视图可用,必须额外加运行时验证(如
SELECT 1 FROM v_xxx LIMIT 1)
字段顺序突变会让按位置取值的应用崩溃
下游若用 rs.getString(2) 或 Python 的 row[1] 按索引取字段,SELECT * 完全无法保证列序稳定。执行 ALTER TABLE MODIFY COLUMN name FIRST 后,原来第 2 列变成第 1 列,所有硬编码位置访问立刻错位——数据没丢,但取到的是完全无关的字段(比如把 phone 当成 created_at 解析)。
这种问题在报表工具、ETL 脚本、旧版 Java DAO 中极常见,且不会报错,只会静默污染数据。
- 嵌套视图放大风险:A 视图基于 B 视图,B 用了
SELECT *→ A 的列序依赖 B 的“当时快照”,B 表结构一变,A 就不可控偏移 - DBA 巡检时手动
SELECT * FROM v_xxx看不出问题,但监控脚本定时跑就崩 - 不能靠文档或注释来约定列序,数据库不认这个
新增字段会悄无声息地暴露敏感数据
SELECT * 把表里所有字段都透出,包括后来加的 password_hash、id_card、internal_note。只要视图权限开了,这些字段就自动出现在所有调用方结果里——没人记得它存在,也没人主动审计它是否该被暴露。
更麻烦的是“谁在用”无法追溯:你没法快速回答“这个视图返回哪些字段?哪些字段被下游哪几个接口强依赖?”。模糊性导致 DBA 不敢改表,开发不敢复用,安全审计时也难以确认数据契约。
- 显式写
SELECT id, name, email是把数据契约从“靠猜”变成“可验证” - 字段增删必须同步修改视图定义,这本身就是一次显式决策点
- 配合
GRANT SELECT (id, name) ON v_users TO app_user还能进一步限制列级权限(PostgreSQL/SQL Server 支持)
性能与资源浪费是附带但真实的问题
哪怕只是多查一个 avatar_blob(平均 2MB),1000 行结果就多传 2GB 数据。网络带宽、数据库 I/O、客户端内存全被拖慢——这不是理论开销,而是线上接口响应时间从 80ms 飙到 450ms 的真实案例。
覆盖索引也失效:SELECT created_at FROM orders WHERE status = 'paid' 可走 status + created_at 覆盖索引;但 SELECT * FROM orders WHERE status = 'paid' 强制回表,哪怕你只想要时间戳。
- 视图里
SELECT *不等于“方便”,它掩盖了真实访问意图:你到底要什么字段、为什么需要、会不会变 - 临时排障或本地调试可以
SELECT *,但进生产环境的视图必须明确列名 - MySQL 5.7+ 甚至拒绝创建未加表别名的
SELECT *视图,报错:View's SELECT contains a '*' wildcard with no table qualifier
真正难处理的不是语法本身,而是它让数据契约变得不可见、不可验证、不可追溯。显式列名不是多打几个字,是把“谁在用什么”钉死在 DDL 里。

















