STVM
STVM(truck of Virtual memory table)是一个开源的使用ANSI C语言编写、支持本地API调用和网络调用,全表数据基于IPC共享内存方式存储、是一款全新的内存数据库、百万级的内存无锁队列,第一个版本于2018年3月1号提交至githup。
基于C语言struck结构定义记录行,RB-Tree和hash作为主要算法的内存数据库,是一款介于SQL和NOSQL之间的一款高速缓存数据库, 一款将队列与数据库完美结合的缓存数据库。
数据全部存储在内存中,支持本地接口和网络接口。
定义
stvm是一款其介于SQL和NOSQL之间的数据库,拥有以下主要特点
支持SQL基本语法(本版本支持insert、update、delete、select、group order、count,first,游标)功能。
操作语法简单。
支持序列
支持百万级别队列(gcc版本 >= 4.12)
支持唯一索引、查询索引和组合索引(组合索引不必考虑索引字段先后)
支持条件动态查询
内置记录点击量
集群、主-子分布同步。
事务功能(假性事务)
数据持久性,进程异常退出,不会导致数据丢失,除非系统宕机。
支持网络API和函数API, 支持多线程、多进程等等
内置数据版本,维护数据一致安全。
多机集群,数据分区
当然也有一些缺陷,索引长度限制目前版本64位,修改长度需编译项目、启动后不能修改、删除表字段,不能联表查询。
STVM是一款高速缓存数据库,高速队列表,表字段定义来源与C语言STRUCK,动态条件赋值只需要对结构体成员赋值即为查询条件。
整个操作提供特定API访问,减少解析SQL和字段的时间。
全表数据分3个区(表头区、索引区、数据区),并且全部存储在一块内存中。
每张表定义一个数据库块,每张表拥有自己读写锁,支持线程。
预留拓展表空间参数,但效率会降低扩展个数倍
性能测试
STVM本地调用性能主要优势:
- 不需要任何IO
- 不要任何解析
- 整个过程在内存中完成。
- 每张表都有独自的读写锁。
STVM网络调用性能主要优势:
- 数据解析模块。
支持语言
C | C++ | Java | …… |
支持 | 支持 | 待支持 | 待支持 |
字段类型
字段类型有LONG,CHAR,DOUB(对应整型、字符型、浮点型)
在走IO调用下,STVM为了自我保护最大单条记录最大1M,因此建议不要超过此值,但是本地调用方式行记录大小将受该机器限制。
为了避免内存的浪费,唯一索引和查询索引最多各一组。
使用手册
启动环境变量:
TVMDBD=/home/stvm/tvmdb 指向stvm工作目录
TVMCFG=/home/stvm/tvmdb/.tvm.run 启动参数
配置完成后,stvm -w 将采用缺省参数以本机模式启动系统。
如果集群模式请配置 stvm.conf,然后用stvm -c stvm.conf编译配置文件即可。
STVM对外提供2个开发运维工具stvm和调试工具detvm。
stvm -w启动系统
stvm -s停止系统
STVM也提供一个类型sqlpuls类型简单工具,输入:
stvm SQL 进入SQL界面(该工具仅仅用来运维调试使用)。
stvm DOM 进入域的维护
建议运行在64位机器上,因为32位机器最大寻址4G,因此创建单表不能超过4G。
使用场景
该软件本版本建议用于数据库缓存, 减少对数据的操作,提高应用性能。
目前该软件仅本地api功能用于公司生产服务器上的数据库缓存。
开发流程
创建表
方式一:
// 定义表序号
#define TBL_USER_INFO 10
必须用C定义STRUCK:
typedef struct __TBL_USER_INFO
{
long acct_id;
char user_no[21];
char user_type[2];
char user_nm[81];
char user_addr[161];
}dbUser;
定义创建表函数
CREATE lCreateUserInfo()
{
DEFINE(TBL_USER_INFO, "", dbUser)
FIELD(dbUser, acct_id, FIELD_LONG)
FIELD(dbUser, user_no, FIELD_CHAR)
FIELD(dbUser, user_type, FIELD_CHAR)
FIELD(dbUser, user_nm, FIELD_CHAR)
FIELD(dbUser, user_addr, FIELD_CHAR)
CREATE_IDX(NORMAL) // 创建查询索引
IDX_FIELD(dbUser, acct_id, FIELD_LONG)
CREATE_IDX(UNQIUE) // 创建唯一索引
IDX_FIELD(dbUser, user_no, FIELD_CHAR)
IDX_FIELD(dbUser, user_type, FIELD_CHAR)
FINISH
}
调用api:
lCreateTable(pstSavm, TBL_USER_INFO, 100000, lCreateUserInfo) 完成对TBL_USER_INFO表的创建。
TBL_USER_INFO 创建的表序号,其中pstSavm为线程句柄
100000初始化内存记录空间条数。
lCreateUserInfo创建表定义函数。
调用成功则返回RC_SUCC, 失败返回RC_FAIL, 失败可调用pstSavm->m_lErrno获取处理失败错误码,sGetTError(pstSavm->m_lErrno)获取错误信息。
方式二:
set TABLE=20
set TABLESPACE=10000
create table TBL_USER_INFO
(
acct_id long,
user_no char(21),
user_type char(2),
user_nm char(81),
user_addr char(161),
user_phone char(31)
);
-- Create indexes
create unique index (user_no, user_type);
create index (acct_id);
comment on TBL_USER_INFO.user_phone is 'phone';
将创建表脚本,保存至文件 tbl_user_info.def, 表字段支持类型char、shor、int、long、llong、double、float。
在stvm SQL执行 create tbl_user_info.def来创建该表。
表对应的结构体可以通过 stvm -t table来导出。
操作表(单条查询)
dbUser stUser;
SATvm *pstSavm = (SATvm *)pGetSATvm();
// 初始化TBL_USER_INFO表,每张表都需要初始化一次, 对于表重建后,需要重新初始化一次。
if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
return ;
}
conditinit(pstSavm, stUser, TBL_USER_INFO); // 绑定变量
conditnum(pstSavm, stUser, acct_id, 10021); // 查询条件赋值
conditstr(pstSavm, stUser, user_no, "20180223"); // 查询条件赋值
if(RC_SUCC != lSelect(pstSavm, (void *)&stUser)) // 单条记录查询
{
fprintf(stderr, "Select错误ep:%d, err:(%d)(%s)\n", pstSavm->m_lEType, pstSavm->m_lErrno,
sGetTError(pstSavm->m_lErrno));
return ;
}
fprintf(stdout, "acct_id:%ld, user_no:%s, user_type:%s, user_nm:%s, user_addr:%s\n",
stUser.acct_id, stUser.user_no, stUser.user_type, stUser.user_nm, stUser.user_addr);
操作表(多条查询)
size_t i = 0, lOut;
dbUser *pstUser = NULL;
SATvm *pstSavm = (SATvm *)pGetSATvm();
// 初始化TBL_USER_INFO表,每张表都需要初始化一次, 对于表重建后,需要重新初始化一次。
if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
return ;
}
conditinit(pstSavm, stUser, TBL_USER_INFO); // 绑定变量
conditstr(pstSavm, stUser, user_type, "1"); // 查询条件赋值
conditstr(pstSavm, stUser, user_no, "20180223"); // 查询条件赋值
if(RC_SUCC != lQuery(pstSavm, &lOut, (void **)&pstUser)) // 批量查询查询
{
fprintf(stderr, "Select错误ep:%d, err:(%d)(%s)\n", pstSavm->m_lEType, pstSavm->m_lErrno,
sGetTError(pstSavm->m_lErrno));
return ;
}
for(i = 0; i < lOut; i ++)
{
fprintf(stdout, "acct_id:%ld, user_no:%s, user_type:%s, user_nm:%s, user_addr:%s\n",
pstUser[i].acct_id, pstUser[i].user_no, pstUser[i].user_type, pstUser[i].user_nm,
pstUser[i].user_addr);
}
TFree(pstUser);
操作表(新增记录)
dbUser stUser;
SATvm *pstSavm = (SATvm *)pGetSATvm();
// 初始化TBL_USER_INFO表,每张表都需要初始化一次, 对于表重建后,需要重新初始化一次。
if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
return ;
}
conditinit(pstSavm, stUser, TBL_USER_INFO); // 绑定变量
#if 0 // 效率高
stUser.acct_id = 10021; // 对结构体赋值
strcpy(stUser.user_no, "20180223"); // 对结构体赋值
strcpy(stUser.user_type, "1"); // 对结构体赋值
#else
conditnum(pstSavm, stUser, acct_id, 10021); // 对结构体赋值
conditstr(pstSavm, stUser, user_type, "1"); // 对结构体赋值
conditstr(pstSavm, stUser, user_no, "20180223"); // 对结构体赋值
#endif
if(RC_SUCC != lInsert(pstSavm)) // 插入记录
{
fprintf(stderr, "Select错误ep:%d, err:(%d)(%s)\n", pstSavm->m_lEType, pstSavm->m_lErrno,
sGetTError(pstSavm->m_lErrno));
return ;
}
操作表(删除记录)
dbUser stUser;
SATvm *pstSavm = (SATvm *)pGetSATvm();
// 初始化TBL_USER_INFO表,每张表都需要初始化一次, 对于表重建后,需要重新初始化一次。
if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
return ;
}
conditinit(pstSavm, stUser, TBL_USER_INFO) // 对结构体赋值
conditstr(pstSavm, stUser, user_type, "1"); // 查询条件赋值
conditstr(pstSavm, stUser, user_no, "20180223"); // 查询条件赋值
if(RC_SUCC != lDelete(pstSavm)) // 删除记录
{
fprintf(stderr, "Delete err:(%d)(%s)\n", pstSavm->m_lEType, pstSavm->m_lErrno,
sGetTError(pstSavm->m_lErrno));
return ;
}
操作表(修改记录)
dbUser stUser, stUpd;
SATvm *pstSavm = (SATvm *)pGetSATvm();
if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
return ;
}
updateinit(pstSavm, stUpd);
conditinit(pstSavm, stUser, TBL_USER_INFO) // 绑定变量
conditstr(pstSavm, stUser, user_type, "1"); // 查询条件赋值
conditstr(pstSavm, stUser, user_no, "20180223"); // 查询条件赋值
updatestr(pstSavm, stUpd, user_addr, "china");
if(RC_SUCC != lUpdate(pstSavm, (void *)&stUpd))
{
fprintf(stderr, "Update err:(%d)(%s), (%ld)\n", pstSavm->m_lErrno,
sGetTError(pstSavm->m_lErrno), lGetEffect());
return ;
}
其他函数lTruncate、lCount、lExtreme、lGroup,lDropTable、lRenameTable 和网络API如lTvmDelete、lTvmInsert、lTvmSelect、lTvmUpdate、lTvmTruncate、lTvmCount、lTvmExtreme、lTvmGroup、lTvmQuery等,用法相似,不做一一枚举。[1]
版本发布
2018年03月01日,首次开源
2018年06月01日,STVM 1.2.4 。 https://github.com/DeffPuzzL/STVM