spark参数优化

spark引擎跑数后查询慢

sql 复制代码
insert overwrite table tmp.tmp1 partition(etl_date=${last_date})
select * from tmp.tmp2 where etl_date=${last_date};
输入参数为20260211引擎为spark,插到结果表,用hive引擎去count的时间为93s,如果输入参数为20260211引擎为hive,插到结果表,再用hive引擎去count的时间为1s。

差异的根源不是count阶段的引擎,而是Spark和 Hive写入时生成的底层文件格式/数量/元数据完全不同 ------Hive写入会生成带「行数元数据」的列式文件,而Spark默认写入的文件要么是Text格式、要么是元数据缺失的列式文件,导致Hive count时无法走元数据优化,只能全量扫描。

一、核心原因:Spark vs Hive 写入的文件差异(关键!)

用Hive引擎写入后count快,本质是Hive默认会对输出文件做「列式存储+元数据统计」;而Spark默认写入的文件不满足这个条件,具体差异如下:

a、文件格式:

Hive默认将结果表存储为Orc格式(数仓主流列式存储),且会自动统计每个文件的「行数、字段统计信息」并写入文件Footer;Hive count时直接读Footer的行数,无需扫描数据。

Spark默认写入的是TextFile格式(行式存储),即使表定义是Orc,Spark也可能因配置缺失以Text格式写入;即使是Orc格式,Spark默认不开启「文件元数据统计」,Footer中无行数信息,Hive count时只能逐行扫描。

b、文件数量:

Hive写入时默认合并小文件,一个分区仅生成1-2个大文件,扫描开销低。

Spark按Executor数量拆分输出,一个分区生成数十/数百个小文件,Hive count时要打开成百上千个文件,IO开销暴增。

c、元数据同步:

Hive写入后会立即更新分区元数据(如文件行数、大小),count时直接用元数据。

Spark写入后仅更新表分区的文件列表,不统计文件行数等元数据,Hive无法直接复用。

二、验证:查看两种写入方式的文件格式/数量

-- 1.查看表的存储格式(确认表定义是否为Orc)

sql 复制代码
desc formatted mid.dwd_mkt_user_behavior_analyse_datas_dd;

--重点看:Storage Format →若显示ORC,则表定义是列式存储;若显示TextFile,说明表定义本身有问题。

-- 2.查看Hive引擎写入后的文件(etl_date=20260211)

sql 复制代码
dfs -ls /user/hive/warehouse/mid.db/dwd_mkt_user_behavior_analyse_datas_dd/etl_date=20260211;
--正常结果:1-2个.orc文件,大小几十/上百MB。

-- 3.查看Spark引擎写入后的文件

sql 复制代码
dfs -ls /user/hive/warehouse/mid.db/dwd_mkt_user_behavior_analyse_datas_dd/etl_date=20260211;
--异常结果:数十/数百个.txt或.orc文件,每个文件几KB(小文件)。

三、解决方案:让Spark写入「带元数据的 Orc 文件」

核心思路是:修改Spark的写入配置,让Spark和Hive一样生成「Orc格式+元数据统计+合并小文件」的文件,这样Hive count时就能走元数据优化,耗时降到1秒级。

步骤1:修改表定义(确保表是Orc格式,若已定义可跳过)

如果表定义是TextFile,先修改为Orc格式(数仓必须的优化):

sql 复制代码
ALTER TABLE mid.dwd_mkt_user_behavior_analyse_datas_dd SET FILEFORMAT ORC;
--同时开启Orc元数据统计
ALTER TABLE mid.dwd_mkt_user_behavior_analyse_datas_dd SET TBLPROPERTIES (
  'orc.compress'='SNAPPY',  --压缩格式,减少文件大小
  'orc.stats.autogather'='true'  --自动收集元数据
);

步骤2:在Spark引擎执行SQL时,添加以下配置(核心!)

新增Spark专属配置,强制Spark以Orc格式写入,并开启元数据统计、合并小文件:

sql 复制代码
--Spark写入Orc格式的核心配置(必须加在SQL最前面)
SET spark.sql.execution.arrow.enabled=true;
SET spark.sql.parquet.writeLegacyFormat=false;
--强制输出Orc格式,开启元数据统计
SET spark.sql.files.format=orc;
SET spark.sql.orc.impl=native;
SET spark.sql.orc.statistics.enabled=true;
SET spark.sql.orc.filesystem.cache.enabled=true;
--合并小文件(关键:减少文件数量)
SET spark.sql.adaptive.enabled=true;
SET spark.sql.adaptive.coalescePartitions.enabled=true;
SET spark.sql.adaptive.coalescePartitions.minPartitionNum=1;
SET spark.sql.adaptive.maxPartitionBytes=128MB;  --每个文件128MB,合并小文件
--减少Executor开销(适配轻量任务)
SET spark.executor.instances=1;
SET spark.executor.cores=2;
SET spark.executor.memory=2g;
SET spark.driver.memory=1g;
insert overwrite table tmp.tmp1 partition(etl_date=${last_date})
select * from tmp.tmp2 where etl_date=${last_date};

(适配Parquet格式的Spark配置)

