往 Hive 表 insert 数据后,查询时出现个别行字段错位,插入语句如下:
insert overwrite table A select col1,col2,col3 from table B where dayid = '';
首先测试源表数据查询:
select col1,col2,col3 from B
查询来的数据没发现有什么异常;照理说逐字段查出来没问题,再逐字段插入应该不会错位。实际上 hive 的 insert 跟想象中传统的 insert 不太一样。
由于不是全表错位,而是个别行错位,首先根据关键字查询 hive 错位那行数据,导出文本到本地。肉眼查看发现有部分"乱码"(异常字符: ^M
,如果经验丰富一眼就能看出这个是 \001
,vim 下可以通过组合键 ctrl + a 输出),怀疑是异常字符导致,通过 linux od 命令查看 16 进制编码,如图所示:有好几个 \001
,多么眼熟的数字啊 - 这是 hive 默认字段分隔符。
一般insert A from select B
我们没有关注 A 表的字段分隔符,看到\001
直觉跟 A 表的字段分隔符有关:
查看 A 的表结构,字段分隔符默认的 \001
。存储类型:textfile
。
进一步分析:textfile 是 hive 默认的存储结构,行存储,存储的实际数据结构跟表逻辑结构一致。导入数据时会直接把数据文件拷贝到 hdfs上不进行处理。源文件可以直接通过hadoop fs -cat
查看; 例如 text 字段分隔符: \001
, 换行符: \n
,表在 hdfs 实际存储的格式为:
v1\001v2\001v3\n
v4\001v5\001v5
猜测字段值缺失错位的根源在于:文本中的不可见字符 \001
插入到表中,而表以 \001
作为字段分隔符,导致查询字段错位。
再来看这条 SQL:
insert overwrite table A select col1,col2,col3 from table B where dayid = '';
我们可以还原这条 SQL 从插入到查询异常的全流程:
select col1,col2,col3 from table B where dayid = '';
查询出的数据按照 A 表的字段分隔符以及定义的换行符拼接起来。textfile
,直接把查询的数据 insert (append)到 hdfs 上\001
被插入到 A 表中,查询的时候必然错乱。\001
orc
等(不用关心字段分隔符)第一种方式可行且更加合理;
第二种方式可行,一种补救方案,但是 orc
等格式不支持 load 操作
第三种方式临时解决问题,不能根本上解决问题;
推荐解决方法
insert overwrite table A select
regexp_replace(trim(col1),'\t|\n|\001|\r','') as col1,
regexp_replace(trim(col2),'\t|\n|\001|\r','') as col2,
regexp_replace(trim(col3),'\t|\n|\001|\r','') as col3
from table B where dayid = '';
讲问题之前,先简单创建一个表:
CREATE TABLE IF NOT EXISTS `my.test_table`(
`col1` int COMMENT "第一列",
`col2` int COMMENT "第二列"
)
COMMENT "测试表"
PARTITIONED BY (`pt` int COMMENT "测试分区")
ROW FORMAT SERDE
"org.apache.hadoop.hive.ql.io.orc.OrcSerde"
STORED AS INPUTFORMAT
"org.apache.hadoop.hive.ql.io.orc.OrcInputFormat"
OUTPUTFORMAT
"org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat";
初始表有col1,col2两列,pt分区只是为了对比结果
hive不像mysql、oracle这些数据库一样在insert的时候可以指定字段,必须在插入的时候插入的是全字段。所以我一直以为可以通过指定插入数据的别名来改变插入字段的顺序,好吧,事实证明我是错的!
我们来简单作一个设想,假如我们执行以下的sql会发生什么:
insert overwrite table my.test_table partition(pt=1) select 2 as col2, 1 as col1 from my.online_table;
按常规逻辑来说,查询的时候应该是col1字段都为1,col2字段都为2。但是事实上执行
select col1, col2 from my.test_table;
结果是:
2 1
2 1
2 1
2 1
....
事实上,hive并不关心你执行insert语句所用的别名,别名代表的字段可以不存在,甚至比别名都可以相同。下面的语句执行也是一样的效果:
insert overwrite table my.test_table partition(pt=1) select 2 as invalid_col, 1 as invalid_col from my.online_table;
是不是觉得很惊喜。所以,请严格保证insert语句中的字段和建表语句中的字段的顺序一致!!!
**hive比较特殊的地方,在于它的表结构和数据其实是分开的。**这个会导致,对hive分区表新增字段后,在执行插入分区的动作,会发现其实数据文件中已经有新字段值了,但是在查询的时候新字段的值还是显示为null。
例如我执行了下面的方法新增了一列col3:
alter table my.test_table add columns(col3 int comment '第三列')
然后想插入一些数据:
insert overwrite table my.test_table partition(pt=1) select 1 as col1, 2 as col2, 3 as col3 from my.online_table;
结果查询col1,col2,col3发现结果其实是:
1 2 NULL
1 2 NULL
...1
这是因为你对表结构进行了改变,但是历史分区的数据却没有做改变(新增分区不会出现这个情况)。
为了解决上面的问题,可以采用两种方式:
使用replace 恢复表结构,这样历史的分区数据都不会消失
alter table industry_db.product replace columns(product_name string comment ‘产品名’);
alter table my.test_table add columns(col3 int comment '第三列') cascade
官方文档描述如下:
The CASCADE|RESTRICT clause is available in Hive 1.1.0. ALTER TABLE ADD|REPLACE COLUMNS with CASCADE command changes the columns of a table’s metadata, and cascades the same change to all the partition metadata. RESTRICT is the default, limiting column changes only to table metadata.
如上所述,在1.1.0中表和分区的元数据就是分开处理的,对于在添加字段的时候没有指定的cascade的情况,在增加字段的时候添加CASCADE能同时更新表和分区
因为我们在重跑数据的时候,虽然HDFS上的数据更新了,但是我们查询的时候仍然查询的是旧的元数据信息(即Mysql中的信息)
注意:对于执行了add column语句之后新生成的分区,是不会有问题的,Hive会自动维护新分区中的元数据。
参考:https://www.cnblogs.com/fnlingnzb-learner/p/13472266.html
https://www.jianshu.com/p/568a43eb5114