MaxCompute单表六万分区限制原因和使用方法

更新时间:
复制为 MD 格式

MaxCompute限制单张表最多60,000个分区。本文说明该限制的原因,并提供三种方案帮助合理规划分区策略、降低现有表的分区数量。

限制原因

表分区功能,目的是根据业务字段组织数据,并在查询中通过分区过滤表中数据。但是在大数据场景下,分区过多会带来以下问题:

  • 数据倾斜:分区列取值不均匀,导致数据分布不平衡。多级分区的总数是各级取值的笛卡尔积,长尾分区的元数据开销可能超过实际数据量。

  • 管理成本高:设置、维护大量的分区比较复杂。冷热标记、自动过期、备份恢复、跨集群复制等操作都以分区为粒度,分区过细会显著增加管理负担。

  • 元数据瓶颈:分区元信息集中存储在元数据服务中,分区数过多容易成为性能瓶颈。

为了保障稳定性和高效使用,MaxCompute设置了单表六万分区的限制。

使用方法

因此,推荐在分区下构建聚簇分桶 、重排数据以及设置索引等,把数据的元信息分散到需要访问数据的链路中,提高稳定性和计算效率。

分区结合聚簇分桶可以提供更细粒度、更高效的索引能力。

  • 如果原业务的分区数过多,减少分区数同时要获得好的过滤效果,可以采用分区聚簇组合方案替代全分区方案。

  • 如果单纯限制分区数,避免稳定性风险,可以合并历史分区,减少分区数量。

  • 如果保持分区不变,仍要查询分区总数超过六万的数据,可以将历史分区通过clone归档为历史表,然后用视图union多张历史表的方式实现,但此种情况下扫描数据过多,有稳定性风险。

下面介绍详细的使用方法。

分区表示例

假设有如下两张分区表,如果分区数达到几万,对该表的读写操作性能都会有很大影响。

  • 以天、小时、region、终端类型分为四个分区

    CREATE TABLE IF NOT EXISTS test01 (
        user_id STRING COMMENT '用户ID',
        action STRING COMMENT '行为:点击、购买、浏览等',
        view_time BIGINT COMMENT '时间戳'
    )
    PARTITIONED BY (
        dt STRING COMMENT '日期,格式: yyyy-MM-dd',
        hour STRING COMMENT '小时,00-23',
        region STRING COMMENT '地区:如 beijing、shanghai',
        type STRING COMMENT '终端品牌:如 huawei、xiaomi、apple'
    );
  • 以天、小时、region、uid分为四个分区

    CREATE TABLE IF NOT EXISTS test02 (
        action STRING COMMENT '行为:点击、购买、浏览等',
        view_time BIGINT COMMENT '时间戳',
        type STRING COMMENT '终端品牌:如 huawei、xiaomi、apple'
    )
    PARTITIONED BY (
        dt STRING COMMENT '日期,格式: yyyy-MM-dd',
        hour STRING COMMENT '小时,00-23',
        region STRING COMMENT '地区:如 beijing、shanghai',
        user_id STRING COMMENT '用户ID'
    );

