HBase 支持事务吗?单行原子性和 CheckAndMutate 能保证什么?
本文目录
面试怎么回答
HBase 不是原生支持通用多行 ACID 事务的关系库,但单行变更具有原子性,范围可以跨同一行的列族。先 Get 再 Put 是两个操作,不能保证并发判断安全;应使用 CheckAndMutate 把条件和修改放在一次原子操作里。批量写多行不等于多行事务,普通 Scan 也不是全表同一时刻快照。还要注意超时结果未知,以及业务是否需要跨行协调。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
“HBase 不支持事务。”这句话短,但容易答过头。继续问:同一行的多个字段会不会只改了一半?两个请求同时抢一个任务,先查再改安全吗?这时需要讲的不是一个“支持或不支持”,而是原子操作的边界。
HBase 的原生原子边界主要在单行;条件检查与修改要合成一次操作,多行批量不能当成事务。
单行原子性,指的是一次原子操作
HBase 提供单行变更的原子性,一次单行 Put 可以包含同一行多个列族的字段:这次变更不会只让一半字段成功。需要组合该行多个变更时,应使用相应的单行原子 API,而不是假设任意多次调用自动成组。
“都改同一行”和“在一次原子操作里修改”不同。先写状态、再独立写负责人,中间发生故障,业务仍可能留下只完成第一步的状态。它不违反单次操作原子性,因为应用本来就提交了两个操作。
超时也要单独处理。原子性可以保证这次变更没有只做一半,却不保证客户端一定知道它做没做。HBase 官方语义明确说明:超时的操作可能成功,也可能失败。
先 Get 再 Put,挡不住并发竞争
比如一行表示一个待处理任务,status 为 pending。请求 A 和 B 都先 Get,二者都读到 pending,然后分别写入自己的 owner。最后表里也许只有一个 owner,但两个请求都可能已经对外宣布“我领取成功”。
每次 Put 都可以是原子的,整个“检查后执行”仍然有竞态。问题不是 HBase 写出了半行,而是条件判断和修改之间留了可被并发者插入的空隙。
应使用 CheckAndMutate 一类条件修改操作,在同一行内原子地判断预期值并执行修改,再核对返回结果。具体 builder 或方法签名以客户端版本为准,不要把旧版示例直接复制到所有版本。

条件最好能识别版本,而不只识别一个状态
只判断 status 等于 pending,有时仍不足以表达业务。假如状态经历 pending、running、pending,旧请求可能把后来重新开放的任务误认为自己最初看到的那一版,这类似 ABA 问题。
可以把版本号或领取代次纳入条件,并在同一次成功修改中更新它。领取时还可记录请求标识,使超时后的回查能够判断是不是本次请求已经成功;不能只因为条件再次不成立,就断言之前一定失败。
这里说的是业务设计方式,不是所有场景都必须增加同样字段。关键是让原子条件表达真正想保护的不变量,而不是只选择最容易比较的列。
batch 不等于跨行事务,Scan 也不等于全表快照
客户端批量提交多行操作,主要是调用组织与效率手段,不提供这些行全成全败的事务保证。一行成功另一行失败时,不能期待自动回滚;即使两行位于同一 Region,也不能据此推导有跨行事务。
普通 Scan 也不是全表同一时刻的快照隔离。逐行读取过程中可能看到不同提交时点的数据;若拆分同一行进行多次读取,还要核对相应读取设置的语义。需要严格对账快照时,应采用明确的快照或外部协调方案。
此外,持久性和副本读取一致性有可调配置。不能一边使用弱化保证的选项,一边把默认强一致读取或正常持久化路径的承诺原封不动套上去。
一个典型场景:状态正确,却执行了两次
比如任务表最终显示一个领取者,外部任务却被执行了两次。最初怀疑表里出现了重复行,按 RowKey 检查没有重复;查看请求链路才发现两个工作进程都通过了“先读 pending”的检查。
根因是检查与修改分离,不是行主键失效。原测试串行执行,同一时刻只有一个请求,所以从未暴露。修复后用条件修改决定领取资格,并做并发与超时测试;外部任务仍需自己的幂等标识,因为 HBase 单行操作不能把外部副作用一并包进事务。