1. 背景

LTMDB SQL 内存引擎是基于 LDP MDB 存储引擎和 OpenGuass 的自主研发的内存数据库。

1.1. 术语定义

  • LTMDB: 一种 SQL 计算引擎,它集成 MDB 内存数据库作为存储层,为内存数据库提供 SQL 能力,可嵌入应用程序通过 C/C++ API 访问,也可以通过 Socket 访问(后续将支持 JNI 调用)

  • MDB:是一款内存数据库,拥有存储、索引、事务等能力

1.2. 整体结构

SQL 内存引擎与 MDB 内存数据库是集成使用的。

SQL 内存引擎是一个动态库,用户需要编写程序来使用 SQL 内存引擎中的功能。

SQL 内存引擎支持 C API 接入,与 Socket 接入;不过这些方式都需要用户编写程序来加载 MDB ,并启动 SQL 内存引擎。

_images/struct.png

链接时主要需要两个动态库:

_images/link-sight.png

2. 安装与配置

首先应获取 LTMDB 的安装包,目前需要在效能平台获取,请邮件联系 LightDB 团队,以获取安装包。

安装包解压后的目录结构为:

.
├── bin
│   ├── gaussdb
│   └── gsql
├── env_gsql.sh
├── example
│   ├── CMakeLists.txt
│   ├── demo-app
│   ├── gen
│   │   ├── codegen.py
│   │   └── tables.definition
│   ├── main.cpp
│   ├── Makefile
│   ├── mdb_test.h
│   ├── README.txt
│   └── run-example.sh
├── include
│   └── sql_engine.h
├── lib
│   ├── 其他so
│   └── libltmdb.so
├── LT_BUILD
└── share
    └── postgresql
            ├── 其他支撑文件
            └── lightdb_basedata.tar.gz

要使用 LTMDB ,可参考 example 目录,其中包含了一个使用 SQL 内存引擎 API 的示例.

example 使用前需要做一些配置:

  • 配置 UFTMDB_HOME 环境变量,指向 MDB 安装目录

  • LTMDB_HOME 指向 LTMDB 解压路径,以及对应的 PATH, LD_LIBRARY_PATH

环境变量的处理方式可参考 example/run-example.sh 。

3. C API 接入方法

SQL 内存引擎提供了 C API ,相关的 API 在 安装包/include/sql_engine.h 中定义,发布包的 example 目录中包含调用方法的示例。

3.1. 接口列表

初始化类

函数名

用途

LTsetEngineThreadCallback

设置引擎线程回调函数

LTstartengine

启动引擎

LTstopengine

停止引擎

LTsetTableActionCallback

设置增删改回调函数

连接管理类

函数名

用途

LTconnectdb

连接数据库

LTstatus

获取状态

LTerrorMessage

获取错误信息

LTfinish

结束操作

执行类

函数名

用途

LTexec

执行SQL命令

LTexecCached

执行SQL命令(支持缓存)

LTexecParams

执行带参数的SQL命令

LTexecParamsCached

执行带参数的SQL命令(支持缓存)

LTprepare

预准备SQL命令

LTexecPrepared

执行预准备的SQL命令

LTresultStatus

获取结果状态

LTresStatus

获取结果状态

LTresultErrorMessage

获取结果错误信息

LTresultErrorField

获取结果错误字段(比如错误码)

LTclear

清除结果

结果获取类

函数名

用途

LTntuples

获取元组数

LTnfields

获取字段数

LTfname

获取字段名称

LTfnumber

获取字段编号

LTgetvalue

获取字段值

LTgetlength

获取字段长度

LTgetisnull

检查字段是否为空

LTcmdTuples

获取命令影响的元组数

LTgetCacheId

获取带缓存接口的cacheId

LTftype

获取某列的类型,类型参考 sql_engine.h 中的宏定义

LTftable

获取某列对应表的 oid

LTftablecol

获取某列在对应表的顺序,从1开始

LTftableName

获取某列对应表的名字,小写返回

详细的 API 使用方法参考头文件 sql_engine.h 的注释。

3.2. SQL 接口使用示例

典型的 SQL 内存引擎 API 使用方式可参考以下代码结构:

#include <iostream>
#include <chrono>
#include <iomanip>
#include <unistd.h>

#include "uftmdb/Include/uftmdb.h"
#include "uftmdb/Include/admin.h"
#include "uftmdb/Include/uftmdb_lib.h"

#include "gen/simple_example_impl.h"
#include "gen/simple_example_2_impl.h"
#include "mdb_test.h"

#include "sql_engine.h"