方案一:使用分区聚簇组合替代全分区

  • 创建分区表时设置分桶属性,对数据进行哈希散列后,将数据均匀分布到固定数量的Bucket中。可参考Hash Clustering

    • 使用方式:建表时加上聚簇参数。

    • 示例

      对于上述test02表,原始分区是dthourregionuser_id,其中user_id存在许多不重复的值,可以将user_id改为clustered key

      CREATE TABLE IF NOT EXISTS test02_bucket (
          user_id STRING COMMENT '用户ID',
          action STRING COMMENT '行为:点击、购买、浏览等',
          view_time BIGINT COMMENT '时间戳',
          type STRING COMMENT '终端品牌:如 huawei、xiaomi、apple'
      )
      PARTITIONED BY (
          dt STRING COMMENT '日期,格式: yyyy-MM-dd',
          hour STRING COMMENT '小时,00-23',
          region STRING COMMENT '地区:如 beijing、shanghai'
      )
      CLUSTERED BY (user_id)
      SORTED by (user_id) 
      INTO 128 BUCKETS;
    • 使用建议

      • 选择高基数且均匀分布的字段,比如user_id

      • 分桶数量一般设置为 2 的幂次方(便于哈希计算),如 64、128、256。

      • 单个桶存储量建议在500MB ~ 1GB之间。

      • 哈希桶的数目必须提供,由数据量大小来决定。

  • 当分桶key不均衡时,可使用Range Clustering

    Range Clustering作为一种新的数据切分方式,提供了一个全局有序的数据分布,一是可以避免Hash Clustering可能造成的数据倾斜问题;二是在数据有序分布的前提下,创建两级索引(Index),支持对Clustering Key的区域查询以及多键的组合查询等场景。

    • 使用方式

      Hash Clustering类似,比较大的区别是RANGE关键字以及Bucket数目对于Range Clustering是可以省略的。

      CREATE TABLE IF NOT EXISTS test02_range_bucket (
          user_id STRING COMMENT '用户ID',
          action STRING COMMENT '行为:点击、购买、浏览等',
          view_time BIGINT COMMENT '时间戳',
          type STRING COMMENT '终端品牌:如 huawei、xiaomi、apple'
      )
      PARTITIONED BY (
          dt STRING COMMENT '日期,格式: yyyy-MM-dd',
          hour STRING COMMENT '小时,00-23',
          region STRING COMMENT '地区:如 beijing、shanghai'
      )
      RANGE CLUSTERED BY (user_id)
      SORTED by (user_id) 
      INTO 128 BUCKETS;

方案二:合并历史分区,减少分区数量

将高频细粒度分区(如按小时)的历史数据,合并为低频大分区(统一按天),从而减少总分区数。这种情况下,对历史分区的查询条件要扩大到更高一级,否则用合并掉的分区条件查不到数据。

规划哪些属于历史分区。比如test01表:2024-04-15这天的数据为历史分区,那么将2024-04-15这天的所有分区的数据都合并到dt='2024-04-15',hour='04',region='beijing',type='meizu'这一个分区。近期业务数据仍按小时分区。具体操作如下:

  1. 执行合并分区的命令,详情可参考分区操作

    -- 可以先列出当天有多少分区
    SHOW PARTITIONS test01; 
    
    -- 合并当天的多个分区,到指定的一个分区。
    ALTER TABLE test01 MERGE IF EXISTS 
            PARTITION(dt='2024-04-15',hour='04',region='beijing',type='meizu'),
            PARTITION(dt='2024-04-15',hour='05',region='nanjing',type='oneplus'),
            PARTITION(dt='2024-04-15',hour='08',region='hangzhou',type='huawei'),
            PARTITION(dt='2024-04-15',hour='16',region='zhangjiakou',type='vivo'),
            PARTITION(dt='2024-04-15',hour='19',region='zhangjiakou',type='apple'),
            PARTITION(dt='2024-04-15',hour='20',region='zhangjiakou',type='vivo')
        OVERWRITE PARTITION(dt='2024-04-15',hour='04',region='beijing',type='meizu') purge;
  2. 删除源表中已经合并的分区,可通过指定筛选条件的方式删除分区,详情可参考删除分区

    ALTER TABLE test01 DROP IF EXISTS 
      PARTITION(dt='2024-04-15',
                hour RLIKE '^(0[0-35-9]|[12][0-9])$',
                region LIKE '%',
                type LIKE '%');

方案三:使用CLONE归档历史分区

将历史数据从当前主表迁出,形成独立的历史表,主表只保留近期活跃数据。

