1 关于分片键
分布式数据库通过将表的数据打散,按一定规则将数据分布在多个独立的主机上,常见的策略如HASH、RANGE、LIST等分布方式。
不考虑中间件的解决方案,在原生分布式数据库产品中,表中主键字段为分片键(Sharding Key),或在业务字段外单独设定一个分片字段作为分片键,数据库提供服务的过程中,所有请求都离不开分片键的协调定位。
2 唯一索引的方案
计算与存储的分布的设计,目的在提升系统的吞吐和容量,同时带来更高的可用性、更好的可扩展性。在传统集中数据库向分布式数据库迁移的过程中,如果应用系统在设计上多次使用到唯一约束或索引,问题来了:
分布式数据库如何实现非主键列的唯一约束或唯一索引?
- a.尽量选取唯一索引字段作为Sharding Key来实现
该方案能够满足一般场景的需求,但局限也很明显,唯一索引字段不作为Sharding Key且同时要创建多个的情况无法满足。
- b.在a方案不能满足的情况下,如单表多个唯一索引的情况,使用克隆方式存储数据,需考虑空间消耗
支持克隆模式的产品可考虑使用,但对于量级较大的表并不适用,会牺牲很多的存储空间、也不是分布的概念,更适合参数配置表。
- c.考虑通过应用改写的方式来实现唯一索引的使用需求
最后,在分布式数据库不能满足的情况下,在应用层面做限制,性能无法保证。
知乎上有一个问题就是“分布式数据库如何实现非主键列的唯一约束或唯一索引?”其中一位网友给出的解决方案如下:
可以为这个非主键列建立一个分布式表,表名为主表名加该非主键列名(table_colum),这个新表的主键就是主表要建立唯一约束的非主键列,这样每次往主表插入数据的时候,都先查询这张新表,如果新表已经存在数据就说明违反唯一约束。唯一索引的话,就是这张新表再加一列,这一列存得就是主表的主键。使用该唯一索引查询的话,就是先根据列值,查询到主表的主键值,再根据该主键值反查主表,获取全部的内容。
上面可能说的比较抽象,我举个例子:在分布式数据库上建立一张有唯一索引的表create table t (a int primary key, b int , c int ) unique key b;数据库在建立这张表的同时,建立一张关联表create table t_b(b int primary key , a int)这样用户往表t 插入数据时,数据库相应的往关联表插入数据insert into t(a, b, c) values (1, 2 , 2),(2, 6, 7) , (3 , 100, 2);//数据库关联插入 insert into t_b (b , a) values (2,1),(6,2) , (100, 3).当用户在插入重复的值时,就可以从关联表中发现了,同时也可以用关联表做索引
3 产品现状
在能够检索到的产品资料中,创建唯一索引时都是需要指定Sharding Key的。如TBase、TDSQL、Sequoiadb 3.2、OceanBase 2.2.50、TiDB 4.0、KingbaseES V8R3、GoldenDB 5.0、PolarDB-X,唯一索引的创建都需要包含Sharding Key。
TBase
建议每个 table 都使用 shard key 作为主键或者唯一索引。
建议建表时一步到位,一起建立主键或者唯一索引。
非 shard key 不可以建立 primary key 或者 unique index。
创建唯一索引
postgres=# create unique index t_first_col_share_id_uidx on t_first_col_share using btree(id);
CREATE INDEX
非 shard key 字段不能建立唯一索引
postgres=# create unique index t_first_col_share_nickname_uidx on t_first_col_share using btree(nickname);
ERROR: Unique index of partitioned table must contain the hash/modulo distribution column.
TDSQL
分表创建时必须在最后面指定分表键(shardkey)的值,该值为表中的一个字段名字,会用于后续 SQL 的路由选择:
mysql> create table test1 (
a int,
b int,
c char(20),
primary key (a,b),
unique key u_1(a,c)
) shardkey=a;
Query OK, 0 rows affected (0.07 sec)
在分布式实例中,shardkey 对应后端数据库的分区字段,因此每一个唯一索引和主键都必须要包含这个 shardkey,否则无法创建表。
存在多个唯一索引时报错。此时有一个唯一索引u_2不包含 shardkey,无法创建表,会报如下错误:
mysql> create table test1 (
a int,
b int,
c char(20),
primary key (a,b),
unique key u_1(a,c),
unique key u_2(b,c)
) shardkey=a;;
ERROR 1105 (HY000): A UNIQUE INDEX must include all columns in the table's partitioning function
主键索引或者 unique key 索引意味着需要全局唯一,而要实现全局唯一索引,则必须包含 shardkey 字段。
Sequoiadb 3.2
在分区集合中,所有的唯一索引必须包含集合分区键中所指定的全部字段。
分区集合中,“$id”索引仅保证单节点内记录的唯一性。如果用户希望指定全局唯一的字段,需要额外创建唯一索引,且该索引必须包含集合分区键中所指定的全部字段。
OceanBase 2.2.50
由于 OceanBase 的表是索引组织表(IOT),为了保证主键的含义,即给定主键的查询能很快定位到所在的分区。所以分区键必须是主键的子集。
如果表里还包含唯一索引,那么要求 Oracle 模式的 IOT 表和索引分区的分区键就必须是所有唯一索引列(包括主键列)交集的子集,对表不做要求。
TiDB 4.0
分区表的每个唯一键,必须包含分区表达式中用到的所有列。这里所指的唯一也包含了主键,因为根据主键的定义,主键必须是唯一的。
DDL 变更时,添加唯一索引也需要考虑到这个限制。比如创建了这样一个表:
CREATE TABLE t_no_pk (c1 INT, c2 INT)
PARTITION BY RANGE(c1) (
PARTITION p0 VALUES LESS THAN (10),
PARTITION p1 VALUES LESS THAN (20),
PARTITION p2 VALUES LESS THAN (30),
PARTITION p3 VALUES LESS THAN (40)
);
Query OK, 0 rows affected (0.12 sec)
通过 ALTER TABLE 添加非唯一索引是可以的。但是添加唯一索引时,唯一索引里面必须包含 c1 列。
KingbaseES V8R3
KingbaseES会自动为定义了一个唯一约束或主键的表创建一个唯一索引。该索引包含组成主键或唯一约束的所有列(可能是一个多列索引),它也是用于强制这些约束的机制。
GoldenDB 5.0
当表是多节点分布存储时,如果唯一索引字段包含了分发字段,唯一索引功能可以正常使用,否则会存在一个明显的限制:不同DB节点间数据没有唯一性约束。
PolarDB-X
全局二级索引定义子句
[UNIQUE] GLOBAL:定义全局二级索引,UNIQUE GLOBAL代表全局唯一索引。
index_name:索引名,也是索引表的名称。
index_type:索引表中分库分表键上局部索引的类型,支持范围请参见MySQL 文档。
index_sharding_col_name,…:索引列,包含且仅包含索引表的全部分库分表键,详情请参见全局二级索引。
global_secondary_index_option:PolarDB-X 1.0全局二级索引的扩展语法。
COVERING (col_name,…):覆盖列,索引表中除索引列以外的其他列,默认包含主键和主表的分库分表键,详情请参见全局二级索引。
drds_partition_options:索引表的分库分表子句,详情请参见分库分表子句和参数。
index_option:索引表中分库分表键上局部索引的属性,详情请参见MySQL 文档。