static bool demo_normal()
{
    char output[OUTPUT_MESSAGE_BUFFER_SIZE];
    LTresult *res;
    LtExecStatusType status;

    // 2. 执行 SQL 前必须建立‘连接’
    LTconn *conn = LTconnectdb(NULL);

    // 3. 清空数据
    printf("准备执行 DELETE 语句 清空 simple_example 与 simple_example_2 表\n");
    res = LTexec(conn, "delete from simple_example");
    status = LTresultStatus(res);
    if (status != LTRES_COMMAND_OK)
    {
        fprintf(stderr, "DELETE simple_example 执行失败(%d):%s\n", status, LTerrorMessage(conn));
        goto error;
    }
    else
    {
        printf("DELETE simple_example 已执行\n");
    }
    printf("\n");
    LTclear(res);

    // 4. 简单 SQL
    // 4.1 INSERT
    printf("1. 准备执行 INSERT 语句\n");
    res = LTexec(conn, "insert into simple_example (id, int01, varchar01) values (1, 10001, 'Data01'), (2, 10002, 'Data02');");
    status = LTresultStatus(res);
    if (status != LTRES_COMMAND_OK)
    {
        fprintf(stderr, "INSERT 执行失败(%d):%s\n", status, LTerrorMessage(conn));
        goto error;
    }
    else
    {
        printf("INSERT 已执行\n");
    }
    printf("\n");
    LTclear(res);

    // 4.2 UPDATE
    printf("2. 准备执行 UPDATE 语句\n");
    res = LTexec(conn, "update simple_example set int01 = 1000200 where id = 2");
    status = LTresultStatus(res);
    if (status != LTRES_COMMAND_OK)
    {
        fprintf(stderr, "UPDATE 执行失败(%d):%s\n", status, LTerrorMessage(conn));
        goto error;
    }
    else
    {
        printf("UPDATE 已执行\n");
    }
    printf("\n");
    LTclear(res);

    // 4.3 DELETE
    printf("3. 准备执行 DELETE 语句\n");
    res = LTexec(conn, "delete from simple_example where id = 1");
    status = LTresultStatus(res);
    if (status != LTRES_COMMAND_OK)
    {
        fprintf(stderr, "DELETE 执行失败(%d):%s\n", status, LTerrorMessage(conn));
        goto error;
    }
    else
    {
        printf("DELETE 已执行\n");
    }
    printf("\n");
    LTclear(res);

    // 4.4 SELECT ,注意查询是 LTRES_TUPLES_OK 不是 LTRES_COMMAND_OK
    printf("4. 准备执行 SELECT 语句\n");
    res = LTexec(conn, "select * from simple_example where id in (1, 2)");
    status = LTresultStatus(res);
    if (status != LTRES_TUPLES_OK)
    {
        fprintf(stderr, "SELECT 执行失败(%d):%s\n", status, LTerrorMessage(conn));
        goto error;
    }
    else
    {
        printLTResult(res, output, OUTPUT_MESSAGE_BUFFER_SIZE);
        printf("SELECT 已执行,结果:\n%s\n", output);
    }
    printf("\n");
    LTclear(res);

    // 4.5 DELETE
    printf("5. 准备执行 DELETE 语句清空\n");
    res = LTexec(conn, "delete from simple_example where id = 2");
    status = LTresultStatus(res);
    if (status != LTRES_COMMAND_OK)
    {
        fprintf(stderr, "DELETE 执行失败(%d):%s\n", status, LTerrorMessage(conn));
        goto error;
    }
    else
    {
        printf("DELETE 已执行\n");
    }
    printf("\n");
    LTclear(res);

    // 连接回收
    LTfinish(conn);
    return true;
error:
    LTfinish(conn);
    return false;
}

int main(int argc, char *argv[])
{
    char error[ERROR_MAX_LENGTH];
    IUftmdb *uftmdb;
    int32_t errorno;

    // 初始化 UFTMDB (MDB 加载过程省略)
    errorno = init_uftmdb(&uftmdb, error, ERROR_MAX_LENGTH);
    if (errorno != 0) {
        printf("Error init_uftmdb: %s\n", error);
        return 1;
    }

    // 1. 开启 SQL 内存引擎,一个进程只能运行一次
    LtEngineConfigs *engine_configs = LTinitEngineConfig(3);
    LTsetEngineConfigBystr(engine_configs, "work_mem=64MB");
    LTsetEngineConfig(engine_configs, "shared_buffers", "64MB");
    // 定义端口
    LTsetEngineConfigBystr(engine_configs, "port=29576");
    LtEngineInitStatusType start_type = LTstartengine(uftmdb, "example-test-data", NULL, engine_configs);
    if (start_type != LT_ENGINE_OK)
    {
        printf("Start engine failed! %d\n", start_type);
        return 1;
    }
    LTreleaseEngineConfig(engine_configs);

    printf("Main start\n");

    // 在这里进行 sleep 可测试 socket 接入方式
    // sleep(100000);

    // 在 demo_normal 函数中模拟用户业务代码
    demo_normal();

    // 引擎终止,整个程序只能运行一次
    printf("退出引擎\n");
    LTstopengine();
    return 0;
}