sql 复制代码
-- ===================== Spark Parquet 核心优化配置 =====================
-- 1. 强制Spark按表定义的Parquet格式写入(关键)
SET spark.sql.files.format=parquet;
SET spark.sql.parquet.writeLegacyFormat=true;  -- 兼容Hive MR的Parquet格式(必开!)
SET spark.sql.parquet.enableVectorizedReader=true;  -- 开启Parquet矢量化读取/写入,提升效率

-- 2. 开启Parquet元数据统计(让Footer包含行数)
SET spark.sql.parquet.statistics.enabled=true;  -- 生成行数等元数据
SET spark.sql.parquet.filesystem.cache.enabled=true;  -- 缓存Parquet文件元数据

-- 3. 合并小文件(核心:减少文件数量,从数百个→1-2个)
SET spark.sql.adaptive.enabled=true;  -- 开启自适应执行
SET spark.sql.adaptive.coalescePartitions.enabled=true;  -- 自动合并小分区
SET spark.sql.adaptive.coalescePartitions.minPartitionNum=1;  -- 最少保留1个分区(文件)
SET spark.sql.adaptive.maxPartitionBytes=128MB;  -- 每个Parquet文件128MB(数仓最佳大小)
SET spark.sql.adaptive.forceApply=true;  -- 强制生效合并规则

-- 4. 减少Spark启动/资源开销(适配轻量任务)
SET spark.executor.instances=1;  -- 仅启动1个Executor(足够处理单分区数据)
SET spark.executor.cores=2;      -- 每个Executor 2核
SET spark.executor.memory=2g;    -- 2G内存(避免OOM,足够解析JSON)
SET spark.driver.memory=1g;      -- Driver 1G内存(轻量调度)
SET spark.task.cpus=1;           -- 每个Task 1核
SET spark.sql.shuffle.partitions=2;  -- 减少Shuffle分区(无Shuffle但兜底)
insert overwrite table tmp.tmp1 partition(etl_date=${last_date})
select * from tmp.tmp2 where etl_date=${last_date};

必须开启 writeLegacyFormat=true:Spark默认生成的Parquet格式和Hive MR不兼容,开启该配置后,Spark写入的Parquet文件才能被Hive正确读取元数据;

步骤3:写入后手动补全元数据(兜底方案)

若Spark写入后count仍慢,执行以下命令手动更新Parquet文件的统计信息,Hive会立即识别行数:

sql 复制代码
-- 手动计算分区的元数据(行数、大小等)
ANALYZE TABLE mid.dwd_mkt_user_behavior_analyse_datas_dd
PARTITION (etl_date='20260211') 
COMPUTE STATISTICS;

四、验证优化效果

执行优化后的 Spark SQL写入后,再执行count:

sql 复制代码
--用Hive引擎count,验证耗时
SET hive.execution.engine=mr;
SELECT COUNT(*) FROM mid.dwd_mkt_user_behavior_analyse_datas_dd WHERE etl_date='20260211';

五、生产级最佳实践(避免后续踩坑)

统一表存储格式:数仓所有表默认用Orc格式,开启元数据统计(orc.stats.autogather=true);

Spark写入必加配置:所有Spark写入Hive表的SQL,都要加「Orc格式+小文件合并」配置,避免生成无效文件;

sql 复制代码
分区后优化:写入分区后,执行ANALYZE TABLE 表名 PARTITION (etl_date='20260211') COMPUTE STATISTICS;,手动补全元数据(兜底方案);

避免Text格式:绝对不要用TextFile格式存储数仓表,列式存储(Orc/Parquet)是count/聚合查询快的核心。

总结

核心原因:Spark默认写入的文件是「Text格式/小文件/元数据缺失」,Hive count时无法走元数据优化,只能全量扫描;Hive写入的是「Orc格式+大文件+元数据完整」,count直接读元数据;

解决关键:给Spark添加「强制Orc格式+元数据统计+小文件合并」配置,让Spark写入的文件和Hive一致;

相关推荐
春日见5 小时前
拉取与合并:如何让个人分支既包含你昨天的修改,也包含 develop 最新更新
大数据·人工智能·深度学习·elasticsearch·搜索引擎
你这个代码我看不懂6 小时前
@RefreshScope刷新Kafka实例
分布式·kafka·linq
Elastic 中国社区官方博客7 小时前
如何防御你的 RAG 系统免受上下文投毒攻击
大数据·运维·人工智能·elasticsearch·搜索引擎·ai·全文检索
YangYang9YangYan8 小时前
2026中专大数据与会计专业数据分析发展路径
大数据·数据挖掘·数据分析
W133309089079 小时前
工业大数据方向,CDA证书和工业数据工程师证哪个更实用?
大数据
Re.不晚9 小时前
可视化大数据——淘宝母婴购物数据【含详细代码】
大数据·阿里云·云计算
Elastic 中国社区官方博客9 小时前
Elasticsearch:交易搜索 - AI Agent builder
大数据·人工智能·elasticsearch·搜索引擎·ai·全文检索
SQL必知必会10 小时前
使用 SQL 进行 RFM 客户细分分析
大数据·数据库·sql
YangYang9YangYan10 小时前
2026大专大数据技术专业学数据分析指南
大数据·数据挖掘·数据分析