全部产品
Search
文档中心

云原生数据库 PolarDB:CREATE TABLE(AUTO模式)

更新时间:Mar 29, 2024

本文主要介绍使用DDL语句创建分区表的语法、子句、参数和基本方式。本语法仅适用于AUTO模式数据库。

注意事项

  • 使用分区表语法之前,请务必确认创建当前的逻辑库时模式指定为自动分区模式(mode='auto' ),非自动分区模式不允许使用分区表的建表语法。您也可以通过SHOW CREATE DATBASE db_name语法查看当前逻辑库的建表模式。示例如下:

    CREATE DATABASE part_db mode='auto';
    Query OK, 1 row affected (4.29 sec)
    
    SHOW CREATE DATABASE part_db;
    +----------+-----------------------------------------------+
    | DATABASE | CREATE DATABASE                               |
    +----------+-----------------------------------------------+
    | part_db  | CREATE DATABASE `part_db` /* MODE = 'auto' */ |
    +----------+-----------------------------------------------+
    1 row in set (0.18 sec)

    创建数据库的语法详情,请参见CREATE DATABASE

  • 如果分区表的主键不含分区键, 且不是自增主键,需要业务保证主键的唯一性。

  • 若创建表时要使用二级分区的相关功能,实例版本必须为5.4.17-16952556及以上

语法

CREATE [PARTITION] TABLE [IF NOT EXISTS] tbl_name
    (create_definition, ...)
    [table_options]
    [table_partition_definition]
    [local_partition_definition]

create_definition:
    col_name column_definition
  | mysql_create_definition
  | [UNIQUE] GLOBAL INDEX index_name [index_type] (index_sharding_col_name,...)
      [global_secondary_index_option]
      [index_option] ...

index_sharding_col_name:
    col_name [(length)] [ASC | DESC]

index_option:
    KEY_BLOCK_SIZE [=] value
  | index_type
  | WITH PARSER parser_name
  | COMMENT 'string'

index_type:
    USING {BTREE | HASH}

# 全局二级索引相关
global_secondary_index_option:
    [COVERING (col_name,...)]
    [partition_options]
    [VISIBLE|INVISIBLE]

table_options:
    table_option [[,] table_option] ...

table_option: {
# 指定tablegroup
    TABLEGROUP [=] value,...,}
    
# 分区表类型定义
table_partition_definition:
		single
  |	broadcast
  | partition_options

# 分区定义
partition_options:
	partition_columns_definition
	[subpartition_columns_definition]
	[subpartition_specs_definition]/*用于定义模板化二级分区*/ 
  partition_specs_definition 

# 一级分区的分区列定义
partition_columns_definition:
		PARTITION BY
          HASH({column_name | partition_func(column_name)}) partitions_count
        | KEY(column_list) partitions_count
        | RANGE ({column_name | partition_func(column_name)}) 
        | RANGE COLUMNS(column_list)
        | LIST ({column_name | partition_func(column_name)}) 
        | LIST COLUMNS(column_list) 
        | CO_HASH({column_expr_list}) partitions_count 

# 二级分区的分区列定义
subpartition_columns_definition:
		SUBPARTITION BY
          HASH({column_name | partition_func(column_name)}) subpartitions_count
        | KEY(column_list) subpartitions_count
        | RANGE ({column_name | partition_func(column_name)}) 
        | RANGE COLUMNS(column_list)
        | LIST ({column_name | partition_func(column_name)})
        | LIST COLUMNS(column_list)
        | CO_HASH({column_expr_list}) partitions_count

column_expr_list:
	{column_name | partition_func(column_name)},{column_name | partition_func(column_name)}[,{column_name | partition_func(column_name)},...]

partitions_count:
   PARTITIONS partition_count

subpartitions_count:
   SUBPARTITIONS partition_count

# 分区函数定义
partition_func:
    YEAR
  | TO_DAYS
  | TO_MONTHS
  | TO_WEEKS
  | TO_SECOND
  | UNIX_TIMESTAMP
  | MONTH
  | DAYOFWEEK
  | DAYOFMONTH
  | DAYOFYEAR
  | SUBSTR
  | SUBSTRING
  | RIGHT
  | LEFT


# 一级分区的三种分区类型定义
partition_specs_definition:
	hash_partition_list
  | range_partition_list
  | list_partition_list

# 二级分区的三种分区类型定义
subpartition_specs_definition:
	hash_subpartition_list
  | range_subpartition_list
  | list_subpartition_list

# 一级分区的Hash/Key分区定义
hash_partition_list:
	  /*hash允许不指定各个具体分区定义*/
	| ( hash_partition [, hash_partition, ...] )

hash_partition:
    PARTITION partition_name [partition_spec_options] /*适用于纯一级分区或使用模板化子分区*/
  | PARTITION partition_name subpartitions_count [subpartition_specs_definition] /*适用于定义一级分区下的非模板化子分区*/

# 二级级分区的Hash/Key分区定义
hash_subpartition_list:
  | empty
  | ( hash_subpartition [, hash_subpartition, ...] )

hash_subpartition:
	SUBPARTITION subpartition_name [partition_spec_options]

# 一级分区的Range/Range Columns分区定义
range_partition_list:
    ( range_partition [, range_partition, ... ] )

range_partition:
    	PARTITION partition_name VALUES LESS THAN (range_bound_value) [partition_spec_options] /*适用于纯一级分区或使用模板化子分区*/
    |	PARTITION partition_name VALUES LESS THAN (range_bound_value) [[subpartitions_count] [subpartition_specs_definition]] /*适用于定义一级分区下的非模板化子分区*/
   
# 二级分区的Range/Range Columns分区定义
range_subpartition_list:
	( range_subpartition [, range_subpartition, ... ] )

range_subpartition:
    SUBPARTITION subpartition_name VALUES LESS THAN (range_bound_value) [partition_spec_options]
  
range_bound_value:
	  maxvalue /*适用于定义range的maxvalue分区*/
	| expr /*适用于使用单个分区列时range边界值*/
	| value_list /*适用于使用多个分区列时range边界值*/

# 一级分区的List/List Columns分区定义
list_partition_list:
    (list_partition [, list_partition ...])

list_partition:
    	PARTITION partition_name VALUES IN (list_bound_value) [partition_spec_options] /*适用于纯一级分区或使用模板化子分区*/
    |	PARTITION partition_name VALUES IN (list_bound_value) [[subpartitions_count] [subpartition_specs_definition]] /*适用于定义一级分区下的非模板化子分区*/

# 二级分区的List/List Columns分区定义
list_subpartition_list:
	(list_subpartition [, list_subpartition ...])

list_subpartition:
	SUBPARTITION subpartition_name VALUES IN (list_bound_value) [partition_spec_options]

list_bound_value:
		default  /*适用于定义list的default分区*/
	|	value_set

value_set:
	  value_list  /*适用于使用单个分区列时values集合*/
	| (value_list) [, (value_list), ...] /*适用于使用多个分区列时的values集合*/

value_list:
	value [, value, ...]

partition_spec_options:
	    [[STORAGE] ENGINE [=] engine_name]
        [COMMENT [=] 'string']
        [LOCALITY [=] locality_option]

table_option:
 	    [[STORAGE] ENGINE [=] engine_name]
        [COMMENT [=] 'string']
        [{CHARSET | CHARACTER SET} [=] charset]
        [COLLATE [=] collation]
        [TABLEGROUP [=] table_group_id]
        [LOCALITY [=] locality_option]  

