很多同学已经跨入了MaxCompute的殿堂,对分区表和非分区表有了初步了解,也充分意识到“大数据量”的信息表要尽可能的采用分区方式,因为这样在查询、统计、分析的时候,在WHERE条件中可以利用分区列进行过滤,从而提升查询效率,减少不必要的计算开销。有细心的同学就会将问题引申:如果有个分区表,分区列为A、B、C三列,我要查询B列为某些值的数据,MaxCompute还能充分发挥分区表的优势吗?答案是肯定的,MaxCompute依然能发挥分区列的优势!在底层解析SQL执行计划时,只会将符合条件的分区纳入计算,而不是进行全表扫描。接下来我们用MaxCompute提供的工具来分析下相关SQL的执行效果。
一、数据准备
实验中使用的数据保存在文件《测试分区列过滤.csv》中,共27条记录,分区列为A、B、C三列。从分区(a=’a1’,b=’b1’,c=’c1’)到分区(a=’a3’,b=’b3’,c=’c3’)共27个分区,每个分区中有1条数据。
实验数据最终要插入到分区表中,因为Tunnel工具不支持直接将数据导入到分区表,所以我们要通过一个“类似结构”的非分区表搭桥——先将文件中的数据导入到一个非分区表中,然后再使用动态SQL将非分区表的数据插入到分区表中。接下来进行具体操作:
1.创建中间数据表
在DataWorks的【数据开发】中【新建脚本文件】,然后执行下面的建表SQL语句:
-- 创建对应的非分区表
CREATE TABLE temp_test_wfq(
bh BIGINT COMMENT '编号',
bt STRING COMMENT '标题',
a STRING COMMENT '分区列A',
b STRING COMMENT '分区列B',
c STRING COMMENT '分区列C'
) COMMENT '测试分区列过滤_无分区' LIFECYCLE 30;
2.将实验数据导入表【temp_test_wfq】中
将文件《测试分区列过滤.csv》中的样例数据导入中间数据表【temp_test_wfq】中。详细操作参见阿里云官方文档导入本地数据,本文不再赘述。
3.创建实验使用的分区表
在DataWorks的【数据开发】中【新建脚本文件】,然后执行下面的建表SQL语句:
-- 创建分区表,分区列为A、B、C共3列
CREATE TABLE temp_test(
bh BIGINT COMMENT '编号',
bt STRING COMMENT '标题'
) COMMENT '测试分区列过滤'
PARTITIONED BY (
a STRING COMMENT '分区列A',
b STRING COMMENT '分区列B',
c STRING COMMENT '分区列C'
) LIFECYCLE 30;
4.使用动态SQL将非分区表中的数据插入到分区表中
在前面操作中,已经将数据导入到了非分区表中,接下来使用动态SQL语句,将非分区表中的数据迁移到分区表中,具体SQL如下:
-- 使用动态SQL,将非分区表中的数据插入到分区表
INSERT OVERWRITE TABLE temp_test PARTITION (a,b,c)
SELECT * FROM temp_test_wfq
;
5.确认实验数据已准确插入到分区表中
在DataWorks的【数据开发】中【新建脚本文件】,然后执行下面的建表SQL语句:
-- 查询数据表【temp_test】中的数据
SELECT * FROM temp_test ORDER BY bh LIMIT 100;
返回结果应该有27条数据,如下图所示:
二、测试分区列过滤
为了能够看到SQL的执行计划,需要使用EXPLAIN命令来进行展现分析结果,具体SQL语句如下:
-- 解释SQL
EXPLAIN SELECT * FROM (
SELECT * FROM temp_test WHERE a = 'a1' --SQL1:M1_Stg1
UNION ALL
SELECT * FROM temp_test WHERE b = 'b2' --SQL2:M2_Stg1
UNION ALL
SELECT * FROM temp_test WHERE c = 'c3' --SQL3:M3_Stg1
UNION ALL
SELECT * FROM temp_test WHERE b = 'b1' AND c = 'c2' --SQL4:M4_Stg1
UNION ALL
SELECT * FROM temp_test WHERE a = 'a3' AND c = 'c1' AND bt LIKE '%b2%' --SQL5:M5_Stg1
) a;
EXPLAIN的SQL语句,是将5个子查询的结果UNION后,统一返回一个结果集。最终解释执行计划如下图所示:
1.Job0解读
上面的EXPLAIN结果我们逐段展开解释:
上图中包含了2部分信息:1.【job0 is root job】表示该查询语句只转换为了1个作业,所以只有一条关于“job0”的作业信息。2.【In Job job0】后续描述了该作业包含的ROOT任务有“M1_Stg1, M4_Stg1, M2_Stg1, M3_Stg1, M5_Stg1”,共5个。
2.Task M1_Stg1解读
【In Task M1_Stg1】对应的内容,则详细阐释了此任务的执行语义:
【Data source】说明了,该任务的数据源自数据表test_create_projecta.temp_test的9个分区,也就是说参与本次运算的数据为这9个分区的数据。仔细观察,还会发现所列出的分区,a列对应的值都是“a1”。
【TS】块说明对表“temp_test”进行了“TableScanOperator”操作,SQL语句中对应的部分就是“FROM temp_test”。
【FIL】块说明了对数据进行等值过滤,SQL语句相当于“WHERE temp_test.a = 'a1'”。
【SEL】块说明了返回的结果数据列,有5列:temp_test.bh, temp_test.bt, temp_test.a, temp_test.b, temp_test.c。
【UNION】块说明还要跟后续的【SEL】块的查询结果进行UNION操作。
【FS】块说明进行最终的输出表操作,因为本条SQL语句没有将结果输出到具体表中,所以输出的目标为“None”。
通过以上分析,该TASK对应的SQL应该是子查询SQL1“SELECT * FROM temp_test WHERE a = 'a1'”,最终分区列过滤发挥了作用,参与运算的有9个分区。
3.Task M2_Stg1解读
【Data source】说明了,该任务的数据源自数据表test_create_projecta.temp_test的9个分区,也就是说参与本次运算的数据为这9个分区的数据。仔细观察,还会发现所列出的分区,b列对应的值都是“b2”。
【TS】块说明对表“temp_test”进行了“TableScanOperator”操作,SQL语句中对应的部分就是“FROM temp_test”。
【FIL】块说明了对数据进行等值过滤,SQL语句相当于“WHERE temp_test.b = 'b2'”。
【SEL】块说明了返回的结果数据列,有5列:temp_test.bh, temp_test.bt, temp_test.a, temp_test.b, temp_test.c。
【UNION】块说明还要跟后续的【SEL】块的查询结果进行UNION操作。
【FS】块说明进行最终的输出表操作,因为本条SQL语句没有将结果输出到具体表中,所以输出的目标为“None”。
通过以上分析,该TASK对应的SQL应该是子查询SQL1“SELECT * FROM temp_test WHERE b = 'b2'”,最终分区列B的过滤条件也发挥了作用,只有(b = 'b2')的9个分区参与了运算。
另外3个TASK,感兴趣的同学也可以自己深入分析一下,相信对分区列的过滤查询会有更深刻体会。
三、小结
通过上述实验,我们可以直观的看到,分区表进行查询时,WHERE条件中对分区列进行了等值过滤,会很有效的避免了全表参与运算。MaxCompute在解释SQL时,会只将符合条件的分区数据,纳入到运算里面来,从而显著提升了计算效率,节省了运算成本。