Hadoop学习教程,从入门到精通,Sqoop数据迁移 — 完整知识点与案例代码(10)

Sqoop数据迁移 --- 完整知识点与案例代码


一、Sqoop概述

1.1 Sqoop简介

知识点:

  • Sqoop (SQL-to-Hadoop)是 Apache 旗下的一款开源工具,用于在 Hadoop(HDFS/Hive/HBase)关系型数据库(MySQL/Oracle/PostgreSQL等) 之间进行批量数据迁移
  • Sqoop 本质是一个 MapReduce 程序,只有 Map 阶段,没有 Reduce 阶段。
  • Sqoop1 (1.4.x):客户端工具,架构简单;Sqoop2(1.99.x):服务端架构,引入了 REST API 和安全机制,但功能反而受限,社区主流仍使用 Sqoop1。
  • 核心能力:导入(Import) :RDBMS → Hadoop;导出(Export):Hadoop → RDBMS。

1.2 Sqoop原理

知识点:

导入原理(Import)
  1. Sqoop 通过 JDBC 连接关系型数据库,获取表的元数据(列名、列类型等)。
  2. 根据元数据生成对应的 Java 类(--class-name 可指定)。
  3. 将导入任务转化为只有 Map 的 MapReduce 任务。
  4. Map 任务并行读取数据库数据,将数据写入 HDFS(默认以分隔符分隔的文本文件或 Avro/SequenceFile 格式)。
导出原理(Export)
  1. Sqoop 读取 HDFS 上的文件,解析每一行数据。
  2. 生成 INSERT 语句 将数据批量插入目标数据库表中。
  3. 同样转化为只有 Map 的 MapReduce 任务。
并行控制
  • -m--num-mappers :指定并行的 Map 任务数。默认是 4
  • 每个 Map 任务负责读取数据库表的一个分片(split) ,分片依据是主键 或指定的 --split-by 列。
  • 如果表没有主键,必须通过 --split-by 指定一个数值型列作为分片依据。

二、Sqoop安装配置

2.1 安装步骤

知识点:

Sqoop1 的安装本质是解压 + 配置环境变量 + 放置 JDBC 驱动包。

bash 复制代码
# ========== 步骤1:解压Sqoop安装包 ==========
# 将下载的sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz解压到指定目录
tar -zxvf sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz -C /opt/module/

# ========== 步骤2:重命名解压后的目录 ==========
# 将长文件名简化,方便后续操作
mv /opt/module/sqoop-1.4.7.bin__hadoop-2.6.0 /opt/module/sqoop-1.4.7

# ========== 步骤3:配置环境变量 ==========
# 编辑系统环境变量配置文件
sudo vim /etc/profile.d/my_env.sh
# 在文件末尾追加以下内容:
# SQOOP_HOME指向Sqoop的安装根目录
export SQOOP_HOME=/opt/module/sqoop-1.4.7
# 将Sqoop的bin目录加入PATH,使命令全局可用
export PATH=$PATH:$SQOOP_HOME/bin

# 使环境变量立即生效
source /etc/profile.d/my_env.sh

# ========== 步骤4:配置sqoop-env.sh ==========
# Sqoop提供了一个模板配置文件,复制一份作为正式配置
cp /opt/module/sqoop-1.4.7/conf/sqoop-env-template.sh \
   /opt/module/sqoop-1.4.7/conf/sqoop-env.sh

# 编辑配置文件,设置Hadoop相关组件的安装路径
vim /opt/module/sqoop-1.4.7/conf/sqoop-env.sh
# 修改以下配置项(根据实际路径调整):
export HADOOP_COMMON_HOME=/opt/module/hadoop-3.1.3     # Hadoop公共组件路径
export HADOOP_MAPRED_HOME=/opt/module/hadoop-3.1.3     # Hadoop MapReduce路径
export HBASE_HOME=/opt/module/hbase-2.2.5               # HBase安装路径(可选)
export HIVE_HOME=/opt/module/hive-3.1.2                 # Hive安装路径
export ZOOCFGDIR=/opt/module/zookeeper-3.5.7/conf       # ZooKeeper配置路径(可选)

# ========== 步骤5:拷贝JDBC驱动到Sqoop的lib目录 ==========
# Sqoop通过JDBC连接数据库,必须将对应数据库的驱动jar包放到lib目录下
# MySQL驱动示例
cp /opt/module/mysql-connector-java-5.1.49/mysql-connector-java-5.1.49.jar \
   /opt/module/sqoop-1.4.7/lib/

# ========== 步骤6:验证安装 ==========
# 测试Sqoop是否能正确连接MySQL数据库
sqoop list-databases \
  --connect jdbc:mysql://hadoop102:3306/ \
  --username root \
  --password 123456

# 如果输出了MySQL中的所有数据库名称,说明Sqoop安装成功

三、Sqoop命令介绍

3.1 核心命令分类

知识点:

