06 分区表和分桶表

分区表

在这里插入图片描述

当Hive表对应HDFS中数据量大、文件多时,为了避免查询时全表扫描数据,Hive支持根据用户指定的字段进行分区,分区的字段可以是日期、地域、种类等具有标识意义的字段。比如把一整年的数据根据月份划分12个月(12个分区),后续就可以查询指定月份分区的数据,尽可能避免了全表扫描查询。

在这里插入图片描述

分区表特点/优点:

分区的概念提供了一种将Hive表数据分离为多个目录的方法。 不同分区对应着不同的文件夹,同一分区的数据存储在同一个文件夹下。 只需要根据分区值找到对应的文件夹,扫描本分区下的文件即可,避免全表扫描。

使用重点:

分区表的使用重点在于:

一、建表时根据业务场景设置合适的分区字段。比如日期、地域、类别等;

二、查询的时候尽量先使用where进行分区过滤,查询指定分区的数据,避免全表扫描。( 在查询数据的时不使用分区字段去筛选数据,效率不变)

-- 避免全表扫描,提升查询效率
select * from one_part_order where year='2023'; -- 提升效率
-- 注意: 如果查询的时候条件不是分区字段,效率不会改变
select * from one_part_order where price=20; -- 不会

分区表的注意事项:

  1. 分区表不是建表的必要语法规则,是一种优化手段表;
  2. 分区字段不能是表中已有的字段,不能重复(因为分区字段会作为一个字段拼接到表最后 );
  3. 分区字段是虚拟字段,其数据并不存储在底层的文件中;
  4. 分区字段值的确定来自于手动指定(静态分区)或者根据查询结果位置自动推断(动态分区
  5. Hive支持多重分区,也就是说在分区的基础上继续分区,划分更加细粒度。

建表

一级分区
创建分区表: create [external] table [if not exists] 表名(字段名 字段类型 , 字段名 字段类型 , ... )partitioned by (分区字段名 分区字段类型)... ; 

自动生成分区目录并插入数据: load data [local] inpath '文件路径' into table 分区表名 partition (分区字段名='值');

注意: 如果加local后面文件路径应该是linux本地路径,如果没有加那么就是hdfs文件路径
多级分区

多重分区下,分区之间是一种递进关系,可以理解为在前一个分区的基础上继续分区。从HDFS的角度来看就是文件夹下继续划分子文件夹。

创建分区表: create [external] table 表名(
			字段名 字段类型 ,
            字段名 字段类型 , ... 
          )partitioned by (一级分区字段名 分区字段类型, 二级分区字段名 分区字段类型 , ...) ; 

自动生成分区目录并插入数据: load data [local] inpath '文件路径' into table 分区表名 partition (一级分区字段名='值',二级分区字段名='值' , ...);

注意: 如果加local后面文件路径应该是linux本地路径,如果没有加那么就是hdfs文件路径

示例:

-- 创建表
create table multi_part_order(
    oid string,
    name string,
    price float,
    num int
)partitioned by (year string,month string,day string)
    row format delimited
fields terminated by ' ';
-- 加载数据
-- 思考数据文件在哪里?如果想从hdfs加载,怎么操作?上传到hdfs指定位置
load data inpath '/source/order202251.txt' into table multi_part_order partition (year=2022,month=05,day=01);
load data inpath '/source/order202351.txt' into table multi_part_order partition (year=2023,month=05,day=01);
load data inpath '/source/order202352.txt' into table multi_part_order partition (year=2023,month=05,day=02);
load data inpath '/source/order2023415.txt' into table multi_part_order partition (year=2023,month=04,day=15);
-- 验证数据
select * from multi_part_order;

-- 分区表的好处:避免全表扫描,提升查询效率
-- 需求: 统计2023年商品总销售额
select sum(price*num) from multi_part_order where year='2023'; -- 提升效率
-- 需求: 统计2023年5月份商品总销售额
select sum(price*num) from multi_part_order where year='2023'and month='5'; -- 进一步提升效率
-- 需求: 统计2023年5月1日的商品总销售额
select sum(price*num) from multi_part_order where year='2023'and month='5' and day='1'; 

分区增删改查操作

添加分区(add): alter table 分区表名 add partition (分区字段名='值' , ...);
删除分区(drop): alter table 分区表名 drop partition (分区字段名='值' , ...);
修改分区名(rename): alter table 分区表名 partition (分区字段名='旧值' , ...) rename to partition (分区字段名='新值' , ...);

查看所有分区: show partitons 分区表名;

-- 如果在hdfs上创建符合分区目录格式的文件夹,可以使用msck repair修复
同步/修复分区: msck repair table 分区表名;
-- 分区操作
-- 注意: 先确定有一级分区和多级分区表,如果没有先创建再做分区操作
select * from one_part_order limit 20;
select * from multi_part_order limit 20;

-- 添加分区(本质在hdfs上创建分区目录)
alter table one_part_order add partition (year=2024);
alter table multi_part_order add partition (year=2024,month=5,day=1);

-- 修改分区(本质在hdfs上修改分区目录名)
alter table one_part_order partition (year=2024) rename to partition (year=2030);
alter table multi_part_order  partition (year=2024,month=5,day=1) rename to partition (year=2030,month=6,day=10);

-- 查看所有分区
show partitions one_part_order;
show partitions multi_part_order;

-- 删除分区
alter table multi_part_order drop partition (year=2030,month=6,day=10);
alter table multi_part_order drop partition (year=2023,month=4);
alter table multi_part_order drop partition (year=2022);

-- 如果在hdfs上创建符合分区目录格式的文件夹,可以使用msck repair修复
-- 举例:手动创建一个year=2033目录
msck repair table one_part_order;
msck repair table multi_part_order;
-- 修复后再次查看所有分区
show partitions one_part_order;
show partitions multi_part_order;

在这里插入图片描述

分桶表

分桶表也叫做桶表,源自建表语法中bucket单词。是一种用于优化查询而设计的表类型。该功能可以让数据分解为若干个部分易于管理。
请添加图片描述

分桶表特点/优点:

需要产生分桶文件, 查询的时候特定操作上提升效率(过滤,join,分组 以及 抽样):

  1. 基于分桶字段查询不再需要进行全表扫描,提高抽样效率

  2. JOIN时可以提高MR程序效率,减少笛卡尔积数量

    对于JOIN操作两个表有一个相同的列,如果对这两个表都进行了分桶操作。那么将保存相同列值的桶进行JOIN操作就可以,可以大大较少JOIN的数据量。

  3. 分桶表便于数据进行抽样

使用重点:

  1. 分桶字段名必须是原有字段名, 因为分桶需要根据对应字段值取余数把余数相同的数据放到同一个分桶文件中。

  2. 在查询数据的时使用分桶字段查询。如果没有使用分桶字段去筛选数据,效率不变。

重要参数

-- 默认开启,hive2.x版本已经被移除
set hive.enforce.bucketing; -- 查看未定义因为已经被移除
set hive.enforce.bucketing=true; -- 修改

-- 查看reduce数量
-- 参数优先级: set方式 > hive文档 > hadoop文档
set mapreduce.job.reduces; -- 查看默认-1,代表自动根据桶数量匹配reduce数量
set mapreduce.job.reduces=3; -- 设置参数

基础建表:

创建基础分桶表:  
create [external] table 表名(
	字段名 字段类型 
)
clustered by (分桶字段名) 
into 桶数量 buckets ;

分桶表排序:

创建基础分桶表,然后桶内排序:   
create [external] table 表名(
	字段名 字段类型 
)
clustered by (分桶字段名)sorted by(排序字段名 asc|desc)   # 注意:asc升序(默认) desc降序
into 桶数量 buckets ;

注意事项:

数据倾斜问题: 分桶字段值如果大量重复,相同的会分到同一个桶内,导致数据倾斜

解决方法:随机化处理:可以用rand()对分桶字段进行随机化处理再分桶

还有动态分区,列式存储,调整mapreduce参数以及倾斜算法来解决。

分桶原理

分桶原理: 
如果是数值类型分桶字段: 直接使用数值对桶数量取模   
如果是字符串类型分桶字段: 使用hash函数计算出哈希值然后再对桶数量取模
ps:Hash函数是一种将输入数据映射为固定长度输出的函数。它具有以下特点:(1)输出长度固定,不受输入长度影响;(2)同一输入必定得到相同输出;(3)不同输入几乎不可能得到相同输出。

问题1:简述下分区表和分桶表的区别?

	分区表
	创建表的时候使用关键字: partition by (分区字段名 分区字段类型)
	分区字段名注意事项: 是一个新的字段,需要指定类型,且不能和其他字段重名
	分区表好处: 使用分区字段作为条件的时候,底层直接找到对应的分区目录,能够避免全表扫描,提升查询效率
	分区表最直接的效果: 在hfds表目录下,分成多个分区目录(year=xxxx,month=xx,day=xx)
	不建议直接上传文件在hdfs表根路径下: 分区表直接不能识别对应文件中数据,因为分区表会找分区目录下的数据文件
	使用load方式加载hdfs中文件: 本质是移动文件到对应分区目录下

分桶表
	创建表的时候使用关键字: clustered by (分桶字段名) into 桶数量 buckets
	分桶字段名注意事项: 是指定一个已存在的字段,不需要指定类型
	分桶表好处: 使用分桶字段做抽样等特定操作的时候,也能提升性能效率
	分桶表最直接的效果: 在hdfs表目录或者分区目录下,分成多个分桶文件(000000_0,000001_0,000002_0...)
	不建议直接上传文件在hdfs表根路径下: 分桶表可以识别对应文件中数据,但是并没有分桶效果,也是不建议的
	使用load方式加载hdfs中文件: 本质是复制数据到各个分桶文件中

问题2: 假设Hive中有表A

现在需要将表A的月分区 202309 中user_id为20000的user_dinner字段更新为bonc8920 ,其他用户user_dinner字段数据不变 ,请列出更新的方法步骤。

( Hive实现,提示:Hive中无update语法 ,请通过其他办法进行数据更新)**

insert overwrite table A partition (mon_par = '202309')
select f1, f2, f3, f4, 'bonc8920' as user_dinner
from A
where mon_par = '202309' and user_id = 20000
union all
select f1, f2, f3, f4, user_dinner
from A
where mon_par = '202309' and user_id != 20000;
perties
insert overwrite table A partition (mon_par = '202309')
select f1, f2, f3, f4, 'bonc8920' as user_dinner
from A
where mon_par = '202309' and user_id = 20000
union all
select f1, f2, f3, f4, user_dinner
from A
where mon_par = '202309' and user_id != 20000;
<think>我们正在讨论如何在Doris中查询分区表。根据用户提供的引用内容,我们可以总结以下几点: 1. Doris支持复合区(先区再动态分区表(自动管理区生命周期)。 2. 动态区功能主要用于自动添加删除区(如按时间区),但只支持Range区。 3. 在查询分区表时,可以通过在WHERE条件中指定区列的条件来实现区剪枝,从而减少扫描的数据量,提高查询效率。 因此,查询分区表的方法与普通类似,但关键在于利用区键的条件来触发区剪枝。具体步骤如下: ### 1. **创建分区表(回顾)** 在查询之前,我们需要先有一个分区表。以动态分区表为例,创建一个按天区的: ```sql CREATE TABLE example_db.dynamic_partition_table ( date_key DATE, user_id INT, city VARCHAR(50), age INT, cost DECIMAL(10,2) ) PARTITION BY RANGE (date_key) () DISTRIBUTED BY HASH(user_id) BUCKETS 10 PROPERTIES ( "dynamic_partition.enable" = "true", -- 开启动态区 "dynamic_partition.time_unit" = "DAY", -- 按天区 "dynamic_partition.start" = "-30", -- 保留最近30天的区 "dynamic_partition.end" = "3", -- 提前创建未来3天的区 "dynamic_partition.prefix" = "p", -- 区名前缀 "dynamic_partition.buckets" = "10" -- 数 ); ``` 此会按`date_key`自动创建管理区(如`p20240101`代2024年1月1日的数据)[^1][^3]。 ### 2. **查询分区表** 查询时,在WHERE条件中指定区列(如`date_key`)的范围,Doris会自动过滤掉不相关的区: ```sql -- 查询特定日期范围的数据(触发区剪枝) SELECT user_id, SUM(cost) FROM example_db.dynamic_partition_table WHERE date_key >= '2024-05-01' AND date_key <= '2024-05-07' GROUP BY user_id; ``` 执行计划中会显示`partitions=3/10`(假设有10个区,只扫描了3个),证明区剪枝生效[^4][^5]。 ### 3. **注意事项** - **区键条件**:确保WHERE条件中包含区列(如`date_key`),且使用范围(`BETWEEN`、`>=`等)或等值(`=`)过滤。 - **避免函数转换**:直接在区列上使用函数(如`DATE_FORMAT(date_key, '%Y%m%d')`)会导致无法剪枝。应改为: ```sql -- 错误示例(无法剪枝): SELECT ... WHERE DATE_FORMAT(date_key, '%Y%m%d') = '20240501'; -- 正确示例(直接使用日期范围): SELECT ... WHERE date_key = '2024-05-01'; ``` - **复合区**:如果是复合区(如先按`date_key`区,再按`city`),查询时优先使用区键过滤,再使用键过滤[^1][^4]。 ### 4. **验证区剪枝** 使用`EXPLAIN`命令查看查询计划,确认是否剪枝: ```sql EXPLAIN SELECT ... WHERE date_key = '2024-05-01'; ``` 输出中应包含: ``` partitions=1/10 -- 只扫描了1个区(假设总共有10个区) ``` ### 5. **动态区的查询优化** 动态分区表的数据可能随时间自动增减(如TTL删除旧区)。查询时需注意: - 避免查询已删除的区(如`date_key < '2023-01-01'`),否则会报错`Partition not exists`。 - 通过`SHOW DYNAMIC PARTITION TABLES`监控区状态[^3]。 ### 示例:复杂查询 结合区键键进行高效查询: ```sql -- 查询2024年5月北京地区的数据(假设区键为date_key,键为user_id) SELECT city, AVG(age), SUM(cost) FROM example_db.dynamic_partition_table WHERE date_key BETWEEN '2024-05-01' AND '2024-05-31' AND city = '北京' GROUP BY city; ``` 此查询会先按`date_key`剪枝区,再按`city`过滤数据,最后在内聚合[^1][^4]。 ### 总结 在Doris中查询分区表的关键是**在WHERE条件中直接使用区列进行范围或等值过滤**,避免对区列使用函数或转换。动态分区表会自动维护区生命周期,但查询时仍需确保区存在。通过`EXPLAIN`验证区剪枝效果,可显著提升查询性能。 §§相关问题§§ 1. 如何监控Doris动态区的状态? 2. 区剪枝失效的常见原因有哪些? 3. Doris分区表查询性能上的差异? 4. 如何为时间序列数据设计高效的区策略? [^1]: Doris 创建复合分区表动态分区表[^1] [^2]: Doris hdfs数据导入doris动态分区表[^2] [^3]: Doris Doris分区表-动态区[^3] [^4]: Doris的分区表[^4] [^5]: palo(doris)区的正确使用[^5]</think>### Doris 查询分区表的方法 在 Doris 中查询分区表的核心是通过 **区剪枝(Partition Pruning)** 优化查询性能,即利用区键条件过滤无关区,减少数据扫描量。以下是具体操作方法注意事项: --- #### 1. **基础查询语法** 分区表查询语法与普通一致,只需在 `WHERE` 子句中**显式指定区键条件**即可触发区剪枝: ```sql -- 查询特定区(如日期区) SELECT * FROM sales WHERE dt = '2024-05-01'; -- 仅扫描 dt='2024-05-01' 的区[^4] -- 查询区范围 SELECT * FROM sales WHERE dt >= '2024-05-01' AND dt <= '2024-05-07'; -- 仅扫描该日期范围内的区[^5] ``` --- #### 2. **区剪枝的关键规则** - **直接使用区列**: 区键(如 `dt`)必须作为过滤条件**直接出现在 `WHERE` 子句中**,不可嵌套函数或计算: ```sql ✅ 正确:WHERE dt = '2024-05-01' -- 触发剪枝 ❌ 错误:WHERE DATE_FORMAT(dt, '%Y%m%d') = '20240501' -- 无法剪枝(全扫描)[^5] ``` - **范围区需明确边界**: 使用 `VALUES [...]` 定义的区(左闭右开区间),查询时需匹配区定义: ```sql -- 区定义:PARTITION p202405 VALUES [('2024-05-01'), ('2024-06-01')) ✅ 有效:WHERE dt >= '2024-05-01' AND dt < '2024-06-01' ❌ 无效:WHERE dt = '2024-06-01' -- 不属于该区[^4] ``` --- #### 3. **动态分区表查询** 动态分区表(如按天自动创建区)的查询方式与静态区一致,但需注意: - **自动区名匹配**: 动态区的命名格式为 `prefix + 时间`(如 `p20240501`),但查询时**无需关注区名**,直接使用区键过滤: ```sql -- 动态分区表(按天区) SELECT * FROM log_table WHERE event_date = '2024-05-01'; -- 自动定位到区 p20240501[^3] ``` - **避免查询未来/过期区**: 动态区可能自动删除旧区(如 TTL 设置),查询历史数据前需确认区是否存在。 --- #### 4. **复合分区表查询** 若同时有区(`PARTITION BY`)(`DISTRIBUTED BY`),优先用区键过滤,再结合键: ```sql -- 先按 dt 区,再按 user_id SELECT city, SUM(amount) FROM sales WHERE dt BETWEEN '2024-05-01' AND '2024-05-07' -- 区剪枝 AND user_id IN (101, 102) -- 过滤 GROUP BY city; ``` --- #### 5. **验证区剪枝效果** 通过 `EXPLAIN` 命令检查查询计划,确认是否触发剪枝: ```sql EXPLAIN SELECT * FROM sales WHERE dt = '2024-05-01'; ``` 输出中若包含 `partitions=1/100`(示 100 个区中只扫描了 1 个),则剪枝成功[^1]。 --- #### 6. **性能优化建议** 1. **区粒度适中**: 避免区过多(如 >1000 个),否则元数据压力增大[^1]。 2. **冷热数据离**: 频繁查询的热数据使用 SSD 存储,冷数据转存至 HDD[^3]。 3. **并行查询**: 数(`BUCKETS`)建议为集群节点数的倍数,充利用并行计算。 > **💡 关键总结** > - **必做**:在 `WHERE` 子句中直接使用区键作为过滤条件。 > - **禁止**:对区键使用函数或达式(如 `YEAR(dt)=2024`)。 > - **动态区**:无需手动管理区名,按区键查询即可。 > - **验证**:通过 `EXPLAIN` 确认区剪枝是否生效。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值