跳至主要內容

KlustronDB 的概要和优势

Klustron大约 33 分钟

KlustronDB 的概要和优势

泽拓昆仑KlustronDB 是一个同时兼容MySQL和PostgreSQL的分布式数据库系统,是增强型的 MySQL和PostgreSQL。通过利用多台服务器的计算和存储资源,KlustronDB 可以管理的数据规模和数据访问负载大幅超越了 MySQL和PostgreSQL,可以按需持续增长;同时应用软件系统与KlustronDB 的交互方法与MySQL和PostgreSQL数据库完全相同,并且仍然能够提供完善的事务 ACID ,故障自动恢复和数据一致性保障。 支撑如此简洁优雅的用法和健壮性的,是KlustronDB研发团队设计和研发的大量先进技术,包括数据自动分区,水平弹性扩容能力,分布式事务处理和分布式并行查询处理能力,完全一致性的主备复制,分布式事务故障恢复,全局一致性视图(Global MVCC)等等。

KlustronDB 解决海量数据 存储、管理和分析、利用的系列技术挑战(如下所列),支撑高并发高负载的在线事务处理(OLTP),提供高吞吐率和低延时的极致性能。在关系数据模型基础上,KlustronDB 一站式支持GIS, JSON, 文本,向量(vector) 数据管理和查询检索,极大地简化应用系统架构设计和研发复杂度,大幅降低后台系统运维复杂度和硬件资源开销,提供与上层应用系统和其他数据处理组件的标准的互操作性和兼容性,帮助用户实现可插拔的标准化组件式的系统架构。

快速开始使用KlustronDB

对于应用系统开发者来说,KlustronDB 的用法与 MySQL 和 PostgreSQL 数据库完全一样。 KlustronDB 支持 JDBC,ODBC,Hibernate,MyBatis 以及所有常见编程语言的客户端连接库,所有这些语言编写的软件都可以连接到 KlustronDB 并正确执行所有符合标准的 SQL 语句,以及 MySQL 和 PostgreSQL 私有的 DML SQL语句,因此原本使用 MySQL 和 PostgreSQL 的应用软件可以不做任何修改就使用 KlustronDB。

连接和初始化

首先可以使用psql或者mysql 程序、JDBC/ODBC 以及各种编程语言的PostgreSQL和MySQL 连接库来连接KlustronDB,方法与参数与连接和使用 PostgreSQL和MySQL 相同。通过XPanel安装一个KlustronDB集群期间,用户可以设置KlustronDB计算节点的默认用户名和密码。如果没有设置那么默认是abc/abc。

$ ./psql -h192.168.12.34 -p23456 -Uabc postgres
用户 abc 的口令:

每个KlustronDB 集群安装好之后,在计算节点中默认创建的数据库名称也是postgres,这也是KlustronDB开发团队对PostgreSQL的致敬。然后用户可以创建更多的数据库用于各类业务系统。比如本例中我们创建 KunlunDB 并且使用它用于功能讲解。

KlustronDB 支持并扩展了MySQL风格的SHOW 系列命令,可以使用SHOW DATABASES 语句查询当前集群中有哪些database。也可以查询 pg_database 等元数据表来查询更详细的系统元数据信息。

PostgreSQL的所有catalog中的系统表在KlustronDB中仍然存在并且绝大部分仍然有效使用,并且KlustronDB中新增了更多的系统表用于KlustronDB的相关功能。用户不可以直接修改这些系统catalog表的数据,执行DDL会间接修改其中一些表的数据。

postgres=# show databases;
 Database
-----------
 postgres
 template1
 template0
(3 行记录)

上面的列表中postgres 是默认创建和可用的database,不要使用template0/1, 这两个是用于创建更多database的模板。

MySQL用户需要注意的是,每个数据库连接只能操作和访问一个database中的数据,不可以访问其他database中的数据。因此必须退出当前连接重新连接到KunlunDB 这个database才可以在其中执行DDL/DML SQL语句。这也是继承自PostgreSQL的行为,这样可以实现最彻底的数据隔离和安全性,在多租户模式下特别重要。

MySQL用户所熟悉的database,对应于PostgreSQL和KlustronDB的schema,也就是名字空间,MySQL简化了database的概念,将其与schema完全等价,这样做对于规模更大的应用系统来说是不足的。 在PostgreSQL和KlustronDB中,一个database中可以有多个schema ,下面马上会介绍到。

postgres=# create database KunlunDB;
CREATE DATABASE
postgres=# quit

退出postgres 数据库的连接,然后连接KunlunDB 这个database。注意KlustronDB沿用了PostgreSQL 对各类名称的大小写规则 --- 大小写敏感并且创建时统一转为小写。因此虽然指定了database名称为KunlunDB,实际要使用kunlundb 才能找到这个database。

$ ./psql -h192.168.12.34 -p23456 -Uabc KunlunDB
用户 abc 的口令:

psql: 错误: FATAL:  database "KunlunDB" has disappeared from pg_database
描述:  Database OID 1188114 now seems to belong to "kunlundb".

$ ./psql -h192.168.12.34 -p23456 -Uabc kunlundb
用户 abc 的口令:

使用MySQL风格 SHOW SCHEMAS 列出当前database中所有的schema。这些schema中public 是每个database默认创建的给用户使用的schema,其他几个都是系统使用的schema,其中含有KlustronDB 系统的元数据表和视图。不建议用户在这些系统使用的schema中创建数据库对象,用户也不可以直接修改这些系统schema中的任何数据。

kunlundb=# show schemas;
      Schemas
--------------------
 pg_toast
 pg_temp_1
 pg_toast_temp_1
 pg_catalog
 public
 information_schema
(6 行记录)

对于KlustronDB和PostgreSQL来说,schema 就是名字空间,同一个database里面可以有多个schema,这些schema里面可以有同名的数据库对象(即table, index, procedure, function, view, materialized view, sequence 等)名称而不会发生名字冲突,比如 schema_1.table_1schema_2.table_1 是两个不同的表,查询时应该写全名,比如 select * from schema_1.table_1 。如果不指定全名,比如 select * from table_1 , 那么会优先在当前schema里面寻找表名。