命令 说明
list-databases 列出MySQL中所有数据库
list-tables 列出指定数据库中所有表
import 从RDBMS导入数据到Hadoop
export 从Hadoop导出数据到RDBMS
create-hive-table 根据RDBMS表结构在Hive中创建表
eval 执行SQL语句并显示结果(用于测试)
import-all-tables 导入某个数据库下的所有表
job 创建/管理可重复执行的Sqoop作业

3.2 通用连接参数

知识点:

参数 说明
--connect <jdbc-url> JDBC连接字符串,如 jdbc:mysql://host:3306/dbname
--username <user> 数据库用户名
--password <pwd> 数据库密码(明文,不安全)
--password-file <path> 从HDFS文件中读取密码(更安全)
--driver <class> 指定JDBC驱动类名(默认自动检测)
--num-mappers <n> / -m <n> 指定Map任务并行数
--split-by <column> 指定分片列(无主键时必须指定)
--target-dir <hdfs-path> 指定HDFS目标目录
--delete-target-dir 导入前先删除目标目录(避免冲突)
--fields-terminated-by <char> 指定字段分隔符
--null-string <str> MySQL中字符串类型NULL值替换为指定值
--null-non-string <str> MySQL中非字符串类型NULL值替换为指定值

3.3 命令案例

bash 复制代码
# ========== 案例1:list-databases 列出所有数据库 ==========
# 通过JDBC连接MySQL服务器,列出该服务器上所有的数据库名称
sqoop list-databases \
  --connect jdbc:mysql://hadoop102:3306/ \    # JDBC连接字符串,不指定数据库名即连接整个MySQL实例
  --username root \                            # MySQL登录用户名
  --password 123456                            # MySQL登录密码

# ========== 案例2:list-tables 列出指定数据库中的所有表 ==========
# 在JDBC URL中指定数据库名(company),列出该库下所有表
sqoop list-tables \
  --connect jdbc:mysql://hadoop102:3306/company \   # 连接company数据库
  --username root \                                  # MySQL登录用户名
  --password 123456                                  # MySQL登录密码

# ========== 案例3:eval 执行SQL查询并显示结果 ==========
# eval命令不执行导入导出,仅在Sqoop中直接执行SQL并返回结果,常用于测试
sqoop eval \
  --connect jdbc:mysql://hadoop102:3306/company \    # 连接company数据库
  --username root \                                   # MySQL登录用户名
  --password 123456 \                                 # MySQL登录密码
  --query "SELECT * FROM staff WHERE id <= 3"         # 执行的SQL查询语句

# ========== 案例4:eval 插入测试数据 ==========
# 使用eval执行INSERT语句,向staff表插入一条测试数据
sqoop eval \
  --connect jdbc:mysql://hadoop102:3306/company \    # 连接company数据库
  --username root \                                   # MySQL登录用户名
  --password 123456 \                                 # MySQL登录密码
  --query "INSERT INTO staff VALUES(5, 'xiaoming', 'Beijing')"  # 插入一行数据

四、Sqoop数据的导入(Import)

4.1 数据准备

知识点:

在 MySQL 中创建测试数据库和测试表,并插入测试数据。

sql 复制代码
-- ========== 步骤1:创建数据库 ==========
-- 创建名为company的测试数据库,指定字符集为UTF-8(支持中文)
CREATE DATABASE IF NOT EXISTS company DEFAULT CHARSET utf8mb4;

-- 使用company数据库
USE company;

