MySQL之所以普及,并不单单是在服务器市场,更广阔的是单机的市场,也就是线下客户端,应用和数据库部署在一台终端上,而随着MySQL各种各样的“问题”,线下的客户端,单机使用的数据库可选项好像只有PG,再或者Sqlite。
一些国有的项目中,要求必须使用国产数据库产品,之前写过sqlite的一些研究,可sqlite只是一个非常简单的数据库产品,并不是一个可以支持复杂的读写并发的数据库产品。
市场急需一个可以替代MySQL兼容性的国产数据库产品。正好在此时seekdb出来了,我并没有根据他的一些介绍,把他高大上化,恰恰相反seekdb来做一些,低端的,特别接地气的事情。
替换mysql数据库线下的单机产品,所以此次我开始研究seekdb,看看他是否可以作为替换线下单机的MySQL的国产数据库产品。
目前seekdb 支持红帽或类似红帽的操作系统,版本是 7 和 9,跳过了8.不过大部分线下的LINUX系统还是在使用centos7类似的部分。同时更广阔的终端市场是WINDOWS的天下,装机量超大。
所以此次采用centos 7 来作为此次的测试系统,下面是一组实验和测试的截图和代码

seekdb

seekdb

安装

客户端登录
安装很简单,直接rpm包安装就可以了,登录页很简单,obclient -h 127.0.0.1 -P 2881 -u root@sys 只需要一条命令
[root@seekdb ~]# obclient -h 127.0.0.1 -P 2881 -u root@sys
Welcome to the OceanBase. Commands end with ; or \g.
Your OceanBase connection id is 3221488349
Server version: OceanBase 4.3.5.3 SeekDB (r1.0.0.0) (Built 100000262025111218-5343637512e28c346f938516af53b7879d4d5974 Nov 12 2025)
Copyright (c) 2000, 2018, OceanBase and/or its affiliates. All rights reserved.
Type 'help;' or '\h'forhelp. Type '\c' to clear the current input statement.
obclient(root@sys)[(none)]> CREATE DATABASE IF NOT EXISTS seekdb_demo;
Query OK, 1 row affected (0.871 sec)
obclient(root@sys)[(none)]> USE seekdb_demo;
Database changed
obclient(root@sys)[seekdb_demo]> CREATE TABLE customers (
-> customer_id BIGINT PRIMARY KEY AUTO_INCREMENT,
-> customer_name VARCHAR(100) NOT NULL,
-> email VARCHAR(100),
-> phone VARCHAR(30),
-> city VARCHAR(50),
-> created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-> ) COMMENT='客户表';
Query OK, 0 rows affected (0.883 sec)
obclient(root@sys)[seekdb_demo]> CREATE TABLE products (
-> product_id BIGINT PRIMARY KEY AUTO_INCREMENT,
-> product_name VARCHAR(100) NOT NULL,
-> category VARCHAR(50),
-> price DECIMAL(10,2) NOT NULL,
-> created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-> ) COMMENT='商品表';
Query OK, 0 rows affected (1.190 sec)
obclient(root@sys)[seekdb_demo]> CREATE TABLE orders (
-> order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
-> customer_id BIGINT NOT NULL,
-> order_date DATETIME NOT NULL,
-> total_amount DECIMAL(12,2) NOT NULL,
-> status VARCHAR(20) DEFAULT 'NEW',
-> KEY idx_customer_date (customer_id, order_date)
-> ) COMMENT='订单表';
Query OK, 0 rows affected (1.487 sec)
obclient(root@sys)[seekdb_demo]> CREATE TABLE order_items (
-> item_id BIGINT PRIMARY KEY AUTO_INCREMENT,
-> order_id BIGINT NOT NULL,
-> product_id BIGINT NOT NULL,
-> quantity INT NOT NULL,
-> unit_price DECIMAL(10,2) NOT NULL,
-> KEY idx_order (order_id)
-> ) COMMENT='订单明细表';
Query OK, 0 rows affected (1.278 sec)
obclient(root@sys)[seekdb_demo]> INSERT INTO customers (customer_name, email, city) VALUES
-> ('Alice', 'alice@test.com', 'Beijing'),
-> ('Bob', 'bob@test.com', 'Shanghai'),
-> ('Carol', 'carol@test.com', 'Shenzhen');
Query OK, 3 rows affected (0.239 sec)
Records: 3 Duplicates: 0 Warnings: 0
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]> INSERT INTO products (product_name, category, price) VALUES
-> ('Laptop', 'Electronics', 8000.00),
-> ('Phone', 'Electronics', 5000.00),
-> ('Desk', 'Furniture', 1200.00);
Query OK, 3 rows affected (0.055 sec)
Records: 3 Duplicates: 0 Warnings: 0
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]> INSERT INTO orders (customer_id, order_date, total_amount, status) VALUES
-> (1, NOW(), 13000.00, 'PAID'),
-> (2, NOW(), 5000.00, 'NEW');
Query OK, 2 rows affected (0.081 sec)
Records: 2 Duplicates: 0 Warnings: 0
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]> INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES
-> (1, 1, 1, 8000.00),
-> (1, 2, 1, 5000.00),
-> (2, 2, 1, 5000.00);
Query OK, 3 rows affected (0.038 sec)
Records: 3 Duplicates: 0 Warnings: 0
obclient(root@sys)[seekdb_demo]> SELECT * FROM customers;
+-------------+---------------+----------------+-------+----------+---------------------+
| customer_id | customer_name | email | phone | city | created_at |
+-------------+---------------+----------------+-------+----------+---------------------+
| 1 | Alice | alice@test.com | NULL | Beijing | 2025-12-18 15:57:20 |
| 2 | Bob | bob@test.com | NULL | Shanghai | 2025-12-18 15:57:20 |
| 3 | Carol | carol@test.com | NULL | Shenzhen | 2025-12-18 15:57:20 |
+-------------+---------------+----------------+-------+----------+---------------------+
3 rows inset (0.021 sec)
obclient(root@sys)[seekdb_demo]> SELECT * FROM orders WHERE status = 'PAID';
+----------+-------------+---------------------+--------------+--------+
| order_id | customer_id | order_date | total_amount | status |
+----------+-------------+---------------------+--------------+--------+
| 1 | 1 | 2025-12-18 15:57:20 | 13000.00 | PAID |
+----------+-------------+---------------------+--------------+--------+
1 row inset (0.013 sec)
obclient(root@sys)[seekdb_demo]> explain SELECT
-> o.order_id,
-> c.customer_name,
-> o.total_amount,
-> o.status
-> FROM orders o
-> JOIN customers c ON o.customer_id = c.customer_id;
+-----------------------------------------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------------------------------------+
| =================================================== |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| --------------------------------------------------- |
| |0 |MERGE JOIN | |2 |6 | |
| |1 |├─TABLE FULL SCAN |c |3 |3 | |
| |2 |└─SORT | |2 |3 | |
| |3 | └─TABLE FULL SCAN|o |2 |3 | |
| =================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([o.order_id], [c.customer_name], [o.total_amount], [o.status]), filter(nil), rowset=16 |
| equal_conds([o.customer_id = c.customer_id]), other_conds(nil) |
| merge_directions([ASC]) |
| 1 - output([c.customer_id], [c.customer_name]), filter(nil), rowset=16 |
| access([c.customer_id], [c.customer_name]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([c.customer_id]), range(MIN ; MAX)always true |
| 2 - output([o.order_id], [o.total_amount], [o.status], [o.customer_id]), filter(nil), rowset=16 |
| sort_keys([o.customer_id, ASC]) |
| 3 - output([o.order_id], [o.customer_id], [o.total_amount], [o.status]), filter(nil), rowset=16 |
| access([o.order_id], [o.customer_id], [o.total_amount], [o.status]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([o.order_id]), range(MIN ; MAX)always true |
+-----------------------------------------------------------------------------------------------------+
23 rows inset (0.162 sec)
obclient(root@sys)[seekdb_demo]> explain SELECT
-> c.city,
-> SUM(o.total_amount) AS city_sales
-> FROM orders o
-> JOIN customers c ON o.customer_id = c.customer_id
-> GROUP BY c.city;
+-----------------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------------+
| ===================================================== |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| ----------------------------------------------------- |
| |0 |HASH GROUP BY | |2 |6 | |
| |1 |└─MERGE JOIN | |2 |5 | |
| |2 | ├─TABLE FULL SCAN |c |3 |3 | |
| |3 | └─SORT | |2 |3 | |
| |4 | └─TABLE FULL SCAN|o |2 |3 | |
| ===================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([c.city], [T_FUN_SUM(o.total_amount)]), filter(nil), rowset=16 |
| group([c.city]), agg_func([T_FUN_SUM(o.total_amount)]) |
| 1 - output([c.city], [o.total_amount]), filter(nil), rowset=16 |
| equal_conds([o.customer_id = c.customer_id]), other_conds(nil) |
| merge_directions([ASC]) |
| 2 - output([c.customer_id], [c.city]), filter(nil), rowset=16 |
| access([c.customer_id], [c.city]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([c.customer_id]), range(MIN ; MAX)always true |
| 3 - output([o.customer_id], [o.total_amount]), filter(nil), rowset=16 |
| sort_keys([o.customer_id, ASC]) |
| 4 - output([o.customer_id], [o.total_amount]), filter(nil), rowset=16 |
| access([o.customer_id], [o.total_amount]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([o.order_id]), range(MIN ; MAX)always true |
+-----------------------------------------------------------------------------+
26 rows inset (0.080 sec)
obclient(root@sys)[seekdb_demo]> EXPLAIN SELECT * FROM orders WHERE customer_id = 1;
+--------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan |
+--------------------------------------------------------------------------------------------------------------------------------------------+
| ===================================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| --------------------------------------------------------------------- |
| |0 |TABLE RANGE SCAN|orders(idx_customer_date)|1 |7 | |
| ===================================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([orders.order_id], [orders.customer_id], [orders.order_date], [orders.total_amount], [orders.status]), filter(nil), rowset=16 |
| access([orders.order_id], [orders.customer_id], [orders.order_date], [orders.total_amount], [orders.status]), partitions(p0) |
| is_index_back=true, is_global_index=false, |
| range_key([orders.customer_id], [orders.order_date], [orders.order_id]), range(1,MIN,MIN ; 1,MAX,MAX), |
| range_cond([orders.customer_id = 1]) |
+--------------------------------------------------------------------------------------------------------------------------------------------+
12 rows inset (0.031 sec)
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]> CREATE INDEX idx_orders_status ON orders(status);
Query OK, 0 rows affected (2.807 sec)
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]>
obclient(root@sys)[seekdb_demo]> EXPLAIN SELECT * FROM orders WHERE status = 'PAID';
+--------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan |
+--------------------------------------------------------------------------------------------------------------------------------------------+
| ===================================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| --------------------------------------------------------------------- |
| |0 |TABLE RANGE SCAN|orders(idx_orders_status)|1 |7 | |
| ===================================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([orders.order_id], [orders.customer_id], [orders.order_date], [orders.total_amount], [orders.status]), filter(nil), rowset=16 |
| access([orders.order_id], [orders.status], [orders.customer_id], [orders.order_date], [orders.total_amount]), partitions(p0) |
| is_index_back=true, is_global_index=false, |
| range_key([orders.status], [orders.order_id]), range(PAID,MIN ; PAID,MAX), |
| range_cond([orders.status = 'PAID']) |
+--------------------------------------------------------------------------------------------------------------------------------------------+
12 rows inset (0.162 sec)
obclient(root@sys)[seekdb_demo]> SELECT
-> o.order_id,
-> c.customer_name,
-> o.total_amount,
-> o.status
-> FROM orders o
-> JOIN customers c ON o.customer_id = c.customer_id;
+----------+---------------+--------------+--------+
| order_id | customer_name | total_amount | status |
+----------+---------------+--------------+--------+
| 1 | Alice | 13000.00 | PAID |
| 2 | Bob | 5000.00 | NEW |
+----------+---------------+--------------+--------+
2 rows inset (0.044 sec)
obclient(root@sys)[seekdb_demo]> explain SELECT
-> o.order_id,
-> c.customer_name,
-> o.total_amount,
-> o.status
-> FROM orders o
-> JOIN customers c ON o.customer_id = c.customer_id;
+-----------------------------------------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------------------------------------+
| =================================================== |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| --------------------------------------------------- |
| |0 |MERGE JOIN | |2 |6 | |
| |1 |├─TABLE FULL SCAN |c |3 |3 | |
| |2 |└─SORT | |2 |3 | |
| |3 | └─TABLE FULL SCAN|o |2 |3 | |
| =================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([o.order_id], [c.customer_name], [o.total_amount], [o.status]), filter(nil), rowset=16 |
| equal_conds([o.customer_id = c.customer_id]), other_conds(nil) |
| merge_directions([ASC]) |
| 1 - output([c.customer_id], [c.customer_name]), filter(nil), rowset=16 |
| access([c.customer_id], [c.customer_name]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([c.customer_id]), range(MIN ; MAX)always true |
| 2 - output([o.order_id], [o.total_amount], [o.status], [o.customer_id]), filter(nil), rowset=16 |
| sort_keys([o.customer_id, ASC]) |
| 3 - output([o.order_id], [o.customer_id], [o.total_amount], [o.status]), filter(nil), rowset=16 |
| access([o.order_id], [o.customer_id], [o.total_amount], [o.status]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([o.order_id]), range(MIN ; MAX)always true |
+-----------------------------------------------------------------------------------------------------+
23 rows inset (0.052 sec)
这里我们稍微比较一下上面的执行计划,与MYSQL8的执行语句的方式比一下看看有什么差异。数据量比较小,这里只是简单的比较差异
1 SEEKDB 使用了merge join ,这个JOIN 的方法在,mysql 是没有的,MYSQL 目前只有两种多表连接的方式, Nested loop 和 hash join。
2 explain SELECT -> o.order_id, -> c.customer_name, -> o.total_amount, -> o.status -> FROM orders o -> JOIN customers c ON o.customer_id = c.customer_id;
在seekdb 为了连接针对ORDERS 进行了排序的处理,而这个部分在MYSQL 你是想都不要想,从这里简单的看出OCEANBASE的SQL优化执行体系和MYSQL完全不一样,是用心的。
3 执行计划的输出,这个也是非常让人惊喜的,和MYSQL的explain的执行计划粗陋不堪,SEEKDB的执行计划,怎么这么好看。
写到这里,后续还得做测试,对于大一点的数据,基于sstable 和 BTREE 的Oceanbase seekdb 和 Mysql数据处理速度的不同,如果OB 快,那么MYSQL的死刑执行日,也就快了。
最后解释一下题目,线下的终端市场一直是很多数据库厂商不注意的地方,大量的MySQL,PostgreSQL在这一个领域,上亿的装机量那一定是有的,如果是SQLite几十亿的装机量都是有的。
SeekDB 名义是AI数据库,其实我看,线下的MYSQL POSTGRESQL 的市场才是要被侵害的,甚至SQLite 一部分市场,因为SEEKDB的功能更强,安装简单,使用方便,也会被终端体系所接受,国外垄断线下的终端数据库市场的体系可能就此瓦解,加油SEEKDB,备不住你就是第二个线下终端数据库的王者。(嵌入式,终端设备,工业数据库小型终端,小型智能AI数据库等等)

本文分享自 AustinDatabases 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!