备注

更多使用方式可参考发布物中的示例代码 example/main.cpp 。

3.3. MDB 接口使用示例

若应用程序对性能有更高的要求,可直接使用内存数据库 MDB 的 API 来操作数据,以获得更高的运行效率。

要使用 MDB 接口,有以下几个主要步骤:

  1. 事先使用 C++ 语言定义表结构

  2. 初始化 MDB 实例

  3. 加载表结构

  4. 使用事务控制器访问数据


4. Socket 接入方法

当成功调用 LTstartengine 之后,SQL 内存引擎会开启主线程,开始监听 Socket ,默认端口为 5432。 当然,如果您的主线程运行结束,或调用了 LTstopengine 之后,Socket 就会停止监听。

使用 Socket 连接的方法主要有:

  • 使用 gsql 工具连接

  • 使用 OpenGauss 提供的 jdbc 连接

  • 使用客户端工具(如 navicat, dbeaver 等)

备注

可通过 LTstartengine 传入配置项 "port=端口号" 来修改监听端口号,同时 gsql 或 jdbc 连接时也要对应修改。

4.1. gsql 连接

SQL 内存引擎的发布包中带了 gsql 工具,可使用 gsql 工具连接 SQL 内存引擎的 Socket 接口。

使用前需要先配置环境变量,包括 LTMDB_HOME, PATH, LD_LIBRARY_PATH (可使用发布包的 env_gsql.sh)。

典型连接方法: gsql -r postgres -U ltmdb -p 5432 ,成功连接后可输入 \h 查看 gsql 使用方法,或参考更详细的 网上文档

通过 gsql 可以查看 SQL 的执行计划:

ltmdb=# prepare x_local(varchar) as select * from simple_example_local where varchar01 = $1;
PREPARE
Time: 0.624 ms
ltmdb=# explain analyze execute x_local('a');
                                            QUERY PLAN
-----------------------------------------------------------------------------------------------------
Seq Scan on simple_example_local  (cost=0.00..3.26 rows=1 width=2280) (Actual time: never executed)
  Filter: ((varchar01)::text = ($1)::text)
Total runtime: 0.088 ms
(3 rows)

4.2. JDBC 连接

目前 JDBC 需要使用 OpenGauss 的驱动,引用方法:

<dependency>
    <groupId>org.opengauss</groupId>
    <artifactId>opengauss-jdbc</artifactId>
    <version>5.0.1</version>
</dependency>

JDBC 驱动的详细介绍可参考 在线文档

4.3. 客户端工具连接

推荐使用 DBeaver 连接,方法可参考 网上文章 ;但要注意驱动版本要选用 5.0.1

5. 常用参数配置

参数配置可通过 LTstartengine 接口传入,以覆盖默认配置;具体用法可参照 example/main.cpp 中的写法。

常用参数配置

配置项

默认值

描述

listen_addresses

localhost

监听地址,可使用 *0.0.0.0 来允许所有来源的连接

port

5432

监听端口号

max_connections

200

Socket 最大连接数

session_timeout

10min

会话超时时间,可设置 0 使 session 不过期

max_process_memory

12GB

最大内存使用量

shared_buffers

1024MB

System V 共享内存大小,会缓存执行计划等信息

mdb_update_limit

10000

控制 MDB 在一次事务中最多可以执行的 update 的次数,超过则会报错

6. SQL 参考

本章列举 SQL 引擎支持的 SQL 特性情况。

6.1. SQL 特性参考

在以下清单中的 SQL 特性是明确支持的,其他未提及的特性也可能支持,但不建议使用,可能导致非预期的行为!

常用参数配置

主项

分项

子项

子元素项

描述

是否支持

支持说明

DDL

CREATE

索引创建(CREATE INDEX)

在临时表上创建索引

普通的 CREATE INDEX 语句,在临时表创建索引

UNIQUE 子句

创建唯一索引

临时表创建(CREATE TEMPORARY TABLE)

create temporary table

会话临时表(会话退出后就消失了)