规划哪些属于历史分区。例如,test012025-10-01这天的数据为历史分区,那么把2025-10-01这天前的数据都迁移到新表中。具体操作如下:

  1. 创建新分区表作为历史表,以CLONE TABLE的方式将历史分区数据拆分到历史表中,详情可参考CLONE TABLE

    -- 创建新分区表作为历史表,以时间命名。
    CREATE TABLE IF NOT EXISTS test01_20251001 (
        user_id STRING COMMENT '用户ID',
        action STRING COMMENT '行为:点击、购买、浏览等',
        view_time BIGINT COMMENT '时间戳'
    )
    PARTITIONED BY (
        dt STRING COMMENT '日期,格式: yyyy-MM-dd',
        hour STRING COMMENT '小时,00-23',
        region STRING COMMENT '地区:如 beijing、shanghai',
        type STRING COMMENT '终端品牌:如 huawei、xiaomi、apple'
    );
    
    -- 通过clone命令复制历史分区数据。
    CLONE TABLE test01 
        PARTITION (dt='2025-10-01',hour='00',region='shenzhen',type='realme'),
        PARTITION (dt='2025-10-01',hour='01',region='beijing',type='realme')
        TO test01_20251001;
  2. 删除源表中的历史数据,可通过指定筛选条件的方式删除分区,详情可参考删除分区

    ALTER TABLE test01 DROP IF EXISTS 
      PARTITION(dt='2025-10-01',hour LIKE '%',region LIKE '%',type LIKE '%');
  3. 如果后续需要查询所有数据,可以进一步构建基于所有历史表的union视图。

    CREATE VIEW IF NOT EXISTS test01_view (
        user_id ,action ,view_time,dt,hour,region,type)
    AS SELECT * FROM test01
    UNION ALL
    SELECT * FROM test01_20251001;
    
    -- 查询视图
    SET odps.sql.allow.fullscan=true; 
    SELECT * FROM test01_view WHERE dt='2025-10-01' OR dt='2024-04-15';

    返回结果如下

    -- 可以看到04151001这两部分数据都在
    +---------+--------+------------+----+------+--------+------+
    | user_id | action | view_time  | dt | hour | region | type |
    +---------+--------+------------+----+------+--------+------+
    | user_10018 | share  | 1189910    | 2024-04-15 | 04   | beijing | meizu |
    | user_10015 | view   | 1602602    | 2024-04-15 | 04   | beijing | meizu |
    | user_10019 | share  | 1390674    | 2024-04-15 | 04   | beijing | meizu |
    | user_10057 | login  | 1740907    | 2024-04-15 | 04   | beijing | meizu |
    | user_10085 | share  | 1498282    | 2024-04-15 | 04   | beijing | meizu |
    | user_10011 | logout | 1516989    | 2024-04-15 | 04   | beijing | meizu |
    | user_10000 | login  | 1278246    | 2025-10-01 | 00   | shenzhen | realme |
    | user_10001 | purchase | 1503029    | 2025-10-01 | 00   | shenzhen | realme |
    | user_10002 | view   | 1609992    | 2025-10-01 | 00   | shenzhen | realme |
    | user_10003 | click  | 1558936    | 2025-10-01 | 00   | shenzhen | realme |
    | user_10004 | click  | 1286816    | 2025-10-01 | 00   | shenzhen | realme |
    | user_10000 | login  | 1278246    | 2025-10-01 | 01   | beijing | realme |
    | user_10001 | purchase | 1503029    | 2025-10-01 | 01   | beijing | realme |
    | user_10002 | view   | 1609992    | 2025-10-01 | 01   | beijing | realme |
    | user_10003 | click  | 1558936    | 2025-10-01 | 01   | beijing | realme |
    | user_10004 | click  | 1286816    | 2025-10-01 | 01   | beijing | realme |
    | user_10000 | login  | 1278246    | 2025-10-01 | 01   | beijing | realme |
    | user_10001 | purchase | 1503029    | 2025-10-01 | 01   | beijing | realme |
    | user_10002 | view   | 1609992    | 2025-10-01 | 01   | beijing | realme |
    | user_10003 | click  | 1558936    | 2025-10-01 | 01   | beijing | realme |
    | user_10004 |click   | 1286816    | 2025-10-01 | 01   | beijing | realme |
    +---------+--------+------------+----+------+--------+------+