ORC 格式的表,和 text 格式的表,如果分区的字段数量和表的字段数量不一致,则 select 的结果不一致。

1. 测试内容

1.1 ORC 格式的表

CREATE EXTERNAL TABLE `test_part`(                                             
   `id` string,                                                                           
   `t1` string,
   `t2` string                                                                     )                                                                      
 PARTITIONED BY (                                                                         
   `dt` string)                                                                 
 ROW FORMAT DELIMITED                                                                     
   FIELDS TERMINATED BY '\t'                                                              
 STORED AS ORC;
 insert into test_part partition (dt='124') values (1,1,1),(2,2,2),(3,3,NULL),(4,4,NULL);
 --删除表结构,但是不删除文件
 drop table test_part;
 -- 数据准备完毕

 CREATE EXTERNAL TABLE `test_part`(                                             
   `id` string,                                                                           
   `t1` string                                                                   )                                                                      
 PARTITIONED BY (                                                                         
   `dt` string)                                                                 
 ROW FORMAT DELIMITED                                                                     
   FIELDS TERMINATED BY '\t'                                                              
 STORED AS ORC;
 -- 先添加分区 再添加字段
 alter table test_part add partition(dt='124');
 alter table test_part add columns (t2 string);
 -- 字段格式与表格式不同
1.1.1 select

结果如下,能识别新添加的字段。

hive>  select * from test_part;
OK
1	1	1	124
2	2	2	124
3	3	NULL	124
4	4	NULL	124

1.2 Text 格式的表

drop table if exists `test_part_text`;
CREATE EXTERNAL TABLE `test_part_text`(                                             
   `id` string,                                                                           
   `t1` string,
   `t2` string                                                                     )                                                                      
 PARTITIONED BY (                                                                         
   `dt` string)                                                                 
 ROW FORMAT DELIMITED                                                                     
   FIELDS TERMINATED BY '\t'                                                              
 STORED AS textfile;
 insert into test_part_text partition (dt='124') values (1,1,1),(2,2,2),(3,3,NULL),(4,4,NULL);
 --删除表结构,但是不删除文件
 drop table test_part_text;
 -- 数据准备完毕

重新创建表,数据指向原来的地址

CREATE EXTERNAL TABLE `test_part_text`(                                             
   `id` string,                                                                           
   `t1` string                                                                   )                                                                      
 PARTITIONED BY (                                                                         
   `dt` string)                                                                 
 ROW FORMAT DELIMITED                                                                     
   FIELDS TERMINATED BY '\t'                                                              
 STORED AS textfile;
 -- 先添加分区 再添加字段
 alter table test_part_text add partition(dt='124');
 alter table test_part_text add columns (t2 string);
 -- 字段格式与表格式不同
1.2.1 select

可以看到t2 字段的内容为 NULL,和 orc 格式不一致。

hive> select * from test_part_text;
OK
1 1 NULL  124
2 2 NULL  124
3 3 NULL  124
4 4 NULL  124

3. 查看表的信息

可以看到表的 StorageDescriptor 有3个字段。

hive> desc extended test_part_text;
OK
id                  	string              	                    
t1                  	string              	                    
t2                  	string              	                    
dt                  	string              	                    
	 	 
# Partition Information	 	 
# col_name            	data_type           	comment             
dt                  	string              	                    
	 	 