kunlundb=# create schema ecom;
CREATE SCHEMA
kunlundb=# use ecom;
USE

每次连接到KlustronDB后,默认的当前schema就是public。只要当前数据库用户具备所需的权限,那么可以在一个数据库连接中随意切换到当前database中的任何 schema,以及同时访问其中任意schema中的数据。如果要切换当前schema到其他schema就如上使用 USE 语句,这也是KlustronDB为了MySQL 兼容性而增加的语法。

用MySQL连接时可以指定连接上去后的当前schema,比如 ./mysql -h192.168.12.34 -P23457 -uabc -pabc kunlundb.ecom 就会连接到 kunlundb 这个database里面的 ecom 这个schema。同时为了MySQL兼容性,默认在KlustronDB的MySQL连接中,执行 CREATE/DROP DATABASE dbnameSHOW DATABASES 就相当于执行了 CREATE/DROP SCHEMA dbnameSHOW SCHEMAS,从而符合MySQL用户对于database的预期--- 可以来回去切换,并且可以在同一个连接中访问所有database。

当然,也可以使用PostgreSQL的方式来设置搜索路径(SET SEARCH_PATH)。KlustronDB遵循与PostgreSQL完全相同的名字寻找规则,所以实际搜索时,优先在当前schema中寻找名称,找不到的话按照search_path 变量的schema序列,依次在后面的schema中寻找。

注意一个KlustronDB集群的每个计算节点都会监听一对相同的端口,即一个PostgreSQL端口(本例为23456)和一个MySQL协议的端口(本例为 23457),这对端口号是安装期间由KlustronDB的相关模块自动分配的。

从CREATE TABLE 开始

在KlustronDB中,一个表的数据是存储在其存储分片(storage shards)中,如果它是一个分区表,那么KlustronDB会把其分片大致均匀地分布在集群的所有shards中;如果它是一个单表,那么它就会被分配在某个shard中。

