Skip to content

昆仑分布式数据库中的分库分表方案与规则

kunlundb edited this page Oct 9, 2021 · 1 revision

Database及其schema

在昆仑数据库中,数据的分布有‘database', 'schema' 和'table' 、索引、存储过程等3个层级。一个database里面可以包含任意数量的schema。schema是对表以及其他数据库对象的逻辑分组,每个database在创建后默认的schma是public,用户还可以随时按需增加更多schema。每个schema里面可以创建任意数量的table。所有对数据库对象比如表、索引、存储过程等的引用,如果没有指定schema那么总是使用public;显式指定(引用)schema的方法是在各类数据库对象名前面前缀schema的名称,例如 schemaName.tableName。连接到昆仑数据库的任何一个计算节点时需要指定database,该连接只能访问该database里面的schema、表和其他数据库对象,不可以访问其它database里面的schema和表和其他数据库对象。系统元数据连接到任何database都可以访问。Schema在PostgreSQL和昆仑数据库中也成为namespace,因为它是对数据库对象的逻辑分组,也就是名字空间。存储schema元数据的表叫做pg_nanamespace. 存储数据库元数据的元数据表叫做pg_database.

昆仑的每个database里面的每个schema都在后端所有的存储集群都对应有一个mysql 的database来存储其中所有的表分片。命名的对应规则是计算节点中名为myDB的database里面名为mySchema的schema,在每一个mysql存储集群中的database名为 myDB_$$mySchema,这个myDB.mySchema 里面的任何一个表或者表分区 对应于存储集群的 数据表就存储在某一个存储集群的 myDB$$_mySchema这个database中。每次新建一个schema都会自动在当前所有存储集群新建这个database和schema对应的database。

用户通过计算节点创建一个不分区的表myDB.mySchema.t1,那么计算节点自动分配某个存储集群来存储t1,昆仑会在该存储集群的myDB_$$mySchema database中创建 同名的表 myDB$$_mySchema.t1 来存储 myDB.mySchema.t1的数据, 并且昆仑会在计算节点的元数据表中记录下来t1的数据存储在SS1。这样,读写数据时候,计算节点就可以自动找到t1的存储节点,然后与目标节点交互来读写myDB.mySchema.t1表的数据。如果用户创建分区表t2,那么用户最终还需要创建分区表t2的若干个表分区(即分区树的叶子结点。分区表可以多级分区)来存储数据,比如用户需要创建表分区t20,t21,t22...等,然后这些表分区也会被自动分配到某个后端存储集群。每个叶子结点的处理方式也也类似于单表,计算节点会自动分配存储集群给它来创建同名表。

昆仑的所有database的所有schema里面的单表或者表分区被自动均匀分布到后端所有存储集群中。 如果需要增加存储集群,那么昆仑会自动搬迁一部分表分片到新的存储集群以便达到均匀分布。

所以,如果用户需要把数据按照业务逻辑分组,并且分组之间的数据还需要做表连接,那么这些分组应该是同一个database里面的若干个schema。同时,用户需要根据其业务和数据的特点,来决定一个表是否需要分区(例如,数据量不大时,比如10万行以内,100MB以内,可以不分区),决定分区的话,选择哪些列作为分区列以及如何分区(有hash,range,list三种选项)。并且,决定分区方式以后,每个具体的分区也需要DBA去创建好。

由于昆仑数据库不同的database之间的数据完全不可以在同一个数据库连接中关联使用,而是完全独立使用,所以我们不建议在一个昆仑数据库集群中创建很多个database。需要对数据表按业务分隔的话,可以针对每个业务创建若干个schema,然后在其中创建表。这样同一个数据库连接中可以同时操作其database里面的所有schema中的所有的表。 不同的database使用不同的昆仑数据库集群,这样做的好处是可以按database做计算/存储资源分配,以及按database来做数据备份/恢复/回档等管理工作。

存储集群元数据