locality_option:
    'dn=storage_inst_id_list'
    
storage_inst_id_list:
    storage_inst_id[,storage_inst_id_list]

local_partition_definition:
		LOCAL PARTITION BY RANGE (column_name)
    [STARTWITH 'yyyy-MM-dd']
    INTERVAL interval_count [YEAR|MONTH|DAY]
    [EXPIRE AFTER expire_after_count]
    [PRE ALLOCATE pre_allocate_count]
    [PIVOTDATE pivotdate_func]
    [DISABLE SCHEDULE]
    
pivotdate_func:
		NOW()
  |	DATE_ADD(...)
  | DATE_SUB(...)

说明

PolarDB-X DDL语法基于MySQL语法,以上主要列出了差异部分,详细语法请参见MySQL 文档

名词解释

  • 分区键:分区表中用于进行水平切分的一个或多个列。

  • 分区列:水平切分后,参与分区路由及计算的列,它通常是分区键的一部分,一个分区键可以含有一个或多个分区列。

  • 向量分区键:由一个或多个分区列组成的分区键。

  • 单列分区键:由一个分区列组成的分区键。

  • 前缀分区列:若一个向量分区键由N(N>1)个分区列组成,它的前K(1<=K<=N)个分区列便组成这个向量分区键的前缀分区列。

  • 分区函数:将分区列作为一个函数的输入参数,并将该函数的输出结果作为原始值参与路由计算,该函数被称为分区函数。

  • 分区裁剪:根据分区定义及查询条件,最大限度地过滤不需要扫描的分区的查询优化手段。

  • 热点分裂:当向量分区键的前缀分区列存在访问热点或分布不均衡时,允许使用下一个分区列对热点分区进行分裂,以达到负载均衡效果。

  • 物理分区:在DN节点有一个物理分表与之相对应的分区,物理分区与物理分表一一对应。

  • 逻辑分区:对应一个或多个物理分区的虚拟分区,相当于一个逻辑概念。例如,当使用二级分区构建分区表时,它的一级分区就是逻辑分区。

参数说明

参数

说明

CHARSET | CHARACTER SET

指定表中列的默认字符集,可使用字符集如下:

  • utf8

  • utf8mb4

  • gbk

COLLATE

指定表中列的默认字符序,可使用字符集如下:

  • utf8_bin

  • utf8_general_ci

  • utf8_unicode_ci

  • gbk_bin

  • gbk_chinese_ci

  • utf8mb4_general_ci

  • utf8mb4__general_cs

  • utf8mb4_bin

  • utf8mb4_unicode_ci

TABLEGROUP

用于指定分区表所属于的表组。若不指定,会自动查找或创建与之分区方式完全一致的表组。

LOCALITY

用于指定分区表的所在存储节点。

单表

PolarDB-X支持创建表时通过指定关键字SINGLE来创建单表(不进行任何分区的表),示例如下:

CREATE TABLE single_tbl(
 id bigint not null auto_increment, 
 bid int, 
 name varchar(30), 
 primary key(id)
) SINGLE;

广播表

PolarDB-X支持创建表时通过指定关键字BROADCAST来创建广播表(该表将在所有DN节点上有一份数据完全相同的拷贝),示例如下:

CREATE TABLE broadcast_tbl(
 id bigint not null auto_increment, 
 bid int, 
 name varchar(30), 
 primary key(id)
) BROADCAST;

分区表

分区类型介绍

PolarDB-X允许创建表时通过指定分区子句的语法,来创建符合业务需求的分区表。PolarDB-X支持四大类型的分区:

  • Hash类型:基于用户指定的分区列或分区函数表达式的值,使用内置的一致性哈希算法计算其哈希值并进行分区路由的策略。按是否支持使用分区函数表达式或使用多个分区列作为分区键,Hash分区策略又可以细分为Key分区Hash分区两种分区策略。

  • Range类型:基于用户指定的分区列或分区函数表达式的值,通过比较计算来确定数据位于哪些预定义分区的范围并进行分区路由的策略。按是否支持使用分区函数表达式或使用多个分区列作为分区键,Range分区策略又可以细分为Range Columns分区Range分区两种分区策略。

  • List类型:与Range分区策略类似,基于用户指定的分区列或分区函数表达式的值,通过比较计算来确定数据位于哪些预定义分区的取值集合并进行分区路由的策略。按是否多个分区列作为分区键以及其使用方式的不同,List类型也分为List Columns分区List分区两种分区策略。

  • CoHash类型PolarDB-X还针对比较常见的特定的应用场景新扩展了一种新的名为CoHash的哈希分区策略,该策略可有效解决一个表需要同时按多个不同的相互有协同关系的分区列进行水平分区的问题。

Hash类型

PolarDB-X的Hash类型分区可细分为Hash分区与Key分区两种类型。Hash分区与Key分区是原生MySQL的标准分区语法之一,PolarDB-X为提供灵活强大的分区管理能力(例如分裂、合并与迁移等)以及考虑支持向量分区键下热点散裂,PolarDB-X不仅在语法上尽量兼容了MySQL的Hash分区与Key分区的建表语法(PolarDB-X仅兼容了语法,分区路由的实现与MySQL并非一致),但是对于Key分区与Hash分区的路由行为重新进行了定义。Key分区与Hash分区使用区别如下表所示:

表 1. Key分区策略与Hash分区策略对比

分区策略

分区键支持

是否支持分区函数

语法示例

特点与限制

路由描述(点查)

Key(默认的分区策略)

单列分区键

PARTITION BY KEY(c1)

  • 不支持热点分裂;

  • 最多有1个分区列参与路由计算;

  1. 按c1列的值,使用一致性哈希算法计算其哈希c1_hash;

  2. 按哈希值 c1_hash进行分区路由。

向量分区键

PARTITION BY KEY(c1,c2,...,cn)

  • 支持热点分裂;

  • 建表后默认仅第1个分区列c1实际参与路由计算,热点分裂时可以使用c2,...,cn等更多的分区列;

  • 最多允许n个分区列同时参与路由计算;

  • 分区列数目n默认不能超过5个。

  1. (c1,c2,...,cn)这N个列的值组成一组分区键向量,并使用一致性哈希算法为向量的各个列的值计算其哈希值,从而将一个分区键向量映射成一个哈希值向量(c1_hash,c2_hash,...,cn_hash)

  2. 将哈希值向量(c1_hash,c2_hash,...,cn_hash)按range路由并找到目标分区。

Hash

单列分区键

PARTITION BY HASH(c1)

  • 不支持热点分裂;

  • 一个分区列参与路由。

  • 目前支持11个分区函数:

    • YEAR

    • MONTH

    • DAYOFMONTH

    • DAYOFWEEK

    • DAYOFYEAR

    • TO_DAYS

    • TO_MONTHS

    • TO_WEEKS

    • TO_SECONDS

    • UNIX_TIMESTAMP

    • SUBSTR/SUBSTRING

PARTITION BY HASH(c1)与PARTITION BY KEY(c1)完全等同 ,其路由算法与PARTITION BY KEY(c1)完全一致。

PARTITION BY HASH(YEAR(c1))

  1. 按c1列的值,使用YEAR分区函数计算其对应的年份year;

  2. 将年份year使用一致性哈希算法计算其哈希值year_hash;

  3. 按哈希值year_hash进行分区路由。

向量分区键