视图创建 CREATE VIEW

create view

普通视图的创建

create or replace view

普通视图的创建或替换

ALTER

ALTER INDEX

alter index rebuild

兼容ORACLE alter index rebuild

对临时表有效

表更改(ALTER TABLE)

alter table xxx drop constraint xxx drop index

在Oracle中,当在alter table时,可以将同一列的索引和约束同时进行删除

对临时表有效

alter table add constraint

支持alter table add constraint语法

对临时表有效

指定列默认值、NOT NULL等标准SQL属性

Oracle兼容alter modify修改字段类型, 默认值, 非空

对临时表有效

DROP

DROP INDEX

DROP INDEX

索引删除

函数删除(DROP FUNCTION)

过程删除(DROP PROCEDURE)

临时表删除(DROP TEMPORARY TABLE)

视图删除(DROP VIEW)

TRUNCATE

表清空(TRUNCATE TABLE)

TRUNCATE

临时表

部分支持

仅对临时表有效,对 mdb 表无效

DML

SELECT

子查询

select * from (select ...) [[as] t]

子查询as别名可选

字段、对象名、别名为关键字

astable等关键字作为字段别名

as支持作为别名,select as as from t;

astable等关键字作为表名

astable等关键字作为表别名

select for update

select for update

支持 select for update 进行行锁

分页查询

12c之后fetch写法

[OFFSET n] FETCH NEXT m ROWS ONLY

11g之前的rownum写法

rownum > n and rownum < m

WITH查询语句(WITH AS CLAUSE)

普通with

CONNECT BY

connect by

支持 connect by 语法,level、prior关键字

level

connect by支持绑定变量

prior

oracle树形查询

distinct

支持distinct … conncet by level用法

where level op length(col)

oracle connect by是用于解决层次查询的高效特性,能够提供比分析函数、PL/SQL更高效、更高性能

集合操作

UNION和UNION ALL

MINUS

INTERSECT

外关联

外关联

Oracle外关联支持(+)

LEFT JOIN

左外关联

RIGHT JOIN

右外关联

GROUP BY

表达式

CUBE/GROUPING/GROUPING SETS/ROLLUP

列别名

数字

ORDER BY

NULLS LAST/NULLS FIRST

列别名

支持聚合函数与order by结合运算

数字

INSERT

insert into t values(a,b,c,default)

普通 INSERT 带 DEFAULT

DEFAULT字面值

目标表带别名

支持insert into t(a,b,c) v values(...)

INSERT INTO 表带别名

部分支持

必须带 AS

UPDATE

普通更新语句(UPDATE CLAUSE)

update t t1 set t1.v = 'abc'

update单表支持别名

DELETE

删除语句(DELETE CLAUSE)

delete tableName

oracle支持delete不带from关键字

MERGE

MERGE更新语句(MERGE MATCHED UPDATE)

普通matched

计划支持

带where条件matched

计划支持

MERGE插入语句(MERGE NOT MATCHED INSERT)

merge into insert语句分支(a.column)支持insert中用表别名

计划支持

merge结合with cte

"with xx as merge into t using (select * from xx)"

支持merge into with cte 语法

计划支持

命令查看执行计划(EXPLAIN)

命令查看执行计划(EXPLAIN)

普通 EXPLAIN 语句

EXPLAIN query

支持递归语句执行计划查看

支持connect by递归语句执行计划显示正确

优化器提示(hint_plan)

扫描方法

INDEX

强制对表进行索引扫描。如果指定了,则限制为指定的索引

PL/SQL

PL/SQL对象创建、删除、管理

函数创建 CREATE FUNCTION

create function

创建oracle函数

function 名称或者出入参带有关键字支持

比如参数名index,level等

支持in、out和in out三种参数模式

支持参数超过100个

过程创建 CREATE PROCEDURE

支持OR REPLACE重建存储过程

支持参数超过100个

存储过程

存储过程

PL/SQL 存储过程支持

Oracle 格式的存储过程

部分支持

部分支持 PL/SQL 的特性,完整支持 PL/pgSQL 特性

函数

函数

PL/SQL 函数支持

Oracle 格式的函数

部分支持

部分支持 PL/SQL 的特性,完整支持 PL/pgSQL 特性

操作符

操作符(oracle)

通用运算符

通用运算符

!= * + - / < <= < = > >= ! AND LIKE OR

NULL 判断运算符

IS NULL, IS NOT NULL

部分支持

可在临时表使用

mod运算符

支持mod取余运算符

||

双竖线运算符兼容,主要是字符串和非字符串的拼接兼容,非字符串和非字符串的兼容

