news 2026/9/10 9:46:14

TiDB 视图(VIEW)功能的设计与实现:从元数据建模到 SQL 查询展开

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TiDB 视图(VIEW)功能的设计与实现:从元数据建模到 SQL 查询展开

TiDB 视图(VIEW)功能的设计与实现:从元数据建模到 SQL 查询展开

【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb

本指南以 View Support 设计提案 为核心脉络,结合 TiDB 当前仓库中 视图元数据定义、CREATE VIEW 执行器 与 查询计划构建器 的源码实现,系统讲解视图从 DDL 解析、元数据存储到 SELECT 展开的完整技术链路。读完本文,你将理解 TiDB 为何用TableInfo.View指针区分视图与基表、ViewInfo各字段的语义与序列化方式,以及视图列在基表结构变更后如何保持"冻结"特性。

视图不存储数据,而是由一条查询定义的可搜索数据库对象,常被称为"虚拟表",可对两张及以上基表做 JOIN 并仅暴露子集信息。

一、为什么要实现视图:背景与目标

在关系数据库中,视图(View)是由一条查询定义的、可被检索的数据库对象。视图本身不存储任何数据,因此常被称为"虚拟表"——它可以像表一样出现在SELECT语句中,把两张及以上表的 JOIN 结果或某张表的字段子集封装为一个逻辑对象,从而抽象并隐藏复杂查询

设计文档 2018-10-24-view-support.md 给出的经典场景是创建一本"热门书籍"视图:

CREATE OR replace VIEW popularbooks AS SELECT isbn, title, author, publishdate FROM books WHERE ispopular = 1;

创建后即可把视图当作普通表查询,甚至叠加任意SELECT子句(如GROUP BY):

SELECT Author, Title FROM PopularBooks ORDER BY Author;

TiDB 引入视图功能的核心目标是:让 SQL 更易书写(make SQL easier to write),同时提升与 MySQL 的兼容性,而不影响既有功能。该提案将范围收敛在"基础 VIEW 特性"上,只覆盖四类 DDL 操作:

  • CREATE OR REPLACE VIEW
  • SELECT FROM VIEW
  • DROP VIEW
  • SHOW TABLE STATUS

其余进阶特性(如可更新视图、ALTER VIEW等)被列入兼容性清单,留待后续版本讨论(详见文末实施计划)。

二、元数据建模:ViewInfoTableInfo.View

TiDB 引入ViewInfo来存储视图元数据,并在TableInfo上新增一个名为View*ViewInfo属性。判定规则极简:若TableInfo.View != nil,则该TableInfo是视图;否则是基表。

提案中的ViewInfo结构定义如下:

type ViewInfo struct { Algorithm ViewAlgorithm `json:"view_algorithm"` Definer UserIdentity `json:"view_definer"` Security ViewSecurity `json:"view_security"` SelectStmt string `json:"view_select"` CheckOption ViewCheckOption `json:"view_checkoption"` Cols []model.CIStr `json:"view_cols"` }

该设计最终完整落地于当前仓库 pkg/meta/model/table.go,类型引用演化为ast.ViewAlgorithmauth.UserIdentityast.ViewSecurityast.ViewCheckOptionast.CIStr

// ViewInfo provides meta data describing a DB view. type ViewInfo struct { Algorithm ast.ViewAlgorithm `json:"view_algorithm"` Definer *auth.UserIdentity `json:"view_definer"` Security ast.ViewSecurity `json:"view_security"` SelectStmt string `json:"view_select"` CheckOption ast.ViewCheckOption `json:"view_checkoption"` Cols []ast.CIStr `json:"view_cols"` }

各字段语义与设计要点如下。

1. Algorithm:视图算法特征

对应视图 SQL 的ALGORITHM特性,取值应为UNDEFINEDMERGETEMPTABLE三者之一;当CREATE VIEW未携带ALGORITHM子句时,默认值为UNDEFINED。枚举定义在 pkg/parser/ast/model.go:

// ViewAlgorithm values. const ( AlgorithmUndefined ViewAlgorithm = iota AlgorithmMerge AlgorithmTemptable )

提案明确:首版只实现MERGE算法(即将视图的SELECT语句与外部查询合并后统一优化执行),TEMPTABLE(先物化临时表再查询)列为 P3 阶段能力。

2. Definer:视图定义者