PARTITIONBY HASH(c1,c2,...,cn)

  • 不支持热点分裂;

  • 建表后,n个分区列将同时与路由;

  • 分区列数目n默认不能超过5个。

  1. (c1,c2,...,cn)这N个分区列的值组成一组向量,并使用一致性哈希算法,计算该向量对应哈希值hashVal;

  2. 按哈希hashVal进行分区路由。

示例1-1:Key分区

Key分区也是PolarDB-X的默认分区方式。 Key分区支持向量分区键。例如,用户想按用户名字name列与用户ID两个列作为分区键进行分区,预建分区数目可以指定为8,可以使用以下的语法建表:

CREATE TABLE key_tbl(
 id bigint not null auto_increment,
 bid int,
 name varchar(30),
 birthday datetime not null,
 primary key(id)
)
PARTITION BY KEY(name, id)
PARTITIONS 8;

根据KEY分区的特点,如上所示向量分区键的分区表,当它进行路由计算时,默认只使用向量分区键的第1个分区列(name)进行路由。因此,业务查询SQL的WHERE条件表达式中,只需要含有第1个分区列的等值条件,即可命中分区裁剪的优化,如下所示:

##(命中分区裁剪,只需扫描一个分区)
SELECT id from key_tbl where name='Jack';

若第1个分区列name存在分布不均衡或者出现数据热点,您也可以通过分区分裂操作(详细请参考变更表组级分区(AUTO模式)),并使用下一个分区列(如id)进行分区分裂,从而解决数据不均衡的问题。

如果一个由N个分区列组成的向量分区键,若它实际路由使用到的分区列数目是前K个(1<=K<=N),则查询SQL的WHERE条件表达式只需要包含由这前K个分区列组成的前缀分区列即可命中分区裁剪。

示例1-2:Hash分区

如果您想使用用户ID作为分区键进行水平分区,可以使用Hash分区进行建表,分区数目可以指定为8,建表语法示例如下:

CREATE TABLE hash_tbl(
 id bigint not null auto_increment,
 bid int,
 name varchar(30),
 birthday datetime not null,
 primary key(id)
)
partition by hash(id)
partitions 8;

Hash分区支持使用分区函数表达式(例如YEAR/TO_DAYS/...等)来将时间类型转换成整数类型。因此,如果您想按用户出生日期birthday列进行分区, 并且预建的Hash分区数目是8, 也可以使用如下语句建表:

CREATE TABLE hash_tbl_todays(
 id bigint not null auto_increment,
 bid int,
 name varchar(30),
 birthday datetime not null,
 primary key(id)
)
PARTITION BY HASH(TO_DAYS(birthday))
PARTITIONS 8;

目前PolarDB-X的分区函数仅支持以下的函数列表:

  • YEAR

  • MONTH

  • DAYOFMONTH

  • DAYOFWEEK

  • DAYOFYEAR

  • TO_DAYS

  • TO_MONTHS

  • TO_WEEKS

  • TO_SECONDS

  • UNIX_TIMESTAMP

  • SUBSTR/SUBSTRING

因此,除SUBSTR/SUBSTRING的分区键类型必须为字符串类型以外,其余分区函数的分区键的类型必须是时间类型(DATE/DATETIME/TIMESTAMP),其它类型不支持使用分区函数。

示例1-3:Hash分区扩展