字符集

默认排序规则

安装时指定服务端字符集

计划支持

字符集

字符集支持

GBK和GB18030-2022

支持客户端和服务端选择GBK/GB18030,能够正确的相互转换

部分支持

数据类型兼容

字符类型

可变长度字符类型(varchar2)

varchar2

varchar2类型采用varchar实现

varchar类型兼容

varchar类型兼容

字符类型(char)

字符类型

char类型字符长度兼容

部分支持

可在临时表使用

nchar

支持nchar类型

部分支持

可在临时表使用

nvarchar2

支持nvarchar2类型

部分支持

可在临时表使用

单字节和多字节字符数据(clob)

部分支持

可在临时表使用

二进制大对象(blob)

oracle blob

对oracle blob对null特殊处理如empty_blob的兼容

部分支持

可在临时表使用

日期类型

日期和时间(date)

Oracle模式下date类型做减法

结果支持numeric类型,oracle为number类型

部分支持

可在临时表使用

数字类型

数值类型(number)

number和boolean类型比较

支持number和boolean类型比较

32位浮点数(binary_float)

binary_float类型

兼容binary_float

64位浮点数(binary_double)

binary_double类型

兼容binary_double

整数类型(int)

int类型,Oracle 底层依旧是 number

支持 int 类型

由于 number 与 int4 存在转换函数,行为方面兼容

表达式兼容

系统参数

nls_timestamp_format

指定时间戳的默认格式

表达式兼容

null与空兼容

Oracle is null/is not null语义兼容,含各种||拼接

仅限Oracle模式,pg&mysql不受影响

部分支持

临时表的行为与 Oracle 一致,MDB 表不会有 NULL 字段

case when then

case 表达式

case 匹配表达式

rownum

功能支持

实现rownum

支持DML

rownum及limit支持update

执行计划

rownum的COUNT STOPKEY实现

标识符默认惯例兼容

聚合函数不带别名兼容

"select max(id) from t; max ----- "

聚合函数不带别名时,返回聚合函数名,不带括号

系统函数

标量函数

oracle 条件函数

decode函数

decode函数兼容

oracle 字符串函数

rawtohex函数

rawtohex 转 HEX 字符串

to_clob函数

实现to_clob函数将数据从文本字符串类型转换为 CLOB 类型,从而实现与 Oracle 数据库的兼容性

substr

兼容oracle字符串截取函数

bit_and, bit_or函数

兼容oracle位计算函数

to_char

兼容oracle转换为字符类型的函数

oracle其他函数

ADD_MONTHS

Trim

abs

instr

length

lpad

ltrim

nvl

nvl函数支持传入带类型的null,发生错误时返回正确合适的错误码

部分支持

MDB 表中不存在 NULL

replace

round

rpad

sign

substr

substrb

to_char

to_date

to_number

trim

trunc

oracle 数学函数

ceil

ceil函数兼容

oracle 日期时间函数

to_date语法兼容

支持格式不完全一致的匹配

to_char支持格式'HH24MiSS'

to_timestamp语法兼容

日期格式兼容oracle的to_timestamp格式码

sysdate语义和存储兼容

支持 sysdate 函数

非标量函数

分析函数

nth_value

返回分组中第n个值

聚合函数

count

统计记录数

max

最大值

min

最小值

sum

求和

listagg

listagg函数兼容

listagg支持分析函数

6.2. 特殊数据类型行为

由于 MDB 内存数据库的数据类型与 SQL 标准类型有一定出入,所以在此节中对一些特殊情况做了说明。

特殊数据类型行为

场景

OpenGauss 行为

SQL 内存引擎实际行为

列类型 int32 ,使用 int64 做插入

ERROR: integer out of range

ERROR: integer out of range

列类型 int32 ,使用 int64 做 where 匹配,这个 64 位整数截断后两者应相等

无数据

无数据

列类型 int32 ,与 int64 值做运算

结果类型为 int8

结果类型为 int8

列类型 int32 ,与 double precision 值做运算

结果为 numeric 类型

结果为 numeric 类型

列类型 int32 ,用 int32 相加

若溢出则报错

若溢出则报错

列类型 int64 ,使用 int32 做插入

正常插入

正常插入

列类型 int64 ,使用 int32 做 where 匹配,这个 64 位整数截断后两者应相等

无数据

无数据

列类型 int64 ,与 int32 值做运算

若溢出则报错

若溢出则报错

列类型 int64 ,与 double precision 值做运算

结果为 numeric 类型

结果为 numeric 类型

列类型 double ,做索引,使用标量小数做匹配

