STVM(truck of Virtual memory table)是一个开源的使用ANSI C语言编写、支持本地API调用和网络调用,全表数据基于IPC共享内存方式存储,
基于C语言struck结构定义记录行,RB-Tree和hash作为主要算法的内存数据库,是一款介于SQL和NOSQL之间的一款高速缓存数据库。
由于数据全部存储在内存中,相比较其他类型的缓存,运行速度之快可想而知。
stvm是一款其介于介于SQL和NOSQL,拥有以下主要特点
支持SQL基本语法(本版本支持insert、update、delete、select、group order、count,first,游标)功能。
支持序列
支持唯一索引、查询索引和组合索引(组合索引不必考虑索引字段先后)
支持条件动态查询
内置记录点击量
集群、主-子分布同步。
事务功能(假性事务)
数据持久性,进程异常退出,不会导致数据丢失,除非系统宕机。
支持网络API和函数API, 支持多线程、多进程等等
内置数据版本,维护数据一致安全。
多机集群,数据分区
当然也有几点缺陷,索引长度限制目前版本64位,修改长度需编译项目、启动后不能修改表字段(ddl语句),不能联表查询。
stvm是一款高速缓存数据库,表字段定义来源与C语言STRUCK,动态条件赋值只需要对结构体成员赋值即为查询条件。
整个操作提供特定API访问,减少解析SQL和字段的时间。
全表数据分3个区(表头区、索引区、数据区),并且全部存储在一块内存中。
每张表定义一个数据库块,每张表拥有自己读写锁,支持线程。
预留拓展表空间参数,但效率会降低扩展个数倍
非常抱歉, 由于上次忘记对redis缓存持久化功能关闭和系统参数未调优,造成测试有偏差,本次测试案例
在64位 Ubuntu上测试,机器配置如下:
4G内存,CUP:Intel(R) Core(TM) i3 CPU M 380 @ 2.53GHz
以下以测试fastdb和redis作为测试比较,以常用操作为测试案例(STVM上采用单表操作,主键为单字段)
1、以此新增200W条记录
2、依次删除单笔记录 (200W次 )
3、按主键循环单次获取每条数据(200W次)
4、按主键循环更新单挑数据(200W次 )
5、操作3和操作4同时进行(总共400W次)
对以上操作5次,平均取值,数据如下:
缓存软件 | 设计版本 | 新增(ms) | 查询(ms) | 删除(ms) | 更新(ms) | 更新/查询(ms) |
fastdb | - | 待测试 | 待测试 | 待测试 | 待测试 | 待测试 |
redis | - | 103,303 | 77,191 | 79,566 | 101,631 | 91,935 |
stvm(net) | V1(C) | 59,882 | 62,652 | 58,400 | 62,200 | 63,840 |
stvm(net) | V2(JAVA) | 待测试 | 待测试 | 待测试 | 待测试 | 待测试 |
stvm(api) | - | 3,364 | 3,520 | 3,422 | 4,063 | 13,317 |
以上数据仅供参考
稍后更新fastdb测试结果
支持语言
C | C++ | Java | …… |
支持 | 支持 | 待支持 |
字段类型有LONG,CHAR,DOUB(对应整型、字符型、浮点型)
行记录建议不要超过500KB
为了避免内存的浪费,唯一索引和查询索引最多各一组。
该软件本版本建议用于数据库缓存, 减少对数据的操作,提高应用性能。
目前该软件仅本地api功能用于公司服务器上的数据库缓存,已在生产验证。其他功能均为个人测试通过但未生产验证,请悉知。
启动环境变量:
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。
方式一:
// 定义表序号
#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, "TBL_USER_INFO", "", dbUser)
FIELD(dbUser, acct_id, "acct_id", FIELD_LONG)
FIELD(dbUser, user_no, "user_no", FIELD_CHAR)
FIELD(dbUser, user_type, "user_type", FIELD_CHAR)
FIELD(dbUser, user_nm, "user_nm", FIELD_CHAR)
FIELD(dbUser, user_addr, "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);
将创建表脚本,保存至文件 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); // 绑定变量
numberset(pstSavm, stUser, acct_id, 10021); // 查询条件赋值
stringset(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); // 绑定变量
numberset(pstSavm, stUser, user_type, "1"); // 查询条件赋值
stringset(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);
}
操作表(新增记录)
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 ;
}
stUser.acct_id = 10021; // 对结构体赋值
strcpy(stUser.user_no, "20180223"); // 对结构体赋值
strcpy(stUser.user_type, "1"); // 对结构体赋值
insertinit(pstSavm, stUser, TBL_USER_INFO); // 绑定变量
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) // 对结构体赋值
numberset(pstSavm, stUser, user_type, "1"); // 查询条件赋值
stringset(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(stUpd);
conditinit(pstSavm, stUser, TBL_USER_INFO) // 绑定变量
numberset(pstSavm, stUser, user_type, "1"); // 查询条件赋值
stringset(pstSavm, stUser, user_no, "20180223"); // 查询条件赋值
stringupd(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]