Detailed Table Information	Table(tableName:test_part_text, dbName:default, owner:hive, createTime:1667954622, lastAccessTime:0, retention:0, sd:StorageDescriptor(cols:[FieldSchema(name:id, type:string, comment:null), FieldSchema(name:t1, type:string, comment:null), FieldSchema(name:t2, type:string, comment:null), FieldSchema(name:dt, type:string, comment:null)], location:bos://bmr-rd-wh/houzhizhen/warehouse/test_part_text, inputFormat:org.apache.hadoop.mapred.TextInputFormat, outputFormat:org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat, compressed:false, numBuckets:-1, serdeInfo:SerDeInfo(name:null, serializationLib:org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, parameters:{serialization.format=\t, field.delim=\t}), bucketCols:[], sortCols:[], parameters:{}, skewedInfo:SkewedInfo(skewedColNames:[], skewedColValues:[], skewedColValueLocationMaps:{}), storedAsSubDirectories:false), partitionKeys:[FieldSchema(name:dt, type:string, comment:null)], parameters:{last_modified_time=1667954645, totalSize=0, EXTERNAL=TRUE, numRows=0, rawDataSize=0, COLUMN_STATS_ACCURATE={\"BASIC_STATS\":\"true\"}, numFiles=0, numPartitions=1, transient_lastDdlTime=1667954645, bucketing_version=2, last_modified_by=hive}, viewOriginalText:null, viewExpandedText:null, tableType:EXTERNAL_TABLE, rewriteEnabled:false, catName:hive, ownerType:USER)

3.2 查看分区的信息

可以看到 partition 的 StorageDescriptor 只有2个字段。

hive> desc extended  test_part_text partition(dt='124');
OK
col_name	data_type	comment
id                  	string              	                    
t1                  	string              	                    
dt                  	string              	                    
	 	 
# Partition Information	 	 
# col_name            	data_type           	comment             
dt                  	string              	                    
	 	 
Detailed Partition Information	Partition(values:[124], dbName:default, tableName:test_part_text, createTime:1667957398, lastAccessTime:0, sd:StorageDescriptor(cols:[FieldSchema(name:id, type:string, comment:null), FieldSchema(name:t1, type:string, comment:null), FieldSchema(name:dt, type:string, comment:null)], location:hdfs://localhost:9000/home/disk1/hive/hive-313/test_part_text/dt=124, inputFormat:org.apache.hadoop.mapred.TextInputFormat, outputFormat:org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat, compressed:false, numBuckets:-1, serdeInfo:SerDeInfo(name:null, serializationLib:org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe, parameters:{serialization.format=	, field.delim=

2. 原因分析

2.1 根本原因总结

orc 格式的表和text 格式的表,在表的定义层面都是3个字段,在分区的 serde 都是2个字段。并且在文件里都是存储了3个字段的内容。但是 text 格式的表在读取文件的时候使用的是分区的 serde,所以第3个字段 t2 的内容始终为 NULL。orc 格式的表在读取文件时使用的是表的 serde,所以文件中的3个字段都可以读取。

2.2 代码分析

2.2.1 FetchOperator.getRecordReader

getRecordReader 因为是分区表,所以走 else 逻辑 currSerDe = needConversion(currDesc) ? currDesc.getDeserializer(job) : tableSerDe;.
currrDesc 是分区的 desc, tableSerDe 是表的 serde。
也就是说明,如果 needConversion(currDesc) 返回 true,则用分区的 serde(2个字段),否则用表的 serde(3个字段)。
ORC 格式的表 needConversion(currDesc) 返回 false。text 格式的表 needConversion(currDesc) 返回 true。

private RecordReader<WritableComparable, Writable> getRecordReader() {
.//
if (!isPartitioned || convertedOI == null) {
  currSerDe = tableSerDe;
  ObjectConverter = null;
} else {
  currSerDe = needConversion(currDesc) ? currDesc.getDeserializer(job) : tableSerDe;
  ObjectInspector inputOI = currSerDe.getObjectInspector();
  ObjectConverter = ObjectInspectorConverters.getConverter(inputOI, convertedOI);
}
2.2.2 needConversion 代码

外部表 isAcid 是 false。

private boolean needConversion(PartitionDesc partitionDesc) {
    boolean isAcid = AcidUtils.isTablePropertyTransactional(partitionDesc.getTableDesc().getProperties());
    if (Utilities.isSchemaEvolutionEnabled(job, isAcid) && Utilities.isInputFileFormatSelfDescribing(partitionDesc)) {
      return false;
    }
    return needConversion(partitionDesc.getTableDesc(), Arrays.asList(partitionDesc));
  }
2.2.3 isSchemaEvolutionEnabled

配置项 ConfVars.HIVE_SCHEMA_EVOLUTION 默认为 true。

public static boolean isSchemaEvolutionEnabled(Configuration conf, boolean isAcid) {
    return isAcid || HiveConf.getBoolVar(conf, ConfVars.HIVE_SCHEMA_EVOLUTION);
  }
2.2.4 Utilities.isInputFileFormatSelfDescribing(partitionDesc)

SelfDescribingInputFormatInterface 有3个子类,分别是 SelfDescribingInputFormatInterface、 VectorizedOrcInputFormat ,OrcInputFormat,LlapInputFormat。所以 orc 格式的返回true,text 格式返回 false。
所以 orc 格式的表在 2.2.2 节 needConversion(PartitionDesc partitionDesc)节返回 false。

public static boolean isInputFileFormatSelfDescribing(PartitionDesc pd) {
    Class<?> inputFormatClass = pd.getInputFileFormatClass();
    return SelfDescribingInputFormatInterface.class.isAssignableFrom(inputFormatClass);
  }
2.2.5 needConversion(TableDesc tableDesc, List partDescs)

Text 格式的表继续运行此函数,因为表的字段数量和字段类型和分区的不一样,所以返回 true。在 2.2.1 节走分区 desc 的逻辑(2个字段),虽然文件内容也有3列,但是只取出 2 列。

private boolean needConversion(TableDesc tableDesc, List<PartitionDesc> partDescs) {
    Class<?> tableSerDe = tableDesc.getDeserializerClass();
    SerDeSpec spec = AnnotationUtils.getAnnotation(tableSerDe, SerDeSpec.class);
    if (null == spec) {
      // Serde may not have this optional annotation defined in which case be conservative
      // and say conversion is needed.
      return true;
    }
    String[] schemaProps = spec.schemaProps();
    Properties tableProps = tableDesc.getProperties();
    for (PartitionDesc partitionDesc : partDescs) {
      if (!tableSerDe.getName().equals(partitionDesc.getDeserializerClassName())) {
        return true;
      }
      Properties partProps = partitionDesc.getProperties();
      for (String schemaProp : schemaProps) {
        if (!org.apache.commons.lang3.StringUtils.equals(
            tableProps.getProperty(schemaProp), partProps.getProperty(schemaProp))) {
          return true;
        }
      }
    }
    return false;
  }

4. hive.exec.schema.evolution 参数的影响

以上分析都是在 hive.exec.schema.evolution=true的情况下。
hive.exec.schema.evolution=false 时,两张表都用分区的描述,t2 字段的内容都为 NULL。

4.1 设置 hive.exec.schema.evolution=false

set hive.exec.schema.evolution=false;
4.1.1 orc 格式的表
hive> select * from test_part;
OK
1	1	NULL	124
2	2	NULL	124
3	3	NULL	124
4	4	NULL	124
4.1.2 text 格式的表
hive> select * from test_part_text;
OK
1	1	NULL	124
2	2	NULL	124
3	3	NULL	124
4	4	NULL	124