文章目录二、Hive 抽样的三大类型三、分桶抽样TABLESAMPLEBUCKET 最推荐1. 分桶表的核心设计2. 分桶抽样执行流程四、块抽样TABLESAMPLE1、按数据大小百分比抽样2、按固定大小抽样3、按行数抽样4、❌TABLESAMPLE WHERE4.1、一起使用导致的问题4.2、替代方案1WHERE RAND2子查询包装3CTE表达式4临时表存储5精确行数抽样五、随机抽样RAND5.1、概率过滤5.2、可复现随机样本5.3、局部随机5.4、全局随机×分层抽样按分组比例五、实战案例用户行为分析抽样场景分析国庆期间高活跃用户我的网站原文https://eleanora-lyh.github.io/MyLearningNotes/csdn处的文章会尽快同步更新欢迎大家来访问在 Hive 场景中数据量TB / PB 级查询方式MapReduce / Spark高延迟全表扫描成本极高 所以抽样的本质是用小数据近似大数据特征加速分析/调试二、Hive 抽样的三大类型Hive 一共有 3 种主流抽样方式类型关键语法随机性性能是否需要分桶✅ 分桶抽样Bucket SamplingTABLESAMPLE(BUCKET …)✅ 好✅ 高✅ 必须✅ 块抽样Block SamplingTABLESAMPLE(…)❌ 差✅ 非常高❌✅ 随机抽样Random Samplingrand() limit✅ 真随机❌ 较差❌三、分桶抽样TABLESAMPLEBUCKET 最推荐最推荐的高性能抽样方式利用Hive分桶机制直接定位数据块速度最快。1. 分桶表的核心设计通过建表的分桶设计在物理层面上按照bucket_id hash(user_id) % 32让数据均匀分布到32个桶中之前学到的分桶可以优化两个分桶表的JOIN操作使得JOIN时user_id相同的一定落在同一个桶中一个节点内避免了不同node节点之间的shuffle无需将同一个键值的记录汇总到一起分桶建表时就已经放入同一个桶中了。传统HDFS存储 data_2023-10-01.txt(包含所有user_id的记录)data_2023-10-02.txt 分桶表存储结构/user/hive/warehouse/user_bucketed/dt2023-10-01/├──000000_0(bucket-00000)-user_id哈希值%320的记录 ├──000001_0(bucket-00001)-user_id哈希值%321的记录 ├──...└──000031_0(bucket-00031)-user_id哈希值%3231的记录 分桶文件命名规则 分桶文件名格式 bucket文件:00000N_0-前6位: 桶编号5位数字从00000开始-下划线: 分隔符-最后数字: 桶内文件编号分桶文件可再分小文件 示例000025_0-桶编号:25-文件编号:0(第一个文件)我们可以理解为分桶优化了表的存储方式使得在查找时无需进行复杂的数据交换。分桶抽样之所以成立是因为 hash(user_id)近似均匀分布→ 随机选一个 bucket ≈ 全局数据的一个“随机子集”。2. 分桶抽样执行流程相信现在你已经明白为什么会有分桶抽样了通过分桶建表我们将数据随机分布在桶中所以通过抽取桶就可以等价于随机抽样。可以理解为空间换时间事先将数据准备好了这样随机抽样时无需遍历所有的行来达到随机的效果。-- 1. 创建分桶表CREATETABLEuser_bucketed(user_idBIGINT,user_name STRING,)CLUSTEREDBY(user_id)INTO32BUCKETS STOREDASORC;-- 2. 查询示例 基于分桶列的抽样需已创建分桶表SELECT*FROMuser_bucketed TABLESAMPLE(BUCKET3OUTOF8ONuser_id);-- 注意Hive中的桶编号是0-based从0开始但在TABLESAMPLE语法中x是1-based从1开始-- 执行过程分解1.计算抽样桶号桶数32,抽样组数32/84最后会取出4个桶相当于抽取了4/321/8的数据2.目标桶 k0:20×82k1:21×810k2:22×818k3:23×826k4:24×834≥32停止 得到3,3811,31619,324273.直接读取4个对应的HDFS文件000002_0,000010_0,000018_0,000026_04.无需全表扫描直接定位物理文件返回结果参数说明BUCKET x OUT OF y ON coly总桶数必须是分桶数的约数x抽取的桶编号从1开始抽样比例 1/y如果表不是分桶表但是仍使用分桶抽样如下-- 随机分桶抽样无需分桶列SELECT*FROMuser_bucketed TABLESAMPLE(BUCKET3OUTOF8ONRAND())s;则具体执行过程1️⃣ 启动MapReduce任务全表扫描每一行因为没有预分桶2️⃣ 对每一行执行调用RAND()生成随机数计算RAND()值的哈希hash(rand())哈希值对8取模得到桶号(0-7)bucket_id hash(rand()) % 32(虚拟bucket)3️⃣ 过滤出桶号2的行4️⃣ 输出数据 本质 全表 scan 行级 hash 过滤四、块抽样TABLESAMPLE本质是直接在 HDFS 数据块block默认128MB/256MB层面做抽样而不是逐行随机抽步骤1️⃣ 获取文件列表Partition / Directory2️⃣ 生成 InputSplitHDFS block3️⃣ 按比例选部分 InputSplit ✅关键1-3步多是发生在scan之前4️⃣ Map任务只读取这些 blockscan5️⃣ 输出数据举例已知表 1TBblock 128MB。执行快抽取10%的步骤如下总共 1T/128M ≈ 8000 个 block执行TABLESAMPLE(10 PERCENT)时Hive从8000个block中随机选800个只启动对应的 map task只启动对应的 map task 因此✅speed非常快减少IO❌不严格随机受数据分布影响比如按日期分区写入 block 抽样可能只抽到某几天的数据抽的是文件块而不是打散后的数据1、按数据大小百分比抽样抽取原hive表中10%的数据并保存到新表中SELECT*FROMlogs TABLESAMPLE(0.1PERCENT)2、按固定大小抽样抽样SELECT*FROMlogs TABLESAMPLE(100M)3、按行数抽样按行数抽样可能不准确因Hive不严格维格维护行数SELECT*FROMlogs TABLESAMPLE(1000ROWS);✅ 适合场景✔ 快速预览数据✔ 调试 pipeline❌ 不适合统计分析不随机4、❌TABLESAMPLE WHERE4.1、一起使用导致的问题❌无法和where一起使用根据块抽样的执行步骤可知TABLESAMPLE发生在scan读取之前 WHERE发生在数据读取后所以二者同时出现就会导致结果比例不确定、抽样失去意义、Hive 难以保证正确性的问题具体如下1结果比例不确定举个例子10% 的 genderF意思是先找到genderF的所有记录再从中抽取10%但是如果使用TABLESAMPLE和WHERE 先选取10% block → 再筛选 gender‘F’。 最终可能 10%2抽样失去意义genderF本来只有 5%。 先抽block可能抽不到任何 female 记录3Hive 难以保证正确性的问题Hive设计原则抽样应该严格作用在表级别而不是过滤后数据4.2、替代方案1WHERE RAND先过滤再抽样的逻辑正确SELECT*FROMtableWHEREgenderFANDRAND()0.1;2子查询包装-- 先抽样后过滤SELECT*FROM(SELECT*FROMuser_behavior TABLESAMPLE(1PERCENT)-- 全表1%的块)sampled_dataWHEREevent_typeclick;-- 在抽样结果中过滤-- 先过滤后抽样SELECT*FROM(SELECT*FROMuser_behaviorWHEREdt2023-10-01-- 先按条件筛选)filtered_data TABLESAMPLE(10PERCENT);-- 在筛选结果上抽样 0.1;3CTE表达式WITHsampled_ordersAS(SELECT*FROMorders TABLESAMPLE(100M))SELECT*FROMsampled_ordersWHEREamount1000ANDstatusCOMPLETED;4临时表存储-- 1. 创建抽样临时表CREATETEMPORARYTABLEtemp_sampleASSELECT*FROMlarge_table TABLESAMPLE(0.5PERCENT);-- 2. 在临时表上执行复杂查询SELECTuser_id,COUNT(*)ascntFROMtemp_sampleWHEREevent_time2023-10-01GROUPBYuser_idHAVINGcnt5;5精确行数抽样SELECT*FROM(SELECT*FROMtableWHEREgenderFDISTRIBUTEBYrand()SORTBYrand())tLIMIT1000;五、随机抽样RAND最灵活的抽样方式但需要全表扫描性能较差。limit关键字限制抽样返回的数据其中rand函数前的distribute和sort关键字可以保证数据在mapper和reducer阶段是随机分布的。5.1、概率过滤步骤Map scan过滤每一行执行rand(123) - 0~1满足条件 0.1的保留。没有固定种子每次就可以得到不同的随机序列输出 本质每行独立 Bernoulli 抽样概率采样所以没有Reduce阶段、全局排序、网络shuffle-- 随机抽取10%的数据SELECT*FROMordersWHERERAND()0.1;适合场景✔ 大数据快速抽样✔ SQL调试✔ 统计分析近似结果行数 ≠ 精确10% 因为WHERE RAND() 0.1的概率抽样是波动的5.2、可复现随机样本步骤Map scan过滤每一行执行rand(123) - 0~1满足条件 0.05的保留。固定种子为123每次就可以得到相同的随机序列shuffleDISTRIBUTE BY RAND(123)把数据随机打散到 reducer局部排序SORT BY RAND(123)每个reducer内部排序-- 可重现的随机抽样设置随机种子SELECT*FROMordersWHERERAND(123)0.05-- 固定种子结果可重现DISTRIBUTEBYRAND(123)SORTBYRAND(123)LIMIT1000;适合场景✔ 实验可复现A/B Test✔ 调试稳定样本✔ 模型训练数据抽样5.3、局部随机步骤Map scanshuffleDISTRIBUTE BY RAND()把数据随机打散到 reducer局部排序SORT BY RAND()每个reducer内部排序SELECT*FROMordersWHEREcolxxxDISTRIBUTEBYRAND()SORTBYRAND()LIMIT1000;比ORDER BY RAND()快比WHERE RAND()更随机但是它不是全局随机因为多个 reducer 各自排序 偏随机但不是严格均匀5.4、全局随机步骤Map scanshuffle所有数据 shuffle 到一个reducer节点全局排序ORDER BY RAND()1个reducer内部全局排序SELECT*FROMordersORDERBYrand()LIMIT1000;完全随机但是单reducer和全shuffle会导致整个过程很慢适合场景✔ 少量数据✔ 强随机要求❌ 不适合大表写法抽样方式是否全表扫描是否Shuffle随机性性能WHERE RAND() p✅ 概率过滤✅ 是❌ 否✅ 好✅ 高RAND(seed) distribute/sort✅ 可复现随机✅ 是✅ 有✅ 很好⚠️ 中distribute by rand() sort by rand()✅ 局部随机✅ 是✅ 有⚠️ 一般⚠️ 中order by rand()✅ 全局随机✅ 是✅ 超大✅ 最好❌ 慢×分层抽样按分组比例-- 每个城市抽取5%的用户SELECT*FROM(SELECT*,ROW_NUMBER()OVER(PARTITIONBYcityORDERBYRAND())asrn,COUNT(*)OVER(PARTITIONBYcity)astotalFROMusers)tWHERErntotal*0.05;五、实战案例用户行为分析抽样场景分析国庆期间高活跃用户-- 错误做法可能漏掉目标用户SELECTuser_id,COUNT(*)aslogin_countFROMuser_logs TABLESAMPLE(100M)-- 随机块可能不包含国庆数据WHERElogin_dateBETWEEN2023-10-01AND2023-10-07GROUPBYuser_idHAVINGlogin_count10;-- 正确做法1先过滤后抽样SELECT*FROM(SELECTuser_id,COUNT(*)aslogin_countFROMuser_logsWHERElogin_dateBETWEEN2023-10-01AND2023-10-07GROUPBYuser_idHAVINGlogin_count10)active_users TABLESAMPLE(20PERCENT);-- 在结果集上抽样-- 正确做法2分桶表高效查询CREATETABLEuser_logs_bucketed(user_idBIGINT,login_date STRING)CLUSTEREDBY(user_id)INTO100BUCKETS PARTITIONEDBY(dt STRING)STOREDASORC;-- 分区剪枝 分桶抽样SELECTuser_id,COUNT(*)aslogin_countFROMuser_logs_bucketed TABLESAMPLE(BUCKET1OUTOF20ONuser_id)WHEREdtBETWEEN2023-10-01AND2023-10-07GROUPBYuser_id;
【Hive】三、Hive 抽样:讲解 Hive 三大抽样方式:分桶抽样、块抽样、随机抽样的原理、语法、性能对比与实战案例
文章目录二、Hive 抽样的三大类型三、分桶抽样TABLESAMPLEBUCKET 最推荐1. 分桶表的核心设计2. 分桶抽样执行流程四、块抽样TABLESAMPLE1、按数据大小百分比抽样2、按固定大小抽样3、按行数抽样4、❌TABLESAMPLE WHERE4.1、一起使用导致的问题4.2、替代方案1WHERE RAND2子查询包装3CTE表达式4临时表存储5精确行数抽样五、随机抽样RAND5.1、概率过滤5.2、可复现随机样本5.3、局部随机5.4、全局随机×分层抽样按分组比例五、实战案例用户行为分析抽样场景分析国庆期间高活跃用户我的网站原文https://eleanora-lyh.github.io/MyLearningNotes/csdn处的文章会尽快同步更新欢迎大家来访问在 Hive 场景中数据量TB / PB 级查询方式MapReduce / Spark高延迟全表扫描成本极高 所以抽样的本质是用小数据近似大数据特征加速分析/调试二、Hive 抽样的三大类型Hive 一共有 3 种主流抽样方式类型关键语法随机性性能是否需要分桶✅ 分桶抽样Bucket SamplingTABLESAMPLE(BUCKET …)✅ 好✅ 高✅ 必须✅ 块抽样Block SamplingTABLESAMPLE(…)❌ 差✅ 非常高❌✅ 随机抽样Random Samplingrand() limit✅ 真随机❌ 较差❌三、分桶抽样TABLESAMPLEBUCKET 最推荐最推荐的高性能抽样方式利用Hive分桶机制直接定位数据块速度最快。1. 分桶表的核心设计通过建表的分桶设计在物理层面上按照bucket_id hash(user_id) % 32让数据均匀分布到32个桶中之前学到的分桶可以优化两个分桶表的JOIN操作使得JOIN时user_id相同的一定落在同一个桶中一个节点内避免了不同node节点之间的shuffle无需将同一个键值的记录汇总到一起分桶建表时就已经放入同一个桶中了。传统HDFS存储 data_2023-10-01.txt(包含所有user_id的记录)data_2023-10-02.txt 分桶表存储结构/user/hive/warehouse/user_bucketed/dt2023-10-01/├──000000_0(bucket-00000)-user_id哈希值%320的记录 ├──000001_0(bucket-00001)-user_id哈希值%321的记录 ├──...└──000031_0(bucket-00031)-user_id哈希值%3231的记录 分桶文件命名规则 分桶文件名格式 bucket文件:00000N_0-前6位: 桶编号5位数字从00000开始-下划线: 分隔符-最后数字: 桶内文件编号分桶文件可再分小文件 示例000025_0-桶编号:25-文件编号:0(第一个文件)我们可以理解为分桶优化了表的存储方式使得在查找时无需进行复杂的数据交换。分桶抽样之所以成立是因为 hash(user_id)近似均匀分布→ 随机选一个 bucket ≈ 全局数据的一个“随机子集”。2. 分桶抽样执行流程相信现在你已经明白为什么会有分桶抽样了通过分桶建表我们将数据随机分布在桶中所以通过抽取桶就可以等价于随机抽样。可以理解为空间换时间事先将数据准备好了这样随机抽样时无需遍历所有的行来达到随机的效果。-- 1. 创建分桶表CREATETABLEuser_bucketed(user_idBIGINT,user_name STRING,)CLUSTEREDBY(user_id)INTO32BUCKETS STOREDASORC;-- 2. 查询示例 基于分桶列的抽样需已创建分桶表SELECT*FROMuser_bucketed TABLESAMPLE(BUCKET3OUTOF8ONuser_id);-- 注意Hive中的桶编号是0-based从0开始但在TABLESAMPLE语法中x是1-based从1开始-- 执行过程分解1.计算抽样桶号桶数32,抽样组数32/84最后会取出4个桶相当于抽取了4/321/8的数据2.目标桶 k0:20×82k1:21×810k2:22×818k3:23×826k4:24×834≥32停止 得到3,3811,31619,324273.直接读取4个对应的HDFS文件000002_0,000010_0,000018_0,000026_04.无需全表扫描直接定位物理文件返回结果参数说明BUCKET x OUT OF y ON coly总桶数必须是分桶数的约数x抽取的桶编号从1开始抽样比例 1/y如果表不是分桶表但是仍使用分桶抽样如下-- 随机分桶抽样无需分桶列SELECT*FROMuser_bucketed TABLESAMPLE(BUCKET3OUTOF8ONRAND())s;则具体执行过程1️⃣ 启动MapReduce任务全表扫描每一行因为没有预分桶2️⃣ 对每一行执行调用RAND()生成随机数计算RAND()值的哈希hash(rand())哈希值对8取模得到桶号(0-7)bucket_id hash(rand()) % 32(虚拟bucket)3️⃣ 过滤出桶号2的行4️⃣ 输出数据 本质 全表 scan 行级 hash 过滤四、块抽样TABLESAMPLE本质是直接在 HDFS 数据块block默认128MB/256MB层面做抽样而不是逐行随机抽步骤1️⃣ 获取文件列表Partition / Directory2️⃣ 生成 InputSplitHDFS block3️⃣ 按比例选部分 InputSplit ✅关键1-3步多是发生在scan之前4️⃣ Map任务只读取这些 blockscan5️⃣ 输出数据举例已知表 1TBblock 128MB。执行快抽取10%的步骤如下总共 1T/128M ≈ 8000 个 block执行TABLESAMPLE(10 PERCENT)时Hive从8000个block中随机选800个只启动对应的 map task只启动对应的 map task 因此✅speed非常快减少IO❌不严格随机受数据分布影响比如按日期分区写入 block 抽样可能只抽到某几天的数据抽的是文件块而不是打散后的数据1、按数据大小百分比抽样抽取原hive表中10%的数据并保存到新表中SELECT*FROMlogs TABLESAMPLE(0.1PERCENT)2、按固定大小抽样抽样SELECT*FROMlogs TABLESAMPLE(100M)3、按行数抽样按行数抽样可能不准确因Hive不严格维格维护行数SELECT*FROMlogs TABLESAMPLE(1000ROWS);✅ 适合场景✔ 快速预览数据✔ 调试 pipeline❌ 不适合统计分析不随机4、❌TABLESAMPLE WHERE4.1、一起使用导致的问题❌无法和where一起使用根据块抽样的执行步骤可知TABLESAMPLE发生在scan读取之前 WHERE发生在数据读取后所以二者同时出现就会导致结果比例不确定、抽样失去意义、Hive 难以保证正确性的问题具体如下1结果比例不确定举个例子10% 的 genderF意思是先找到genderF的所有记录再从中抽取10%但是如果使用TABLESAMPLE和WHERE 先选取10% block → 再筛选 gender‘F’。 最终可能 10%2抽样失去意义genderF本来只有 5%。 先抽block可能抽不到任何 female 记录3Hive 难以保证正确性的问题Hive设计原则抽样应该严格作用在表级别而不是过滤后数据4.2、替代方案1WHERE RAND先过滤再抽样的逻辑正确SELECT*FROMtableWHEREgenderFANDRAND()0.1;2子查询包装-- 先抽样后过滤SELECT*FROM(SELECT*FROMuser_behavior TABLESAMPLE(1PERCENT)-- 全表1%的块)sampled_dataWHEREevent_typeclick;-- 在抽样结果中过滤-- 先过滤后抽样SELECT*FROM(SELECT*FROMuser_behaviorWHEREdt2023-10-01-- 先按条件筛选)filtered_data TABLESAMPLE(10PERCENT);-- 在筛选结果上抽样 0.1;3CTE表达式WITHsampled_ordersAS(SELECT*FROMorders TABLESAMPLE(100M))SELECT*FROMsampled_ordersWHEREamount1000ANDstatusCOMPLETED;4临时表存储-- 1. 创建抽样临时表CREATETEMPORARYTABLEtemp_sampleASSELECT*FROMlarge_table TABLESAMPLE(0.5PERCENT);-- 2. 在临时表上执行复杂查询SELECTuser_id,COUNT(*)ascntFROMtemp_sampleWHEREevent_time2023-10-01GROUPBYuser_idHAVINGcnt5;5精确行数抽样SELECT*FROM(SELECT*FROMtableWHEREgenderFDISTRIBUTEBYrand()SORTBYrand())tLIMIT1000;五、随机抽样RAND最灵活的抽样方式但需要全表扫描性能较差。limit关键字限制抽样返回的数据其中rand函数前的distribute和sort关键字可以保证数据在mapper和reducer阶段是随机分布的。5.1、概率过滤步骤Map scan过滤每一行执行rand(123) - 0~1满足条件 0.1的保留。没有固定种子每次就可以得到不同的随机序列输出 本质每行独立 Bernoulli 抽样概率采样所以没有Reduce阶段、全局排序、网络shuffle-- 随机抽取10%的数据SELECT*FROMordersWHERERAND()0.1;适合场景✔ 大数据快速抽样✔ SQL调试✔ 统计分析近似结果行数 ≠ 精确10% 因为WHERE RAND() 0.1的概率抽样是波动的5.2、可复现随机样本步骤Map scan过滤每一行执行rand(123) - 0~1满足条件 0.05的保留。固定种子为123每次就可以得到相同的随机序列shuffleDISTRIBUTE BY RAND(123)把数据随机打散到 reducer局部排序SORT BY RAND(123)每个reducer内部排序-- 可重现的随机抽样设置随机种子SELECT*FROMordersWHERERAND(123)0.05-- 固定种子结果可重现DISTRIBUTEBYRAND(123)SORTBYRAND(123)LIMIT1000;适合场景✔ 实验可复现A/B Test✔ 调试稳定样本✔ 模型训练数据抽样5.3、局部随机步骤Map scanshuffleDISTRIBUTE BY RAND()把数据随机打散到 reducer局部排序SORT BY RAND()每个reducer内部排序SELECT*FROMordersWHEREcolxxxDISTRIBUTEBYRAND()SORTBYRAND()LIMIT1000;比ORDER BY RAND()快比WHERE RAND()更随机但是它不是全局随机因为多个 reducer 各自排序 偏随机但不是严格均匀5.4、全局随机步骤Map scanshuffle所有数据 shuffle 到一个reducer节点全局排序ORDER BY RAND()1个reducer内部全局排序SELECT*FROMordersORDERBYrand()LIMIT1000;完全随机但是单reducer和全shuffle会导致整个过程很慢适合场景✔ 少量数据✔ 强随机要求❌ 不适合大表写法抽样方式是否全表扫描是否Shuffle随机性性能WHERE RAND() p✅ 概率过滤✅ 是❌ 否✅ 好✅ 高RAND(seed) distribute/sort✅ 可复现随机✅ 是✅ 有✅ 很好⚠️ 中distribute by rand() sort by rand()✅ 局部随机✅ 是✅ 有⚠️ 一般⚠️ 中order by rand()✅ 全局随机✅ 是✅ 超大✅ 最好❌ 慢×分层抽样按分组比例-- 每个城市抽取5%的用户SELECT*FROM(SELECT*,ROW_NUMBER()OVER(PARTITIONBYcityORDERBYRAND())asrn,COUNT(*)OVER(PARTITIONBYcity)astotalFROMusers)tWHERErntotal*0.05;五、实战案例用户行为分析抽样场景分析国庆期间高活跃用户-- 错误做法可能漏掉目标用户SELECTuser_id,COUNT(*)aslogin_countFROMuser_logs TABLESAMPLE(100M)-- 随机块可能不包含国庆数据WHERElogin_dateBETWEEN2023-10-01AND2023-10-07GROUPBYuser_idHAVINGlogin_count10;-- 正确做法1先过滤后抽样SELECT*FROM(SELECTuser_id,COUNT(*)aslogin_countFROMuser_logsWHERElogin_dateBETWEEN2023-10-01AND2023-10-07GROUPBYuser_idHAVINGlogin_count10)active_users TABLESAMPLE(20PERCENT);-- 在结果集上抽样-- 正确做法2分桶表高效查询CREATETABLEuser_logs_bucketed(user_idBIGINT,login_date STRING)CLUSTEREDBY(user_id)INTO100BUCKETS PARTITIONEDBY(dt STRING)STOREDASORC;-- 分区剪枝 分桶抽样SELECTuser_id,COUNT(*)aslogin_countFROMuser_logs_bucketed TABLESAMPLE(BUCKET1OUTOF20ONuser_id)WHEREdtBETWEEN2023-10-01AND2023-10-07GROUPBYuser_id;