不丢精度,正常匹配

精度低,但可以匹配

列类型 double ,做索引,使用整数做匹配

不丢精度,正常匹配

精度低,但可以匹配

列类型 double ,做索引,使用 text 匹配

不丢精度,正常匹配

精度低,但可以匹配

列类型 double ,使用标量小数做 insert

不丢精度

精度有丢失

列类型 char[] ,存储空串

视为 NULL

一种特殊的空串

列类型 char[] ,insert 时不指定任何内容

默认 NULL

一种特殊的空串

列类型 char[] ,无内容时使用 IS NULL 匹配

正常匹配

无法匹配

列类型 char[] ,无内容时使用 IS NOT NULL 匹配

正常匹配

全部满足

列类型 int32 ,使用 int32 做除法运算(1/3)

0.333333

0.333333

7. 运维管理

本章包含一些常用的问题排查手段。

7.1. 排查问题会话

可通过 pg_stat_activity 视图来寻找问题会话。

典型查询:

SELECT datid, pid, application_name, state, query FROM pg_stat_activity;

 datid |       pid       |    application_name    | state  |                                  query
-------+-----------------+------------------------+--------+--------------------------------------------------------------------------
 15663 | 140734724306688 | gsql                   | active | SELECT datid, pid, application_name, state, query FROM pg_stat_activity;
 15663 | 140735028393728 | WLMArbiter             | idle   |
 15663 | 140735103891200 | workload               | active | WLM fetch collect info from data nodes
 15663 | 140735227098880 | Asp                    | active |
 15663 | 140735206651648 | statement flush thread | idle   |
 15663 | 140735077611264 | WorkloadMonitor        | idle   |
 15663 | 140735351355136 | ApplyLauncher          | idle   |
 15663 | 140735256983296 | PercentileJob          | active |
 15663 | 140735304169216 | TxnSnapCapturer        | idle   |
 15663 | 140735279527680 | CfsShrinker            | idle   |
 15663 | 140737353930240 | ltapi                  | active | select pg_sleep(12343342)
 15663 | 140735433135872 | JobScheduler           | active |

application_name 显示为 ltapi 的,就是通过 C API 接入使用的会话。

若发现某个语句长时间执行,可通过 pg_terminate_backend(pid) 将会话终止,或通过 pg_cancel_backend(pid) 终止当前语句的执行,而不是终止整个会话。

为了避免用户线程被停止,在 C API 场景, pg_terminate_backend 无法终止整个会话,只尝试终止当前语句的执行,实际作用等同于 pg_cancel_backend(pid)

7.2. 慢 SQL 排查

慢SQL能根据用户提供的执行时间阈值( log_min_duration_statement ),记录所有超过阈值的执行完毕的作业信息。

之后用户可以通过系统表 statement_history 查询慢 SQL 信息。

查询结果例子:

ltmdb=# \x
ltmdb=# select * from statement_history;
-[ RECORD 1 ]--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
db_name              | postgres
schema_name          | "$user",public
origin_node          | 0
user_name            | ltmdb
application_name     | ltapi
client_addr          |
client_port          |
unique_query_id      | 2183605531
debug_query_id       | 281474976710665
query                | select * from simple_example where id in (?, ?)
start_time           | 2024-05-29 19:50:29.430159+08
finish_time          | 2024-05-29 19:50:29.431996+08
slow_sql_threshold   | 1000
transaction_id       | 0
thread_id            | 140737353930240
session_id           | 140737353930240
n_soft_parse         | 0
n_hard_parse         | 1
query_plan           |
n_returned_rows      | 1
n_tuples_fetched     | 142
n_tuples_returned    | 71
n_tuples_inserted    | 0
n_tuples_updated     | 0
n_tuples_deleted     | 0
n_blocks_fetched     | 92
n_blocks_hit         | 82
db_time              | 1837
cpu_time             | 1832
execution_time       | 171
parse_time           | 34
plan_time            | 1268
rewrite_time         | 4
pl_execution_time    | 0
pl_compilation_time  | 0
data_io_time         | 56
net_send_info        | {"time":74, "n_calls":4, "size":288}
net_recv_info        | {"time":0, "n_calls":0, "size":0}
net_stream_send_info | {"time":0, "n_calls":0, "size":0}
net_stream_recv_info | {"time":0, "n_calls":0, "size":0}
lock_count           | 68
lock_time            | 0
lock_wait_count      | 0
lock_wait_time       | 0
lock_max_count       | 5
lwlock_count         | 0
lwlock_wait_count    | 0
lwlock_time          | 0
lwlock_wait_time     | 0
details              | \x6100000002415700000003000000011300427566486173685461626c65536561726368005c000000000000000112005374726174656779476574427566666572000a00000000000000010d004461746146696c6552656164003800000000000000
is_slow_sql          | t
trace_id             |
advise               |