kunlundb=# use ecom;
USE
kunlundb=# CREATE TABLE products (
kunlundb(#     product_id INT PRIMARY KEY AUTO_INCREMENT,
kunlundb(#     product_name VARCHAR(100) NOT NULL,
kunlundb(#     category VARCHAR(50) NOT NULL,
kunlundb(#     price DECIMAL(10, 2) NOT NULL,
kunlundb(#     stock_quantity INT NOT NULL,
kunlundb(#     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
kunlundb(# );
CREATE TABLE

从KlustronDB-1.4 版本开始,KlustronDB支持默认hash分区,也就是在auto_hash_partitions 为正数时,无需详细指定分区方式,默认以主键为hash分区键,将表分为auto_hash_partitions 个分区。

因此上面的CREATE TABLE语句虽然没有指定任何分区方式和参数,执行SHOW CREATE TABLE 可以看到,上面的products表实际上被创建为一个HASH分区表,有4个分区。

用户总是直接使用 products表来读写数据即可,KlustronDB会自动完成数据行的映射,将其写入适当的分区,或者从适当的分区查询出来。必须仅使用总表来读写数据行,禁止直接插入数据行到表分区,因为那样做不仅繁琐而且容易出错,可能导致数据行被插入错误的分区,然后某些情况下(比如按照分区条件查询)就查询不出来了。

kunlundb=# show create table products;
  Table   |                                                         Create Table
----------+-------------------------------------------------------------------------------------------------------------------------------
 products | CREATE TABLE products (                                                                                                      +
          |  product_id integer NOT NULL,                                                                                                +
          |  product_name character varying(100) COLLATE utf8mb4_0900_bin NOT NULL,                                                      +
          |  category character varying(50) COLLATE utf8mb4_0900_bin NOT NULL,                                                           +
          |  price numeric(10,2) NOT NULL,                                                                                               +
          |  stock_quantity integer NOT NULL,                                                                                            +
          |  created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,                                                           +
          |  CONSTRAINT products_pkey PRIMARY KEY (product_id)                                                                           +
          | ) PARTITION BY HASH (product_id) WITH (engine=innodb);                                                                       +
          |                                                                                                                              +
          | ALTER TABLE products ALTER COLUMN product_id ADD AUTO_INCREMENT (                                                            +
          |   SEQUENCE NAME products_product_id_seq                                                                                      +
          |   START WITH 1                                                                                                               +
          |   INCREMENT BY 1                                                                                                             +
          |   MINVALUE 1                                                                                                                 +
          |   NO MAXVALUE                                                                                                                +
          |   CACHE 1                                                                                                                    +
          |   SHARD 1);                                                                                                                  +
          |                                                                                                                              +
          | CREATE TABLE products_auto_p0 PARTITION OF products FOR VALUES WITH (modulus 4, remainder 0) WITH (engine=innodb, shard='2');+
          |                                                                                                                              +
          | CREATE TABLE products_auto_p1 PARTITION OF products FOR VALUES WITH (modulus 4, remainder 1) WITH (engine=innodb, shard='1');+
          |                                                                                                                              +
          | CREATE TABLE products_auto_p2 PARTITION OF products FOR VALUES WITH (modulus 4, remainder 2) WITH (engine=innodb, shard='2');+
          |                                                                                                                              +
          | CREATE TABLE products_auto_p3 PARTITION OF products FOR VALUES WITH (modulus 4, remainder 3) WITH (engine=innodb, shard='1')
(1 行记录)


kunlundb=# show auto_hash_partitions;
 auto_hash_partitions
----------------------
 4
(1 行记录)

-- 使用总表做读写,不要直接读写表分区
kunlundb-# insert into products(product_name, category, price, stock_quantity) values
kunlundb-# ('iphone', 'cell phones', 500, 10000),
kunlundb-# ('xiaomi', 'cell phones', 450, 15000),
kunlundb-# ('huawei', 'cell phones', 550, 15000),
kunlundb-# ('oppo', 'cell phones', 450, 10000),
kunlundb-# ('vivo', 'cell phones', 400, 10000);

-- 使用总表做读写,不要直接读写表分区
kunlundb=# select*from products;
 product_id | product_name |  category   | price  | stock_quantity |     created_at
------------+--------------+-------------+--------+----------------+---------------------
          1 | iphone       | cell phones | 500.00 |          10000 | 2026-03-29 09:38:44
          3 | huawei       | cell phones | 550.00 |          15000 | 2026-03-29 09:38:44
          5 | vivo         | cell phones | 400.00 |          10000 | 2026-03-29 09:38:44
          2 | xiaomi       | cell phones | 450.00 |          15000 | 2026-03-29 09:38:44
          4 | oppo         | cell phones | 450.00 |          10000 | 2026-03-29 09:38:44

用下面的语句可以查询出products 表的每行位于哪个表分区,该分区位于哪个shard。

kunlundb=# select p.*, p.tableoid, c.relname, n.id as shard_id from products p, pg_shard n, pg_class c where p.tableoid=c.oid and c.relshardid=n.id;
 product_id | product_name |  category   | price  | stock_quantity |     created_at      | tableoid |     relname      | shard_id
------------+--------------+-------------+--------+----------------+---------------------+----------+------------------+----------
          1 | iphone       | cell phones | 500.00 |          10000 | 2026-03-29 09:38:44 |  1188124 | products_auto_p0 |  2
          3 | huawei       | cell phones | 550.00 |          15000 | 2026-03-29 09:38:44 |  1188130 | products_auto_p1 |  1
          5 | vivo         | cell phones | 400.00 |          10000 | 2026-03-29 09:38:44 |  1188130 | products_auto_p1 |  1
          2 | xiaomi       | cell phones | 450.00 |          15000 | 2026-03-29 09:38:44 |  1188136 | products_auto_p2 |  2
          4 | oppo         | cell phones | 450.00 |          10000 | 2026-03-29 09:38:44 |  1188142 | products_auto_p3 |  1


可以在执行CREATE TABLE之前显式修改 auto_hash_partitions 以便将一个表创建为适当数量的分区。

这个products 表的4个分区 products_auto_p0/1/2/3 分别被放置在编号是1和2的两个shard中,并且在存储节点中使用InnoDB存储引擎来存储数据。可以在CREATE TABLE 中显式设置 engine 属性来指定使用rocksdb 存储引擎,也可以(极少需要)显式指定shard 属性来存放到特定的shard,详见下文。

自定义数据存储方案

KlustronDB 支持丰富和灵活的数据存储方案,并且支持用户灵活、细粒度定制数据存储方案。本节导览相关功能和用法。除了本节所列的存储选项定制能力之外,KlustronDB还支持更多的存储选项定制功能

选择存储引擎

可以在CREATE TABLE 语句中显式设置 engine=rocksdb 属性来指定使用rocksdb 存储引擎存储表分片数据。并且可以设置column_family 实现来使用特定的column family,以便RocksDB 达到更好的性能。还可以设置default_storage_engine 配置变量(默认是'innodb',还可设置为 'rocksdb'),这样在CREATE TABLE语句中不用设置 engine属性就可使用 default_storage_engine 指定的引擎。

目前KlustronDB支持innodbrocksdb两种事务存储引擎来存储数据,RocksDB在追加型写入场景(比如时序数据、运行日志)中性能较好,而且压缩率可以达到InnoDB的10倍以上,可以大幅解约存储空间。使用RocksDB还有一些额外的注意事项需要务必遵守执行


kunlundb=# CREATE TABLE orders (
kunlundb(#     order_id INT PRIMARY KEY AUTO_INCREMENT,
kunlundb(#     customer_id INT NOT NULL,
kunlundb(#     product_id INT NOT NULL,
kunlundb(#     quantity INT NOT NULL,
kunlundb(#     order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
kunlundb(#     status enum('pending','paied','delivered','completed') NOT NULL DEFAULT 'pending',
kunlundb(#     total_amount DECIMAL(10, 2) NOT NULL
kunlundb(# ) with (engine='rocksdb', column_family='cf_orders');
CREATE TABLE
kunlundb=# show create table orders;
 Table  |                                                                    Create Table
--------+-----------------------------------------------------------------------------------------------------------------------------------------------------
 orders | CREATE TABLE orders (                                                                                                                              +
        |  order_id integer NOT NULL,                                                                                                                        +
        |  customer_id integer NOT NULL,                                                                                                                     +
        |  product_id integer NOT NULL,                                                                                                                      +
        |  quantity integer NOT NULL,                                                                                                                        +
        |  order_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP,                                                                                 +
        |  status enum_orders_status DEFAULT 'pending'::enum_orders_status NOT NULL,                                                                         +
        |  total_amount numeric(10,2) NOT NULL,                                                                                                              +
        |  CONSTRAINT orders_pkey PRIMARY KEY (order_id)                                                                                                     +
        | ) PARTITION BY HASH (order_id) WITH (engine=rocksdb, column_family=cf_orders);                                                                     +
        |                                                                                                                                                    +
        | ALTER TABLE orders ALTER COLUMN order_id ADD AUTO_INCREMENT (                                                                                      +
        |   SEQUENCE NAME orders_order_id_seq                                                                                                                +
        |   START WITH 1                                                                                                                                     +
        |   INCREMENT BY 1                                                                                                                                   +
        |   MINVALUE 1                                                                                                                                       +
        |   NO MAXVALUE                                                                                                                                      +
        |   CACHE 1                                                                                                                                          +
        |   SHARD 1);                                                                                                                                        +
        |                                                                                                                                                    +
        | CREATE TABLE orders_auto_p0 PARTITION OF orders FOR VALUES WITH (modulus 4, remainder 0) WITH (engine=rocksdb, column_family=cf_orders, shard='1');+
        |                                                                                                                                                    +
        | CREATE TABLE orders_auto_p1 PARTITION OF orders FOR VALUES WITH (modulus 4, remainder 1) WITH (engine=rocksdb, column_family=cf_orders, shard='1');+
        |                                                                                                                                                    +
        | CREATE TABLE orders_auto_p2 PARTITION OF orders FOR VALUES WITH (modulus 4, remainder 2) WITH (engine=rocksdb, column_family=cf_orders, shard='2');+
        |                                                                                                                                                    +
        | CREATE TABLE orders_auto_p3 PARTITION OF orders FOR VALUES WITH (modulus 4, remainder 3) WITH (engine=rocksdb, column_family=cf_orders, shard='2')
(1 行记录)

上面的语句中可以看到,KlustronDB支持MySQL的 enum数据类型及其语法,而且比PostgreSQL更加便捷的是,KlustronDB会自动创建这个enum 类型,即enum_orders_status,并且为这个列使用该类型。

自动加主键

如果一个用户表既没有主键也没有唯一索引的话,KlustronDB 从1.4 版本开始会为用户表自动添加主键以实现更好的性能。具体做法是增加一个自增的64位整数列 __sys_auto_rowid__ 来创建主键。上述自动分区功能会以这个自动添加的主键列作为分区列自动做HASH 分区,这种结合达到了非常惊奇的效果。


kunlundb=# create table syslogs(msg text, rcv_ts timestamp, level enum ('error', 'warning', 'info')) with (engine='rocksdb', column_family='cf_syslogs');
CREATE TABLE
kunlundb=# show create table syslogs;
  Table  |                                                                      Create Table
---------+--------------------------------------------------------------------------------------------------------------------------------------------------------
 syslogs | CREATE TABLE syslogs (                                                                                                                                +
         |  msg text COLLATE utf8mb4_0900_bin,                                                                                                                   +
         |  rcv_ts timestamp without time zone,                                                                                                                  +
         |  level enum_syslogs_level,                                                                                                                            +
         |  __sys_auto_rowid__ bigint DEFAULT "nextval"('syslogs___sys_auto_rowid___seq'::regclass) NOT NULL,                                                    +
         |  CONSTRAINT syslogs_pkey PRIMARY KEY (__sys_auto_rowid__)                                                                                             +
         | ) PARTITION BY HASH (__sys_auto_rowid__) WITH (engine=rocksdb, column_family=cf_syslogs);                                                             +
         |                                                                                                                                                       +
         | CREATE TABLE syslogs_auto_p0 PARTITION OF syslogs FOR VALUES WITH (modulus 4, remainder 0) WITH (engine=rocksdb, column_family=cf_syslogs, shard='1');+
         |                                                                                                                                                       +
         | CREATE TABLE syslogs_auto_p1 PARTITION OF syslogs FOR VALUES WITH (modulus 4, remainder 1) WITH (engine=rocksdb, column_family=cf_syslogs, shard='2');+
         |                                                                                                                                                       +
         | CREATE TABLE syslogs_auto_p2 PARTITION OF syslogs FOR VALUES WITH (modulus 4, remainder 2) WITH (engine=rocksdb, column_family=cf_syslogs, shard='2');+
         |                                                                                                                                                       +
         | CREATE TABLE syslogs_auto_p3 PARTITION OF syslogs FOR VALUES WITH (modulus 4, remainder 3) WITH (engine=rocksdb, column_family=cf_syslogs, shard='1')
(1 行记录)

除非显式指定,否则 __sys_auto_rowid__ 列不会出现在SELECT的结果中,它也无需出现在 INSERT 语句中,虽然确实可以显示设置其字段值;在UPDATE/DELETE 语句中如果需要指定特定行来更新、删除就正好可以使用__sys_auto_rowid__ 列来标识行,也可以更新该字段,只要不与其他行的该列字段冲突即可。


kunlundb=# insert into syslogs(msg) values('hello world!');
INSERT 0 1
kunlundb=# insert into syslogs(msg, rcv_ts) values('exciting things is about to happen', current_timestamp);
INSERT 0 1
kunlundb=# select*from syslogs;
                msg                 |       rcv_ts        | level
------------------------------------+---------------------+-------
 hello world!                       |                     |
 exciting things is about to happen | 2026-03-29 11:39:51 |
(2 行记录)

kunlundb=# select __sys_auto_rowid__, *from syslogs;
 __sys_auto_rowid__ |                msg                 |       rcv_ts        | level
--------------------+------------------------------------+---------------------+-------
                  1 | hello world!                       |                     |
                  2 | exciting things is about to happen | 2026-03-29 11:39:51 |
(2 行记录)


kunlundb=# update syslogs set rcv_ts=current_timestamp where __sys_auto_rowid__ = 1;
kunlundb=# select __sys_auto_rowid__, *from syslogs;
 __sys_auto_rowid__ |                msg                 |       rcv_ts        | level
--------------------+------------------------------------+---------------------+-------
                  1 | hello world!                       | 2026-03-29 11:41:11 |
                  2 | exciting things is about to happen | 2026-03-29 11:39:51 |
(2 行记录)

可以设置enable_auto_generate_pk 为FALSE 将此功能关闭,但是强烈不建议关闭。

自定义分区方式

如果不确定一个表该如何分区,那么就使用上述默认的自动的HASH分区即可,也就是不需要任何分区表设置和操作。如果要显式安排一个表的各个分区位于特定的shard,那么必须通过显式分区方式来分区,但是强烈不建议显式安排表分区到特定shard,通常用户显式安排存储分片的需求由KlustronDB的TABLE GROUP 功能来满足。

KlustronDB支持PostgreSQL的所有 3 种分区方式,除了上述HASH方式外还有RANGE 和LIST方式,这三种分区方式都可以显示指定分区列和分区参数,语法与PostgreSQL完全相同,但是可以增加KlustronDB特有的 WITH 选项来显式设置存储方案,见下文示例。

在下面的分区表语句末尾,加上 WITH 选项来指定表分区所在的shard,以及所用的存储引擎。

CREATE TABLE order_info (
    id          bigserial,
    order_no    varchar(32),
    create_time timestamp NOT NULL,
    amount      decimal(10,2),
    primary key(id, create_time)
) PARTITION BY RANGE (create_time); -- 按时间范围分区

-- 2. 创建子分区(按月分区)
-- 分区1:2025-01月数据
CREATE TABLE order_info_202501 PARTITION OF order_info
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01') with (shard=1);

-- 分区2:2025-02月数据
CREATE TABLE order_info_202502 PARTITION OF order_info
FOR VALUES FROM ('2025-02-01') TO ('2025-03-01') with (shard=2, engine='innodb');

kunlundb=# show create table order_info;
   Table    |                                                                        Create Table
------------+----------------------------------------------------------------------------------------------------------------------------
 order_info | CREATE TABLE order_info(
            |  id bigint DEFAULT "nextval"('order_info_id_seq'::regclass) NOT NULL,
            |  order_no character varying(32) COLLATE utf8mb4_0900_bin,
            |  create_time timestamp without time zone NOT NULL,
            |  amount numeric(10,2),
            |  CONSTRAINT order_info_pkey PRIMARY KEY (id, create_time)
            | ) PARTITION BY RANGE (create_time);
            | CREATE TABLE order_info_202501 PARTITION OF order_info FOR VALUES FROM ('2025-01-01 00:00:00') TO ('2025-02-01 00:00:00') WITH (engine=innodb, shard='1');
            | CREATE TABLE order_info_202502 PARTITION OF order_info FOR VALUES FROM ('2025-02-01 00:00:00') TO ('2025-03-01 00:00:00') WITH (engine=innodb, shard='2');
(1 行记录)

注意, 如果不以主键列作为分区列,那么这个分区列必须是NOT NULL的,因为一个表的所有分区列都必须出现在主键和唯一索引中。如果主键为KlustronDB自动添加,那么该主键也会自动遵循此规则,也就是KlustronDB会自动把分区列放入主键中,见下例。


CREATE TABLE user_info (
    name    varchar(32),
    region  varchar(16) NOT NULL -- 按地区分区
) PARTITION BY LIST (region);

-- 2. 创建子分区
-- 华东地区
CREATE TABLE user_info_east PARTITION OF user_info FOR VALUES IN ('上海', '江苏', '浙江', '安徽');

-- 华南地区
CREATE TABLE user_info_south PARTITION OF user_info FOR VALUES IN ('广东', '广西', '福建');

-- 默认分区(可选,存不匹配的数据)
CREATE TABLE user_info_default PARTITION OF user_info DEFAULT;

kunlundb=# show create table user_info;
   Table   |                                                            Create Table
-----------+------------------------------------------------------------------------------------------------------------------------------------
 user_info | CREATE TABLE user_info (                                                                                                          +
           |  name character varying(32) COLLATE utf8mb4_0900_bin,                                                                             +
           |  region character varying(16) COLLATE utf8mb4_0900_bin NOT NULL,                                                                  +
           |  __sys_auto_rowid__ bigint DEFAULT "nextval"('user_info___sys_auto_rowid___seq'::regclass) NOT NULL,                              +
           |  CONSTRAINT user_info_pkey PRIMARY KEY (__sys_auto_rowid__, region)                                                               +
           | ) PARTITION BY LIST (region);                                                                                                     +
           |                                                                                                                                   +
           | CREATE TABLE user_info_east PARTITION OF user_info FOR VALUES IN ('上海', '江苏', '浙江', '安徽') WITH (engine=innodb, shard='1');+
           |                                                                                                                                   +
           | CREATE TABLE user_info_south PARTITION OF user_info FOR VALUES IN ('广东', '广西', '福建') WITH (engine=rocksdb, shard='2');       +
           |                                                                                                                                   +
           | CREATE TABLE user_info_default PARTITION OF user_info DEFAULT WITH (engine=innodb, shard='2')

注意上面两个分区 user_info_east 和 user_info_south 被设置使用不同的存储引擎,KlustronDB支持这样的灵活性。在一些场景中这会非常有用,比如冷热数据混合查询。KlustronDB支持以parquet和ORC格式只读查询冷数据(不再改动的历史数据),如果需要冷热数据混合查询,即同一条SELECT 语句查询冷、热数据产生查询结果,那么可以使用KlustronDB 特有的 Parquet 存储引擎和 ORC 存储引擎来存储冷数据以便高压缩率存储,并且存储冷数据的那些shard 可以用低配置低成本的服务器;而热数据仍然使用 InnoDB或者RocksDB引擎存储和高性能服务器,这样可以在成本可控的情况下支持历史数据查询。

事务处理

KlustronDB的事务处理功能可以达到主流集中式关系数据库完全相同的ACID保障,并且事务的用法与MySQL相同,比PostgreSQL 略有不同,更加丰富。

DDL 语句与事务

首先,与MySQL一样,DDL语句不可以出现在显式事务中,而是单独作为一个语句事务来执行。PostgreSQL 用户要注意的是,出现在显式事务中的DDL语句会在开始执行之前隐式提交这个事务然后在一个单独的事务来执行DDL。于是下例中 PostgreSQL 的一个事务在KlustronDB中执行,就被切分为3个事务了。

BEGIN
INSERT INTO ...
SELECT * FROM ...
CREATE TABLE ... -- 此语句提交当前事务Txn1 然后在独立的事务Txn2 中运行并提交
INSERT INTO ... -- autocommit=off 时,新的事务Txn3隐式启动
SELECT * FROM ...
COMMIT  --- 第 Txn3个事务提交

autocommit 和隐式事务启动和提交

类似MySQL, KlustronDB也有autocommit 变量,也就是可以切换是否启用自动提交;同时也支持事务的隐式启动和隐式提交。

在一个数据库连接中执行 set autocommit=true 后,每个增删改查SQL语句都自成一个事务(即语句事务),开始执行该语句时KlustronDB系统内自动启动事务,执行完毕后自动提交该事务。也因此,执行这个设置语句时KlustronDB还会自动提交当前连接中正在运行的活跃事务(如有);

执行 set autocommit=false 后,如果当前数据库连接中没有活跃事务,那么在此连接中首次执行一个增删改查语句时,KlustronDB会隐式自动启动一个事务,在其中执行后续收到的增删改查SQL语句,直到遇到提交该事务的语句(有多种,见下节) 再提交这个事务。

由于set autocommit=true 执行之后,之后当前连接中收到的增删改查SQL语句就会自动提交,

PostgreSQL没有autocommit和隐式的事务启动与提交,所有不在显式事务中的增删改查SQL语句都是自动提交的,因此PostgreSQL 用户使用KlustronDB之后,可以得到更多的操作事务的灵活性。

隐式提交当前运行中的事务的语句

与MySQL类似,在一个数据库连接中执行下列语句,会提交其中活跃的事务(如有)。

  1. DDL语句
  2. set autocommti=true
  3. commit

错误处理和隐式回滚

当一个语句执行出错时,PostgreSQL会自动回滚其所在的事务。与PostgreSQL类似,KlustronDB 在此情况下默认也会如此处理。这与MySQL 不同。MySQL允许客户端(应用侧)决定此情况下是否回滚当前事务。为了支持MySQL的此行为,在KlustronDB中增加了enable_stmt_subxact 变量,设置其为true后,就可以实现MySQL的行为,即语句执行出错后,保留当前事务,由应用侧决定事务回滚这个事务。应用侧可以决定忽略这个错误并且继续运行并且最终提交这个事务,或者可以决定立刻或者执行完后续语句后再回滚这个事务。

全局一致的多版本并发控制(Global MVCC)

当一个SELECT 语句查询多个shard的数据时,为了保持查询结果的数据一致性,避免查询到某个正在提交的分布式事务的部分事务分支的改动同时查不到另一部分事务分支的改动,KlustronDB支持Global MVCC 也就是在一个KlustronDB集群范围内实现一个全局一直的快照。此功能少许性能代价,因此默认并不启用,在安装一个KlustronDB集群时设置是否启用 Global MVCC。启用后,可以看到配置变量 enable_global_mvcc 是TRUE。

KlustronDB 集群

下面的语句查询出当前计算节点所在的KlustronDB集群有哪些shard,每个shard有哪些节点,及其详细信息。用户禁止修改任何此类元数据信息,否则系统将无法正常工作。


kunlundb=# select*from pg_shard;
  name   | id | master_node_id | num_nodes | space_volumn | num_tablets | db_cluster_id |         when_created
---------+----+----------------+-----------+--------------+-------------+---------------+-------------------------------
 shard_2 |  1 |              2 |         3 |       311296 |          76 |             1 | 2026-02-26 13:02:43.465621+08
 shard_1 |  2 |              6 |         3 |       258048 |          63 |             1 | 2026-02-26 13:02:43.465621+08
(2 行记录)


kunlundb=# select*from pg_shard_node;
 id | port  | shard_id | svr_node_id | ro_weight | ping | latency | user_name |   hostaddr    | passwd  |         when_created          | extra
----+-------+----------+-------------+-----------+------+---------+-----------+---------------+---------+-------------------------------+-------
  2 | 22067 |        1 |           0 |        10 |    0 |       0 | pgx       | 192.168.0.136 | *** | 2026-02-26 13:02:43.465621+08 |
  5 | 22064 |        2 |           0 |        10 |    0 | 1500519 | pgx       | 192.168.0.132 | *** | 2026-02-26 13:02:43.465621+08 |
  6 | 22064 |        2 |           0 |        10 |    0 |       0 | pgx       | 192.168.0.136 | *** | 2026-02-26 13:02:43.465621+08 |
  1 | 22067 |        1 |           0 |        10 |    0 | 1303656 | pgx       | 192.168.0.132 | *** | 2026-02-26 13:02:43.465621+08 |
  4 | 22064 |        2 |           0 |        10 |    0 |       3 | pgx       | 192.168.0.125 | *** | 2026-02-26 13:02:43.465621+08 |
  3 | 22067 |        1 |           0 |        10 |    0 |  109135 | pgx       | 192.168.0.125 | *** | 2026-02-26 13:02:43.465621+08 |
(6 行记录)

下面的SQL语句查询出当前计算节点以及Metashard集群的全局元数据信息。


kunlundb=# select*from pg_cluster_meta;
 comp_node_id | cluster_id | cluster_master_id | ha_mode |       cluster_name        | comp_node_name | meta_ha_mode
--------------+------------+-------------------+---------+---------------------------+----------------+--------------
            1 |          1 |                 2 |       2 | cluster_1772081642_000001 | comp1          |            2
(1 行记录)


kunlundb=# select*from pg_cluster_meta_nodes;
 server_id | cluster_id | is_master | port  | user_name |   hostaddr    | passwd
-----------+------------+-----------+-------+-----------+---------------+---------
         1 |          1 | f         | 22061 | pgx       | 192.168.0.125 | ***
         2 |          1 | t         | 22061 | pgx       | 192.168.0.132 | ***
         3 |          1 | f         | 22061 | pgx       | 192.168.0.136 | ***
(3 行记录)

存储shard中的实际情况

下面我们看一下在每个storage shard中,数据是如何存储的。要强调的是,禁止用户直接连接存储分片(storage shard) 的节点来读写数据或者执行DDL 来创建、删除、修改KlustronDB的数据表,否则将导致KlustronDB运行异常,因为在shard中执行的语句的效果是在KlustronDB的计算节点中不可见的。

  1. databases

每个KlustronDB 的database下面的schema,都对应storage shard中的一个database。连接到存储节点可以看到,kunlundb_$$_ecomkunlundb_$$_public 对应于 kunlundb 这个database的两个schema ecompublic 的database,在该集群所有storage shard中都有这些database。

mysql> show databases;
+---------------------+
| Database            |
+---------------------+
| information_schema  |
| kunlun_sysdb        |
| kunlundb_$$_ecom    |
| kunlundb_$$_public  |
| mysql               |
| performance_schema  |
| postgres_$$_public  |
| postgres_$$_tpcc    |
| postgres_$$_tpcc2   |
| sys                 |
+---------------------+
10 rows in set (0.08 sec)
  1. tables

然后我们进入 kunlundb_$$_ecom ,可以看到其中有一部分表分区,另一部分在另一个shard中,此处不再贴出来。可以看到每个表分区在shard中是作为一个单表存在的,其表定义完美对应KlustronDB集群中对应主表的定义。


mysql> use kunlundb_$$_ecom;
Database changed
mysql> show tables;
+----------------------------+
| Tables_in_kunlundb_$$_ecom |
+----------------------------+
| order_info_202502          |
| orders_auto_p3             |
| products_auto_p0           |
| products_auto_p2           |
| syslogs_auto_p1            |
| user_info_default          |
| user_info_south            |
+----------------------------+
7 rows in set (0.04 sec)

mysql> show create table order_info_202502;
+--------------------------------------------------------------------------------------+
| Table             | Create Table                                                     |
+-------------------+------------------------------------------------------------------+
| order_info_202502 | CREATE TABLE `order_info_202502` (
  `id` bigint NOT NULL,
  `order_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,
  `create_time` datetime NOT NULL,
  `amount` decimal(10,2) DEFAULT NULL,
  PRIMARY KEY (`id`,`create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin                        |
+---------------------------------------------------------------------------------------+
1 row in set (0.04 sec)

mysql> show create table user_info_south;
+-----------------+----------------------------------------------------------------------------+
| Table           | Create Table                                                               |
+-----------------+----------------------------------------------------------------------------+
| user_info_south | CREATE TABLE `user_info_south` (
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,
  `region` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,
  `__sys_auto_rowid__` bigint NOT NULL,
  PRIMARY KEY (`__sys_auto_rowid__`,`region`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin                                |
+-----------------+-----------------------------------------------------------------------------+
1 row in set (0.06 sec)


小结

至此,你已经学会了使用KlustronDB的基本方法,对KlustronDB有了初步的了解。可以看出,KlustronDB与PostgreSQL和MySQL保持着高度的兼容性,使用方便简单,并且有其独特的水平扩展能力。KlustronDB还有更多强大而有用的功能等待你的发现和使用,希望KlustronDB 能够为你创造价值;并且期待你的反馈open in new window ,这将有助于我们持续改进KlustronDB的品质。

为什么应该从老旧系统架构迭代到KlustronDB

1. 分库分表中间件的重大缺陷

海量数据存储管理和分析利用的技术挑战包括:

  1. 单台服务器有限的计算和存储资源与数据管理规模和访问负载持续增长形成巨大矛盾;
  2. 各种软硬件故障都可能导致计算机服务器节点故障和网络故障,从而导致持续保持数据读写服务的长期可靠性和持续性以及数据持久性和一致性 是巨大的挑战和困难;
  3. 在弹性波动的数据读写负载下,保持持续稳定的高吞吐率和低延时,具有巨大的技术难度,但对于流畅顺滑的终端用户体验极其重要,因而必须做到;
  4. 应用生态的兼容性具有巨大价值,兼容现有的使用 MySQL和 PostgreSQL 两大世界级数据库的应用软件有巨大的价值。
  5. 需要使用多种数据库管理关系数据,JSON,GIS,文本,向量数据,运维负担很重,应用系统开发复杂。

为了应对第 1 类挑战,以前 MySQL 用户使用分库分表中间件或者在应用系统中实现分表逻辑,这些“土方法”有下述的一系列严重缺陷,并且无法应对第 2,3,4 类挑战。使用这些‘土方法’ ,本质上用户需要在业务系统中 case by case 实现数据管理功能,甚至是事务处理和容错功能,这对于绝大多数应用软件开发团队来说是不可能完成的任务,其应用软件系统的可靠性、稳定性、可维护性都会面临严重的问题,并且开发难度大大增加,开发周期不可控,项目延期风险大大增加,项目所需人力成本大大增加。同时也无法完成自动弹性伸缩,因为数据拆分逻辑与应用软件深度绑定。

使用 KlustronDB 可以完全地、彻底地、可靠地应对所有上述挑战!KlustronDB 把数据分片存储查询、事务处理、全局一致的视图、容灾、故障恢复等复杂性完全封装起来,应用软件开发者只需要按照集中式数据库完全相同的方法实现业务逻辑。无论需要存储和管理的数据量和在线访问负载有多大,用户(DBA,应用软件开发者和架构师)都可以把数据管理任务完全交给 KlustronDB,只需要 DBA 按需增减数据库服务器硬件,KlustronDB 即可自动完成弹性伸缩来承载这些弹性变化的负载。 这就极大地提升应用软件程序员的工作效率,极大降低其应用系统开发工作量和技术难度,确保应用软件系统的质量,稳定性和可靠性,并且大大降低项目开发周期和成本。 同时,KlustronDB 支持从所有常见的关系数据库导入全量和增量数据,方便用户随时迁移到 KlustronDB 或者从 KlustronDB 迁移到其他数据库。

KlustronDB 让程序员们可以聚焦于开发和实现应用逻辑和功能需求,完全不需要在应用系统中承担数据管理功能,大大提升其工作效率和其业务系统和产品的可靠性和用户体验,降低公司IT系统软硬件和人员成本,保障其在线业务系统的上线时间可控可预期。

下面我们具体分析一下使用应用层分库分表或者使用分库分表中间件的问题。 诸如 mysql_proxy,mysql_router,mycat 等数据行路由中间件的问题在于:

1.1 它们不支持完备的分布式查询处理

用户程序发给这些中间件的合法的 SQL 语句,只要涉及一些高级 SQL 功能,比如多表连接,子查询,CTE,window function,aggregation 等,这类中间件通常无法处理;这导致大量 SQL 生态下的工具,比如低代码工具,OR 映射中间件(例如 hibernate),机器学习算法等各种数据分析工具和算法 都无法与这些中间件交互和协作。

如果使用应用层分库分表,则应用软件程序员需要在业务代码中,分别到目标数据所在的存储集群查询数据片段,然后在业务代码中组装出最终结果。这些操作就是在应用层针对一个特定的 SQL 语句做了一次查询处理和执行。一旦需要修改“查询语句”,则需要修改大量应用代码,所以应用软件维护代价非常大。

这个工作本来可以直接发送SQL语句给分布式数据库就得到结果的,但是没有分布式数据库就只能针对每一个SQL语句针对具体查询实现一次查询处理功能,工作量自然非常巨大。特别是,这样的查询处理代码还可能因为业务逻辑的需求的变化和迭代而需要反复修改,这个开发工作量比直接修改SQL语句要大而且复杂太多了。

1.2 它们不支持可靠容灾的分布式事务处理

很多应用程序员并没有意识到分布式事务不做两阶段提交到底会有什么业务风险,处于“不知不觉”状态;少数应用程序员想到了这个潜在风险,但是无法解决,于是得过且过。

少数程序员认识到了问题并且能够解决,但也只能 case by case 地解决,比如为了实现可靠的转账功能,需要设计出一套技术在业务层实现转账场景的容灾能力。遇到其它场景又需要重新设计和实现一套算法。

这导致应用开发的技术门槛和工作量陡增,产品可靠性和稳定性有巨大风险和不确定性;项目延期风险大,开发成本高。尽管有些中间件使用了 MySQL 的 XA 功能做两阶段提交,但是无法可靠地保障在节点宕机/断网/超时等异常情况发生时确保用户数据的一致性(ACID)等属性。

上述一个共同的问题是,应用软件开发者需要知道他的每一个表到底存储在哪个存储集群中,才能做出正确的数据管理和查询功能,这让数据管理与业务逻辑产生了进一步的绑定,违背了数据库系统的初衷 --- 把数据管理完全封装起来,让应用软件开发者完全不考虑数据存储管理的任何细节。

1.3 它们无法做到自动水平弹性扩容

扩容需要 DBA 手动完成,需要暂停服务一段时间(比如若干个小时),停服会严重影响业务持续性和用户体验。

2. 石器时代的应用分库分表

业界还有一种更加原始的方法来解决数据存储规模和访问负载过大的问题——应用层数据分区。这种做法之所以更加原始,是因为它除了具有所有上述令人“头都大了”的问题之外,还有以下一系列严重的问题,以至于我们可以说这些公司的产品和服务还处于石器时代。令人吃惊的是,这样的石器时代的公司还不在少数。

应用层分表的独有问题包括:

2.1 分表逻辑硬编码

这样要针对每一个表都做相似的功能实现,开发负担和复杂性很高。特别是如果多个应用软件/web服务需要使用同一套数据表的话(这是常见情况),还需要保持所有这些程序对每一个表的分表规则相同,开发工作量和复杂性将指数上升而不仅仅是线性成倍上升。

即使做的聪明一些像上述头都大了的中间件那样使用配置文件,也仍然有问题——需要实现分表逻辑,仍然大大增加了业务开发工作量。并且到最后你就实现了一个平庸的中间件。之所以说它平庸是因为只有你们公司/团队在用,并且可能只适用于你们的特定业务场景。这又让数据管理与应用逻辑进一步紧密绑定和依赖,是非常拙劣的系统设计。

2.2 水平扩容比“头都大了”更加困难

在分库分表逻辑硬编码的情况下,弹性扩容几乎无法完成,因为你需要修改业务代码实现新的数据分区规则才能扩容,那简直是开发人员和 DBA 的噩梦。

所以,我们决定研发一款真正的分布式数据库产品,把上述“头都大了”的用户和处于“石器时代”的用户彻底解救出来,让他们来到现在这个科技时代,感受到前沿的现代科技的魅力。

从此,他们将不再绞尽脑汁设计和实现分布式数据管理和查询程序,只要简单地发送 SQL 语句即可发起和提交分布式事务,以及执行分布式查询并直接得到查询结果。

这样就真正把数据管理重新与应用软件隔离开来,把数据管理从应用逻辑中抽象出来——这正是50年前数据库理论和技术先驱们的初心,也是他们用无数人/月和美金换来的教训:

用独立的数据库系统做数据管理,把应用软件开发与通用的数据管理逻辑分离开来,达到最大程度地软件复用和应用研发简单化,大大提升开发者的工作效率和其业务逻辑与产品的可靠性,降低用户业务系统的技术门槛并大大提升其可靠性,降低公司开发成本,保障其在线业务系统的上线时间可控可预期。

3. KlustronDB 的融合能力

KlustronDB可以同时管理关系数据、JSON、向量、空间(GIS)、文本数据,在同一个数据库系统中管理所有种类的数据,可以避免同时多个数据库系统带来的跨库事务和跨库查询,极大简化应用系统架构和实现。否则横跨多个数据库的应用架构将承受类似上述分库分表架构的巨大的可靠性、稳定性、复杂性和成本缺陷,为应用系统后期维护带来长久的风险和代价。

KlustronDB 支持PostgreSQL社区广泛的插件,获得并且放大了这些插件的能力。在这些插件中,Klustron团队扩展了 PostGIS,PGVector插件,而其他插件可以直接使用社区版本源码和Klustron 头文件编译构建即可使用。KlustronDB 的PostGIS 和PGVector插件赋予其 GIS和向量数据管理能力,使其成为分布式的PostGIS和PGVector。KlustronDB 可以管理和查询JSON数据,可以提供文本数据存储检索,特别是文本与向量联合检索能力。用户可以在同一个SQL语句中使用关系数据的谓词条件,JSON 内容片段,空间位置关系,向量距离和文本关键字来查询目标数据。详见本章第五篇

下面就阅读本章主要内容,详细了解 Klustron 的架构、基本概念和技术优势,为使用 Klustron 做一个简单的准备。

一、Klustron 系统架构

二、Klustron 核心能力

三、Klustron 技术优势

四、KunlunBase FAQ

五、多数据模型融合

END