昆仑数据库集群的每个计算节点的元数据表含有该集群的所有存储集群的元数据信息。在pg_shard表存储这每个存储集群的信息,包括ID,名称,数据量,分片数量等;而在pg_shard_node表则存储这每个存储集群的每个存储节点的元数据信息,包括其IP,端口,用户名和密码等。

在pg_class表中,我们新增了relshardid字段,它存储的是这个表/索引/sequence等对应的数据表所在的存储集群的ID,这个ID就是pg_shard表的ID列。计算节点使用这些信息找到每个表和其他数据库对象所在的存储集群和节点的信息,然后连接其节点并与之交互完成数据读写和事务处理。

示例说明

我们使用的昆仑数据库集群由两个存储集群,其详细信息如下:

  1. postgres=# select t1.name, t2.shard_id, t2.ip, t2.port, t2.user_name, t2.passwd from pg_shard t1, pg_shard_node t2 where t2.shard_id=t1.id;
  2. name | shard_id | ip | port | user_name | passwd
  3. --------+----------+-----------+------+-----------+---------
  4. shard1 | 3 | 127.0.0.1 | 4001 | pgx | pgx_pwd
  5. shard2 | 4 | 127.0.0.1 | 4002 | pgx | pgx_pwd

在postgres这个数据库中有public和 benchmarksql两个schema,那么shard1和shard2 存储集群上就有postgres_$$public和postgres$$_benchmarksql两个database。分别连接其主节点可以看到:

  1. mysql> select @@port;
  2. +--------+
  3. | @@port |
  4. +--------+
  5. | 4001 |
  6. +--------+
  7. 1 row in set (0.00 sec)
  1. mysql> show databases;
  2. +--------------------------+
  3. | Database |
  4. +--------------------------+
  5. | information_schema |
  6. | mysql |
  7. | performance_schema |
  8. | postgres_$$_benchmarksql |
  9. | postgres_$$_public |
  10. | regression_$$_public |
  11. | sys |
  12. +--------------------------+
  13. 7 rows in set (0.00 sec)
  1. mysql> select @@port;
  2. +--------+
  3. | @@port |
  4. +--------+
  5. | 4002 |
  6. +--------+
  7. 1 row in set (0.00 sec)
  1. mysql> show databases;
  2. +--------------------------+
  3. | Database |
  4. +--------------------------+
  5. | information_schema |
  6. | mysql |
  7. | performance_schema |
  8. | postgres_$$_benchmarksql |
  9. | postgres_$$_public |
  10. | regression_$$_public |
  11. | sys |
  12. +--------------------------+
  13. 7 rows in set (0.02 sec)

使用如下查询获得每个表所属的schema(即nspname列),其表文件在哪个存储集群(即relshardid)等信息。然后分别在两个存储集群上看一下这些表或者表分片在存储集群上面对应的表。

  1. select t1.nspname, t2.relname, t2.relshardid, t2.relkind from pg_namespace t1 join pg_class t2 on t1.oid=t2.relnamespace where t2.relshardid != 0 order by t1.nspname;
  2. postgres=# select t1.nspname, t2.relname, t2.relshardid, t2.relkind from pg_namespace t1 join pg_class t2 on t1.oid=t2.relnamespace where t2.relshardid != 0 order by t1.nspname;
  3. nspname | relname | relshardid | relkind
  4. --------------+--------------------+------------+---------
  5. benchmarksql | customer | 4 | r
  6. benchmarksql | hist_id_seq | 3 | S
  7. benchmarksql | history | 4 | r
  8. benchmarksql | oorder | 3 | r
  9. benchmarksql | new_order | 4 | r
  10. benchmarksql | order_line | 3 | r
  11. benchmarksql | stock | 4 | r
  12. benchmarksql | item | 3 | r
  13. benchmarksql | warehouse | 4 | r
  14. benchmarksql | district | 3 | r
  15. public | t101_pkey | 4 | i
  16. public | t102 | 3 | r
  17. public | t102_pkey | 3 | i
  18. public | tt14t | 4 | r
  19. public | scores1 | 4 | r
  20. public | scores1_pkey | 4 | i
  21. public | t1 | 4 | r
  22. public | uv_iocu_tab_a_seq | 4 | S
  23. public | uv_iocu_tab | 4 | r
  24. public | uv_iocu_tab_pkey | 4 | i
  25. public | warehouse2 | 4 | r
  26. public | warehouse2_pkey | 4 | i
  27. public | district2 | 3 | r
  28. public | warehouse1 | 4 | r
  29. public | warehouse1_pkey | 4 | i
  30. public | district1 | 3 | r
  31. public | district1_pkey | 3 | i
  32. public | customer1 | 4 | r
  33. public | customer1_pkey | 4 | i
  34. public | history1 | 3 | r
  35. public | orders1 | 4 | r
  36. public | orders1_pkey | 4 | i
  37. public | new_orders1 | 3 | r 。。。。。。由于行数较多,此处省略若干行。。。。。