'user_name'@'host_name'格式记录创建视图的账号。在创建视图时会将其写入ViewInfo.Definer与视图自身的 SQL SECURITY 语义绑定。

3. Security:SQL SECURITY 特征

取值仅DEFINERINVOKER,见 pkg/parser/ast/model.go。DEFINER表示按视图定义者权限解析基表访问;INVOKER表示按当前调用者权限解析。当前设计阶段仅解析并保存该值,完整的权限生效语义在 P3 阶段完善。

4. CheckOption:WITH CHECK OPTION 子句

WITH CHECK OPTION用于可更新视图:阻止向那些不满足select_statementWHERE条件的行执行插入,也阻止把满足WHERE条件的可见行更新为不满足条件的行(即禁止把可见行"改没")。LOCALCASCADED关键字决定视图基于另一视图定义时检查测试的生效范围,未给出关键字时默认CASCADED,见 pkg/parser/ast/model.go:

// ViewCheckOption values. const ( CheckOptionLocal ViewCheckOption = iota CheckOptionCascaded )

5. SelectStmt 与 Cols 的分工

这是视图元数据中较关键的一对约定:

  • SelectStmt:保存视图原始的SELECTSQL 语句字符串(创建时经规范化还原,见下文BuildViewInfo)。
  • Colsmodel.CIStr数组,保存视图的列别名(column alias names)
  • TableInfo.Columns:只保存视图列的原始列名(column origin names)

两者配合,既能在SELECT * FROM view时精确还原视图对外暴露的列集合,又能在内层把列映射回基表原始字段。

6. 判定辅助方法

在 pkg/meta/model/table.go,TableInfo同时提供三个语义明确的方法,供 DDL、Planner、Information Schema 等模块统一判定对象类型:

// IsView checks if TableInfo is a view. func (t *TableInfo) IsView() bool { return t.View != nil }

IsView()正是该设计文档中"TableInfo.View != nil即视图"约定的直接实现;同文件还提供了IsSequence()IsBaseTable()Sequence == nil && View == nil)以区分序列与基表。

三、CREATE VIEW语法支持与解析

提案仅支持如下完整语法:

CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] [DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | INVOKER }] VIEW view_name [(column_list)] AS select_statement [WITH [CASCADED | LOCAL] CHECK OPTION]

该文法是 TiDB SQL Parser 中CreateViewStmt的核心产生式。在语法定义 pkg/parser/parser.y 中可以看到完整规则与字段装配:

"CREATE" OrReplace ViewAlgorithm ViewDefiner ViewSQLSecurity "VIEW" ViewName ViewFieldList "AS" CreateViewSelectOpt ViewCheckOption ... Algorithm: $3.(ast.ViewAlgorithm), Security: $5.(ast.ViewSecurity), ... x.CheckOption = $11.(ast.ViewCheckOption)

对应 AST 节点ast.CreateViewStmt(pkg/parser/ast/ddl.go)持有Algorithm / Definer / Security / Cols / CheckOption等字段,语法树层面的类型定义与ViewInfo元数据一一对应。

创建视图的校验流程(提案 Rationale 部分)如下:

  1. 解析CREATE VIEW语句,并为其中的SELECT子句构建逻辑计划(logical plan);任何语法错误直接返回给 parser。
  2. 检查视图定义者(Definer)权限:Definer 需同时拥有CREATE_VIEW_PRIV与基表SELECT权限。提案阶段以复用CREATE_PRIV代替,完整的CREATE_VIEW_PRIV细粒度检查列在 P2 计划中。
  3. 检查ViewFieldList(即[(column_list)]列清单):若为空,则由SelectStmt子句推导视图列名;否则校验len(ViewFieldList) == len(Columns from SelectStmt),随后把列名写入TableInfo.Columns

四、视图创建的执行链路:从 Executor 到 DDL Job

设计落地于当前仓库后,CREATE VIEW不再是停留在纸面的语法,其完整执行链路如下。

1. 语法树的元数据序列化:BuildViewInfo

DDL Executor 首先调用 pkg/ddl/create_table.go 中的BuildViewInfo,把ast.CreateViewStmt转换为model.ViewInfo。关键点是SELECT语句的还原使用了受控的restore标志位:

