首页 > 数据库 > 国内数据库 > 一文读懂 GoldenDB 的分片与分区
谈到分布式数据库,最核心的诉求是将数据分散存储于多个数据库实例中,这也是传统
Mycat 等中间件的核心能力。然而,
GoldenDB 与
Mycat 有着本质区别:
GoldenDB 并非简单地将分片路由逻辑叠加在
MySQL 之上,而是将计算层(CN)与存储层(DN)进行了深度融合。
| 概念 | Mycat 传统中间件 | GoldenDB 分布式 |
|---|---|---|
| 架构 | Proxy 层分片 + 普通 MySQL 实例 | 计算节点+ 数据节点原生融合 |
| 分片单位 | 逻辑库和逻辑表 | 物理分片组 |
| 后端存储 | 普通 MySQL | MySQL + 主从复制 + 水位 |
| 事务 | 对业务侵入大且性能损耗高 | GTM 全局事务 |
| 分区→分片 | 中间件路由,如 order_id % 4 | 元数据 |
| 分区对应 | MySQL 实例 | DBGroup(1 主 N 备) |
从上表可以看出,
Mycat 的本质是“拼装”,底层的 MySQL 只是一个机械的执行者,不知道自己身处分布式集群中,也不知道兄弟节点存放了什么;而
GoldenDB 的本质是“融合”。当我们将分区表的单个分区对应到一个物理分片(DBGroup)时,计算节点在解析 SQL 的瞬间就能完成
“分布式分片裁剪 + 节点内分区裁剪”。
DISTRIBUTED BY) 解决的是
横向扩展(Scale-out) 问题,决定数据分散到哪一个
DBGroup。PARTITION BY) 解决的是
单机纵向性能(Scale-up) 问题,在
DBGroup 内部通过时间或范围再细分,配合
分区裁剪 能够大幅减少单次 SQL 扫描的数据量。数据分片(Sharding):即通过分片键,决定一条数据去往哪一个物理存储组( DBGroup)。数据分片具有以下特性:
在
GoldenDB 中支持的分片规则包括:Range 分片、Hash 分片、List 分片、复制表以及多级分片。
CREATE TABLE tb_order_range ( order_id INT NOT NULL, user_id INT NOT NULL, amount DECIMAL(10,2) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_id) ) DISTRIBUTED BY RANGE (order_id) ( g1 VALUES LESS THAN (10000), g2 VALUES LESS THAN (20000), g4 VALUES LESS THAN MAXVALUE ); goldendb_client> SELECT 'g1_count' as shard, COUNT(*) FROM tb_order_range STORAGEDB g1; +----------+----------+ | SHARD | COUNT(*) | +----------+----------+ | g1_count | 9999 | +----------+----------+ 1 row in set (2.10 sec) goldendb_client> goldendb_client> SELECT 'g1_count' as shard, COUNT(*) FROM tb_order_range STORAGEDB g2; +----------+----------+ | SHARD | COUNT(*) | +----------+----------+ | g1_count | 10000 | +----------+----------+ 1 row in set (2.64 sec) goldendb_client> SELECT 'g1_count' as shard, COUNT(*) FROM tb_order_range STORAGEDB g4; +----------+----------+ | SHARD | COUNT(*) | +----------+----------+ | g1_count | 20001 | +----------+----------+ 1 row in set (2.78 sec)
GoldenDB 的分布式层采用的是
一致性 HASH 算法,能大幅减少了扩容时的数据迁移量。CREATE TABLE tb_order_hash ( order_id INT NOT NULL, user_id INT NOT NULL, amount DECIMAL(10,2) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id, order_id) ) DISTRIBUTED BY HASH(user_id) (g1, g2, g4); goldendb_client> SELECT 'g1_count' as shard, COUNT(*) FROM tb_order_hash STORAGEDB g1; +----------+----------+ | SHARD | COUNT(*) | +----------+----------+ | g1_count | 13354 | +----------+----------+ 1 row in set (5.98 sec) goldendb_client> SELECT 'g1_count' as shard, COUNT(*) FROM tb_order_hash STORAGEDB g2; +----------+----------+ | SHARD | COUNT(*) | +----------+----------+ | g1_count | 13436 | +----------+----------+ 1 row in set (2.98 sec) goldendb_client> SELECT 'g1_count' as shard, COUNT(*) FROM tb_order_hash STORAGEDB g4; +----------+----------+ | SHARD | COUNT(*) | +----------+----------+ | g1_count | 13210 | +----------+----------+ 1 row in set (2.16 sec)
CREATE TABLE tb_order_list (
order_id INT NOT NULL,
user_id INT NOT NULL,
region_code VARCHAR(10) NOT NULL,
amount DECIMAL(10,2) DEFAULT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (order_id)
) DISTRIBUTED BY LIST (region_code) (
g1 VALUES IN ('BJ', 'TJ', 'HEB'),
g2 VALUES IN ('SH', 'HZ', 'NJ', 'WH'),
g4 VALUES IN ('GZ', 'SZ', 'XA', 'CD')
);
goldendb_client> SELECT COUNT(*), COUNT(DISTINCT region_code) FROM tb_order_list;
+----------+-----------------------------+
| COUNT(*) | COUNT(DISTINCT REGION_CODE) |
+----------+-----------------------------+
| 30000 | 11 |
+----------+-----------------------------+
1 row in set (0.20 sec)
goldendb_client> SELECT 'g1_count' as shard, COUNT(*) FROM tb_order_list STORAGEDB g1;
+----------+----------+
| SHARD | COUNT(*) |
+----------+----------+
| g1_count | 8131 |
+----------+----------+
1 row in set (4.06 sec)
goldendb_client> SELECT 'g2_count' as shard, COUNT(*) FROM tb_order_list STORAGEDB g2;
+----------+----------+
| SHARD | COUNT(*) |
+----------+----------+
| g2_count | 10875 |
+----------+----------+
1 row in set (2.51 sec)
goldendb_client> SELECT 'g4_count' as shard, COUNT(*) FROM tb_order_list STORAGEDB g4;
+----------+----------+
| SHARD | COUNT(*) |
+----------+----------+
| g4_count | 10994 |
+----------+----------+
1 row in set (2.48 sec)
CREATE TABLE tb_region_dict (
region_code VARCHAR(10) NOT NULL,
region_name VARCHAR(50) NOT NULL,
status TINYINT DEFAULT 1,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (region_code)
) distributed by duplicate(g1,g2,g4);
INSERT INTO tb_region_dict (region_code, region_name, status) VALUES
('BJ', '北京', 1),
('TJ', '天津', 1),
('HEB', '河北', 1),
('SH', '上海', 1),
('HZ', '杭州', 1),
('NJ', '南京', 1),
('WH', '武汉', 1),
('GZ', '广州', 1),
('SZ', '深圳', 1),
('XA', '西安', 1),
('CD', '成都', 1);
goldendb_client> SELECT 'g1_count' as shard, COUNT(*) FROM tb_region_dict STORAGEDB g1;
+----------+----------+
| SHARD | COUNT(*) |
+----------+----------+
| g1_count | 11 |
+----------+----------+
1 row in set (3.52 sec)
goldendb_client> SELECT 'g2_count' as shard, COUNT(*) FROM tb_region_dict STORAGEDB g2;
+----------+----------+
| SHARD | COUNT(*) |
+----------+----------+
| g2_count | 11 |
+----------+----------+
1 row in set (5.89 sec)
goldendb_client> SELECT 'g4_count' as shard, COUNT(*) FROM tb_region_dict STORAGEDB g4;
+----------+----------+
| SHARD | COUNT(*) |
+----------+----------+
| g4_count | 11 |
+----------+----------+
1 row in set (2.89 sec)
多级分片是通过多维属性对数据分片进行精确控制,通常是通过多个字段进行多层次分片,一级分片 用
DISTRIBUTED BY 语句,二级分片用
SUBDISTRIBUTED BY... 语句。
CREATE TABLE t1 (a INT, b INT, c INT) DISTRIBUTED BY CASE c WHEN 1 THEN CASE WHEN b < 100 THEN SUBDISTRIBUTED BY HASH(a) (g1); ELSE SUBDISTRIBUTED BY HASH(a) (g2); END CASE; WHEN 2 THEN SUBDISTRIBUTED BY HASH(a) (g3); ELSE SUBDISTRIBUTED BY HASH(a) (g4); END CASE;
分区(Partition BY) 即纵向切分。表经过
DISTRIBUTED BY 横向分片后,数据已落入各 DBGroup;在此基础上,
GoldenDB 允许在单个 DBGroup 内部通过
PARTITION BY 按指定分区键进一步切分数据。分区发生在本地
MySQL 引擎层,对上层透明,主要作用是缩小单节点扫描范围、提升时间范围类查询的效率。
CREATE TABLE tb_orders_hash_par_range ( order_id BIGINT NOT NULL, user_id BIGINT NOT NULL, region_code VARCHAR(10) NOT NULL, amount DECIMAL(10,2) DEFAULT NULL, create_time DATETIME NOT NULL, ym INT NOT NULL, PRIMARY KEY (order_id, ym) ) PARTITION BY RANGE (ym) ( PARTITION p202609 VALUES LESS THAN (202610), PARTITION p202610 VALUES LESS THAN (202611), PARTITION p202611 VALUES LESS THAN (202612), PARTITION p202612 VALUES LESS THAN (202701), PARTITION pmax VALUES LESS THAN MAXVALUE ) DISTRIBUTED BY HASH (order_id) (g1, g2, g4); SELECT ym, COUNT(*) AS cnt FROM tb_orders_hash_par_range GROUP BY ym ORDER BY ym; +--------+------+ | YM | CNT | +--------+------+ | 202601 | 2993 | | 202602 | 3053 | | 202603 | 2927 | | 202604 | 2925 | | 202605 | 3152 | | 202606 | 3004 | | 202607 | 2873 | | 202608 | 3016 | | 202609 | 3030 | | 202610 | 3027 | +--------+------+
通过分区建查询:
goldendb_client> EXPLAIN SELECT * FROM tb_orders_hash_par_range WHERE ym = 202610 \G *************************** 2. row *************************** ID: 10001 SELECT_TYPE: SQLNode TABLE: 1 PARTITIONS: TYPE: POSSIBLE_KEYS: SELECT `test`.`tb_orders_hash_par_range`.`order_id`,`test`.`tb_orders_hash_par_range`.`user_id`,`test`.`tb_orders_hash_par_range`.`region_code`,`test`.`tb_orders_hash_par_range`.`amount`,`test`.`tb_orders_hash_par_range`.`create_time`,`test`.`tb_orders_hash_par_range`.`ym`,`test`.`tb_orders_hash_par_range`.`gtid` as `gtid1` FROM `test`.`tb_orders_hash_par_range` where (`ym` = 202610) KEY: Cluster1,g1,g2,g4 KEY_LEN: REF: Parent=NULL,Child=NULL,Next=NULL ROWS: FILTERED: EXTRA: cr, test.tb_orders_hash_par_range=hash,digest=d1abd2d84dd534050ff121f24fbb81a3 *************************** 2. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: tb_orders_hash_par_range PARTITIONS: p202610 TYPE: ALL POSSIBLE_KEYS: NULL KEY: NULL KEY_LEN: NULL REF: NULL ROWS: 985 FILTERED: 10.00 EXTRA: Using where, g1 *************************** 3. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: tb_orders_hash_par_range PARTITIONS: p202610 TYPE: ALL POSSIBLE_KEYS: NULL KEY: NULL KEY_LEN: NULL REF: NULL ROWS: 1030 FILTERED: 10.00 EXTRA: Using where, g2 *************************** 4. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: tb_orders_hash_par_range PARTITIONS: p202610 TYPE: ALL POSSIBLE_KEYS: NULL KEY: NULL KEY_LEN: NULL REF: NULL ROWS: 1012 FILTERED: 10.00 EXTRA: Using where, g4 4 rows in set (0.64 sec)
发现使用分区裁剪验证,执行计划中
partitions 列只出现 p202610,而不是全部分区。但是扫描了三个分片。再带上 orders_id 字段构成主键查询:
goldendb_client> EXPLAIN SELECT * FROM tb_orders_hash_par_range WHERE order_id = 15 AND ym = 202610 \G *************************** 2. row *************************** ID: 10001 SELECT_TYPE: SQLNode TABLE: 1 PARTITIONS: TYPE: POSSIBLE_KEYS: SELECT `test`.`tb_orders_hash_par_range`.`order_id`,`test`.`tb_orders_hash_par_range`.`user_id`,`test`.`tb_orders_hash_par_range`.`region_code`,`test`.`tb_orders_hash_par_range`.`amount`,`test`.`tb_orders_hash_par_range`.`create_time`,`test`.`tb_orders_hash_par_range`.`ym`,`test`.`tb_orders_hash_par_range`.`gtid` as `gtid1` FROM `test`.`tb_orders_hash_par_range` where ((`order_id` = 15) and (`ym` = 202610)) KEY: Cluster1,g4 KEY_LEN: REF: Parent=NULL,Child=NULL,Next=NULL ROWS: FILTERED: EXTRA: cr, test.tb_orders_hash_par_range=hash,digest=277702f4e80ac20b5bc8531ca9ed9148 *************************** 2. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: NULL PARTITIONS: NULL TYPE: NULL POSSIBLE_KEYS: NULL KEY: NULL KEY_LEN: NULL REF: NULL ROWS: NULL FILTERED: NULL EXTRA: no matching row in const table, g4 2 rows in set (0.85 sec)
发现使用了 分区裁剪 + 分片路由,只扫描了一个分片和分区。
GoldenDB 支持只建分区表、不写
DISTRIBUTED BY —— 此时它是一张
非分片表(单 DBGroup 表),整张表落在一个 DBGroup 内,但内部按
PARTITION BY 做本地分区。
如果建表时不指定
DISTRIBUTED BY,且集群配置了多个 DBGroup,
默认会创建为
DUPLICATE(复制表),数据全量拷贝到列出的所有组。
DISTRIBUTED BY 语句。goldendb_client> CREATE TABLE orders_part_range ( order_id BIGINT NOT NULL, user_id BIGINT NOT NULL, region_code VARCHAR(10) NOT NULL, amount DECIMAL(10,2) DEFAULT NULL, create_time DATETIME NOT NULL, ym INT NOT NULL, -- 分区键:YYYYMM PRIMARY KEY (order_id, ym) -- 主键必须含分区键 ) PARTITION BY RANGE (ym) ( PARTITION p202608 VALUES LESS THAN (202609), PARTITION p202609 VALUES LESS THAN (202610), PARTITION p202610 VALUES LESS THAN (202611), PARTITION p202611 VALUES LESS THAN (202612), PARTITION p202612 VALUES LESS THAN (202701), PARTITION pmax VALUES LESS THAN MAXVALUE );
goldendb_client> show create table orders_part_range\G
*************************** 1. row ***************************
TABLE: orders_part_range
CREATE TABLE: CREATE TABLE `orders_part_range` (
`order_id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`region_code` varchar(10) COLLATE utf8mb4_bin NOT NULL,
`amount` decimal(10,2) DEFAULT NULL,
`create_time` datetime NOT NULL,
`ym` int NOT NULL,
PRIMARY KEY (`order_id`,`ym`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
/*!50100 PARTITION BY RANGE ("ym")
(PARTITION p202608 VALUES LESS THAN (202609) ENGINE = InnoDB,
PARTITION p202609 VALUES LESS THAN (202610) ENGINE = InnoDB,
PARTITION p202610 VALUES LESS THAN (202611) ENGINE = InnoDB,
PARTITION p202611 VALUES LESS THAN (202612) ENGINE = InnoDB,
PARTITION p202612 VALUES LESS THAN (202701) ENGINE = InnoDB,
PARTITION pmax VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
DISTRIBUTED BY DUPLICATE(g1,g2,g4)
1 row in set (0.01 sec)
goldendb_client> INSERT INTO orders_part_range SELECT * FROM tb_orders_hash_par_range limit 1000;
goldendb_client> select count(*) from orders_part_range storagedb g1;
+----------+
| COUNT(*) |
+----------+
| 1000 |
+----------+
1 row in set (0.17 sec)
goldendb_client> select count(*) from orders_part_range storagedb g2;
+----------+
| COUNT(*) |
+----------+
| 1000 |
+----------+
1 row in set (0.19 sec)
goldendb_client> select count(*) from orders_part_range storagedb g4;
+----------+
| COUNT(*) |
+----------+
| 1000 |
+----------+
1 row in set (0.10 sec)
goldendb_client> EXPLAIN SELECT * FROM orders_part_range WHERE ym = 202610 \G *************************** 1. row *************************** ID: 10001 SELECT_TYPE: SQLNode TABLE: 1 PARTITIONS: TYPE: POSSIBLE_KEYS: SELECT `test`.`orders_part_range`.`order_id`,`test`.`orders_part_range`.`user_id`,`test`.`orders_part_range`.`region_code`,`test`.`orders_part_range`.`amount`,`test`.`orders_part_range`.`create_time`,`test`.`orders_part_range`.`ym`,`test`.`orders_part_range`.`gtid` as `gtid1` FROM `test`.`orders_part_range` where (`ym` = 202610) KEY: Cluster1,g1 KEY_LEN: REF: Parent=NULL,Child=NULL,Next=NULL ROWS: FILTERED: EXTRA: cr, test.orders_part_range=rep,digest=61479bf21572913a6d1e742a646c58bb *************************** 2. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: orders_part_range PARTITIONS: p202610 TYPE: ALL POSSIBLE_KEYS: NULL KEY: NULL KEY_LEN: NULL REF: NULL ROWS: 1 FILTERED: 100.00 EXTRA: Using where, g1 2 rows in set (0.00 sec)
goldendb_client> CREATE TABLE orders_single_group ( order_id BIGINT NOT NULL, user_id BIGINT NOT NULL, region_code VARCHAR(10) NOT NULL, amount DECIMAL(10,2) DEFAULT NULL, create_time DATETIME NOT NULL, ym INT NOT NULL, PRIMARY KEY (order_id, ym) ) PARTITION BY RANGE (ym) ( PARTITION p202608 VALUES LESS THAN (202609), PARTITION p202609 VALUES LESS THAN (202610), PARTITION p202610 VALUES LESS THAN (202611), PARTITION p202611 VALUES LESS THAN (202612), PARTITION p202612 VALUES LESS THAN (202701), PARTITION pmax VALUES LESS THAN MAXVALUE ) DISTRIBUTED BY DUPLICATE(g1); -- 只放 g1,等价于"单组表" goldendb_client> INSERT INTO orders_single_group SELECT * FROM tb_orders_hash_par_range; -- 数据只存放在 g1 分片组上: goldendb_client> select count(*) from orders_single_group storagedb g1; +----------+ | COUNT(*) | +----------+ | 30000 | +----------+ 1 row in set (0.10 sec) goldendb_client> select count(*) from orders_single_group storagedb g2; +----------+ | COUNT(*) | +----------+ | 0 | +----------+ 1 row in set (0.00 sec) goldendb_client> select count(*) from orders_single_group storagedb g4; +----------+ | COUNT(*) | +----------+ | 0 | +----------+ 1 row in set (0.01 sec)
goldendb_client> show create table orders_single_group\G
*************************** 1. row ***************************
TABLE: orders_single_group
CREATE TABLE: CREATE TABLE `orders_single_group` (
`order_id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`region_code` varchar(10) COLLATE utf8mb4_bin NOT NULL,
`amount` decimal(10,2) DEFAULT NULL,
`create_time` datetime NOT NULL,
`ym` int NOT NULL,
PRIMARY KEY (`order_id`,`ym`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
/*!50100 PARTITION BY RANGE ("ym")
(PARTITION p202608 VALUES LESS THAN (202609) ENGINE = InnoDB,
PARTITION p202609 VALUES LESS THAN (202610) ENGINE = InnoDB,
PARTITION p202610 VALUES LESS THAN (202611) ENGINE = InnoDB,
PARTITION p202611 VALUES LESS THAN (202612) ENGINE = InnoDB,
PARTITION p202612 VALUES LESS THAN (202701) ENGINE = InnoDB,
PARTITION pmax VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
DISTRIBUTED BY DUPLICATE(g1)
1 row in set (0.03 sec)
goldendb_client> analyze table orders_single_group; +--------------------------+---------+----------+----------+ | TABLE | OP | MSG_TYPE | MSG_TEXT | +--------------------------+---------+----------+----------+ | test.orders_single_group | analyze | status | OK | +--------------------------+---------+----------+----------+ 1 row in set (0.23 sec) goldendb_client> SELECT PARTITION_NAME, PARTITION_METHOD, PARTITION_EXPRESSION, PARTITION_DESCRIPTION, TABLE_ROWS FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'orders_single_group' AND PARTITION_NAME IS NOT NULL ORDER BY PARTITION_ORDINAL_POSITION; +----------------+------------------+----------------------+-----------------------+------------+ | PARTITION_NAME | PARTITION_METHOD | PARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS | +----------------+------------------+----------------------+-----------------------+------------+ | p202608 | RANGE | `ym` | 202609 | 22063 | | p202609 | RANGE | `ym` | 202610 | 3030 | | p202610 | RANGE | `ym` | 202611 | 3027 | | p202611 | RANGE | `ym` | 202612 | 0 | | p202612 | RANGE | `ym` | 202701 | 0 | | pmax | RANGE | `ym` | MAXVALUE | 0 | +----------------+------------------+----------------------+-----------------------+------------+ 6 rows in set (0.01 sec)
goldendb_client> EXPLAIN SELECT * FROM orders_single_group WHERE ym = 202610 \G *************************** 1. row *************************** ID: 10001 SELECT_TYPE: SQLNode TABLE: 1 PARTITIONS: TYPE: POSSIBLE_KEYS: SELECT `test`.`orders_single_group`.`order_id`,`test`.`orders_single_group`.`user_id`,`test`.`orders_single_group`.`region_code`,`test`.`orders_single_group`.`amount`,`test`.`orders_single_group`.`create_time`,`test`.`orders_single_group`.`ym`,`test`.`orders_single_group`.`gtid` as `gtid1` FROM `test`.`orders_single_group` where (`ym` = 202610) KEY: Cluster1,g1 KEY_LEN: REF: Parent=NULL,Child=NULL,Next=NULL ROWS: FILTERED: EXTRA: cr, test.orders_single_group=rep,digest=df55d9c07dd1f57d13cb4817e1d9495b *************************** 2. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: orders_single_group PARTITIONS: p202610 TYPE: ALL POSSIBLE_KEYS: NULL KEY: NULL KEY_LEN: NULL REF: NULL ROWS: 3027 FILTERED: 10.00 EXTRA: Using where, g1 2 rows in set (0.00 sec)
PARTITIONS: p202610 代表使用了分区裁剪,并只扫描了 g1 分片组。
DROP TABLE IF EXISTS orders_part_hash; CREATE TABLE orders_part_hash ( order_id INT NOT NULL, user_id INT NOT NULL, amount DECIMAL(10,2) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id, order_id) ) Partition BY HASH(user_id) DISTRIBUTED BY DUPLICATE(g2); goldendb_client> INSERT INTO orders_part_hash SELECT * FROM tb_order_hash; goldendb_client> COMMIT; goldendb_client> select count(*) from orders_part_hash storagedb g1; +----------+ | COUNT(*) | +----------+ | 0 | +----------+ 1 row in set (0.00 sec) goldendb_client> select count(*) from orders_part_hash storagedb g2; +----------+ | COUNT(*) | +----------+ | 40000 | +----------+ 1 row in set (0.20 sec) goldendb_client> select count(*) from orders_part_hash storagedb g4; +----------+ | COUNT(*) | +----------+ | 0 | +----------+ 1 row in set (0.01 sec)
goldendb_client> analyze table orders_part_hash; +-----------------------+---------+----------+----------+ | TABLE | OP | MSG_TYPE | MSG_TEXT | +-----------------------+---------+----------+----------+ | test.orders_part_hash | analyze | status | OK | +-----------------------+---------+----------+----------+ 1 row in set (0.20 sec) -- 实际上只有一个分区 goldendb_client> SELECT PARTITION_NAME, PARTITION_METHOD, PARTITION_EXPRESSION, PARTITION_DESCRIPTION, TABLE_ROWS FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'orders_part_hash' AND PARTITION_NAME IS NOT NULL ORDER BY PARTITION_ORDINAL_POSITION; +----------------+------------------+----------------------+-----------------------+------------+ | PARTITION_NAME | PARTITION_METHOD | PARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS | +----------------+------------------+----------------------+-----------------------+------------+ | p0 | HASH | `user_id` | NULL | 40152 | +----------------+------------------+----------------------+-----------------------+------------+ 1 row in set (0.01 sec)
DROP TABLE IF EXISTS orders_part_hash; CREATE TABLE orders_part_hash ( order_id INT NOT NULL, user_id INT NOT NULL, amount DECIMAL(10,2) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id, order_id) ) Partition BY HASH(user_id) PARTITIONS 4 DISTRIBUTED BY DUPLICATE(g2); goldendb_client> INSERT INTO orders_part_hash SELECT * FROM tb_order_hash; goldendb_client> COMMIT;
goldendb_client> analyze table orders_part_hash; +-----------------------+---------+----------+----------+ | TABLE | OP | MSG_TYPE | MSG_TEXT | +-----------------------+---------+----------+----------+ | test.orders_part_hash | analyze | status | OK | +-----------------------+---------+----------+----------+ 1 row in set (0.06 sec) goldendb_client> SELECT PARTITION_NAME, PARTITION_METHOD, PARTITION_EXPRESSION, PARTITION_DESCRIPTION, TABLE_ROWS FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'orders_part_hash' AND PARTITION_NAME IS NOT NULL ORDER BY PARTITION_ORDINAL_POSITION; +----------------+------------------+----------------------+-----------------------+------------+ | PARTITION_NAME | PARTITION_METHOD | PARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS | +----------------+------------------+----------------------+-----------------------+------------+ | p0 | HASH | `user_id` | NULL | 9979 | | p1 | HASH | `user_id` | NULL | 10055 | | p2 | HASH | `user_id` | NULL | 10025 | | p3 | HASH | `user_id` | NULL | 9941 | +----------------+------------------+----------------------+-----------------------+------------+ 4 rows in set (0.01 sec)
goldendb_client> EXPLAIN SELECT * FROM orders_part_hash WHERE user_id = 1000\G *************************** 1. row *************************** ID: 10001 SELECT_TYPE: SQLNode TABLE: 1 PARTITIONS: TYPE: POSSIBLE_KEYS: SELECT `test`.`orders_part_hash`.`order_id`,`test`.`orders_part_hash`.`user_id`,`test`.`orders_part_hash`.`amount`,`test`.`orders_part_hash`.`create_time`,`test`.`orders_part_hash`.`gtid` as `gtid1` FROM `test`.`orders_part_hash` where (`user_id` = 1000) KEY: Cluster1,g2 KEY_LEN: REF: Parent=NULL,Child=NULL,Next=NULL ROWS: FILTERED: EXTRA: cr, test.orders_part_hash=rep,digest=cef8764fed61cc1abfd00932c917df73 *************************** 2. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: orders_part_hash PARTITIONS: p0 TYPE: ref POSSIBLE_KEYS: PRIMARY KEY: PRIMARY KEY_LEN: 4 REF: const ROWS: 1 FILTERED: 100.00 EXTRA: NULL, g2 2 rows in set (0.00 sec) goldendb_client> EXPLAIN SELECT * FROM orders_part_hash WHERE user_id = 1001\G *************************** 1. row *************************** ID: 10001 SELECT_TYPE: SQLNode TABLE: 1 PARTITIONS: TYPE: POSSIBLE_KEYS: SELECT `test`.`orders_part_hash`.`order_id`,`test`.`orders_part_hash`.`user_id`,`test`.`orders_part_hash`.`amount`,`test`.`orders_part_hash`.`create_time`,`test`.`orders_part_hash`.`gtid` as `gtid1` FROM `test`.`orders_part_hash` where (`user_id` = 1001) KEY: Cluster1,g2 KEY_LEN: REF: Parent=NULL,Child=NULL,Next=NULL ROWS: FILTERED: EXTRA: cr, test.orders_part_hash=rep,digest=cef8764fed61cc1abfd00932c917df73 *************************** 2. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: orders_part_hash PARTITIONS: p1 TYPE: ref POSSIBLE_KEYS: PRIMARY KEY: PRIMARY KEY_LEN: 4 REF: const ROWS: 1 FILTERED: 100.00 EXTRA: NULL, g2 2 rows in set (0.01 sec) goldendb_client> -- partitions: p1 goldendb_client> goldendb_client> EXPLAIN SELECT * FROM orders_part_hash WHERE user_id = 1002\G *************************** 1. row *************************** ID: 10001 SELECT_TYPE: SQLNode TABLE: 1 PARTITIONS: TYPE: POSSIBLE_KEYS: SELECT `test`.`orders_part_hash`.`order_id`,`test`.`orders_part_hash`.`user_id`,`test`.`orders_part_hash`.`amount`,`test`.`orders_part_hash`.`create_time`,`test`.`orders_part_hash`.`gtid` as `gtid1` FROM `test`.`orders_part_hash` where (`user_id` = 1002) KEY: Cluster1,g2 KEY_LEN: REF: Parent=NULL,Child=NULL,Next=NULL ROWS: FILTERED: EXTRA: cr, test.orders_part_hash=rep,digest=cef8764fed61cc1abfd00932c917df73 *************************** 2. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: orders_part_hash PARTITIONS: p2 TYPE: ref POSSIBLE_KEYS: PRIMARY KEY: PRIMARY KEY_LEN: 4 REF: const ROWS: 1 FILTERED: 100.00 EXTRA: NULL, g2 2 rows in set (0.01 sec)
user id 为 1000 时查询是 p0 分区; userid 为 1001 时查询是 p1 分区; user_id 为 1002 时查询是 p2 分区,
因为 user_id 经过哈希运算:
WHERE user_id = 1000 → 1000 % 4 = 0 → p0
WHERE user_id = 1001 → 1 → p1
WHERE user_id = 1002 → 2 → p2
CREATE TABLE orders_part_list (
order_id INT NOT NULL,
user_id INT NOT NULL,
region_code VARCHAR(10) NOT NULL,
amount DECIMAL(10,2) DEFAULT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (order_id,region_code)
) Partition BY LIST (region_code) (
PARTITION p_huabei VALUES IN ('BJ','TJ','HEB'),
PARTITION p_huadong VALUES IN ('SH','HZ','NJ','WH'),
PARTITION p_huanan VALUES IN ('GZ','SZ','XA','CD')
)
DISTRIBUTED BY DUPLICATE(g4);
goldendb_client> insert into orders_part_list select * from tb_order_list;
goldendb_client> commit;
goldendb_client> select count(*) from orders_part_list storagedb g1;
+----------+
| COUNT(*) |
+----------+
| 0 |
+----------+
1 row in set (0.00 sec)
goldendb_client> select count(*) from orders_part_list storagedb g2;
+----------+
| COUNT(*) |
+----------+
| 0 |
+----------+
1 row in set (0.00 sec)
goldendb_client> select count(*) from orders_part_list storagedb g4;
+----------+
| COUNT(*) |
+----------+
| 30000 |
+----------+
1 row in set (0.13 sec)
goldendb_client> analyze table orders_part_list; +-----------------------+---------+----------+----------+ | TABLE | OP | MSG_TYPE | MSG_TEXT | +-----------------------+---------+----------+----------+ | test.orders_part_list | analyze | status | OK | +-----------------------+---------+----------+----------+ goldendb_client> SELECT PARTITION_NAME, PARTITION_METHOD, PARTITION_EXPRESSION, PARTITION_DESCRIPTION, TABLE_ROWS FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'orders_part_list' AND PARTITION_NAME IS NOT NULL ORDER BY PARTITION_ORDINAL_POSITION; +----------------+------------------+----------------------+-----------------------+------------+ | PARTITION_NAME | PARTITION_METHOD | PARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS | +----------------+------------------+----------------------+-----------------------+------------+ | p_huabei | LIST | `region_code` | 'BJ','TJ','HEB' | 8070 | | p_huadong | LIST | `region_code` | 'SH','HZ','NJ','WH' | 10827 | | p_huanan | LIST | `region_code` | 'GZ','SZ','XA','CD' | 11103 | +----------------+------------------+----------------------+-----------------------+------------+ 3 rows in set (0.00 sec)
goldendb_client> EXPLAIN SELECT * FROM orders_part_list WHERE region_code = 'BJ'\G *************************** 1. row *************************** ID: 10001 SELECT_TYPE: SQLNode TABLE: 1 PARTITIONS: TYPE: POSSIBLE_KEYS: SELECT `test`.`orders_part_list`.`order_id`,`test`.`orders_part_list`.`user_id`,`test`.`orders_part_list`.`region_code`,`test`.`orders_part_list`.`amount`,`test`.`orders_part_list`.`create_time`,`test`.`orders_part_list`.`gtid` as `gtid1` FROM `test`.`orders_part_list` where (`region_code` = 'BJ') KEY: Cluster1,g4 KEY_LEN: REF: Parent=NULL,Child=NULL,Next=NULL ROWS: FILTERED: EXTRA: cr, test.orders_part_list=rep,digest=a11c25763f3cae4a6542465d743661ac *************************** 2. row *************************** ID: 1 SELECT_TYPE: SIMPLE TABLE: orders_part_list PARTITIONS: p_huabei TYPE: ALL POSSIBLE_KEYS: NULL KEY: NULL KEY_LEN: NULL REF: NULL ROWS: 8070 FILTERED: 10.00 EXTRA: Using where, g4 2 rows in set (0.01 sec)
PARTITIONS: p_huabei 发现可以使用分区裁剪。
本文首先介绍了
MyCat 传统中间件 +
MySQL 数据库 与
GoldenDB 分布式数据库的主要区别,传统
MyCat 架构属于“拼装”逻辑,中间件与底层 MySQL 相互割裂;而
GoldenDB 实现了计算层(CN)与存储层(DN)的
融合。它支持
将逻辑分区直接映射到物理分片组(
DBGroup),在 SQL 解析的瞬间即可完成“分布式分片裁剪 + 节点内分区裁剪”的双重加速。
其次,介绍了 HASH、RANGE、LIST、DUPLICATE 及 组合分片 五大规则(
DISTRIBUTED BY)和 RANGE、LIST、HASH三种分区(
PARTITION BY)方式。
DISTRIBUTED BY)—— 水平维度:决定数据分散到哪一个
DBGroup(物理主备集群);PARTITION BY) —— 垂直维度:在单个
DBGroup 的存储引擎层,按规则将数据文件进一步切分为多个物理段,实现单节点内的数据隔离与裁剪优化。最后,生产中需注意以下三点:
DISTRIBUTED BY 默认为复制表:每个分片组上存放一份完整的数据;对于海量流水表,漏写分片键会导致磁盘空间暴涨。p0 分区文件。COMMIT 时,元数据视图中的
TABLE_ROWS 往往会显示为
0 或失真。必须在跑批或执行计划调优前,显式执行
ANALYZE TABLE 。
GoldenDB 的分片和分区介绍到这里。这里是《实战派K8S&DB》,更多干货敬请关注公众号。如果本文对你有所帮助,欢迎点赞、推荐和转发,也欢迎关注后续文章,一起考证、一起学习
GoldenDB 分布式数据库。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31444449/viewspace-3184719/ ,如需转载,请注明出处,否则将追究法律责任。