testbook

表的分区:PARTITIONED BY 子句

Inceptor支持对表的单值分区和范围分区。 为一张表分区:

  • 在物理上,将表中的数据按分区放在表目录下的对应子目录中,一个分区对应一个子目录;
  • 在逻辑上,分区表和未分区表没有区别。 分区在创建表时完成,也可以通过ALTER TABLE来添加或者删除。
  • 语法:创建单值分区表
    CREATE TABLE table_name (col1 data_type1, col2, data_type2, ... )
    PARTITIONED BY (partition_key1 data_type1, partition_key2 data_type2...)
    
    还是以用户信息表为例子。但是 下面这个例子里我们不再将帐户级别作为列,而是用帐户级别作为分区键为用户信息表来分区。为了和原来的user_info表区分,我们新创建一张表partition_user_info:
    [$host]transwarp> CREATE TABLE exchange_platform.partition_user_info (
                        name            STRING,
                        acc_num         STRING,
                        password        STRING,
                        citizen_id      STRING,
                        bank_acc        STRING,
                        reg_date        STRING
                    ) PARTITIONED BY (acc_level STRING);
    
    这个例子中,acc_level是分区键(partition key)。 分区一旦完成,分区列就可以作为普通的列来使用 -- 所有的列操作对分区列都适用。也正因为这样,同一个表的分区列和列不能重名。如果出现重名,Inceptor会抛出一个失败信息:"FAILED: Error in semantic analysis: org.apache.hadoop.hive.ql.parse.SemanticException: Column repeated in partitioning columns".
    分区改变了Inceptor对数据的存储 -- Inceptor会在表的目录下加上分区对应的子目录:

        .../part_user_info/acc_level=A
        .../part_user_info/acc_level=B
        .../part_user_info/acc_level=C
        .../part_user_info/acc_level=D
        .../part_user_info/acc_level=E

    当我们做一个区内的查询时,Inceptor只需要去读取对应子目录下的信息。比如,要查看所有的A级帐户,我们可以用下面的语句:
    [$host] transwarp> SELECT * FROM partition_user_info WHERE acc_level ='A';
    
    执行这个语句时,Inceptor只读取了/part_user_info/acc_level=A下的信息,而忽略了其他的子目录。如果part_user_info没有被分区,Inceptor会进行全表扫描来完成这次查询。
    多层分区
    我们还可以对一个表进行多层分区。同样是用户信息表,现在我们用帐户级别和开户日期来分区:
    [$host]transwarp> CREATE TABLE exchange_platform.doub_partition_user_info (
                        name            STRING,
                        acc_num         STRING,
                        password        STRING,
                        citizen_id      STRING,
                        bank_acc        STRING,
                    ) PARTITIONED BY (acc_level STRING, reg_date DATE);
    
    user_info2的子目录将是这样的:

        ......
        .../doub_partition_user_info/acc_level=A/reg_date=20121024
        .../doub_partition_user_info/acc_level=A/reg_date=20110101
        ......
        .../doub_partition_user_info/acc_level=B/reg_date=20080214
        ......

    要查询所有2012年1月24日开户的A级用户,我们可以用下面的语句:
    SELECT * FROM user_info WHERE acc_level ='A' AND reg_date='20121024';
    
    关于分区的建议
    分区的目的是减少扫描成本。所以单个分区的大小和总分区数目都应该控制在合理范围内。
  • 使用多层分区带来的直接问题是总分区个数过多,因为总分区个数是所有分区键对应分区个数的乘积。所以我们建议尽量减少使用多层分区
  • 对于时间、日期一类的值,使用单值分区会导致分区过多。推荐使用范围分区(RANGE PARTITION)

  • 范围分区
    语法
    CREATE TABLE table_name (column_name column_type, ...)
    PARTITIONED BY RANGE (partition_key_name, partition_key_type, ...)
    (PARTITION [partition_name] VALUES LESS THAN (partition_value1),
     PARTITION [partition_name] VALUES LESS THAN (partition_value2),
     ...
     PARTITION [partition_name] VALUES LESS THAN (MAXVALUE))
    
    范围分区时,每个分区对应分区键的一个区间。凡是落在指定区间内的记录都会被放入对应的分区下。各个分区之间按顺序排列,前一个分区的最大值即为后一个分区的最小值,第一个分区的最小值为该字段类型所允许的最小值,关键词MAXVALUE代表该字段类型所允许的最大值,一般最后一个分区会用MAXVALUE包住所有其他可能的值

    说明
  • 所有range partition分区均需要手工指定,目前Inceptor只支持建表时一次性建好所有的range partition分区。
  • 每个分区可以给定个一个唯一的分区名partition_name,分区名的意义是会用该分区名作为hdfs上的分区目录名,没有分区名的分区会用(分区字段名 less_than 分区最大值)作为分区目录名。
  • 分区的范围为 [最小值, 最大值) 前闭后开区间,即value less than的字面意义
  • 为了渐少对某些客户SQL修改的工作量,我们的语法解析会识别很多Oracle中的关键字,诸如:
    ... partition A0200701 values less than ('200701')
    tablespace USR_PMS_TBS
    pctfree 0
    initrans 1
    maxtrans 255
    storage
    (
    initial 8M
    next 1M
    minextents 1
    maxextents unlimited
    ) ...
    
    Inceptor可以识别从第二行开始的所有语法,但是不会做任何处理,只是不报语法错误。
  • 不支持从文件导入范围分区表。
  • 支持INSERT INTO/OVERWRITE...SELECT...FROM...;形式向范围分区表中插入数据,插入时不需要像单值分区一样指定分区字段的值,形式上类似于动态分区插入。
  • 不支持和单值分区混用来进行多层分区。

  • 举例
    现在我们将user_info表用reg_date来范围分区,将用户按注册年份放入不同的分区里。比如在2008至2009年之间注册的用户将落入第一个分区。
    [$host] transwarp> CREATE TABLE range_partition_user_info (
                        name STRING,
                        acc_num STRING,
                        password STRING,
                        citizen_id STRING,
                        bank_acc STRING,
                        acc_level string)
                       PARTITIONED BY RANGE (reg_date string) (
                        PARTITION VALUES LESS THAN (20090000),
                        PARTITION VALUES LESS THAN (20100000),
                        PARTITION VALUES LESS THAN (20110000),
                        PARTITION VALUES LESS THAN (20120000),
                        PARTITION VALUES LESS THAN (20130000),
                        PARTITION VALUES LESS THAN (20140000),
                        PARTITION VALUES LESS THAN (MAXVALUE));
    
    然后向表内通过INSERT INTO...SELECT...FROM...向表内插入数据:
    [$host] transwarp> INSERT INTO TABLE range_partition_user_info partition (reg_date) SELECT name, acc_num, password, citizen_id, bank_acc, acc_level, reg_date FROM user_info;
    
    [$host] transwarp> SELECT * FROM range_partition_user_info;
    管淑艳  2394923 783438  330683198005210864      99913863445174  C       20141003
    邱坤    0700735 737297  340811199211252278      14388242322818  A       20121024
    李平    8725869 600709  460100198902070313      43081307046984  E       20130702
    马从筠  6513065 115591  140400198711012307      96080357291141  A       20110101
    潘营泽  6600641 990590  511521198705077435      48471135593608  C       20110430
    李韩瑶  2755506 015859  310230197912126559      42412396242237  D       20110916
    华微    5224133 531547  420529198911075631      32638281095907  B       20080214
    宁新瑶  4580952 986634  420822199001119507      97711008856576  D       20081031
    祝韩恒  6670192 205239  230801197908126178      73790369990971  C       20100101
    魏向卉  3912384 841242  522632199301029404      68537153578048  A       20091202
    
    多层分区
    Inceptor支持多层分区字段,具体用法和普通分区相同。

    举例
    CREATE TABLE t1 (value INT)
    partitioned by range(id1 INT, id2 INT, id3 INT)
    (
    partition values less than (5, 105, 205),
    partition values less than (5, 105, 215),
    partition values less than (5, 115, 205),
    partition values less than (5, 115, 215),
    partition values less than (5, 115, MAXVALUE),
    partition p10_105_205 values less than (10, 105, 205),
    partition p10_105_215 values less than (10, 105, 215),
    partition p10_115_205 values less than (10, 115, 205),
    partition p10_115_215 values less than (10, 115, 215),
    partition pall_max values less than (MAXVALUE, MAXVALUE, MAXVALUE)
    );
    
    注意事项
    分区键在逻辑上是表中的列,但是分区键的值在数据中不存在 -- Inceptor只根据分区表下的路径结构来确定记录的分区键的值。举个例子,在分区的用户信息表例子中,如果一条记录存储在'.../part_user_info/acc_level=A'路径下,那么Inceptor会认为这条记录的acc_level是A,即使这条记录的acc_level不是A,是被错误地放在了'.../part_user_info/acc_level=A'路径下。Inceptor没有任何机制在将数据写入分区时保证分区键的正确性,所以向表中填入数据时,用户必须自己确保记录导入或者插入正确的对应分区中。向表中填入数据的语法请参考数据操作语言章节。