PolarDB-X对Hash分区的语法进行扩展,让Hash分区支持使用向量分区键 (原生MySQL的标准分区语法,可使用如下语句:

CREATE TABLE hash_tbl2(
 id bigint not null auto_increment,
 bid int,
 name varchar(30),
 birthday datetime not null,
 primary key(id)
)
PARTITION BY HASH(name, birthday)
PARTITIONS 8;

与Key分区不同, Hash分区使用向量分区键,这样的分区表在分区路由计算时,是所有分区列同时参与哈希值计算与路由计算,所以,它会要求查询SQL的WHERE条件表达式必须要含包所有的分区列的等值条件才能命中分区裁剪,如下示例的SQL1可以命中hash_tbl2的分区裁剪,而SQL2则不能命中hash_tbl2的分区裁剪:

##SQL1(命中分区裁剪,只扫描一个分区):
SELECT id from hash_tbl2 where name='Jack' and birthday='1990-11-11';

##SQL2(没命中分区裁剪,全分区扫描):
SELECT id from hash_tbl2 where name='Jack';

Hash分区由于一开始就直接使用所有的分区键进行哈希值计算,所以理论上,它比使用向量分区键的Key分区会打散得更均衡,但它不再支持使用下一个列(因为已经没有列可用)进行热点散列。

相关限制

  • 数据类型限制

    • 整数类型: BIGINT/BIGINT UNSINGED/INT UNSINGED/INT/MEDIUMINT/MEDIUMINT UNSINGED/SMALLINT/SMALLINT UNSINGED/TINYINT/TINYINT UNSINGED;

    • 时间类型:DATETIME/DATE/TIMESTAMP;

    • 字符串类型:CHAR/VARCHAR。

  • 语法限制

    • Hash分区的单列分区键支持使用分区函数,且分区键类型必须为时间类型;

    • Hash分区的向量分区键不允许使用分区函数,不支持热点分列;

    • 默认最大分区数目不允许超过8192个;

    • 默认最大分区列数目不允许超过5个。

数据均匀性

  • Key分区与Hash分区内置的一致性Hash散列算法是经过业界广泛测试的、冲突概率低且性能良好的散列算法 MurmurHash3

  • 基于MurmurHash3的特性,一般情况下,当分区键不同取值的数目N大于3000时,Key分区与Hash分区的数据分布才会相对均衡,且N的值越大,数据分布也将越均衡。

Range类型

PolarDB-X的Range类型分区,可细分为Range分区与Range Columns分区两种。同样, Range分区和Range Columns分区属于原生MySQL的标准分区语法。这两种分区的使用区别如下表所示。

表 2. Range Columns分区策略与Range分区策略对比

分区策略

分区键支持

是否支持分区函数

语法示例

特点与限制

路由描述(点查)

Range Columns

单列分区键& 向量分区键

PARTITION BY RANGE COLUMNS (c1,c2,...,cn) ( PARTITION p1 VALUES LESS THAN (1,10,...,1000), PARTITION p2 VALUES LESS THAN (2,20,...,2000) ...)

支持热点分裂(例如c1有热点值88,可以使用c2进行分区分裂解决热点)。

  1. (c1,c2,...,cn)这N个列的值组成一组分区键向量;

  2. 根据这个分区键向量(c1,c2,...,cn)按二分查找算法判断它属于哪个预定义分区,并最终路由到目标分区。

Range

单列分区键

PARTITION BY RANGE(YEAR(c1)) ( PARTITION p1 VALUES LESS THAN (2019), PARTITION p2 VALUES LESS THAN (2021) ...)

  • 不支持热点分裂;

  • 目前支持的分区函数:

    • YEAR

    • MONTH

    • DAYOFMONTH

    • DAYOFWEEK

    • DAYOFYEAR

    • TO_DAYS

    • TO_MONTHS

    • TO_WEEKS

    • TO_SECONDS

    • UNIX_TIMESTAMP

    • SUBSTR/SUBSTRING

  1. 按c1列的值,使用YEAR分区函数计算其对应的年份year;

  2. 年份year按二分查找算法判断它属于哪个预定义分区,并路由到目标分区。

示例2-1:Range Columns分区

Range Columns分区支持使用向量分区键,但它不支持使用分区函数。例如,业务可以按订单ID与订单日期进行Range分区, 可以使用以下的建表语法:

CREATE TABLE orders(
 order_id int,
 order_time datetime not null)
PARTITION BY range columns(order_id,order_time)
(
  PARTITION p1 VALUES LESS THAN (10000,'2021-01-01'),
  PARTITION p2 VALUES LESS THAN (20000,'2021-01-01'),
  PARTITION p3 VALUES LESS THAN (30000,'2021-01-01'),
  PARTITION p4 VALUES LESS THAN (40000,'2021-01-01'),
  PARTITION p5 VALUES LESS THAN (50000,'2021-01-01'),
  PARTITION p6 VALUES LESS THAN (MAXVALUE,MAXVALUE)
);
重要

目前Range Columns分区不支持使用TIMESTAMP/TIME等与时区相关的类型作为分区键。

示例2-2:Range分区

Range分区仅支持单列分区健,但对于时间类型的分区列,它支持使用分区函数(例如YEAR/TO_DAYS/TO_SECONDS/MONTH等)来将时间类型转换成整数类型。

重要

Range分区是不支持直接使用字符串类型作为分区列。

例如,业务想按订单的日期order_time列进行Range分区,并且每个季度一个分区,建表语法如下所示:

CREATE TABLE orders_todays(
 id int,
 order_time datetime not null)
PARTITION BY RANGE(to_days(order_time))
(
  PARTITION p1 VALUES LESS THAN (to_days('2021-01-01')),
  PARTITION p2 VALUES LESS THAN (to_days('2021-04-01')),
  PARTITION p3 VALUES LESS THAN (to_days('2021-07-01')),
  PARTITION p4 VALUES LESS THAN (to_days('2021-10-01')),
  PARTITION p5 VALUES LESS THAN (to_days('2022-01-01')),
  PARTITION p6 VALUES LESS THAN (MAXVALUE)
);
重要

目前Range分区仅支持使用整数类型作为分区键,且分区键仅支持1个分区列。

相关限制

  • 数据类型限制

    • 整数类型: BIGINT/BIGINT UNSINGEDINT/INT/INT UNSINGED/MEDIUMINT/MEDIUMINT UNSINGED/SMALLINT/SMALLINT UNSINGED/TINYINT/TINYINT UNSINGED;

    • 时间类型:DATETIME/DATE;

    • 字符串类型:CHAR/VARCHAR。

  • 语法限制

    • Range Columns分区与Range分区都不支持使用NULL值作为边界值;

    • Range Columns分区目前不支的使用TIMESTAMP类型;

    • Range分区仅支持整数类型,若分区键使用TIMESTAMP类型,必须配套使用分区函数UNIX_TIMESTAMP确保时区一致;

    • Range的分区不支持热点分列;

    • 查询时,NULL值查询会被当作最小值进行分区路由;

    • 默认最大分区数目不允许超过8192个;

    • 默认最大分区列数目不允许超过5个。

List类型

与Range类型类似,PolarDB-X将List分区策略进一步细分为List分区与List Columns分区两种类型。List分区与List Columns分区属于原生MySQL的标准分区语法。此外,PolarDB-X的List分区和List Columns分区还支持Default分区。List Columns分区与List分区的使用区别如下表所示:

表 3. List Columns分区策略与List分区策略对比

分区策略

分区键支持

是否支持分区函数

语法示例

特点与限制

路由描述(点查)

List Columns

单列分区键& 向量分区键

PARTITION BY LIST COLUMNS (c1,c2,...,cn) ( PARTITION p1 VALUES IN ((1,10,...,1000),(2,20,...,2000) ), PARTITION p2 VALUES IN ((3,30,...,3000),(3,30,...,3000) ), ...)

不支持热点分裂

  1. (c1,c2,...,cn)这N个列的值组成一组分区键向量;

  2. 根据这个分区键向量(c1,c2,...,cn)按二分查找算法判断它属于哪个预定义分区,并最终路由到目标分区。

List

单列分区键

PARTITION BY LIST(YEAR(c1)) ( PARTITION p1 VALUES IN (2018,2019), PARTITION p2 VALUES IN (2020,2021) ...)

不支持热点分裂。

示例3-1:List Columns分区

List Columns分区支持使用向量分区键。例如,业务可以按订单的国家country与城市city进行List Columns分区, 建表语法如下所示:

CREATE TABLE orders_region(
 id int,
 country varchar(64),
 city varchar(64),
 order_time datetime not null)
PARTITION BY LIST COLUMNS(country,city)
(
  PARTITION p1 VALUES IN (('China','Hangzhou'), ('China','Beijing')),
  PARTITION p2 VALUES IN (('United States','NewYork'),('United States','Chicago')),
  PARTITION p3 VALUES IN (('Russian','Moscow'))
);
重要

目前List Columns分区不支持使用TIMESTAMP/TIME等与时区相关的类型作为分区键。

示例3-2:List分区

List分区只支持单列分区健,但对于时间类型的分区列,它支持分区函数表达式(例如YEAR/MONTH/DAYOFMONTH/TO_DAYS/TO_SECONDS等)来将时间类型转换成整数类型。

例如,业务想按订单日期order_time的年份进行List分区, 则可以使用以下语法建表:

CREATE TABLE orders_years(
 id int,
 country varchar(64),
 city varchar(64),
 order_time datetime not null)
PARTITION BY LIST(YEAR(order_time))
(
  PARTITION p1 VALUES IN (1990,1991,1992,1993,1994,1995,1996,1997,1998,1999),
  PARTITION p2 VALUES IN (2000,2001,2002,2003,2004,2005,2006,2007,2008,2009),
  PARTITION p3 VALUES IN (2010,2011,2012,2013,2014,2015,2016,2017,2018,2019)
);
重要

目前List仅支持使用整数类型作为分区键,另外要值得注意的是,也不支持直接使用字符串类型作为分区列。

示例3-3:带Default的List Columns分区和List分区

PolarDB-X支持创建带default的List Columns分区和List分区,普通分区中未定义的数据将被路由至default分区。

重要

最多只允许定义一个default分区,且default分区只允许出现在最后一个分区位置。

CREATE TABLE orders_region(
 id int,
 country varchar(64),
 city varchar(64),
 order_time datetime not null)
PARTITION BY LIST COLUMNS(country,city)
(
  PARTITION p1 VALUES IN (('China','Hangzhou'), ('China','Beijing')),
  PARTITION p2 VALUES IN (('United States','NewYork'),('United States','Chicago')),
  PARTITION p3 VALUES IN (('Russian','Moscow')),
  PARTITION pd VALUES IN (DEFAULT)
);

CREATE TABLE orders_years(
 id int,
 country varchar(64),
 city varchar(64),
 order_time datetime not null)
PARTITION BY LIST(YEAR(order_time))
(
  PARTITION p1 VALUES IN (1990,1991,1992,1993,1994,1995,1996,1997,1998,1999),
  PARTITION p2 VALUES IN (2000,2001,2002,2003,2004,2005,2006,2007,2008,2009),
  PARTITION p3 VALUES IN (2010,2011,2012,2013,2014,2015,2016,2017,2018,2019),
  PARTITION pd VALUES IN (DEFAULT)
);

相关限制

  • 数据类型限制

    • 整数类型: BIGINT/BIGINT UNSINGEDINT/INT/INT UNSINGED/MEDIUMINT/MEDIUMINT UNSINGED/SMALLINT/SMALLINT UNSINGED/TINYINT/TINYINT UNSINGED;

    • 时间类型:DATETIME/DATE;

    • 字符串类型:CHAR/VARCHAR。

  • 语法限制

    • List Columns分区目前还不支的使用TIMESTAMP类型;

    • List分区仅支持整数类型;

    • List Columns分区与List分区均不支持热点分裂;

    • 默认最大分区数目不允许超过8192个;

    • 默认最大分区列数目不允许超过5个。

CoHash类型

PolarDB-X的CoHash类型分区策略是PolarDB-X所特有的分区策略。

版本要求

版本必须是5.4.18-17047709及以上。

适用场景

该分区策略常用于解决类似以下的业务场景:

用户的业务表有一个或多个有协同关系的列(例如c1列与c2列,它们的后4位字符总是相同),用户希望将该表同时按照c1列与c2列进行水平分区,并期望业务SQL查询带上c1列或c2列的等值条件,均能路由到相同的单个分区。

因此,使用该分区策略有个必要前提:用户需要自行维护好分区表中同一个的多个分区列的取值上的协同关系。

示例4-1 :CoHash各个分区列独立使用分区函数定义协同关系

假如业务有一张订单表orders,它的每一行录的order_id与buyer_id的后6位的数字总是相同的。那么,如果用户想对订单表orders同时按order_id与buyer_id两个列的后6位数字进行分区,并期望同一行order_id与buyer_id这两个列的等值查询条件均能路由到同一个分区的话,可以使用如下的语法定义:

CREATE TABLE t_orders(
 id bigint not null auto_increment, 
 order_id bigint, 
 buyer_id bigint,
 order_time datetime not null,
 primary key(id)
) 
PARTITION BY CO_HASH(
 	RIGHT(`order_id`,6) /*取c1列的后6位字符*/,
 	RIGHT(`buyer_id`,6) /*取c2列的后6位字符*/
) 
PARTITIONS 8;

示例4-2 :使用Range_Hash语法糖,适配用户1.0迁移2.0

假如业务想从1.0迁移至2.0,原来1.0有一张订单表orders表并使用了range_hash分库分表 ,其定义如下:DBPARTIITION BY RANGE_HASH(`order_id`,`buyer_id`, 6) ,那么,该订单表orders表在2.0的AUTO库的对应分区表定义如下:

CREATE TABLE orders(
 id bigint not null auto_increment, 
 buyer_id bigint,
 order_id bigint,
 ...
 primary key(id)
) 
PARTITION BY RANGE_HASH(order_id, buyer_Id,6) 
PARTITIONS 8;

PolarDB-X支持将RANGE_HASH语法自动转换为对应的CO_HASH的分区定义,比如上述的SQL的RANGE_HASH(order_id, buyer_Id,6) 实质上会自动被转换为以下的使用CO_HASH的定义:

CREATE TABLE orders(
 id bigint not null auto_increment, 
 buyer_id bigint,
 order_id bigint,
 ...
 primary key(id)
) 
PARTITION BY CO_HASH(
 	RIGHT(`order_id`,6) /*取c1列的后6位字符*/,
 	RIGHT(`buyer_id`,6) /*取c2列的后6位字符*/
) 
PARTITIONS 8;

与Hash/Key分区策略的主要区别

由于CoHash分区策略与前边的Hash/Key分区策略有些类似,以下是它们的一些主要用法异同的对比。

主要区别点

CO_HASH

KEY

Hash

语法示例

PARTITION BY

CO_HASH(c1, c2)

PARTITOINS 8

PARTITION BY

KEY(c1, c2)

PARTITOINS 8

PARTITION BY

HASH(c1, c2)

PARTITOINS 8

单列分区键

不支持

支持

支持

向量分区键

支持

支持。

支持

向量分区列是否允许使用分区函数

允许。例如PARTITION BY

CO_HASH(

/*取c1列的后4位字符*/

RIGHT(c1, 4),

/*取c2列的后4位字符*/

RIGHT(c2, 4)

)

PARTITOINS 8

不允许

不允许

分区列之间的关系

协同关系。同一个的分区列取值的协同关系由业务提供并负责维护。例如:

  • c1 与 c2 的取值总是相同

  • 适合使用 CO_HASH(c1, c2)

  • c1 与 c2 的后4位字符总是相同

  • 适合使用 CO_HASH(RIGHT(c1,4), RIGHT(c2,4)))

