Hive 源码导读(六):订单已经写出新文件,查询为什么还不能随便读?
上一篇解释了锁:并发操作动手前,需要先按规则协调。
接着看一个更具体的业务变化。订单1001原来是已支付,金额99.00。后来发生退款,要把它的状态改成REFUNDED。
HDFS 上出现了新的数据文件。此时,另一个查询是不是扫描目录,把新旧文件里的内容全读出来就行?
如果这么做,同一笔订单可能被算两次,甚至把还没提交的修改算进日报。事务要回答的,正是“这次查询允许看到哪些内容”。
本文基于Hive 3.1.2、Hadoop 3.2.1,使用配置好的DbTxnManager 与 full ACID ORC 管理表。本文使用单语句隐式事务,不把多个独立 SQL 当作一笔多语句事务。目录名和时间线是机制示意,不是实测快照。
一、先把订单准备好
这一篇使用独立的orders_acid表。前几篇的orders_raw是非事务外部表,不能直接照搬本篇的 UPDATE。
完整建表和一次性初始化脚本在 实验准备。先查询目标订单:
SELECTorder_id,amount,statusFROMcsdn_hive_lab.orders_acidWHEREdt='2026-10-09'ANDorder_id=1001;按系列样本,初始数据是:
| order_id | amount | status |
|---|---|---|
| 1001 | 99.00 | PAID |
现在执行:
UPDATEcsdn_hive_lab.orders_acidSETstatus='REFUNDED'WHEREdt='2026-10-09'ANDorder_id=1001;业务含义很简单:这笔订单不再进入已支付金额。系统内部却需要协调锁、写入标识、文件和提交状态。
二、锁和事务,各自管哪件事
可以把订单报表想成查账。
锁负责协调某些同时进行的操作:谁可以动哪一部分,哪些动作需要等待。
事务与可见性规则负责确定这次查账使用哪个有效版本:哪些写入已经成功,哪些仍未完成,哪些已经失败。
比如写入者正在准备退款信息,查询不应该因为在文件系统里碰巧看到了半路产生的文件,就把它当作已经完成的业务结果。
协调读写与选择可见版本,是两件配合完成的工作。
三、Txn ID 和 Write ID 为什么要分开
DbTxnManager.openTxn()通过 HMS 申请事务,并启动相关心跳。事务标识用于识别这次事务活动。
涉及某张事务表的写入,还会分配对应表的 Write ID。可以理解为:Txn ID 是事务的编号,Write ID 是这张表记录写入进度时使用的编号。
| 标识 | 作用范围 | 用处 |
|---|---|---|
| Txn ID | Metastore 事务体系 | 识别事务与状态 |
| Write ID | 表内写入序列 | 判断该表上的写入版本与可见性 |
| Statement ID | 同一写入上下文中的语句区分 | 参与区分相应文件或写入动作 |
不要从delta_...目录里的数字直接推断它就是全局 Txn ID。两个编号即使在一个简单实验中刚好相等,也不代表概念相同。
四、文件写出来之后,还差什么
把正常写入路径压缩一下:
开启事务、确定读写上下文 → 编译与锁协调 → 执行写入,产生相应文件 → 查询执行成功 → 向 HMS 提交事务真实实现还有快照校验、失败回滚等分支,这里只强调文件与提交的先后边界。
DbTxnManager.commitTxn()最终会调用:
getMS().commitTxn(txnId);HMS 再交给TxnHandler.commitTxn()处理。某些并发写冲突,也需要在事务提交逻辑中检查。文件已经产生,不等于后续提交一定能成功。
如果事务失败,相关文件也不一定瞬间从磁盘消失。是否能被查询读取,依赖有效事务和写入信息;物理清理是另一个过程。
五、查询像拿着一份“本次可读名单”
Driver 会取得有效事务信息,并记录相关表的有效 Write ID 列表。可以把它理解为给本次查询划出一个读取边界,同时记录不应读取的写入。
它不只是一个最大的号码。某些编号可能仍在进行中,或已经中止,读取时也需要排除。
看一个明确标注条件的时间线:
| 时刻 | 发生的事情 |
|---|---|
| T1 | 查询 Q1 已确定有效读取快照并开始读;快照不包含本次退款修改 |
| T2 | 写入者产生订单 1001 的退款相关文件,事务尚未提交 |
| T3 | 退款事务提交成功 |
| T4 | 新查询 Q2 取得包含这次提交的读取快照 |
在这个条件下,Q1 按它的快照读取旧的已支付状态,Q2 可以读取退款后的状态。Q1 不会仅仅因为读到一半发现事务提交了,就随意切换到另一套名单。
这里特意写“已确定并开始使用快照”,没有把客户端按回车的时刻当作快照建立时刻。Hive 3.1.2 的 Driver 在申请锁后还可能校验快照并重新编译,因此需要根据实际阶段判断。
六、base、delta、delete_delta 是怎样配合的
先记职责,不急着背目录命名规则:
| 类型 | 大白话理解 | 不能误解成什么 |
|---|---|---|
| base | 整理到一定进度的基础数据 | 每张新事务表从第一次写入就必然有 base |
| delta | 某段写入范围的增量内容 | 目录里所有写入都一定对当前查询有效 |
| delete_delta | 删除事件,用于使相应旧记录失效 | 直接把旧数据文件立即删掉 |
Hive 3.1.2 的常见 full ACID split-update 路径,会把更新组织成对应旧记录的删除事件和新记录的插入。新旧文件可能同时存在,但读取层不会简单地把每一行全部累加。
AcidUtils.getAcidState()根据有效写入信息选择相关的基础文件和增量范围;ORC ACID 读取逻辑还会结合行标识、删除事件与有效 Write ID 处理记录。
因此,排查时只执行hdfs dfs -ls,看到目录里有几份文件,就直接判断“重复了几份数据”,是不够的。目录列表回答物理文件存在性,查询结果还受读取规则约束。
七、Compaction 是整理文件,不是替业务提交事务
增量文件越来越多,每次读取都要考虑更多文件和元数据,成本会增加。这时需要 Compaction 帮忙整理。
| 角色 | 负责什么 |
|---|---|
| Initiator | 检查哪些表或分区可能需要压缩整理,并发起相应请求 |
| Worker | 执行压缩整理工作;也可以处理手工提交的压缩请求 |
| Cleaner | 在满足安全条件后,清理不再需要的旧文件 |
Minor compaction 主要合并增量;major compaction 会进一步把相应内容整理到新的 base。具体适用范围与行为以本版本实现、表类型和配置为准。
整理完成后,旧文件也可能还要为旧快照保留一段时间。Cleaner 不能因为看见新 base,就无条件把所有旧目录删掉。
已提交数据的可见性不需要等待一次 compaction 才建立。Compaction 主要改善物理布局和后续读取成本,两者不要混成一个“最终生效开关”。
八、怎样验证订单状态发生了什么变化
普通练习可以先做串行验证:
- 确认使用 full ACID 表和正确事务管理器。
- 按主键条件查到订单 1001 的 PAID 状态。
- 执行文章开头的 UPDATE,确认语句成功完成。
- 用新发起的查询读取同一订单,核对 REFUNDED。
- 按分区重新计算已支付金额,与样本的预期变化对应。
SELECTSUM(amount)ASpaid_amountFROMcsdn_hive_lab.orders_acidWHEREdt='2026-10-09'ANDstatus='PAID';六行原始样本中,已支付金额是299.00;将订单 1001 改为退款后,新的已支付金额应为200.00。这是根据样本算出的预期值,实际集群结果需要运行验证。
还可以查看:
SHOWTRANSACTIONS;SHOWCOMPACTIONS;SHOW TRANSACTIONS不应当作所有历史已提交事务的完整审计表。要关联一次写入,还需要它对应的 Query ID、事务日志和表级信息。
若要验证“文件已产生但提交前不可见”,需要在隔离测试环境控制提交前的暂停位置,并保持心跳与查询线程工作;若要验证旧快照,还需确认 Q1 已固定并开始使用相应快照。本文没有执行这些并发控制实验,不把上面的时间线当成已经采集到的现场。
九、排障时,把三个证据放在一起
| 证据 | 能说明什么 |
|---|---|
| HDFS 文件与目录 | 哪些物理文件已经存在 |
| HMS 事务与锁信息 | 写入、协调和提交目前处于什么状态 |
| 查询快照与最终结果 | 当前查询允许读哪些内容,业务结果是否正确 |
单看其中一个,很容易下结论太早。
到这里,订单查询、MR 提交、锁和事务都已经串起来。最后一篇,我们面对一个没有提前公布原因的报表延迟,把这些知识变成一条真正能执行的排查路线。
本篇关键源码位置
DbTxnManager.openTxn()、commitTxn()、rollbackTxn():客户端事务生命周期。Driver.recordValidWriteIds()与快照校验相关方法:查询有效读取信息。- TxnHandler.java:事务、Write ID 与提交检查。
- AcidUtils.java、
OrcRawRecordMerger:目录选择和记录可见性。 ql/.../txn/compactor/Initiator.java、Worker.java、Cleaner.java:压缩整理分工。
上一篇(后续更新,可从专栏目录查看) · 下一篇:订单日报延迟怎么查(后续更新,可从专栏目录查看)