-- ========== 步骤2:创建staff表 ==========
-- staff表用于存放员工信息,包含id、姓名、地址三个字段
CREATE TABLE IF NOT EXISTS staff (
    id       INT          PRIMARY KEY  COMMENT '员工编号,主键',    -- 整数类型,作为主键(Sqoop分片依据)
    name     VARCHAR(50)  NOT NULL     COMMENT '员工姓名',         -- 变长字符串,最大50字符
    address  VARCHAR(100) COMMENT '员工地址'                        -- 变长字符串,最大100字符
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表';       -- 使用InnoDB引擎,UTF-8字符集

-- ========== 步骤3:插入测试数据 ==========
-- 插入6条测试记录,id从1到6
INSERT INTO staff VALUES(1, 'zhangsan', 'Beijing');
INSERT INTO staff VALUES(2, 'lisi',     'Shanghai');
INSERT INTO staff VALUES(3, 'wangwu',   'Guangzhou');
INSERT INTO staff VALUES(4, 'zhaoliu',  'Shenzhen');
INSERT INTO staff VALUES(5, 'sunqi',    'Hangzhou');
INSERT INTO staff VALUES(6, 'zhouba',   'Chengdu');

-- ========== 步骤4:验证数据 ==========
SELECT * FROM staff;
-- 预期结果:
-- +----+----------+----------+
-- | id | name     | address  |
-- +----+----------+----------+
-- |  1 | zhangsan | Beijing  |
-- |  2 | lisi     | Shanghai |
-- |  3 | wangwu   | Guangzhou|
-- |  4 | zhaoliu  | Shenzhen |
-- |  5 | sunqi    | Hangzhou |
-- |  6 | zhouba   | Chengdu  |
-- +----+----------+----------+

4.2 MySQL导入HDFS

4.2.1 全量导入(默认行为)

知识点:

  • 使用 sqoop import 命令将 MySQL 表数据全量导入 HDFS。
  • 默认目标目录为 /user/<当前用户名>/<表名>
  • 默认字段分隔符为逗号 ,
  • 默认 Map 并行数为 4
  • 如果表有主键,Sqoop 会自动以主键作为分片依据。
bash 复制代码
# ========== 案例1:全量导入staff表到HDFS(最简写法) ==========
# 将MySQL中company.staff表的所有数据导入HDFS的默认路径
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接字符串,指向company数据库
  --username root \                                    # MySQL登录用户名
  --password 123456 \                                  # MySQL登录密码
  --table staff                                        # 要导入的MySQL表名(Sqoop会自动读取表结构)
  # 默认输出路径:/user/root/staff
  # 默认字段分隔符:逗号(,)
  # 默认文件格式:文本文件(TextFile)
  # Map数默认为4,但staff表数据量小,实际只会使用1个Map

# ========== 查看HDFS上的导入结果 ==========
# 列出导入目录下的文件
hdfs dfs -ls /user/root/staff
# 查看导入的数据内容
hdfs dfs -cat /user/root/staff/part-m-00000
# 预期输出(逗号分隔):
# 1,zhangsan,Beijing
# 2,lisi,Shanghai
# 3,wangwu,Guangzhou
# ...
4.2.2 指定目标目录和分隔符

知识点:

  • --target-dir:指定 HDFS 上的目标存储目录(目录不能已存在,除非加 --delete-target-dir)。
  • --fields-terminated-by:指定字段之间的分隔符。
  • --delete-target-dir:在导入前删除目标目录(如果已存在),避免冲突。
bash 复制代码
# ========== 案例2:指定目标目录和分隔符 ==========
# 将staff表数据导入到指定的HDFS目录,使用制表符(\t)作为分隔符
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # 源表:staff
  --target-dir /user/root/staff_target \               # HDFS目标目录(必须不存在或先删除)
  --delete-target-dir \                                # 如果目标目录已存在,先删除再导入
  --fields-terminated-by "\t" \                        # 字段分隔符设为制表符
  --num-mappers 1                                      # 使用1个Map任务(数据量小,无需并行)

# ========== 查看结果 ==========
hdfs dfs -cat /user/root/staff_target/part-m-00000
# 预期输出(制表符分隔):
# 1	zhangsan	Beijing
# 2	lisi	Shanghai
# 3	wangwu	Guangzhou
# ...
4.2.3 指定Map并行数与分片列

知识点:

  • --num-mappers-m):控制并行的 Map 任务数量,值越大并行度越高。
  • --split-by:指定分片列。当表没有主键或不想用主键分片时,必须显式指定。
  • 如果 -m 设为 1,则不需要 --split-by(只有一个Map,无需分片)。
  • 如果 -m > 1 且表没有主键,则必须 指定 --split-by
bash 复制代码
# ========== 案例3:指定Map数量和分片列 ==========
# 使用2个Map任务并行导入,以id列作为分片依据
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # 源表:staff
  --target-dir /user/root/staff_parallel \             # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --fields-terminated-by "\t" \                        # 制表符分隔
  --num-mappers 2 \                                    # 使用2个Map任务并行导入
  --split-by id                                        # 以id列作为分片依据(id为整数,适合做分片)

# ========== 查看结果 ==========
# 由于使用了2个Map,会产生2个输出文件
hdfs dfs -ls /user/root/staff_parallel
# 输出类似:
# -rw-r--r--   1 root supergroup    xxx  part-m-00000
# -rw-r--r--   1 root supergroup    xxx  part-m-00001

hdfs dfs -cat /user/root/staff_parallel/part-m-00000  # 第一个Map的输出
hdfs dfs -cat /user/root/staff_parallel/part-m-00001  # 第二个Map的输出
4.2.4 处理NULL值

知识点:

  • MySQL 中的 NULL 值在导入时默认以字符串 "null" 存储到 HDFS。
  • --null-string:指定字符串类型(VARCHAR/CHAR/TEXT等)的 NULL 值替换文本。
  • --null-non-string:指定非字符串类型(INT/FLOAT/DATE等)的 NULL 值替换文本。
  • 通常将两者都设为 "\\N"(即 Hive 默认的 NULL 表示方式),便于后续导入 Hive 时正确识别。
bash 复制代码
# ========== 案例4:处理NULL值 ==========
# 导入时将NULL值替换为Hive兼容的\N标记
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # 源表:staff
  --target-dir /user/root/staff_null \                 # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --fields-terminated-by "\t" \                        # 制表符分隔
  --num-mappers 1 \                                    # 1个Map任务
  --null-string "\\N" \                                # 字符串类型的NULL值替换为\N
  --null-non-string "\\N"                              # 非字符串类型的NULL值替换为\N