类似联合索引的前缀关系。

类似联合索引的前缀关系。

前缀列等值查询分区裁剪及示例

支持。例如:

  • c1='x': 支持分区裁剪并路由到单个分区;

  • c1='x' and c2='y': 支持分区裁剪并路由到一个或0个分区(如果 c1='x'与c2='y'的路由分区不一样,则返回0个分区)。

支持。例如:

  • c1='x':支持分区裁剪并路由到一个或多个分片(如果x值进行了热点分列,会返回多个分区);

  • c1='x' and c2='y':支持分区裁剪并路由到单个分区。

不支持,必须带上全分区列等值条件才支持分区裁剪。例如:

  • c1='x': 无法裁剪并全分区扫描;

  • c1='x' and c2='y': 支持分区裁剪并路由到单个分区。

非前缀列等查询分区裁剪及示例

支持。所有分区列的等值条件均支持独立的分区裁剪。例如:

  • c2='x': 支持分区裁剪并路由到单个分区;

  • c1='x' or c2='y': 支持分区裁剪并路由到1个或2个分区(如果 c1='x'与c2='y'的路由分区是一样,则返回1个分区,否则扫描2个分区)。

不支持。非前缀分区等值条件必须全分区扫描。例如:

  • c2='x': 全分区扫描;

  • c1='x' or c2='y': 全分区扫描。

不支持。非前缀分区等值条件必须全分区扫描。例如:

  • c2='x': 全分区扫描;

  • c1='x' or c2='y': 全分区扫描。

范围查询

不支持。全分区扫描。

不支持。全分区扫描。

不支持。全分区扫描。

路由描述(点查)

  1. 提取c1列(其他的分区列与之一样)的等值查询的原始值 v1 ;

  2. 如果c1列使用了分区函数,则计算v1列的分区函数取值,f1 = partFunc(v1),否则取 f1 = v1;

  3. 使用一致性哈希算法计算 f1 的哈希值 c1_hash(long类型的整数);

  4. 按哈希值c1_hash 进行分区路由。

可参考前边的“Key分区与Hash 分区”的描述,此处忽略。

可参考前边的“Key分区与 Hash分区”的描述,此处忽略。

热点分裂

不支持。无法对某个具体的热点值(比如c1='88') 进行进一步热点分裂

支持

不支持

分区管理(常见的分区分裂、合并与迁移等)

支持

支持

支持

二级分区

支持

支持

支持

