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
│   └── libsqlengine.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

获取字段数

LTscanCount

获取扫描次数

LTfname

获取字段名称

LTfnumber

获取字段编号

LTgetvalue

获取字段值

LTgetlength

获取字段长度

LTgetisnull

检查字段是否为空

LTcmdTuples

获取命令影响的元组数

LTgetCacheId

获取带缓存接口的cacheId

LTftype

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

LTftable

获取某列对应表的 oid

LTftablecol

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

LTftableName

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

LTgetVersion

获取版本信息

详细的 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");
    // 设置日志路径
    LTsetEngineConfig(engine_configs,"log_directory=/tmp");
    // 设置日志1天自动轮换
     LTsetEngineConfig(engine_configs,"log_rotation_age=1d");
     // 设置自动清理1天前日志
     LTsetEngineConfig(engine_configs,"lightdb_log_retention_age=1d");

    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 连接时也要对应修改。 使用 TCP 连接,初始用户将无法连接。创建用户后,你需要决定该用户可以访问哪些数据库或表以及具有什么样的操作权限。

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 指定ip连接),成功连接后可输入 \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

0.0.0.0

监听地址,可使用 *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 的次数,超过则会报错

log_directory

pg_log

日志文件存放路径,可以是绝对路径,也可以是相对于DATA目录(LTstartengine函数指定)的路径

log_truncate_on_rotation

off

如果参数是on,将截断(覆盖)任何同名的现有日志文件。如果参数为off,追加到任何同名的现有日志文件

log_rotation_age

1d

多久之后日志文件将自动轮换。0代表禁用。如果指定此值时没有单位,则将其视为分钟

lightdb_log_retention_age

1d

此参数确定保留日志文件的最长时间,切换日志文件时将清理超过此时间的日志, 0代表禁用。如果指定此值时没有单位,则将其视为分钟

mdb_allow_question_sign_params

off

是否允许问号作为绑定变量占位符,默认为 off ;开启后可使用 ? 作为占位符,如 LTprepare(conn, "p1", "insert into simple_example (id, int01) values (?, ?)", 2, NULL);

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函数兼容

least函数

least函数兼容,求最小值。忽略NULL值

greatest函数

greatest函数兼容,求最大值。忽略NULL值

oracle 字符串函数

rawtohex函数

rawtohex 转 HEX 字符串

to_clob函数

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

substr

兼容oracle字符串截取函数

hs_substr

当起始位置小于等于0时,返回NULL,其他参数和substr一致

leftb

左边取n个字节

rightb

从右边取n个字节

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

to_int64

将字符串转成64位有符号整数

trim

trunc

oracle 数学函数

ceil

ceil函数兼容

oracle 日期时间函数

to_date语法兼容

将日期格式转成double类型,可使用uftmdbdatetochar转字符串

to_char语法兼容

to_char支持格式'HH24MiSS'

to_timestamp语法兼容

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

uftmdbdatetochar

将double类型表示的日期格式化输出

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. 查询表访问相关的统计信息

可通过 pg_stat_fdw_tables 视图来寻找表的访问相关的统计信息。

典型查询:

SELECT
        relname AS table_name,
        seq_scan AS sequential_scans, -- 顺序扫描次数
        idx_scan AS index_scans,      -- 索引扫描次数
        seq_scan + idx_scan AS total_scans, -- 总扫描次数
        n_tup_ins AS rows_inserted,  -- 插入行数
        n_tup_upd AS rows_updated,   -- 更新行数
        n_tup_del AS rows_deleted    -- 删除行数
FROM
        pg_stat_fdw_tables
ORDER BY
        total_scans DESC
LIMIT 10; -- 显示前10个使用频繁的表

        table_name    | sequential_scans | index_scans | total_scans | rows_inserted | rows_updated | rows_deleted
------------------+------------------+-------------+-------------+---------------+--------------+--------------
 simple_example   |                0 |           0 |           0 |             0 |            0 |            0
 simple_example_2 |                0 |           0 |           0 |             0 |            0 |            0
 ta               |                0 |           0 |           0 |             0 |            0 |            0
 tvar             |                0 |           0 |           0 |             0 |            0 |            0
 tb               |                0 |           0 |           0 |             0 |            0 |            0
(5 rows)

用于显示前10个使用频繁的表。

pg_stat_fdw_tables 视图包含所有用户表的统计信息。可以查询扫描次数等指标。

7.3. 慢 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)

7.4. 内存使用信息查看

可通过 SQL 查询以下视图来查看当前 SQL 引擎已使用的内存。

7.4.1. GS_TOTAL_MEMORY_DETAIL

GS_TOTAL_MEMORY_DETAIL 视图统计当前 SQL 引擎使用内存的信息,单位为MB。

要知道 SQL 引擎当前使用了多少内存,只需要关注 dynamic_used_memory + backend_used_memory + shared_used_memory 即可。

GS_TOTAL_MEMORY_DETAIL字段

名称

类型

描述

nodename

text

节点名称。

memorytype

text