# 查看结果(如果某字段为NULL,会显示为\N)
hdfs dfs -cat /user/root/staff_null/part-m-00000
4.2.5 使用Query导入(自定义SQL)

知识点:

  • --query:使用自定义 SQL 查询语句代替 --table,实现灵活的数据筛选。
  • SQL 中必须包含 $CONDITIONS 占位符(Sqoop 内部用于分片控制)。
  • 使用 --query不能 同时使用 --table
  • 建议搭配 --target-dir 使用。
bash 复制代码
# ========== 案例5:使用query自定义SQL导入 ==========
# 只导入id <= 3的员工数据
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --query 'SELECT id, name, address FROM staff WHERE id <= 3 AND $CONDITIONS' \  # 自定义SQL,$CONDITIONS必须保留
  --target-dir /user/root/staff_query \                # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --fields-terminated-by "\t" \                        # 制表符分隔
  --num-mappers 1                                      # 1个Map任务(query通常配合-m 1使用)

# ========== 查看结果 ==========
hdfs dfs -cat /user/root/staff_query/part-m-00000
# 预期输出(只有id<=3的3条记录):
# 1	zhangsan	Beijing
# 2	lisi	Shanghai
# 3	wangwu	Guangzhou
4.2.6 设置压缩导入

知识点:

  • --compress / -z:启用压缩。
  • --compression-codec:指定压缩编码器,常用值:
    • org.apache.hadoop.io.compress.GzipCodec(Gzip 压缩)
    • org.apache.hadoop.io.compress.SnappyCodec(Snappy 压缩,Hadoop 常用)
    • org.apache.hadoop.io.compress.BZip2Codec(BZip2 压缩)
bash 复制代码
# ========== 案例6:启用Gzip压缩导入 ==========
# 导入数据时启用Gzip压缩,减少HDFS存储空间
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # 源表:staff
  --target-dir /user/root/staff_gzip \                 # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --num-mappers 1 \                                    # 1个Map任务
  --compress \                                         # 启用压缩
  --compression-codec org.apache.hadoop.io.compress.GzipCodec  # 使用Gzip压缩算法

# ========== 查看结果 ==========
hdfs dfs -ls /user/root/staff_gzip
# 输出文件名类似:part-m-00000.gz(带.gz后缀表示已压缩)

# 需要解压查看内容
hadoop fs -text /user/root/staff_gzip/part-m-00000.gz

4.3 增量导入

知识点:

增量导入用于只导入自上次导入以来新增或修改的数据,避免每次都全量导入。

Sqoop 提供两种增量导入模式:

模式 参数 说明
append 模式 --incremental append + --check-column id + --last-value 3 基于某一列的值(通常是自增ID),只导入大于 last-value 的数据
lastmodified 模式 --incremental lastmodified + --check-column update_time + --last-value '2024-01-01' 基于时间戳列,只导入在 last-value 之后修改过的数据
4.3.1 Append模式增量导入
bash 复制代码
# ========== 案例7:Append模式增量导入 ==========
# 场景:第一次全量导入了id=1~6的数据,现在MySQL新增了id=7、8的数据
# 我们只需要导入id > 6的新增数据

# ---------- 第一步:先做一次全量导入(模拟首次导入) ----------
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # 源表:staff
  --target-dir /user/root/staff_incremental \          # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --fields-terminated-by "\t" \                        # 制表符分隔
  --num-mappers 1                                      # 1个Map任务

# ---------- 第二步:在MySQL中插入新数据 ----------
# 模拟业务新增了2条记录
INSERT INTO staff VALUES(7, 'tangjiu', 'Wuhan');
INSERT INTO staff VALUES(8, 'yangshi', 'Nanjing');

# ---------- 第三步:执行增量导入 ----------
# 只导入id > 6的数据(即id=7和id=8)
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # 源表:staff
  --target-dir /user/root/staff_incremental \          # HDFS目标目录(追加到同一目录)
  --incremental append \                               # 增量模式:append(追加)
  --check-column id \                                  # 以id列作为增量检查列
  --last-value 6 \                                     # 上次导入的最大id值为6,导入id > 6的数据
  --fields-terminated-by "\t" \                        # 制表符分隔
  --num-mappers 1                                      # 1个Map任务

# ---------- 第四步:查看结果 ----------
hdfs dfs -ls /user/root/staff_incremental
# 目录下现在有2个文件:
# part-m-00000(第一次全量导入的6条数据)
# part-m-00001(增量导入的2条新数据)

# 查看增量导入的数据
hdfs dfs -cat /user/root/staff_incremental/part-m-00001
# 预期输出:
# 7	tangjiu	Wuhan
# 8	yangshi	Nanjing
4.3.2 Lastmodified模式增量导入