注意事项

  • 分区列取值的协同关系必须由业务保证,分区表仅校验路由结果。CO_HASH由于多个分区列之间的取值存在着由业务维护的协同的关系。因此,对于CO_HASH分区表的每一行记录的插入,不同分区列的值的分区路由结果要求必须是一致的。但是,即使同一行的记录不同的分区列的取值的分区路由结果完全一致,也不一定能保证这些分区列的协同关系不被破坏。因此,分区列之间的协同关系必须由用户自行保证,PolarDB-X只负责检验路由结果,不负责校验数据本身的协同关系。

    例如业务定义c1与c2的后4位字符是相同的,现在假如c1=1001234与c2=1320都能路由分片0,那 insert (c1,c2) values (100234,1320)是允许的,但此时c1与c2的后4位并不相同。

  • DML修改分区列限制。由于CO_HASH的多个分区列之间的取值存在协同的关系,为防止数据分布错误,PolarDB-X对于DML关于CO_HASH分区列的值的修改有如下限制:

    • 对于INSERT/REPLCAE语句,VALUES子句中同一行的不同分区列的值在路由计算后,如果其分区结果不一致,将被禁止插入并报错。

    • 对于UPDATE及UPSERT语句,SET子句在修改分区列的取值时,必须要对所有分区列同时进行修改。例如c1与c2是分区列,那么应该是UPDATE t1 SET c1='xx',c2='yy' WHERE id=1 。如果SET子句在修改分区列后的值,并且会导致同一行的不同分区列的取值产生不同的分区路由结果的话,该UPDATE语句或UPSERT语句将被禁止并报错。

    • 如果使用了CO_HASH作为GSI的分区策略,那么所有对主表的INSERT/UPDATE等操作,若该DML操作会导致主表的GSI表的同一行数据的不同分区列的取值产生不同的分区路由结果,那么,该DML操作也将被禁止并报错。

  • 关于整数类型前缀0的说明。CO_HASH的分区列之间的有协同关系,所以它的分区列通常需要借助使用SUBSTR/LEFT/RIGHT等分区函数进行定义。因此,一些整数类型的数字被截取后,容易出现前缀为0的情况。例如业务定义c1与c2的后4位字符是相同的,现在假如c1=1000034与c2=34, c1的后4位字符是'0034'。CO_HASH对于类型是整数类型的分区列,所有原始的数字被截取后,都会统一自动转为分区列对应的整数类型再进行路由。因此,对于'0034' 的字符串,它实际会被转为整数34 再进行哈希值计算并路由分区,从而自动处理前缀0。

分区函数使用限制

  • RIGHT

  • LEFT

  • SUBSTR

数据类型限制

  • 整数类型: BIGINT/BIGINT UNSINGEDINT/INT/INT UNSINGED/MEDIUMINT/MEDIUMINT UNSINGED/SMALLINT/SMALLINT UNSINGED/TINYINT/TINYINT UNSINGED

  • 定点类型:DECIMAL(小数部分的有效位数必须为0)

  • 时间类型:不支持

  • 字符串类型:CHAR/VARCHR

语法限制

  • 分区列使用分区函数时,不允许嵌套多层的分区函数。例如使用类似SUBSTR(SUBSTR(c1,-6),-4)的定义。

  • 使用RANGE_HASH语法糖时,最后的长度数字不能是负数。

  • 所有分区列的类型必须完全一致,包括:

    • 分区列类型的charset与collation;

    • 分区列类型的长度定义或精度定义等。

  • 默认最大分区数目不允许超过8192。

  • 默认最大分区列数目不允许超过5个。

二级分区

与MySQL类似,PolarDB-X支持使用二级分区语法创建包含二级分区的分区表。二级分区就是允许对所有一级分区按指定分区列及分区策略继续进行二次分区。

  • 二级分区的每个一级分区实际变成一个逻辑分区,对应着一组二级分区的集合;

  • 二级分区的每个二级分区实际上变成一个物理分区,对应着DN节点上的一个具体的物理分表。

模板化与非模板化

从整体功能上看,PolarDB-X二级分区分可为两大类:模板化二级分区与非模板化二级分区:

  • 模板化二级分区:各个一级分区之下的二级分区的分区数目及其分区边界值始终一致;

  • 非模板化二级分区:各个一级分区之下的二级分的分区数目及其分区边界值允许不一致。

语法限制

  • 使用二级分区的分区表,它的物理分区数目默认不允许超过8192;

  • 使用非模板化一级分区,所有二级分区的分区名不允许重复,也不允许与一级分区名字有重复;

  • 使用模板化一级分区,所有模板化的二级分区的分区名不允许重复,也不允许与一级分区名字有重复。

  • 使用二级分区后,分区表的二级分区数目是所有的一级分区下的二级分区数目之和。因此,分区表的分区数将呈倍级上升。因此,请谨慎控制一级分区与二级分区的各自的分片数目,避免过度分区产生副作用,或超出分区总数限制而报错。

示例5-1:模板化二级分区

/*
 * 定义LIST-KEY的模板化子分区,
 * 一级分区按 LIST COLUMNS 分为3个分区,
 * 每个一级分区继续按 KEY 策略分为4个二级分区,
 * 因此,总共会有 12个 物理分区
*/
CREATE TABLE sp_tbl_list_key_tp(
 id int, 
 country varchar(64),
 city varchar(64),
 order_time datetime not null,
 PRIMARY KEY(id)
)
PARTITION BY LIST COLUMNS(country,city) 
SUBPARTITION BY KEY(id) SUBPARTITIONS 4
(
  PARTITION p1 VALUES IN (('China','Hangzhou')),
  PARTITION p2 VALUES IN (('Russian','Moscow')),
  PARTITION pd VALUES IN (DEFAULT)
);

示例5-2:非模板化二级分区

/*
 * 定义LIST-KEY的非模板化子分区,
 * 一级分区按 LIST COLUMNS 分为3个分区,
 * 每个一级分区继续按 KEY 策略进行二级分区,
 * 各一级分区的二级分区数目分别定义为 2,3,4,
 * 因此,总共会有 9 个 物理分区
*/
CREATE TABLE sp_tbl_list_key_ntp(
 id int, 
 country varchar(64),
 city varchar(64),
 order_time datetime not null,
 PRIMARY KEY(id)
)
PARTITION BY LIST COLUMNS(country,city) 
SUBPARTITION BY KEY(id)
(
  PARTITION p1 VALUES IN (('China','Hangzhou')) SUBPARTITIONS 2,
  PARTITION p2 VALUES IN (('Russian','Moscow')) SUBPARTITIONS 3,
  PARTITION pd VALUES IN (DEFAULT) SUBPARTITIONS 4
);

默认自动分区

  • 建表SQL在不指定分区键的情况下,PolarDB-X默认会按主键(如果表没有指定主键,则使用隐式主键)并使用KEY分区进行默认分区,自动分区创建的均为一级分区表。

    默认分区的分区数目=实例创建时逻辑节点数×8。例如,PolarDB-X实例创建时,逻辑节点是2,默认分区数目就是16。

  • 除了主表默认会按主键自动分区,主表中所有索引也会默认以索引列与主键列作为分区键并进行自动分区。

如下示例是标准的MySQL建表语法,主键是id,索引列为name:

CREATE TABLE auto_part_tbl(
 id bigint not null auto_increment,
 bid int,
 name varchar(30),
 primary key(id),
 index idx_name (name)
);

若使用SHOW CREATE TABLE语句查询该建表语句,显示标准的MySQL建表语法,自动隐藏所有分区信息:

show create table auto_part_tbl;
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| TABLE         | CREATE TABLE                                                                                                                                                                                                                     |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| auto_part_tbl | CREATE TABLE `auto_part_tbl` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `bid` int(11) DEFAULT NULL,
    `name` varchar(30) DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `idx_name` (`name`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8 |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.06 sec)

若使用SHOW FULL CREATE TABLE查询该建表语句,则会显示上述主表及其索引表的所有分区信息:

show full create table auto_part_tbl;
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| TABLE         | CREATE TABLE                                                                                                                                                                                                                                                                                                                                                                                                              |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| auto_part_tbl | CREATE PARTITION TABLE `auto_part_tbl` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `bid` int(11) DEFAULT NULL,
    `name` varchar(30) DEFAULT NULL,
    PRIMARY KEY (`id`),
    GLOBAL INDEX /* idx_name_$a870 */ `idx_name` (`name`) PARTITION BY KEY (`name`, `id`) PARTITIONS 16,
    LOCAL KEY `_local_idx_name` (`name`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8
PARTITION BY KEY(`id`)
PARTITIONS 16
/* tablegroup = `tg108` */ |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.03 sec)