包括以下几种内存类型:
  • max_process_memory: 最大内存使用量,系统配置。

  • process_used_memory: 进程所使用的内存,包含 SQL 引擎以外申请的内存。

  • max_dynamic_memory: 最大的动态内存。

  • dynamic_used_memory: 已使用的动态内存,即 SQL 引擎管理的内存。

  • dynamic_peak_memory: 内存的动态峰值。

  • dynamic_used_shrctx: 最大动态共享内存上下文。

  • dynamic_peak_shrctx: 共享内存上下文的动态峰值。

  • max_backend_memory: 后端可以使用的最大内存量。

  • backend_used_memory: 后端已使用的内存量,即 SQL 引擎自身的后台线程内存用量。

  • max_shared_memory: 最大共享内存。

  • shared_used_memory: 已使用的共享内存。

  • max_cstore_memory: 列存所允许使用的最大内存。

  • cstore_used_memory: 列存已使用的内存大小。

  • max_sctpcomm_memory: 通信库所允许使用的最大内存。

  • sctpcomm_used_memory: 通信库已使用的内存大小。

  • sctpcomm_peak_memory: 通信库的内存峰值。

  • other_used_memory: 其它已使用的内存大小。

  • gpu_max_dynamic_memory: GPU 可以使用的最大动态内存。

  • gpu_dynamic_used_memory: GPU 已使用的动态内存。

  • gpu_dynamic_peak_memory: GPU 已使用的动态内存峰值。

  • pooler_conn_memory: 连接池中连接所使用的内存。

  • pooler_freeconn_memory: 连接池中空闲连接所占用的内存。

  • storage_compress_memory: 数据压缩操作使用的内存。

  • udf_reserved_memory: 用户定义函数预留的内存。

memorymbytes

integer

内存类型分配内存的大小。

8. 性能参考

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

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

以下所有SQL的性能数据都在走缓存的情况下+redo在/dev/shm中 。

服务器信息

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                32
On-line CPU(s) list:   0-31
Thread(s) per core:    2
Core(s) per socket:    8
Socket(s):             2
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 85
Model name:            Intel(R) Xeon(R) Gold 6250 CPU @ 3.90GHz
Stepping:              7
CPU MHz:               1760.522
CPU max MHz:           4500.0000
CPU min MHz:           1200.0000
BogoMIPS:              7800.00
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              1024K
L3 cache:              36608K
NUMA node0 CPU(s):     0-31
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 invpcid_single intel_ppin ssbd mba rsb_ctxsw ibrs ibpb stibp ibrs_enhanced tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm mpx rdt_a avx512f avx512dq rdseed adx smap clflushopt clwb intel_pt avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts pku ospke avx512_vnni md_clear spec_ctrl intel_stibp flush_l1d arch_capabilities

表定义

struct FUNDREALTABLERECORD
{
    char client_id[19];
    char fund_account[19];
    char money_type[4];
    char asset_prop;
    double current_balance;
    double enable_balance;
    double cash_balance;
    double check_balance;
    double frozen_balance;
    double unfrozen_balance;
    double entrust_buy_balance;
    double real_buy_balance;
    double real_sell_balance;
    double uncome_buy_balance;
    double uncome_sell_balance;
    double uncome_correct_balance;
    double correct_balance;
    double foregift_balance;
    double mortgage_balance;
    int32_t order_no;
};

索引定义

UFTMDB_INDEX_INFO *GetFundrealtablerecordIndexInfo() {
    static UFTMDB_INDEX_INFO lpIndexInfo[] = {
        {
            { "idx_u_fund_real", "Set", Categoryfundrealtablerecord },
            {
                { "fundrealtablerecordCmpIdx_u_fund_real", fundrealtablerecordCmpIdx_u_fund_real, "fund_account asc, money_type asc" },
                { NULL, NULL, NULL }
            }
        },
        { { NULL, NULL, 0 }, { { NULL, NULL, NULL} } },
        { { "u_fund_account", NULL, 105 }, { { NULL, NULL, NULL} } },
        { { NULL, NULL, 0 }, { { NULL, NULL, NULL} } },
        { { NULL, NULL, 0 }, { { NULL, NULL, NULL} } },
    };
    return lpIndexInfo;
}

索引比较函数

int32_t fundrealtablerecordCmpIdx_u_fund_real(void *left, void *right) {
    FUNDREALTABLERECORD *lpLeft = (FUNDREALTABLERECORD *) left;
    FUNDREALTABLERECORD *lpRight = (FUNDREALTABLERECORD *) right;
    int cmp_fund_account = strcmp(lpLeft->fund_account, lpRight->fund_account);
    if (cmp_fund_account < 0) {
        return LESS;
    } else if (cmp_fund_account > 0) {
        return GREATER;
    }
    int cmp_money_type = strcmp(lpLeft->money_type, lpRight->money_type);
    if (cmp_money_type < 0) {
        return LESS;
    } else if (cmp_money_type > 0) {
        return GREATER;
    }
    return EQUAL;
}

8.1. 增删改查单条记录耗时(ns)

(ARM 数据待更新)

增删改查单条记录耗时(ns)

平台

接口

插入

查询

更新(索引)

删除

sum(秒)

X86

SQL

21726

26688

21257

17680

0.65

8.2. 并发测试性能数据

(ARM 数据待更新)

吞吐量

平台

接口

功能

1线程

2线程

4线程

8线程

16线程

X86

SQL

插入

46025.90

84814.80

163414.00

278434.00

287670.00

X86

SQL

删除

56559.70

95907.90

154654.00

159896.00

126563.00

X86

SQL

更新

47042.20

82573.20

158306.00

248292.00

225797.00

X86

SQL

查询

37469.50

70913.40

104478.00

133834.00

123064.00

单笔耗时(ns)

平台

接口

功能

1线程

2线程

4线程

8线程

16线程

X86

SQL

插入

21726

23580

24477

28732

55619

X86

SQL

删除

17680

20853

25864

50032

126419

X86

SQL

更新

21257

24220

25267

32220

70860

X86

SQL

查询

26688

28203

38285

59775

130013

9. 使用限制

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

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

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

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

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