知识点:

  • 适用于表中有更新时间字段的场景。
  • --incremental lastmodified:按时间戳增量导入。
  • --last-value:指定上次导入的截止时间。
  • 可选参数 --append:将增量数据追加到同一目录。
  • 可选参数 --merge-key id:将增量数据按主键合并到已有数据中(更新旧记录)。
bash 复制代码
# ========== 案例8:Lastmodified模式增量导入 ==========
# 场景:staff表新增了update_time字段,记录数据的最后修改时间

# ---------- MySQL中准备数据(先在表中增加时间字段) ----------
# ALTER TABLE staff ADD COLUMN update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;

# ---------- 执行基于时间戳的增量导入 ----------
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # 源表:staff
  --target-dir /user/root/staff_lastmodified \         # HDFS目标目录
  --incremental lastmodified \                         # 增量模式:lastmodified(基于时间戳)
  --check-column update_time \                         # 以update_time列作为检查列
  --last-value "2024-01-01 00:00:00" \                 # 只导入update_time > 此值的记录
  --fields-terminated-by "\t" \                        # 制表符分隔
  --num-mappers 1                                      # 1个Map任务

4.4 MySQL导入Hive

知识点:

  • 使用 --hive-import 参数将数据直接导入到 Hive 表中。
  • Sqoop 会自动在 Hive 中创建表(默认创建在 default 数据库中)。
  • 默认创建的 Hive 表是内部表(Managed Table)
  • 导入过程:先将数据导入到 HDFS 的 /user/hive/warehouse/<表名> 目录,再通过 Hive 的 LOAD DATA 加载。
  • --hive-database:指定 Hive 目标数据库。
  • --hive-table:指定 Hive 目标表名。
  • --hive-overwrite:覆盖已有数据(默认是追加)。
  • --hive-delims-replacement:将数据中的换行符、制表符等替换为指定字符串(避免字段错乱)。
  • --hive-partition-key--hive-partition-value:指定分区字段和值。
bash 复制代码
# ========== 案例9:MySQL全量导入Hive(基本用法) ==========
# 将MySQL的staff表数据导入到Hive的default数据库中
# Sqoop会自动在Hive中创建staff表(如果不存在)
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # MySQL源表名
  --hive-import \                                      # 开启Hive导入模式
  --hive-database default \                            # Hive目标数据库(默认default)
  --hive-table staff \                                 # Hive目标表名
  --num-mappers 1 \                                    # 1个Map任务
  --hive-overwrite                                     # 覆盖Hive表中已有的数据

# ========== 在Hive中验证 ==========
# 进入Hive命令行
hive
# 查看表是否创建成功
SHOW TABLES;
# 查看表结构
DESC staff;
# 查询数据
SELECT * FROM staff;
bash 复制代码
# ========== 案例10:导入Hive并处理特殊字符 ==========
# 当MySQL数据中包含换行符、制表符等特殊字符时,需要进行替换
# 否则会导致Hive中的字段错位
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # MySQL源表名
  --hive-import \                                      # 开启Hive导入模式
  --hive-database default \                            # Hive目标数据库
  --hive-table staff_clean \                           # Hive目标表名(使用不同的表名)
  --num-mappers 1 \                                    # 1个Map任务
  --hive-overwrite \                                   # 覆盖写入
  --fields-terminated-by "\t" \                        # Hive表的字段分隔符设为制表符
  --null-string "\\N" \                                # 字符串NULL替换为\N(Hive兼容格式)
  --null-non-string "\\N" \                            # 非字符串NULL替换为\N
  --hive-delims-replacement " "                        # 将数据中的换行符、制表符等替换为空格

# ========== 查看HDFS上的Hive表数据 ==========
hdfs dfs -ls /user/hive/warehouse/staff_clean
hdfs dfs -cat /user/hive/warehouse/staff_clean/part-m-00000
bash 复制代码
# ========== 案例11:导入到Hive指定分区表 ==========
# 场景:按日期对数据进行分区存储
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # MySQL源表名
  --hive-import \                                      # 开启Hive导入模式
  --hive-database default \                            # Hive目标数据库
  --hive-table staff_partition \                       # Hive目标表名(分区表)
  --num-mappers 1 \                                    # 1个Map任务
  --hive-partition-key dt \                            # 分区字段名(dt)
  --hive-partition-value "2024-06-01"                  # 分区字段值(指定日期)

# ========== 在Hive中验证分区 ==========
# hive> SHOW PARTITIONS staff_partition;
# 输出:dt=2024-06-01
# hive> SELECT * FROM staff_partition WHERE dt='2024-06-01';

4.5 MySQL过滤导入HDFS

知识点:

除了前面讲过的 --query 方式,还可以使用 --where 参数进行简单过滤。两者区别如下:

方式 参数 说明
--where 简单 WHERE 条件 不需要写完整的SQL,Sqoop 自动拼接
--query 完整 SQL 语句 灵活性更高,支持 JOIN、聚合等复杂查询
bash 复制代码
# ========== 案例12:使用--where过滤导入 ==========
# 只导入address为'Beijing'的员工数据
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # MySQL源表名
  --where "address='Beijing'" \                        # 过滤条件:只导入address为Beijing的记录
  --target-dir /user/root/staff_where \                # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --fields-terminated-by "\t" \                        # 制表符分隔
  --num-mappers 1                                      # 1个Map任务

# ========== 查看结果 ==========
hdfs dfs -cat /user/root/staff_where/part-m-00000
# 预期输出(只有address='Beijing'的记录):
# 1	zhangsan	Beijing
bash 复制代码
# ========== 案例13:使用--query过滤导入(多表关联查询) ==========
# 场景:company库有两张表 staff(员工表) 和 department(部门表)
# 需要关联查询员工及其部门信息

# 假设department表结构:
# CREATE TABLE department (
#     dept_id INT PRIMARY KEY,
#     dept_name VARCHAR(50),
#     staff_id INT
# );
# INSERT INTO department VALUES(1, '技术部', 1);
# INSERT INTO department VALUES(2, '市场部', 3);

sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --query 'SELECT s.id, s.name, s.address, d.dept_name FROM staff s INNER JOIN department d ON s.id = d.staff_id AND $CONDITIONS' \  # 复杂关联查询,$CONDITIONS必须保留
  --target-dir /user/root/staff_dept_join \            # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --fields-terminated-by "\t" \                        # 制表符分隔
  --num-mappers 1                                      # 1个Map任务(复杂查询建议使用-m 1)

# ========== 查看结果 ==========
hdfs dfs -cat /user/root/staff_dept_join/part-m-00000
# 预期输出:
# 1	zhangsan	Beijing	技术部
# 3	wangwu	Guangzhou	市场部
bash 复制代码
# ========== 案例14:使用--query进行聚合过滤导入 ==========
# 只导入每个地址的员工数量统计
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --query 'SELECT address, COUNT(*) AS cnt FROM staff GROUP BY address HAVING $CONDITIONS' \  # 聚合查询(注:此处$CONDITIONS配合-m 1使用)
  --target-dir /user/root/staff_agg \                  # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --fields-terminated-by "\t" \                        # 制表符分隔
  --num-mappers 1                                      # 必须设为1(聚合查询不能并行分片)
  # 注意:当使用GROUP BY等聚合函数时,必须设置 -m 1,否则分片会导致结果不正确

五、Sqoop数据的导出(Export)

5.1 知识点

  • sqoop export:将 HDFS/Hive 中的数据导出到关系型数据库。
  • --export-dir:指定 HDFS 上要导出的数据源目录。
  • --input-fields-terminated-by:指定 HDFS 文件的字段分隔符(导出时需要指定,否则解析错误)。
  • --input-null-string--input-null-non-string:指定 HDFS 文件中 NULL 值的表示方式。
  • 目标 MySQL 表必须提前创建好(Sqoop 不会自动创建导出目标表)。
  • Sqoop 默认生成 INSERT 语句;也可以使用 --update-key 生成 UPDATE 语句。
  • --update-modeupdateonly(只更新已有的)或 allowinsert(更新已有的 + 插入新的,即 upsert)。

5.2 准备导出目标表

sql 复制代码
-- ========== 在MySQL中创建导出目标表 ==========
-- 注意:目标表结构要与HDFS数据的字段对应
USE company;

