Hive内容包含有\001会当成分割符

起因

发现某一行一列数据多了一个\001字符,导致select的时候在这一列后面的结果都不正确。

重现

hive版本:0.7.1cdh3u4

# 建表语句
create table test ( sn string, pv int) ROW FORMAT DELIMITED FIELDS TERMINATED BY '`';
# 在另一个窗口构造一个错误数据文件
printf "a\001`12" > /tmp/test.txt
# 把数据load进表里
LOAD DATA LOCAL INPATH '/tmp/test.txt' OVERWRITE INTO TABLE test;

这里指定了字段的分割符为反引号(`),后来发现中途并不是全部用这个分割字段。
这时如果查询

select sn,pv from test;

返回的值居然是a NULL,而不是 a 12

原因

这条语句(select sn,pv from test;)会有三个Opertor,分别是TableScanOperator, SelectOperator, FileSinkOperator。FileSinkOperator是负责把结果输出到文件,输出前需要把对象做一次序列化存储到文件里。

//FileSinkOperator.java文件
  @Override
  public void processOp(Object row, int tag) throws HiveException {
...
        // use SerDe to serialize r, and write it out
        recordValue = serializer.serialize(row, inputObjInspectors[0]);
...
  }

这里的row是由它的父Operator(SelectOperator)传入的,值是一个数组,暂且用row = [a\001, 12]表示(实际上这里面还包裹一些对象,但不妨碍分析)。
单步跟进后,发现recordValue返回的值是a\001\00112,这里的serializer用的分割符号是\001,所以现在变成了两个\001。
那么,这个serializer哪里来的?

//FileSinkOperator.java文件
      serializer       = (Serializer) conf.getTableInfo().getDeserializerClass().newInstance();
      serializer.initialize(null, conf.getTableInfo().getProperties());

conf.getTableInfo()返回的是一个TableDesc,这个是在编译阶段genFileSinkPlan()构造的。

// SemanticAnalyzer.java
  private Operator genFileSinkPlan(String dest, QB qb, Operator input)
      throws SemanticException {
...
CreateTableDesc tblDesc = qb.getTableDesc(); //到qb里面找
...

      if (tblDesc == null) {
        if (qb.getIsQuery()) {
          String fileFormat = HiveConf.getVar(conf, HiveConf.ConfVars.HIVEQUERYRESULTFILEFORMAT);
          table_desc = PlanUtils.getDefaultQueryOutputTableDesc(cols, colTypes, fileFormat);
        } else {
          table_desc = PlanUtils.getDefaultTableDesc(Integer
              .toString(Utilities.ctrlaCode), cols, colTypes, false);
        }
      } else {
        table_desc = PlanUtils.getTableDesc(tblDesc, cols, colTypes);
      }

...
 }

发现在SemanticAnalyzer.java文件里,只有在CTAS的语句才会调用qb.setTableDesc(),所以这里tblDesc没有值,它自己用默认值创建了一个。PlanUtils.getDefaultQueryOutputTableDesc(cols, colTypes, fileFormat);分割符是Utilities.ctrlaCode,也就是\001 目前还没搞明白为什么分割符这么混乱。但可以知道为什么第二列本来应该返回数字却返回了null,是这里的分割符号是\001,那么就多了一列出来。

结论

就算你指定了分割符(FIELDS TERMINATED BY),但hive在中间交换数据时仍然使用它默认的分割符号\001-\009,暂时只想到在数据中过滤这些字符了,还不了解hive的序列化\反序列化。

updatedupdated2023-12-062023-12-06