起因
发现某一行一列数据多了一个\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的序列化\反序列化。