Spark 读取 Hive insert-only 事务表异常分析


1. 问题现象

在 Spark 程序中执行 Hive 表查询时,程序在 SQL 解析阶段直接失败,异常信息如下:

Exception in thread "main" org.apache.spark.sql.AnalysisException:
org.apache.hadoop.hive.ql.metadata.HiveException:
Unable to fetch table parqt_tracker.
Your client does not appear to support insert-only tables.
To skip capability checks, please set metastore.client.capability.check to false.
This setting can be set globally, or on the client for the current metastore session.
Note that this may lead to incorrect results, data loss, undefined behavior, etc.
if your client is actually incompatible.
You can also specify custom client capabilities via get_table_req API.

完整堆栈中有几段比较关键:

at org.apache.spark.sql.hive.HiveExternalCatalog.getRawTable(HiveExternalCatalog.scala:117)
at org.apache.spark.sql.hive.HiveExternalCatalog.getTable(HiveExternalCatalog.scala:683)
at org.apache.spark.sql.catalyst.catalog.SessionCatalog.lookupRelation(SessionCatalog.scala:669)
at org.apache.spark.sql.catalyst.analysis.Analyzer$ResolveRelations$.apply(Analyzer.scala:640)
at org.apache.spark.sql.SparkSession.sql(SparkSession.scala:642)

以及最底层的异常:

Caused by: MetaException(message:
Your client does not appear to support insert-only tables.
To skip capability checks, please set metastore.client.capability.check to false.
)

从调用链可以看出,这个问题不是 SQL 语法错误,也不是任务执行到某个 Stage 后失败,而是 Spark 在分析 SQL、从 Hive Metastore 获取表元数据时就失败了。

2. 问题原因

这个异常的核心是:

Spark 当前使用的 Hive Metastore Client 没有声明自己支持 Hive insert-only transactional table,所以 Hive Metastore 拒绝返回这张表的元数据。

Hive 3 引入并强化了事务表能力。事务表大致可以分为两类:

类型 说明 常见限制
Full ACID 表 支持 insert、update、delete 通常要求 ORC 格式
Insert-only 事务表 只支持事务化 insert 支持更多文件格式,例如 TextFile、CSV、Avro、JSON 等

Cloudera 的 Hive 3 文档中也说明了这个行为:如果创建 Hive 3 管理表时使用非 ORC 存储格式,例如 TextFile、CSV、Avro、JSON,就会得到 insert-only ACID 表;insert-only 表不能执行 update、delete。

这类表在 Hive 内部访问通常没有问题,因为 Hive 知道如何处理事务目录、write id、delta 文件、compact 等信息。但是 Spark、Presto、Dremio、Greenplum PXF 等外部计算引擎如果没有完整支持 Hive 事务表,就可能在访问 Hive Metastore 时遇到类似错误。

这次异常中,表名是:

parqt_tracker

Spark 执行到 SparkSession.sql 后,需要先到 Hive Metastore 中读取 parqt_tracker 的表结构和表属性。Hive Metastore 发现这是一张 insert-only transactional table,而当前 Spark Hive Client 没有声明支持这种表能力,于是直接抛出了:

Your client does not appear to support insert-only tables

3. 如何确认是不是这个问题

可以先在 Hive 中查看表结构和表属性:

SHOW CREATE TABLE parqt_tracker;

或者:

DESC FORMATTED parqt_tracker;

重点关注以下几类信息:

Table Type: MANAGED_TABLE
transactional=true
transactional_properties=insert_only

也可以关注底层存储格式:

InputFormat: org.apache.hadoop.mapred.TextInputFormat

或者其他非 ORC 格式。

如果同时满足下面几个特征,基本就可以确认是 Hive insert-only 事务表导致的问题:

  1. Hive 中可以查询这张表。
  2. Spark 通过 Hive Metastore 读取这张表时报错。
  3. 报错中包含 Your client does not appear to support insert-only tables
  4. 表属性中存在 transactional=truetransactional_properties=insert_only

4. 临时解决方案

异常提示中给出了一个参数:

metastore.client.capability.check=false

这个参数的含义是关闭 Hive Metastore 的客户端能力检查。配置之后,Hive Metastore 不再因为客户端没有声明支持 insert-only 表而拒绝返回元数据。

如果只是临时验证,可以在 Spark 提交任务时增加:

spark-submit \
  --conf spark.hadoop.metastore.client.capability.check=false \
  --class com.hnbian.export.DataProcess \
  your-job.jar