从返回信息中可以看到:

  • 主表auto_part_tbl默认按ID进行KEY分区,分区数是16;

  • 主表的索引idx_name默认使用了全局索引,全局索引的分区键是 `name`,`id`,分区数也是16。

手动分区

通过在代码中指定分区列、分区函数、分区类型等创建的表为手动分区表,详细类型请参见手动创建分区表(AUTO模式)

数据类型说明

表 4. 各分区策略分区列数据类型的支持情况

数据类型

Hash类型

Range类型

List类型

HASH

Key

Range

Range Columns

List

List Columns

单区分列

多区分列

数值类型

TINYINT

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

TINYINT UNSIGNED

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

SMALLINT

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

SMALLINT UNSIGNED

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

MEDIUMINT

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

MEDIUMINT UNSIGNED

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

INT

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

INT UNSIGNED

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

BIGINT

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

BIGINT UNSIGNED

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

正确支持

定点类型

DECIMAL

正确支持

(该类型作为分区列支持不能使用分区函数)

正确支持

正确支持

错误不支持

正确支持(该类型作为RangeColumns列时,小数位数必须为0)

错误不支持l

正确支持(该类型作为RangeColumns列时,小数位数必须为0

时间类型

DATE

正确支持(该类型的分区列支持使用分区函数)

正确支持

正确支持

正确支持(该类型分区列必须配套使用分区函数)

正确支持

正确支持(该类型分区列必须配套使用分区函数)

正确支持

DATETIME

正确支持(该类型的分区列支持使用分区函数)

正确支持

正确支持

正确支持(该类型分区列必须配套使用分区函数)

正确支持

正确支持(该类型分区列必须配套使用分区函数)

正确支持

TIMESTAMP

正确支持(该类型的分区列支持使用分区函数)

正确支持

正确支持

错误不支持

错误不支持

错误不支持

错误不支持

字符串类型

CHAR

正确支持(该类型作为分区列支持不能使用分区函数)

正确支持

正确支持

错误不支持

正确支持

错误不支持

正确支持

VARCHAR

正确支持(该类型作为分区列支持不能使用分区函数)

正确支持

正确支持

错误不支持

正确支持

错误不支持

正确支持

二进制类型

BINARY

正确支持(该类型作为分区列支持不能使用分区函数)

正确支持

正确支持

错误不支持

错误不支持

错误不支持

错误不支持

VARBINARY

正确支持(该类型作为分区列支持不能使用分区函数)

正确支持

正确支持

错误不支持

错误不支持

错误不支持

错误不支持

关于分区列的数据类型与路由计算的说明

分区表的路由计算是直接依赖于分区列的数据类型的,特别是Key分区与Hash分区。因此,使用不同数据类型的分区列,其哈希值算法或比较算法(例如大小写是否敏感)的实现不一样,会产生不一样的路由行为(MySQL的分区路由算法也是类型强相关的)。

如下所示,假如tbl_int表是分区列,类型是int且分区数是1024, 而tbl_bigint分区列类型是bigint且分区数目也是1024。它们虽然都是整数类型,但是由于数据类型不一样,对于同一个查询值(12345678)的路由结果也不一样:

show create table tbl_int;
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| TABLE   | CREATE TABLE                                                                                                                                                                |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tbl_int | CREATE TABLE `tbl_int` (
    `a` int(11) NOT NULL,
    KEY `auto_shard_key_a` USING BTREE (`a`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4
PARTITION BY KEY(`a`)
PARTITIONS 1024 |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.02 sec)

show create table tbl_bigint;
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| TABLE      | CREATE TABLE                                                                                                                                                                      |
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tbl_bigint | CREATE TABLE `tbl_bigint` (
    `a` bigint(20) NOT NULL,
    KEY `auto_shard_key_a` USING BTREE (`a`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4
PARTITION BY KEY(`a`)
PARTITIONS 1024 |
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.10 sec)mysql> create table if not exists tbl_bigint(a bigint not null)
    -> partition by key(a) partitions 1024;
Query OK, 0 rows affected (28.41 sec)

explain select * from tbl_int where a=12345678;
+---------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN                                                                             |
+---------------------------------------------------------------------------------------------------+
| LogicalView(tables="tbl_int[p260]", sql="SELECT `a` FROM `tbl_int` AS `tbl_int` WHERE (`a` = ?)") |
| HitCache:false                                                                                    |
| Source:PLAN_CACHE                                                                                 |
| TemplateId: c90af636                                                                              |
+---------------------------------------------------------------------------------------------------+
4 rows in set (0.45 sec)

explain select * from tbl_bigint where a=12345678;
+------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN                                                                                      |
+------------------------------------------------------------------------------------------------------------+
| LogicalView(tables="tbl_bigint[p477]", sql="SELECT `a` FROM `tbl_bigint` AS `tbl_bigint` WHERE (`a` = ?)") |
| HitCache:false                                                                                             |
| Source:PLAN_CACHE                                                                                          |
| TemplateId: 9b2fa47c                                                                                       |
+------------------------------------------------------------------------------------------------------------+
4 rows in set (0.02 sec)
            

分区列大小写、字符集及校验集的说明

说明

分区列的字符集(charset)及校验集(collation)对分区表的路由算法会产生直接影响。例如指定分区路由是否需要忽略大小写。如果分区表的校验集是区分大小写的,则分区路由在哈希与比较过程中便会区分大小写;如果分区表的校验集不区分大小写,则分区路由在哈希与比较过程中不会区分大小写。默认情况下,字符类型的分区列会使用字符集utf8及不区分区大小写的校验集 utf8_general_ci。

示例1

如果用户需要让分区表在路由时区分分区列的大小写,在建表时将分区表的校验集设置为区分大小写的校验集即可(如utf8_bin)。如下所示,分区表tbl_varchar_cs的分区表采用了CHARACTER SET utf8 COLLATE utf8_bin ,所以对于大小写不同的两个字符串 'AbcD' 与 'abcd' ,分区表会将它们路由不同的分区:

show create table tbl_varchar_cs;
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| TABLE          | CREATE TABLE                                                                                                                                                                                                          |
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tbl_varchar_cs | CREATE TABLE `tbl_varchar_cs` (
    `a` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
    KEY `auto_shard_key_a` USING BTREE (`a`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8
PARTITION BY KEY(`a`)
PARTITIONS 64 |
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.07 sec)

explain select a from tbl_varchar_cs where a in ('AbcD');
+-------------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN                                                                                                   |
+-------------------------------------------------------------------------------------------------------------------------+
| LogicalView(tables="tbl_varchar_cs[p29]", sql="SELECT `a` FROM `tbl_varchar_cs` AS `tbl_varchar_cs` WHERE (`a` IN(?))") |
| HitCache:false                                                                                                          |
| Source:PLAN_CACHE                                                                                                       |
| TemplateId: 2c49c244                                                                                                    |
+-------------------------------------------------------------------------------------------------------------------------+
4 rows in set (0.11 sec)

explain select a from tbl_varchar_cs where a in ('abcd');
+-------------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN                                                                                                   |
+-------------------------------------------------------------------------------------------------------------------------+
| LogicalView(tables="tbl_varchar_cs[p11]", sql="SELECT `a` FROM `tbl_varchar_cs` AS `tbl_varchar_cs` WHERE (`a` IN(?))") |
| HitCache:true                                                                                                           |
| Source:PLAN_CACHE                                                                                                       |
| TemplateId: 2c49c244                                                                                                    |
+-------------------------------------------------------------------------------------------------------------------------+
4 rows in set (0.02 sec)

示例2

如果需要让分区表在路由时忽略分区列的大小写,在建表时将分区表的校验集设置为不区分大小写的校验集即可(如utf8_general_ci)。如下所示,分区表tbl_varchar_ci的分区表采用了CHARACTER SET utf8 COLLATE utf8_general_ci ,所以对于大小写不同的两个字符串 'AbcD' 与 'abcd' ,分区表则会将它们路由到同样的分区:

show create table tbl_varchar_ci;
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| TABLE          | CREATE TABLE                                                                                                                                                                      |
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tbl_varchar_ci | CREATE TABLE `tbl_varchar_ci` (
    `a` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
    KEY `auto_shard_key_a` USING BTREE (`a`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8
PARTITION BY KEY(`a`)
PARTITIONS 64 |
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.06 sec)

explain select a from tbl_varchar_ci where a in ('AbcD');
+------------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN                                                                                                  |
+------------------------------------------------------------------------------------------------------------------------+
| LogicalView(tables="tbl_varchar_ci[p4]", sql="SELECT `a` FROM `tbl_varchar_ci` AS `tbl_varchar_ci` WHERE (`a` IN(?))") |
| HitCache:false                                                                                                         |
| Source:PLAN_CACHE                                                                                                      |
| TemplateId: 5c97178e                                                                                                   |
+------------------------------------------------------------------------------------------------------------------------+
4 rows in set (0.15 sec)

explain select a from tbl_varchar_ci where a in ('abcd');
+------------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN                                                                                                  |
+------------------------------------------------------------------------------------------------------------------------+
| LogicalView(tables="tbl_varchar_ci[p4]", sql="SELECT `a` FROM `tbl_varchar_ci` AS `tbl_varchar_ci` WHERE (`a` IN(?))") |
| HitCache:true                                                                                                          |
| Source:PLAN_CACHE                                                                                                      |
| TemplateId: 5c97178e                                                                                                   |
+------------------------------------------------------------------------------------------------------------------------+
4 rows in set (0.02 sec)

分区列字符集与校验集的变更

由于分区表的路由算法与数据类型是相关的,如果分区列的字符集与校验集被修改,将会导致全表所有数据的重分布。所以请慎重地修改分区列的数据类型。

关于分区列的类型截断与类型转换的说明

分区列类型截断

SQL在查询或插入时,若指定分区列的常量表达式超过了分区列类型所能表达的范围,PolarDB-X会先产生类型截断,然后再使用类型截断后的值进行路由计算。

例如:tbl_smallint表分区列类型是smallint,smallint的正常取值范围是[-32768, 32767]。因此,如果insert的值超过smallint的范围(例如,12745678或-12345678),则会被截断类型的最大值或最小值(32767或-32768),如下所示 。

show create table tbl_smallint;
+--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| TABLE        | CREATE TABLE                                                                                                                                                                        |
+--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tbl_smallint | CREATE TABLE `tbl_smallint` (
    `a` smallint(6) NOT NULL,
    KEY `auto_shard_key_a` USING BTREE (`a`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4
PARTITION BY KEY(`a`)
PARTITIONS 128 |
+--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.06 sec)
set sql_mode='';
Query OK, 0 rows affected (0.00 sec)

insert into tbl_smallint values (12345678),(-12345678);
Query OK, 2 rows affected (0.07 sec)

select * from tbl_smallint;
+--------+
| a      |
+--------+
| -32768 |
|  32767 |
+--------+
2 rows in set (3.51 sec)

explain select * from tbl_smallint where a=12345678;
+------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN                                                                                            |
+------------------------------------------------------------------------------------------------------------------+
| LogicalView(tables="tbl_smallint[p117]", sql="SELECT `a` FROM `tbl_smallint` AS `tbl_smallint` WHERE (`a` = ?)") |
| HitCache:false                                                                                                   |
| Source:PLAN_CACHE                                                                                                |
| TemplateId: afb464d5                                                                                             |
+------------------------------------------------------------------------------------------------------------------+
4 rows in set (0.16 sec)

explain select * from tbl_smallint where a=32767;
+------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN                                                                                            |
+------------------------------------------------------------------------------------------------------------------+
| LogicalView(tables="tbl_smallint[p117]", sql="SELECT `a` FROM `tbl_smallint` AS `tbl_smallint` WHERE (`a` = ?)") |
| HitCache:true                                                                                                    |
| Source:PLAN_CACHE                                                                                                |
| TemplateId: afb464d5                                                                                             |
+------------------------------------------------------------------------------------------------------------------+
4 rows in set (0.03 sec)

同理,如果查询的常量值超过类型范围,也会按截断后的值进行路由。因此,对于tbl_smallint表,a=12345678与a=32767的分区路由结果是完全相同的。

分区列类型转换

SQL在查询或插入时,若指定分区列的常量表达式与分区列的类型不一致,PolarDB-X会先对常量表达式进行隐式类型转换,然后再使用类型转换后的值进行路由计算。但是,类型转换也有可能会出现转换失败的情况,例如,字符串abc无法转换为整型。

对于分区列出现类型转换及其失败的情况,PolarDB-X按DQL、DML、DDL会有不同的行为:

  • DQL(特指WHERE条件表达式中的分区列的类型转换)

    • 类型转换成功:按类型转换后的值进行分区路由;

    • 类型转换失败:该分区列条件会被直接忽略,走全表扫描。

  • DML(特指Insert或Replace)

    • 类型转换成功:按类型转换后的值进行分区路由;

    • 类型转换失败:直接报错,拒绝执行。

  • DDL(特指分区表相关的DDL,如建表、分裂等)

    • 类型转换成功:直接报错,拒绝执行,DDL不允许出现类型转换;

    • 类型转换失败:直接报错,拒绝执行。

与MySQL分区表的语法差异

差异点

MySQL

PolarDB-X

分区键包含主键

强制要求。

不要求。

Key分区

路由算法:按分区数目取模。

路由算法:一致性哈希算法。

Hash分区

  1. 路由算法:按分区数目取模;

  2. 不支持多分区列;

  3. 支持LINEAR HASH分区;

  4. 单列分区键下,Hash(col)与Key(col)的路由算法不同。

  1. 路由算法:一致性哈希算法;

  2. 支持多分区列(扩展语法);

  3. 不支持LINEAR HASH分区;

  4. 单列分区键下,Hash(col)与Key(col)的路由算法相同。

分区函数

支持。PARTITION BY HASH(expr(col)) ..., expr可以是常见的计算表达式,如YEAR(col) + 1。

有限地支持。PARTITION BY HASH(expr(col)),expr仅限于以下函数,且不允许表达式中出现其它计算操作(如 +,-,*,/ 等):

  • YEAR

  • MONTH

  • DAYOFMONTH

  • DAYOFWEEK

  • DAYOFYEAR

  • TO_DAYS

  • TO_MONTHS

  • TO_WEEKS

  • TO_SECONDS

  • UNIX_TIMESTAMP

  • SUBSTR/SUBSTRING

分区列类型

Key分区支持所有数据类型。

Key分区共支持整数类型、时间类型与字符类型三类。

分区列字符集

支持所有常见字符集。

仅支持3种字符集:

  • utf8

  • utf8mb4

  • gbk

二级分区

支持。

支持。