[1022]Hive insert 字段表错位

2021-07-14 14:47:38 浏览数 (1)

文章目录

      • Hive insert 字段表错位踩坑
        • 1 问题描述
        • 2 排查过程
        • 3 问题定位
        • 4 解决方案
      • hive的insert语句列顺序问题以及新增字段遇到的坑
        • insert语句列顺序
        • 对新增字段插入数据再查询发现是NULL

Hive insert 字段表错位踩坑

1 问题描述

往 Hive 表 insert 数据后,查询时出现个别行字段错位,插入语句如下:

代码语言:javascript复制
insert overwrite table A select col1,col2,col3 from table B where dayid = '';

首先测试源表数据查询:

代码语言:javascript复制
select col1,col2,col3 from B

查询来的数据没发现有什么异常;照理说逐字段查出来没问题,再逐字段插入应该不会错位。实际上 hive 的 insert 跟想象中传统的 insert 不太一样。

2 排查过程

由于不是全表错位,而是个别行错位,首先根据关键字查询 hive 错位那行数据,导出文本到本地。肉眼查看发现有部分"乱码"(异常字符: ^M,如果经验丰富一眼就能看出这个是 01,vim 下可以通过组合键 ctrl a 输出),怀疑是异常字符导致,通过 linux od 命令查看 16 进制编码,如图所示:有好几个 01 ,多么眼熟的数字啊 - 这是 hive 默认字段分隔符。

一般insert A from select B我们没有关注 A 表的字段分隔符,看到01 直觉跟 A 表的字段分隔符有关: 查看 A 的表结构,字段分隔符默认的 01。存储类型:textfile

进一步分析:textfile 是 hive 默认的存储结构,行存储,存储的实际数据结构跟表逻辑结构一致。导入数据时会直接把数据文件拷贝到 hdfs上不进行处理。源文件可以直接通过hadoop fs -cat 查看; 例如 text 字段分隔符: 01 , 换行符: n,表在 hdfs 实际存储的格式为:

代码语言:javascript复制
v101v201v3n
v401v501v5

猜测字段值缺失错位的根源在于:文本中的不可见字符 01 插入到表中,而表以 01 作为字段分隔符,导致查询字段错位。

3 问题定位

再来看这条 SQL:

代码语言:javascript复制
insert overwrite table A select col1,col2,col3 from table B where dayid = '';

我们可以还原这条 SQL 从插入到查询异常的全流程:

  1. 查询:select col1,col2,col3 from table B where dayid = ''; 查询出的数据按照 A 表的字段分隔符以及定义的换行符拼接起来。
  2. 插入:由于 A 表是 textfile ,直接把查询的数据 insert (append)到 hdfs 上
  3. 查询:由于多余的字段符 01 被插入到 A 表中,查询的时候必然错乱。
4 解决方案
  • 入库前清洗掉数据中的 01
  • 修改 A 表的存储格式为 orc 等(不用关心字段分隔符)
  • 修改 A 表字段分隔符为其他不可见字符

第一种方式可行且更加合理; 第二种方式可行,一种补救方案,但是 orc 等格式不支持 load 操作 第三种方式临时解决问题,不能根本上解决问题;

推荐解决方法

代码语言:javascript复制
insert overwrite table A select 
regexp_replace(trim(col1),'t|n|01|r','') as col1,
regexp_replace(trim(col2),'t|n|01|r','') as col2,
regexp_replace(trim(col3),'t|n|01|r','') as col3
from table B where dayid = '';

hive的insert语句列顺序问题以及新增字段遇到的坑

讲问题之前,先简单创建一个表:

代码语言:javascript复制
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分区只是为了对比结果

insert语句列顺序

hive不像mysql、oracle这些数据库一样在insert的时候可以指定字段,必须在插入的时候插入的是全字段。所以我一直以为可以通过指定插入数据的别名来改变插入字段的顺序,好吧,事实证明我是错的!

我们来简单作一个设想,假如我们执行以下的sql会发生什么:

代码语言:javascript复制
insert overwrite table my.test_table partition(pt=1) select 2 as col2, 1 as col1 from my.online_table;

按常规逻辑来说,查询的时候应该是col1字段都为1,col2字段都为2。但是事实上执行

代码语言:javascript复制
select col1, col2 from my.test_table;

结果是:

代码语言:javascript复制
2    1
2    1
2    1
2    1
....

事实上,hive并不关心你执行insert语句所用的别名,别名代表的字段可以不存在,甚至比别名都可以相同。下面的语句执行也是一样的效果:

代码语言:javascript复制
insert overwrite table my.test_table partition(pt=1) select 2 as invalid_col, 1 as invalid_col from my.online_table;

是不是觉得很惊喜。所以,请严格保证insert语句中的字段和建表语句中的字段的顺序一致!!!

对新增字段插入数据再查询发现是NULL

**hive比较特殊的地方,在于它的表结构和数据其实是分开的。**这个会导致,对hive分区表新增字段后,在执行插入分区的动作,会发现其实数据文件中已经有新字段值了,但是在查询的时候新字段的值还是显示为null。

例如我执行了下面的方法新增了一列col3:

代码语言:javascript复制
alter table my.test_table add columns(col3 int comment '第三列')

然后想插入一些数据:

代码语言:javascript复制
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发现结果其实是:

代码语言:javascript复制
1    2    NULL
1    2    NULL
...1

这是因为你对表结构进行了改变,但是历史分区的数据却没有做改变(新增分区不会出现这个情况)。

为了解决上面的问题,可以采用两种方式:

  • 如果已经执行添加操作,并且没有带cascade,可以尝试下面的方法:
代码语言:javascript复制
使用replace 恢复表结构,这样历史的分区数据都不会消失
alter table industry_db.product replace columns(product_name string comment ‘产品名’);
  • 在新增的时候加上cascade关键词
代码语言:javascript复制
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

0 人点赞