也可以将参数写入 Spark 任务使用的 hive-site.xml 中:

<property>
  <name>metastore.client.capability.check</name>
  <value>false</value>
</property>

如果是集群级别调整,则需要把该配置加入 Hive 或 Spark 客户端能读取到的 hive-site.xml,然后重启相关服务或重新提交任务。

但是这个方案只能算绕过检查,不能算真正解决兼容性问题。异常提示里也明确说了,关闭能力检查可能导致错误结果、数据丢失或未定义行为。

原因是 Spark 即使拿到了表元数据,也不代表它一定能正确理解 Hive 事务表的数据组织方式。如果这张表存在事务 delta 目录、未 compact 的数据、失败事务等情况,外部引擎直接读底层文件时就可能读到不完整或不正确的数据。

所以这个参数更适合下面两种场景:

  1. 临时确认问题根因。
  2. 表数据写入方式非常简单,并且已经确认 Spark 读取结果和 Hive 查询结果一致。

5. 推荐解决方案

5.1 将数据同步到普通外部表

如果这张表需要长期被 Spark 读取,推荐将数据同步到一张普通 Hive 外部表中。

例如创建一张非事务外部表:

CREATE EXTERNAL TABLE parqt_tracker_ext (
  -- 这里填写原表字段
)
STORED AS PARQUET
LOCATION '/warehouse/external/parqt_tracker_ext';

然后通过 Hive 将数据写入外部表:

INSERT OVERWRITE TABLE parqt_tracker_ext
SELECT *
FROM parqt_tracker;

后续 Spark 读取 parqt_tracker_ext

val df = spark.sql("select * from parqt_tracker_ext")

这样 Spark 读取的是普通外部表,不需要理解 Hive insert-only 事务表的内部实现。

5.2 建表时避免生成 insert-only 事务表

如果这张表本来就是为了给 Spark、Flink、Presto 等引擎共享读取,不建议创建成 Hive 3 的 insert-only 管理表。

可以在建表时明确使用外部表:

CREATE EXTERNAL TABLE parqt_tracker (
  -- 字段定义
)
STORED AS PARQUET
LOCATION '/warehouse/external/parqt_tracker';

如果数据不是必须由 Hive 完全托管,外部表通常更适合多引擎共享。

5.3 通过 HiveServer2 读取

如果必须读取 Hive 事务表,并且不能改表结构,也可以考虑让 Hive 自己负责读取事务表,Spark 通过 JDBC 访问 HiveServer2:

val df = spark.read
  .format("jdbc")
  .option("url", "jdbc:hive2://hive-server:10000/default")
  .option("driver", "org.apache.hive.jdbc.HiveDriver")
  .option("dbtable", "(select * from parqt_tracker) t")
  .load()

这种方式的优点是事务表读取逻辑交给 Hive 处理,结果更可靠。缺点是数据需要经过 HiveServer2,性能通常不如 Spark 直接读取 HDFS 文件。

6. 不建议直接修改原表事务属性

有些问题看起来可以通过修改表属性解决,例如尝试把 transactional 改掉:

ALTER TABLE parqt_tracker SET TBLPROPERTIES ('transactional'='false');

这种方式不建议使用。

事务表的表属性、元数据库记录、底层目录结构、write id、delta 文件是配套的。只改表属性并不会把已有数据真正转换成普通表,反而可能让 Hive 和其他引擎对这张表的理解不一致。

如果要取消事务表能力,推荐重新建一张普通表,再通过 Hive 查询把数据导过去。

7. 总结

这个异常的直接原因是 Spark 在访问 Hive Metastore 时,客户端能力检查没有通过:

Your client does not appear to support insert-only tables

本质原因是目标表是 Hive insert-only transactional table,而当前 Spark 使用的 Hive Client 不支持或没有声明支持这种表。

处理思路可以分成三层:

方案 是否推荐 说明
metastore.client.capability.check=false 临时使用 可以绕过元数据检查,但有数据正确性风险
同步到普通外部表 推荐 适合 Spark 长期稳定读取
Spark JDBC 读取 HiveServer2 可选 结果更依赖 Hive,性能可能较差

如果是生产任务,建议不要只依赖关闭能力检查。更稳妥的方式是把 Hive 事务表作为 Hive 内部使用的表,再额外产出一张普通外部表供 Spark 等计算引擎读取。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录