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