-- 创建export_staff表作为导出目标
CREATE TABLE IF NOT EXISTS export_staff (
    id       INT          PRIMARY KEY  COMMENT '员工编号',
    name     VARCHAR(50)  NOT NULL     COMMENT '员工姓名',
    address  VARCHAR(100) COMMENT '员工地址'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='导出目标表';

-- 验证表已创建(此时表为空)
SELECT * FROM export_staff;

5.3 导出案例

bash 复制代码
# ========== 案例15:HDFS数据导出到MySQL(基本用法) ==========
# 将HDFS上/staff_target目录的数据导出到MySQL的export_staff表
sqoop export \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table export_staff \                               # MySQL目标表名(必须提前创建)
  --export-dir /user/root/staff_target \               # HDFS源数据目录
  --input-fields-terminated-by "\t" \                  # 指定HDFS文件的字段分隔符为制表符
  --num-mappers 1                                      # 1个Map任务

# ========== 在MySQL中验证 ==========
SELECT * FROM export_staff;
# 预期输出:staff表的全部6条数据已被成功导入
bash 复制代码
# ========== 案例16:导出时处理NULL值 ==========
# 当HDFS数据中用\N表示NULL值时,需要在导出时告知Sqoop
sqoop export \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table export_staff \                               # MySQL目标表名
  --export-dir /user/root/staff_null \                 # HDFS源数据目录(含\N的NULL值)
  --input-fields-terminated-by "\t" \                  # 字段分隔符
  --input-null-string "\\N" \                          # HDFS中字符串类型NULL的表示方式为\N
  --input-null-non-string "\\N" \                      # HDFS中非字符串类型NULL的表示方式为\N
  --num-mappers 1                                      # 1个Map任务
bash 复制代码
# ========== 案例17:使用--update-key进行更新导出 ==========
# 场景:HDFS上有部分员工的地址发生了变化,需要更新MySQL中的数据
# update-key指定以哪些列作为匹配条件(类似于UPDATE ... WHERE id = ?)

# 先在HDFS上准备更新数据(只包含有变化的记录)
# 假设HDFS目录/user/root/staff_update内容为:
# 1    zhangsan    Shanghai
# 3    wangwu      Shenzhen
# (id=1和id=3的地址发生了变化)

sqoop export \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table export_staff \                               # MySQL目标表名
  --export-dir /user/root/staff_update \               # HDFS源数据目录
  --input-fields-terminated-by "\t" \                  # 字段分隔符
  --update-key id \                                    # 以id列作为UPDATE的WHERE匹配条件
  --update-mode updateonly \                           # 只更新已存在的记录,不插入新记录
  --num-mappers 1                                      # 1个Map任务

# ========== 在MySQL中验证 ==========
SELECT * FROM export_staff WHERE id IN (1, 3);
# 预期结果:id=1的address变为Shanghai,id=3的address变为Shenzhen
bash 复制代码
# ========== 案例18:使用--update-mode allowinsert进行Upsert导出 ==========
# allowinsert模式:如果记录存在则更新,如果不存在则插入(MySQL的ON DUPLICATE KEY UPDATE行为)
# 注意:目标表的update-key列必须有唯一约束或主键约束

sqoop export \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table export_staff \                               # MySQL目标表名
  --export-dir /user/root/staff_target \               # HDFS源数据目录
  --input-fields-terminated-by "\t" \                  # 字段分隔符
  --update-key id \                                    # 以id列作为匹配条件
  --update-mode allowinsert \                          # upsert模式:更新已有 + 插入新记录
  --num-mappers 1                                      # 1个Map任务

# ========== 在MySQL中验证 ==========
SELECT * FROM export_staff;
# 如果MySQL中已有对应id的记录则更新,没有则新插入

六、补充知识点与高级案例

6.1 Sqoop Job(作业管理)

知识点:

  • sqoop job --create:创建一个可保存的 Sqoop 作业。
  • sqoop job --list:列出所有已保存的作业。
  • sqoop job --show <job-name>:查看某个作业的详细配置。
  • sqoop job --exec <job-name>:执行某个已保存的作业。
  • sqoop job --delete <job-name>:删除某个作业。
  • 使用 Job 可以避免每次输入大量参数,适合定时调度场景。
bash 复制代码
# ========== 案例19:创建Sqoop Job ==========
# 创建一个名为import_staff_job的导入作业
sqoop job --create import_staff_job \
  -- import \                                          # 注意:--后面跟的是Sqoop子命令(import)
  --connect jdbc:mysql://hadoop102:3306/company \      # JDBC连接
  --username root \                                     # MySQL用户名
  --password 123456 \                                   # MySQL密码
  --table staff \                                       # 源表
  --target-dir /user/root/staff_job \                   # HDFS目标目录
  --delete-target-dir \                                 # 先删除目标目录
  --fields-terminated-by "\t" \                         # 制表符分隔
  --num-mappers 1                                       # 1个Map任务

# ========== 列出所有已保存的Job ==========
sqoop job --list
# 输出:import_staff_job

# ========== 查看Job的详细配置 ==========
sqoop job --show import_staff_job
# 输出会显示Job的所有参数配置信息

# ========== 执行Job ==========
# 执行时会提示输入数据库密码(如果创建时未指定--password)
sqoop job --exec import_staff_job

# ========== 删除Job ==========
sqoop job --delete import_staff_job

6.2 创建Hive表后再导入

知识点:

有时需要先在 Hive 中手动创建表(自定义表结构和存储格式),再用 Sqoop 导入数据,避免 Sqoop 自动生成的表结构不够灵活。

sql 复制代码
-- ========== 步骤1:在Hive中手动创建表 ==========
-- 进入Hive命令行
-- hive>

-- 创建staff_manual表,指定字段分隔符为制表符
CREATE TABLE IF NOT EXISTS staff_manual (
    id       INT       COMMENT '员工编号',         -- 整数类型
    name     STRING    COMMENT '员工姓名',         -- 字符串类型
    address  STRING    COMMENT '员工地址'           -- 字符串类型
)
ROW FORMAT DELIMITED                              -- 使用分隔符格式
FIELDS TERMINATED BY '\t'                         -- 字段间以制表符分隔
STORED AS TEXTFILE;                                -- 以文本文件格式存储
bash 复制代码
# ========== 步骤2:使用Sqoop向已创建的Hive表导入数据 ==========
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # MySQL源表
  --hive-import \                                      # 开启Hive导入模式
  --hive-database default \                            # Hive目标数据库
  --hive-table staff_manual \                          # Hive目标表名(已手动创建)
  --hive-overwrite \                                   # 覆盖写入
  --fields-terminated-by "\t" \                        # 字段分隔符(必须与Hive表定义一致)
  --num-mappers 1                                      # 1个Map任务

# ========== 在Hive中验证 ==========
-- hive>
SELECT * FROM staff_manual;

6.3 导入为Avro和Parquet格式

知识点:

文件格式 参数 说明
TextFile(默认) 无需额外参数 纯文本格式,人类可读
Avro --as-avrodatafile 二进制格式,自带Schema,适合数据序列化
SequenceFile --as-sequencefile Hadoop原生二进制格式
Parquet --as-parquetfile 列式存储格式,适合大数据分析查询
bash 复制代码
# ========== 案例20:导入为Avro格式 ==========
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # MySQL源表
  --target-dir /user/root/staff_avro \                 # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --as-avrodatafile \                                  # 以Avro格式存储
  --num-mappers 1                                      # 1个Map任务

# ========== 查看Avro文件 ==========
# Avro文件是二进制格式,不能直接cat查看,需要使用Avro工具
hdfs dfs -ls /user/root/staff_avro
# 输出文件名类似:part-m-00000.avro

# ========== 案例21:导入为Parquet格式 ==========
sqoop import \
  --connect jdbc:mysql://hadoop102:3306/company \     # JDBC连接:company数据库
  --username root \                                    # MySQL用户名
  --password 123456 \                                  # MySQL密码
  --table staff \                                      # MySQL源表
  --target-dir /user/root/staff_parquet \              # HDFS目标目录
  --delete-target-dir \                                # 先删除目标目录
  --as-parquetfile \                                   # 以Parquet列式格式存储
  --num-mappers 1                                      # 1个Map任务

七、本章小结

7.1 核心知识点汇总

知识点 关键内容
Sqoop本质 基于 MapReduce 的数据迁移工具,只有 Map 阶段
核心命令 import(导入)、export(导出)、eval(测试SQL)、job(作业管理)
全量导入 --table + --target-dir,默认逗号分隔,4个Map
增量导入 append 模式(按值增量)、lastmodified 模式(按时间增量)
导入Hive --hive-import,可指定数据库/表名/分区,注意 --null-string--null-non-string
过滤导入 --where(简单条件)、--query(复杂SQL,必须含 $CONDITIONS
导出 --export-dir + --table,目标表必须提前创建
更新导出 --update-key + --update-mode(updateonly / allowinsert)
压缩 --compress + --compression-codec
文件格式 TextFile(默认)、Avro、SequenceFile、Parquet
并行控制 -m(Map数)、--split-by(分片列)

7.2 常见错误与排查

错误 原因 解决方案
ClassNotFoundException: com.mysql.jdbc.Driver 缺少 MySQL JDBC 驱动 将 mysql-connector-java.jar 放到 $SQOOP_HOME/lib/
No primary key 表没有主键且未指定 --split-by --split-by <数值型列> 或设 -m 1
Hive 导入后数据全部为 NULL 字段分隔符不匹配 确保 --fields-terminated-by 与 Hive 表定义一致
导出时字段错位 HDFS 分隔符与 --input-fields-terminated-by 不匹配 统一分隔符设置
$CONDITIONS 报错 使用 --query 时忘记写 $CONDITIONS SQL 中添加 AND $CONDITIONS
导出时 Duplicate entry 数据重复且目标表有主键约束 使用 --update-key + --update-mode allowinsert

7.3 命令速查表

bash 复制代码
# ======================== 导入相关 ========================

# 全量导入到HDFS
sqoop import --connect jdbc:mysql://host:3306/db --username user --password pwd \
  --table 表名 --target-dir /hdfs路径 --num-mappers N

# 增量导入(append)
sqoop import ... --incremental append --check-column 列名 --last-value 值

# 导入到Hive
sqoop import ... --table 表名 --hive-import --hive-database 库名 --hive-table 表名

# 使用WHERE过滤
sqoop import ... --table 表名 --where "条件"

# 使用Query过滤
sqoop import ... --query 'SQL语句 AND $CONDITIONS'

# 压缩导入
sqoop import ... --compress --compression-codec org.apache.hadoop.io.compress.GzipCodec

# ======================== 导出相关 ========================

# HDFS导出到MySQL
sqoop export --connect jdbc:mysql://host:3306/db --username user --password pwd \
  --table 表名 --export-dir /hdfs路径 --input-fields-terminated-by "\t"

# 更新导出
sqoop export ... --update-key 主键列 --update-mode updateonly

# Upsert导出
sqoop export ... --update-key 主键列 --update-mode allowinsert

# ======================== 作业管理 ========================

# 创建Job
sqoop job --create job名 -- import --connect ... --table ...
# 执行Job
sqoop job --exec job名
# 列出Job
sqoop job --list
# 查看Job
sqoop job --show job名
# 删除Job
sqoop job --delete job名