连接到端口号为4001的存储集群主节点,可以看到对应于计算节点的每个database和schema组合的数据库和其中位于shard1的数据表。

  1. mysql> use postgres_$$_benchmarksql
  2. Database changed
  3. mysql> show tables;
  4. +------------------------------------+
  5. | Tables_in_postgres_$$_benchmarksql |
  6. +------------------------------------+
  7. | district |
  8. | item |
  9. | oorder |
  10. | order_line |
  11. +------------------------------------+
  12. 4 rows in set (0.01 sec)
  1. mysql> use postgres_$$_public
  2. Database changed
  3. mysql> show tables;
  4. +------------------------------+
  5. | Tables_in_postgres_$$_public |
  6. +------------------------------+
  7. | district1 |
  8. | district2 |
  9. | history1 |
  10. | history2 |
  11. | new_orders1 |
  12. | new_orders2 |
  13. | scores |
  14. | stock1 |
  15. | stock2 |
  16. | t100 |
  17. | t102 |
  18. +------------------------------+
  19. 11 rows in set (0.01 sec)

连接到端口号为4002 的存储集群主节点,可以看到对应于计算节点的所有非系统内部使用的database和schema的组合的数据库和其中位于shard2的数据表。

  1. mysql> use postgres_$$_benchmarksql
  2. Database changed
  3. mysql> show tables;
  4. +------------------------------------+
  5. | Tables_in_postgres_$$_benchmarksql |
  6. +------------------------------------+
  7. | customer |
  8. | history |
  9. | new_order |
  10. | stock |
  11. | warehouse |
  12. +------------------------------------+
  13. 5 rows in set (0.02 sec)
  1. mysql> use postgres_$$_public
  2. Database changed
  3. mysql> show tables;
  4. +------------------------------+
  5. | Tables_in_postgres_$$_public |
  6. +------------------------------+
  7. | customer1 |
  8. | customer2 |
  9. | item1 |
  10. | item2 |
  11. | order_line1 |
  12. | order_line2 |
  13. | orders1 |
  14. | orders2 |
  15. | scores1 |
  16. | students |
  17. | t1 |
  18. | t101 |
  19. | tt14t |
  20. | uv_iocu_tab |
  21. | warehouse1 |
  22. | warehouse2 |
  23. +------------------------------+
  24. 16 rows in set (0.02 sec)

然后让我们看一下分区表的表分区及其在存储节点的表。t10 是一个分区表,其3个分区t100, t101, t102分别在id为3,4,3的分区:

  1. postgres=# \d+ t10;
  2.                                             Table "public.t10"
    
  3. Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
  4. --------+---------+-----------+----------+----------------------------------+---------+--------------+-------------
  5. a | integer | | not null | "nextval"('t10_a_seq'::regclass) | plain | |
  6. b | integer | | | | plain | |
  7. Partition key: HASH (a)
  8. Indexes:
  9. "t10_pkey" PRIMARY KEY, btree (a)
    
  10. Partitions: t100 FOR VALUES WITH (modulus 3, remainder 0),
  11.         t101 FOR VALUES WITH (modulus 3, remainder 1),
    
  12.         t102 FOR VALUES WITH (modulus 3, remainder 2)
    
  1. postgres=# select relname, relshardid from pg_class where relname like 't10_' and relkind='r';
  2. relname | relshardid
  3. ---------+------------
  4. t100 | 3
  5. t101 | 4
  6. t102 | 3

