本节摘要:语义分析是编译流水线的第二关:Driver 拿着语法树逐项核对事实——表是否存在、列在哪张表上、类型是否兼容、分区值是否合法。所有核对都依赖 Metastore 提供的元数据。本节讲清核对清单、类型系统的隐式转换规则、视图在语义层的展开,以及一次 Driver 与 Metastore 的完整 RPC 时序。
上一节的结尾留了个悬念:语法分析产出的 AST 里,orders 只是一个名字。接下来 Driver 要把每个名字换成真实对象,这个过程叫语义分析。它像译员拿到稿件后先查专业词典:人名、地名、术语一个个核对,对不上的稿件直接退回,绝不带着错误下车间。
对一条普通查询,语义关要核对的项目比想象中多:
这些核对全部依赖 Metastore。Driver 在编译期会发起一串 Thrift RPC:getDatabase、getTable、getPartitions 或按前缀 listPartitionsByFilter 等。对大分区表,这一步的耗时不可忽视——百万分区的表,光拿分区清单就可能拖慢编译十几秒,这也是第 8 章讲分区数量治理的原因之一。
注意时序图最后一行:编译期 Driver 只碰元数据,不碰数据文件。所以"表元数据在但 HDFS 文件被误删"的表,编译照样通过、EXPLAIN 照样出计划,任务跑起来才报找不到文件。反过来,"元数据被删但数据还在"的表,查询直接死在语义关——两种"表坏了"的修法完全不同,前者修数据,后者要靠备份或 msck 重建分区。
语义关里最容易踩坑的是类型核对。Hive 的类型分族:数值族(TINYINT、INT、BIGINT、DECIMAL、DOUBLE)、字符族(STRING、VARCHAR、CHAR)、时间族(TIMESTAMP、DATE)、复合类型(ARRAY、MAP、STRUCT)。
比较运算两侧类型不一致时,Hive 按"族内提升、跨族转字符串慎用"的思路做隐式转换:
SELECT * FROM orders WHERE customer_id = 1001;
如果 customer_id 是 STRING,字面量 1001 会被转成字符串再比较,语义上没错。但下面这个就危险了:
SELECT * FROM events WHERE event_ts = 1767139200;
event_ts 若是 STRING 类型的 '2026-01-01 00:00:00',这条件永远为假——字符串与数字比较时把字段转成 DOUBLE,日期文本转出来是 0 或不可比,查询安静地返回空集,不报任何错。语义关只拦"转不了",不拦"转了但含义荒谬"。生产上的纪律是:等值比较两侧显式同型,时间戳比较永远 CAST 或改写为字符串字面量。
DECIMAL 与 DOUBLE 的选择也在这关有回响。金额列用 DECIMAL(10,2) 是行业共识,SUM 之后精度受控;若用 DOUBLE,聚合时浮点误差会在对账时冒头。语义分析不会替你做这个决定,它只保证你写下的类型组合能编译通过。
视图在语义分析阶段被展开。你查询一个视图,Driver 取回视图的定义语句,把它当作子查询嵌进你的 AST,再做全套核对。由此推出视图的三个工程特性:
第一,视图不存数据。它是编译期的宏,查询视图的执行计划等价于查询其定义展开后的计划——EXPLAIN 一个视图查询,你在输出里看到的是底层表的 TableScan,找不到任何"视图算子"。
第二,视图是逻辑重组工具,不是性能工具。它能让复杂 JOIN 的口径固化为一个名字,让下游统一取数口径;但它不缓存结果。要缓存,得用物化视图(Hive 3 的能力,第 7 章提)或干脆 INSERT 到汇总表。
第三,视图有编译期绑定风险。底层表加了列,视图定义若是 SELECT 星号则含义漂移;底层表改了列名,视图直接编译失败。核心视图纳入变更管理,是数仓治理的基本动作。
把这一关的常见报错列成清单,排错时先归类再动手:
| 报错关键词 | 含义 | 常见原因 |
|---|---|---|
| Invalid table 或 Database not found | 表或库不存在 | 库名前缀错、库未创建、Metastore 连接错环境 |
| Column cannot be resolved | 列解析失败 | 列名拼错、多表同名列未加限定、别名作用域错误 |
| cannot be applied to | 类型不兼容 | 函数参数类型错误、比较两侧无法转换 |
| Both left and right aliases | JOIN 条件引用了不在此侧的别名 | 复杂 JOIN 的 ON 条件写错侧 |
| Partition not found | 静态分区值不存在或非法 | 分区值类型或格式与定义不符 |
一个实用习惯:把 SemanticException 类报错当作"词典核对失败"处理,先核对三件事——连的哪个环境、哪个库、表结构最近有没有变更。三成以上的"昨天还能跑"问题,根源是连错了环境或表被重建。
核对全部通过后,Driver 把 QB 里的名字全部替换成带类型、带来源的真实对象:每个列引用带上表限定与类型,每个函数绑定到具体实现,每个分区谓词换算成 Metastore 返回的分区目录清单。这份"事实核对完毕"的查询块,就是下一节逻辑计划的原料——算子树的每个节点,都能在 QB 的槽位里找到出处。
语义关把名字换成对象。下一节进入流水线最精彩的一段:优化器拿到对象齐全的查询块,开始在算子树上做手术。