// Always Use `format.RestoreNameBackQuotes` to restore `SELECT` statement // despite the `ANSI_QUOTES` SQL Mode is enabled or not. restoreFlag := format.RestoreStringSingleQuotes | format.RestoreKeyWordUppercase | format.RestoreNameBackQuotes var sb strings.Builder if err := s.Select.Restore(format.NewRestoreCtx(restoreFlag, &sb)); err != nil { return nil, err } return &model.ViewInfo{Definer: s.Definer, Algorithm: s.Algorithm, Security: s.Security, SelectStmt: sb.String(), CheckOption: s.CheckOption, Cols: nil}, nil

注释明确强调:无论ANSI_QUOTESSQL Mode 是否开启,始终使用format.RestoreNameBackQuotes还原 SELECT 语句,从而保证存入SelectStmt的 SQL 规范、确定且可再次解析。

2. 列信息构造与 TableInfo 组装:executor.CreateView

接着在 pkg/ddl/executor.go 的CreateView中,把视图列清单转为table.Column,通过BuildTableInfo构造TableInfo,然后把viewInfo挂到tbInfo.View上,实现设计文档"视图即带 View 元数据的表"的建模:

viewInfo, err := BuildViewInfo(s) ... cols := make([]*table.Column, len(s.Cols)) for i, v := range s.Cols { cols[i] = table.ToColumn(&model.ColumnInfo{ Name: v, ID: int64(i), Offset: i, State: model.StatePublic, }) } ... tbInfo, err := BuildTableInfo(..., s.ViewName.Name, cols, nil, tblCharset, tblCollate) ... tbInfo.View = viewInfo onExist := OnExistError if s.OrReplace { onExist = OnExistReplace } return e.CreateTableWithInfo(ctx, s.ViewName.Schema, tbInfo, nil, WithOnExist(onExist))

注意两点:

  • 视图列使用顺序 IDID: int64(i))与Offset布局,列状态直接为StatePublic——视图不承载存储,无需经历异步回填等状态机,这是它区别于普通建表的地方;
  • OR REPLACE语义通过WithOnExist(OnExistReplace)表达,与建表框架共用"存在即替换"的机制。

3. 元数据持久化:onCreateViewDDL Worker

ActionCreateView作为一个独立的 DDL 动作类型注册进 DDL Job 分发表(pkg/ddl/job_worker.go):

case model.ActionCreateView: ver, err = onCreateView(jobCtx, job)

在 pkg/ddl/create_table.go 的onCreateView中完成真正的元数据写入:

  • 通过metaMut与 infoCache 查重:同名对象已存在时,若不带OR REPLACE则报infoschema.ErrTableExists并取消 Job;
  • 支持OR REPLACE:先DropTableOrView(schemaID, oldTableID)删除旧对象,再重建;
  • 视图状态直接由StateNone推进到StatePublictbInfo.State = model.StatePublic),并通过createTableOrViewWithCheck(内部调用meta.Mutator.CreateTableOrView)落库——复用表对象的元数据写入逻辑,正是设计文档反复强调"复用 DROP TABLE / CREATE TABLE 代码逻辑"的实现印证。

值得注意的是,DDL 层对视图没有专门的物理存储与 backfill 逻辑,同一套 Job 框架、Schema 版本变更与 failpoint 注入机制均可作用于视图(例如 pkg/ddl/executor_test.go 中的TestCreateViewConcurrently通过onDDLCreateViewfailpoint 验证并发建视图的行为)。

五、SELECT FROM VIEW的实现原理:视图展开与列"冻结"

视图查询是整个功能中技术含量最高的部分。设计文档给出了一条重要的背景约束:视图在定义后应当是"冻结"(frozen)的——即视图对外暴露的列集合不随基表结构变化而漂移。

1. 问题来源:SELECT *的语义漂移

考虑如下场景(原文档示例):

create table t(a int, b int); create view v as select * from t; -- 实际改写为:select a as a, b as b from t select * from v;

若随后基表结构变更:

drop table t; create table t(c int, d int);

此时再执行select * from v,若仅把视图SelectStmt按字面重新解析,则等价于select c as c, d as d from t视图对外列集合被基表结构"污染",违背视图"定义即冻结"的语义。

2. 解决思路:顶层Projection固化列

文档给出的临时修复方案是:在原始 select 逻辑计划之上构建一个Projection,等价于把视图改写为:

select a as a, b as b from (select * from t)

即内层子查询仍按视图保存的SelectStmt展开,但外层投影的列名/列序由创建视图时固化的Cols/TableInfo.Columns决定,与当前基表字段解耦。文档同时注明:这是阶段性修复,长远计划是让 TiDB 在重写 SQL 时彻底展开并替换所有通配符(wildcard)。

3. 源码中的落地:buildDataSource的视图分支

在当前 planner 中,视图识别与展开发生在数据源构建函数里。pkg/planner/core/logical_plan_builder.go 的buildDataSource先做一次判定:

if tableInfo.IsView() { if tn.TableSample != nil { return nil, expression.ErrInvalidTableSample.GenWithStackByArgs("Unsupported TABLESAMPLE in views") } // Get the hints belong to the current view. ... return b.BuildDataSourceFromView(ctx, dbName, tableInfo, currentQBNameMap4View, currentViewHints) }

也就是说,当 FROM 子句中的TableName命中视图时,TiDB不会像普通表那样走索引路径选择(getPossibleAccessPaths之前即被拦截),而是进入专门的BuildDataSourceFromView

  • ViewInfo.SelectStmt取出创建时规范化保存的 SELECT 语句并重新解析,递归构建其逻辑计划作为内层数据源;
  • 处理嵌套视图(视图内部再引用视图)、视图上的 query block hint(currentQBNameMap4View)与 hint 匹配;
  • 结合视图元数据中外层可见列集合,保证视图对外 Schema 保持"冻结",从而解决select *随基表 DDL 漂移的问题。

视图判定的影响面远不止数据源构建:从源码可以推断,凡是需要区分"真实存储表"与"逻辑对象"的地方都显式检查了IsView()——例如 pkg/planner/core/planbuilder.go 在构造SHOW语句输出 Schema 时先判定isView再决定列布局;pkg/planner/core/plan_cacheable_checker.go 与 pkg/planner/core/point_get_plan.go 也分别对视图跳过/收敛特定优化。这种"视图是表的轻量子集、处处判定、分路处理"的架构,正是该设计文档影响至今的工程痕迹。

六、DROP VIEWSHOW TABLE STATUS

  • DROP VIEW:设计文档明确要求实现DROP VIEW语法并删除既有视图对应的TableInfo,且复用DROP TABLE代码逻辑。仓库中视图与表共用同一套元数据删除与 Job 处理框架(例如onCreateView中替换旧对象时调用的正是metaMut.DropTableOrView),可见"视图作为表对象的特殊形态"这一建模思路贯穿删除路径。

  • SHOW TABLE STATUS:修改该命令以支持展示视图状态,并把它作为校验CREATE VIEW/DROP VIEW是否成功的观测手段。在 pkg/planner/core/planbuilder.go 中,planner 会先判定目标是否为视图,再构造对应的SHOW输出 Schema,使SHOW TABLE STATUS能正确呈现视图这类无存储对象的信息。

七、兼容性目标与实施计划

兼容性方面,该提案的原则是:在不影响其他既有功能的前提下支持基础 VIEW 特性,使 TiDB 与 MySQL 更加兼容。文档原始工作清单如下:

ActionPriorityDeadlineNotes
Extract ViewAlgorithm/ViewDefiner/ViewSQLSecurity/CheckOption to CreateViewStmt structP12019/01/15--
Add ViewInfo attribute to TableInfo and add ActionCreateView to ddl actionMapP12019/01/15--
CREATE [OR REPLACE] VIEW view_name [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] [(column_list)] AS select_statementP12019/01/15必须早于其他任务完成
SHOW TABLE STATUSP12019/01/30--
DROP VIEW ViewP12019/01/30--
SELECT … FROM VIEWP12019/03/10--
为 CreateView 与 Select…From View 添加测试用例P12019/03/30从 MySQL 测试用例移植
支持 CREATE_VIEW_PRIV 检查P2
UPDATE VIEWP2较难
INSERT VIEWP2较难,依赖 UPDATE VIEW
SHOW CREATE [VIEW | TABLE]P2
ALTER VIEWP2
ALTER | DROP TABLEP2需检查表是否为视图
为 UPDATE | INSERT INTO View 添加测试用例P2
增加 INFORMATION_SCHEMA.VIEWS 视图P3
CREATE [OR REPLACE] VIEW [DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | INVOKER }] AS SELECT_STATEMENTP3
CREATE [OR REPLACE] VIEW [ALGORITHM = {TEMPTABLE}] AS select_statementP3
CREATE [OR REPLACE] VIEW AS select_statement [WITH [CASCADED | LOCAL] CHECK OPTION]P3
支持全局 sql_mode 系统变量P3