这其中 details 字段是二进制的,其中包含一些慢 SQL 的详细信息(对于 MDB 场景用途较小),需要使用 pg_catalog.statement_detail_decode(details, 'plaintext', true) 来查看,效果如下:

ltmdb=# \x
ltmdb=# select query, pg_catalog.statement_detail_decode(details, 'plaintext', true)  from statement_history;
-[ RECORD 1 ]-----------+----------------------------------------------------------------------------------------------------------------------------------------
query                   | select * from simple_example where id in (?, ?)
statement_detail_decode |         ---------------Wait Events Area---------------
                        | '1'     IO_EVENT        BufHashTableSearch                                      92 (us)
                        | '2'     IO_EVENT        DataFileRead                                            83 (us)
                        | '3'     IO_EVENT        StrategyGetBuffer                                       11 (us)
-[ RECORD 2 ]-----------+----------------------------------------------------------------------------------------------------------------------------------------
query                   | delete from simple_example where id = ?
statement_detail_decode |         ---------------Wait Events Area---------------
                        | '1'     IO_EVENT        BufHashTableSearch                                       2 (us)

8. 性能参考

性能测试以两台机器为基准:

  • CentOS 7 x86_64, Intel(R) Xeon(R) Gold 6250 CPU @ 3.90GHz / 4.50GHz

  • OpenEuler SP4 aarch64, Kunpeng-920 2.6 GHz / 3.0 GHz

8.1. INSERT 性能测试

INSERT 用例无存量数据,插入 1000000 条数据,测算总时间。

表结构:

_images/perf-compare-table.png

x86 Intel 测试结果(平均 39us):

内存表的处理效率:

_images/perf-compare-mdb.png

本地表的处理效率:

_images/perf-compare-local.png

8.2. UPDATE 性能测试

UPDATE 用例的 SQL 是 update simple_example set int03 = int03 + 100 where id = $1

存量数据为 100000 条,每次测试更新 100000 次,运行 10 次测试。

表结构用 SQL 表示为:

CREATE TABLE simple_example (
  id INT,
  joinid01 INT,
  joinid02 INT,
  joinid03 INT,
  varchar01 CHAR(128),
  varchar02 CHAR(128),
  varchar03 CHAR(128),
  varchar04 CHAR(128),
  varchar05 CHAR(128),
  varchar06 CHAR(128),
  varchar07 CHAR(128),
  varchar08 CHAR(128),
  int01 INT,
  int02 INT,
  int03 INT,
  int04 INT,
  int05 INT,
  int06 INT,
  double01 DOUBLE PRECISION,
  double02 DOUBLE PRECISION,
  double03 DOUBLE PRECISION,
  float01 DOUBLE PRECISION,
  float02 DOUBLE PRECISION,
  float03 DOUBLE PRECISION
);
CREATE UNIQUE INDEX idx_se_id ON simple_example (id asc);
CREATE INDEX idx_se_vl ON simple_example (varchar01 asc);
CREATE INDEX idx_se_i012 ON simple_example (int01 asc, int02 desc);

ARM Kunpeng-920 测试结果 (平均 78us):

Performance Report for Test: SQL Update Test
Loop Count: 100000
Run 1:
  Total Time: 8022946770 nanoseconds
  TPS: 12464.2
  Average Time per Operation: 80229 nanoseconds
Run 2:
  Total Time: 7761549590 nanoseconds
  TPS: 12884
  Average Time per Operation: 77615 nanoseconds
Run 3:
  Total Time: 7709665680 nanoseconds
  TPS: 12970.7
  Average Time per Operation: 77096 nanoseconds
Run 4:
  Total Time: 7935034480 nanoseconds
  TPS: 12602.3
  Average Time per Operation: 79350 nanoseconds
Run 5:
  Total Time: 7819669690 nanoseconds
  TPS: 12788.3
  Average Time per Operation: 78196 nanoseconds
Run 6:
  Total Time: 7713316640 nanoseconds
  TPS: 12964.6
  Average Time per Operation: 77133 nanoseconds
Run 7:
  Total Time: 7782812210 nanoseconds
  TPS: 12848.8
  Average Time per Operation: 77828 nanoseconds
Run 8:
  Total Time: 7800519260 nanoseconds
  TPS: 12819.7
  Average Time per Operation: 78005 nanoseconds