从计算节点查询t10可以看到其全部数据,这些行位于其3个分区中:

  1. postgres=# select*from t10;
  2. a | b
  3. ----+----
  4. 2 | 2
  5. 4 | 4
  6. 6 | 6
  7. 8 | 8
  8. 15 | 13
  9. 3 | 3
  10. 7 | 7
  11. 10 | 10
  12. 13 | 11
  13. 14 |
  14. 1 | 1
  15. 5 | 5
  16. 9 | 9
  17. 11 |
  18. 12 |
  19. (15 rows)

依次在每个存储集群中查看t10的分区对应的表的定义并查看每个表分片中的数据。可以看到这些表分片中的数据的并集就是在计算节点上通过查询t10得到的所有数据行。

  1. mysql> show create table t100;
  2. +-------+------------------------------------------------------------------------------------------------------------------------------------------------+
  3. | Table | Create Table |
  4. +-------+------------------------------------------------------------------------------------------------------------------------------------------------+
  5. | t100 | CREATE TABLE t100 (
  6. a int(11) NOT NULL,
  7. b int(11) DEFAULT NULL,
  8. PRIMARY KEY (a) USING BTREE
  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
  10. +-------+------------------------------------------------------------------------------------------------------------------------------------------------+
  11. 1 row in set (0.00 sec)
  1. mysql> show create table t102;
  2. +-------+------------------------------------------------------------------------------------------------------------------------------------------------+
  3. | Table | Create Table |
  4. +-------+------------------------------------------------------------------------------------------------------------------------------------------------+
  5. | t102 | CREATE TABLE t102 (
  6. a int(11) NOT NULL,
  7. b int(11) DEFAULT NULL,
  8. PRIMARY KEY (a) USING BTREE
  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
  10. +-------+------------------------------------------------------------------------------------------------------------------------------------------------+
  11. 1 row in set (0.00 sec)
  1. mysql> select*from t100;
  2. +----+------+
  3. | a | b |
  4. +----+------+
  5. | 2 | 2 |
  6. | 4 | 4 |
  7. | 6 | 6 |
  8. | 8 | 8 |
  9. | 15 | 13 |
  10. +----+------+
  11. 5 rows in set (0.00 sec)
  1. mysql> select*from t102;
  2. +----+------+
  3. | a | b |
  4. +----+------+
  5. | 1 | 1 |
  6. | 5 | 5 |
  7. | 9 | 9 |
  8. | 11 | NULL |
  9. | 12 | NULL |
  10. +----+------+
  11. 5 rows in set (0.00 sec)
  1. mysql> show create table t101;
  2. +-------+------------------------------------------------------------------------------------------------------------------------------------------------+
  3. | Table | Create Table |
  4. +-------+------------------------------------------------------------------------------------------------------------------------------------------------+
  5. | t101 | CREATE TABLE t101 (
  6. a int(11) NOT NULL,
  7. b int(11) DEFAULT NULL,
  8. PRIMARY KEY (a) USING BTREE
  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
  10. +-------+------------------------------------------------------------------------------------------------------------------------------------------------+
  11. 1 row in set (0.00 sec)
  1. mysql> select*from t101;
  2. +----+------+
  3. | a | b |
  4. +----+------+
  5. | 3 | 3 |
  6. | 7 | 7 |
  7. | 10 | 10 |
  8. | 13 | 11 |
  9. | 14 | NULL |
  10. +----+------+
  11. 5 rows in set (0.00 sec)