从时间线看:P1 全部为基础能力(DDL + 查询展开),必须先于其他任务完成CREATE VIEW主链路;P2 集中在可更新视图(UPDATE/INSERT)与细粒度权限、SHOW CREATE VIEWALTER VIEW等管理能力;P3 为信息架构视图(INFORMATION_SCHEMA.VIEWS)、DEFINER/SQL SECURITY完整语义、TEMPTABLE算法、CHECK OPTION生效与全局sql_mode支持等长尾兼容项。

需要说明的是,该表是 2018 年提案的原始排期。自提案落地以来,TiDB 视图能力已在此骨架上演进多年:例如当前 pkg/meta/model/table.go 还出现了MaterializedViewBaseInfo(物化视图的基表侧元数据,用于登记依赖该基表的物化视图与物化视图日志),说明视图体系已从"纯虚拟表"向"物化视图"方向扩展。以当前仓库源码为准,判断任意版本下某项能力是否生效,最直接的办法仍是查看TableInfo上的View/Sequence/MaterializedViewBaseInfo字段及 planner 中对应的分支处理。

八、小结:贯穿至今的三个设计决策

回顾该设计文档与当前仓库实现,可以提炼出三个影响深远的决策:

  1. 视图即带视图元数据的表:不新增独立的对象存储体系,仅用TableInfo.View指针标记视图,全部 DDL(创建、删除、状态推进、并发 Job、failpoint)复用表框架——IsView() 一个方法即可完成全链路类型判定。
  2. 元数据与查询能力分层ViewInfo只负责如实保存用户写下的算法/权限/检查选项与规范化后的SelectStmt;真正复杂的行为(MERGE 展开、列冻结、嵌套视图)由 planner 在BuildDataSourceFromView中按需解释,SQL 层与执行层无需感知视图的存在。
  3. 列冻结靠"外层投影 + 固化列集合"保证:即便视图内部仍是select *,对外 Schema 始终锚定创建时解析出的列,杜绝基表 DDL 对视图消费者造成语义漂移。

对希望深入代码的读者,建议按以下路径继续阅读当前仓库:先看 TableInfo.IsView 与 ViewInfo 定义,再到 executor.CreateView 与 BuildViewInfo 理解元数据如何从 AST 落到存储,最后在 logical_plan_builder.go 的视图分支 观察BuildDataSourceFromView的展开入口,即可完整串联"语法解析 → 元数据建模 → 计划展开"的视图实现全貌。

【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 9:46:05

探索 MirrorLeech Telegram Bot:一款高效镜像与资源下载助手

探索 MirrorLeech Telegram Bot:一款高效镜像与资源下载助手 项目简介 是一个由 Anasty17 开发的 Telegram 机器人,专为用户提供便捷的镜像资源下载服务。通过简单的聊天界面,用户可以请求各种类型的文件,如 APK、PDF、ZIP 等&…

作者头像 李华
网站建设 2026/9/10 9:45:39

S7-1200大型PLC项目实战:数据规划、Modbus通信与调试经验

把西门子博图(TIA Portal)里的自学Demo升级成真正能稳定运行在现场的大型项目程序,这个跨度比很多人想象的大得多。我接手过一套超市储藏环境自动控制项目,程序里二十多个FB、上百个DB变量、五路Modbus轮询,还要同时处…

作者头像 李华
网站建设 2026/9/10 9:44:37

三维WSN覆盖优化:基于麻雀搜索算法的空洞修复方案

1. 项目概述:三维WSN覆盖优化与空洞修复 在无线传感器网络(WSN)部署中,三维空间下的节点覆盖优化一直是个棘手问题。传统二维平面部署方案无法满足无人机监测、立体仓储等真实三维场景需求。我们团队最近用Matlab实现了一套基于麻…

作者头像 李华
网站建设 2026/9/10 9:43:17

CANN/ge LLM数据分发API

# TransferWithCacheKeyConfig 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE …

作者头像 李华