Run 9:
  Total Time: 7899320520 nanoseconds
  TPS: 12659.3
  Average Time per Operation: 78993 nanoseconds
Run 10:
  Total Time: 7562629390 nanoseconds
  TPS: 13222.9
  Average Time per Operation: 75626 nanoseconds

x86 Intel 测试结果(平均 32us):

Performance Report for Test: SQL Update Test
Loop Count: 100000
Run 1:
  Total Time: 3208250560 nanoseconds
  TPS: 31169.6
  Average Time per Operation: 32082 nanoseconds
Run 2:
  Total Time: 3181472756 nanoseconds
  TPS: 31432
  Average Time per Operation: 31814 nanoseconds
Run 3:
  Total Time: 3214128341 nanoseconds
  TPS: 31112.6
  Average Time per Operation: 32141 nanoseconds
Run 4:
  Total Time: 3180028406 nanoseconds
  TPS: 31446.3
  Average Time per Operation: 31800 nanoseconds
Run 5:
  Total Time: 3214877247 nanoseconds
  TPS: 31105.4
  Average Time per Operation: 32148 nanoseconds
Run 6:
  Total Time: 3164710369 nanoseconds
  TPS: 31598.5
  Average Time per Operation: 31647 nanoseconds
Run 7:
  Total Time: 3178405717 nanoseconds
  TPS: 31462.3
  Average Time per Operation: 31784 nanoseconds
Run 8:
  Total Time: 3190852710 nanoseconds
  TPS: 31339.6
  Average Time per Operation: 31908 nanoseconds
Run 9:
  Total Time: 3191492838 nanoseconds
  TPS: 31333.3
  Average Time per Operation: 31914 nanoseconds
Run 10:
  Total Time: 3199986427 nanoseconds
  TPS: 31250.1
  Average Time per Operation: 31999 nanoseconds

8.3. SUM 性能测试

SUM 用例的 SQL 是 select sum(current_balance) from FUND_REAL_TABLE_RECORD

存量数据为 10000000 条,每次测试计算 10 次,运行 10 次测试。

表结构用 SQL 表示为:

CREATE TABLE fund_real_table_record (
  client_id CHAR(19),
  fund_account CHAR(19),
  money_type CHAR(4),
  asset_prop CHAR,
  current_balance DOUBLE PRECISION,
  enable_balance DOUBLE PRECISION,
  cash_balance DOUBLE PRECISION,
  check_balance DOUBLE PRECISION,
  frozen_balance DOUBLE PRECISION,
  unfrozen_balance DOUBLE PRECISION,
  entrust_buy_balance DOUBLE PRECISION,
  real_buy_balance DOUBLE PRECISION,
  real_sell_balance DOUBLE PRECISION,
  uncome_buy_balance DOUBLE PRECISION,
  uncome_sell_balance DOUBLE PRECISION,
  uncome_correct_balance DOUBLE PRECISION,
  correct_balance DOUBLE PRECISION,
  foregift_balance DOUBLE PRECISION,
  mortgage_balance DOUBLE PRECISION,
  order_no INT
) ;
CREATE UNIQUE INDEX idx_u_fund_real ON fund_real_table_record (fund_account asc, money_type asc);

ARM Kunpeng-920 测试结果(平均 1.4s):

Performance Report for Test: LTMDB Sum Test
Loop Count: 10
Run 1:
  Total Time: 14803197720 nanoseconds
  TPS: 0.67553
  Average Time per Operation: 1.48032e+09 nanoseconds
Run 2:
  Total Time: 14911542070 nanoseconds
  TPS: 0.670621
  Average Time per Operation: 1.49115e+09 nanoseconds
Run 3:
  Total Time: 14984166000 nanoseconds
  TPS: 0.667371
  Average Time per Operation: 1.49842e+09 nanoseconds
Run 4:
  Total Time: 14975235610 nanoseconds
  TPS: 0.667769
  Average Time per Operation: 1.49752e+09 nanoseconds
Run 5:
  Total Time: 14723287730 nanoseconds
  TPS: 0.679196
  Average Time per Operation: 1.47233e+09 nanoseconds

9. 使用限制

当前的 SQL 内存引擎在使用 MDB 表时存在一些功能上的限制,如下:

  • 使用 OR 条件走不了索引。

    受到目前的实现限制,目前对 MDB 表使用 OR 条件查询时无法使用 Bitmap Index Scan ,导致 OR 条件无法正常走索引。

  • UPDATE FROM 关联表更新锁定范围错误。

    受到目前实现方式的限制,会导致锁记录范围异常增大,建议先不要使用该特性。