Berkeley DB 批量插入更新与删除用法示例
在Berkeley DB 4.8之前,我们可以执行的唯一的批量数据库操作是批量读取。
从Berkeley DB 4.8开始,Berkeley DB支持批量插入/更新/删除,并且用法也与批量读取相似。
批量插入/更新/删除对Berkeley DB的更新性能提升非常大,是一个值得认真学习的新功能。
本文就以一个示例程序展示批量插入和批量删除的用法。
/* 批量插入示例函数。*/
void *
run_bulk_insert()
{
int raw_key[NUM_KEY_INT];
char raw_data[DATA_SIZE];
DBT key, data;
DB_ENV *envp;
DB *dbp;
DB_TXN *tid;
int *insert_load;
int insert_count, id, i, ret, op_flag;
double tmp;
char *key_buf, *data_buf;
void *p;
int j;
/* Initialize structs and arrays */
memset(raw_key, 0, KEY_SIZE);
memset(raw_data, 0, DATA_SIZE);
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
tid = NULL;
/* Initialize bulk insertion buffers */
key_buf = malloc(KEY_SIZE * bulk_size * 2);
data_buf = malloc(DATA_SIZE * bulk_size * 2);
memset(key_buf, 0, KEY_SIZE * bulk_size * 2);
memset(data_buf, 0, DATA_SIZE * bulk_size * 2);
/*
* 初始化Bulk buffer.使用批量操作(bulk operations) 也就是
* 批量插入/删除/更新/读取的时候,必须使用用户提供的内存。
* 所以需要设置DBT对象的flags为DB_DBT_USERMEM,并且设置ulen成员而不是size成员。
*/
key.data = key_buf;
key.ulen = KEY_SIZE * bulk_size * 2;
key.flags = DB_DBT_USERMEM;
data.data = data_buf;
data.ulen = DATA_SIZE * bulk_size * 2;
data.flags = DB_DBT_USERMEM;
op_flag = DB_MULTIPLE;/* 这个flag给put/get/del 表示执行批量插入/更新/读取/删除。 */
/*
* 填充一个bulk buffer DBT 对象. 先调用DB_MULTIPLE_WRITE_INIT初始化该
* DBT。必须传入一个工作指针p和data buffer DBT 对象。
*/
DB_MULTIPLE_WRITE_INIT(p, &data);
for (i = 0; i < bulk_size; i++) {
/*
* 调用DB_MULTIPLE_WRITE_NEXT这个宏来向bulk buffer当中插入数据。
* 需要确保bulk buffer足够大,否则会出现内存访问越界错误。
*
* 各参数说明:
* p: 是这个宏内部使用的工作变量,由DB_MULTIPLE_WRITE_INIT初始化,并且必须在此处一直使用。
* data: 是data buffer DBT对象。
* raw_data: 是一个数据项所在的内存地址。你需要把你要装入的数据项传入这个参数。每个数据项
* 可以含有任意长度的字节,长度限制是一个DBT的总长度限制,也就是2的32次方。
* DATA_SIZE: 是本次宏调用的数据项长度。本例当中所有数据项长度相同,只是特例。完全可以
* 使用变长的数据项。
*
* 循环结束后填充完成,这个data buffer当中有bulk_size个data,
*/
DB_MULTIPLE_WRITE_NEXT(p, &data, raw_data, DATA_SIZE);
}
/*
* 批量插入insert_count条key/data pairs, 每一批插入bulk_size条key/data pairs.
* 本例当中我们只准备了一批数据,所以最终插入的数据是重复的,不过这不影响示例本身。
*/
for (i = 0; i < insert_count / bulk_size; ) {
/*
* 填充key buffer。填好后,这个key buffer当中有bulk_size个key,
* 并且第i个key与data buffer 当中的第i个data做为一对key/data pair
* 被插入数据库当中(i = 0, 1, 2, … bulk_size).
*/
DB_MULTIPLE_WRITE_INIT(p, &key);
for (j = i * bulk_size; j < (i + 1) * bulk_size; j++) {
raw_key[0] = insert_load[j];
/* 在循环当中使用DB_MULTIPLE_WRITE_NEXT依次插入每条data到data buffer当中。
* 循环结束后填充完成。*/
DB_MULTIPLE_WRITE_NEXT(p, &key, raw_key, KEY_SIZE);
}
/* 启动事务准备批量插入。 */
if ((ret = envp->txn_begin(envp, NULL, &tid, 0)) != 0) {
envp->err(envp, ret, “[insert] DB_ENV->txn_begin”);
exit(EXIT_FAILURE);
}
/*
* 执行批量插入。key和data DBT 对象分别是key buffer和data buffer,
* 其中必然含有相同书目的key和data items,key buffer当中的第i个
* key item与data buffer当中的第i个data item 作为一个Key/data pair
* 被插入数据库中。(i = 0, 1, 2, … bulk_size).
*/
switch(ret = dbp->put(dbp, tid, &key, &data, op_flag)) {
case 0: /* 批量插入操作成功,提交事务。*/
if ((ret = tid->commit(tid, 0)) != 0) {
envp->err(envp, ret, “[insert] DB_TXN->commit”);
exit(EXIT_FAILURE);
}
break;
case DB_LOCK_DEADLOCK:
/* 如果数据库操作发生死锁,那么必须abort事务。然后,可以选择重新执行该操作。*/
if ((ret = tid->abort(tid)) != 0) {
envp->err(envp, ret, “[insert] DB_TXN->abort”);
exit(EXIT_FAILURE);
}
continue;
default:
envp->err(envp, ret, “[insert] DB->put ([%d]%d)”, i, insert_load[i]);
exit(EXIT_FAILURE);
}
i++;
}
(void)free(key_buf);
(void)free(data_buf);
return (NULL);
}
/* 批量插入示例函数。*/
void *
run_bulk_delete()
{
int raw_key[NUM_KEY_INT];
DBT key;
DB_ENV *envp;
DB *dbp;
DB_TXN *tid;
int *delete_load;
int delete_count, id, i, ret, op_flag;
double tmp;
char *key_buf;
void *p;
int j;
/* Initialize structs and arrays */
memset(raw_key, 0, KEY_SIZE);
memset(&key, 0, sizeof(DBT));
tid = NULL;
/*
* 初始化批量删除使用的key buffer。由于批量删除不需要data,
* 所以只需要初始化和填充key buffer。我们同样需要使用自己分配的内存。
*/
key_buf = malloc(KEY_SIZE * bulk_size * 2);
memset(key_buf, 0, KEY_SIZE * bulk_size * 2);
/* 初始化key buffer DBT 对象,设置正确的flags和ulen成员。 */
key.data = key_buf;
key.ulen = KEY_SIZE * bulk_size * 2;
key.flags = DB_DBT_USERMEM;
op_flag = DB_MULTIPLE; /* 批量删除同样需要这个flag。*/
/*
* 批量删除所有的数据。每一批删除由key buffer DBT 当中的key
* 指定的bulk_size条key/data pair. 这两个宏的详细用法见上文。
*/
for (i = 0; i < delete_count / bulk_size; ) {
/* 为批量删除初始化并填充一个key buffer DBT 对象。 */
DB_MULTIPLE_WRITE_INIT(p, &key);
for (j = i * bulk_size; j < (i + 1) * bulk_size; j++) {
raw_key[0] = delete_load[j];
DB_MULTIPLE_WRITE_NEXT(p, &key, raw_key, KEY_SIZE);
}
/* 启动事务。*/
if ((ret = envp->txn_begin(envp, NULL, &tid, 0)) != 0) {
envp->err(envp, ret, “[delete] DB_ENV->txn_begin”);
exit(EXIT_FAILURE);
}
/*
* 执行批量删除。key buffer DBT
* 当中的bulk_size条key指定的key/data pairs会被从数据库当中删除。
*/
switch(ret = dbp->del(dbp, tid, &key, op_flag)) {
case 0: /* 批量删除操作成功,提交事务。*/
if ((ret = tid->commit(tid, 0)) != 0) {
envp->err(envp, ret, “[delete] DB_TXN->commit”);
exit(EXIT_FAILURE);
}
break;
case DB_LOCK_DEADLOCK:
/* 如果数据库操作发生死锁,那么必须abort事务。然后,可以选择重新执行该操作。*/
if ((ret = tid->abort(tid)) != 0) {
envp->err(envp, ret, “[delete] DB_TXN->abort”);
exit(EXIT_FAILURE);
}
continue;
default:
envp->err(envp, ret, “[delete] DB->del ([%d]%d)”, i, delete_load[i]);
exit(EXIT_FAILURE);
}
i++;
}
(void)free(key_buf);
return (NULL);
}
你好,我现在的实验要用到Berkeley DB , 用的是MFC 。A 机器发数据,B 机器接收数据,要将B接收的数据连续保存起来(A发一个,B接收一个)。B 接收的数据为字符串。接收的数据有可能重复。接收的数据中key都相同,data不同(都是字符串)。应该怎么弄啊。 还有就是存入接收的数据之后,我通过按钮触发要显示这些数据,在触发函数中要怎么全部获得这些数据。 单个数据的存与取已经实现。 还有一个问题就是,建立数据库时大小为16KB 为何存入一个或两个数据后还是16KB
期待你的回答!!!
你好,请问如何将连续接收的数据保存在DB。我用的MFC,其线程函数接收到数据就存入到数据库,已有数据来就触发线程接收。数据是字符串。不知道要怎么弄啊。主要是用DB_MULTIPLE时,要用到 DbMultipleDataBuilder 这一块不太懂。望指教,单个的数据接收可以实现。
@cc
如果你仔细读完BDB的文档和相关示例的话,我相信你一定可以找到答案的。毕竟,你的问题都是常识性的东西。如果再有困难,欢迎给我发邮件。
你好,我在VC6.0下调用的BerkeleyDB,每次我在dbp->open的参数中写入事务的指针的时候,都会报错,invalid argument。我仔细的检查过我用到的函数,都没有错误,如果在dbp->open中事务的指针为NULL时,数据就都可以写进去的。还有我在写程序的时候发现一下几个问题。1、当存入的数据有相同的key就会报错,怎么可以写入相同的key呢。2、我做了一个时间的测试,发现写入txt的速度比写入数据库的速度快很多,理论上应该蛮快的呀,这个问题要怎么解决呢。3、我写入数据只有4000k,写到数据库却有6000多k。期待您的回答。谢谢~
hi, 我在bdb forums中问过问题,可惜没人回答,可能是我表述不清,这里用中文再问一次,希望没有冒犯:)
http://forums.oracle.com/forums/thread.jspa?threadID=2162345&tstart=0
我在用BDB(C API非SQL版)作为一个小BBS的后台数据库,这是个多线程程序,有forum/topic/post三个数据库,当选择一个forum进入时,该forum的topic按其回帖时间降序排列,与其他BBS一样,如果topic较多时,第一页之显示最新回贴的前25个topic,当有用户翻看第2个25个回帖时,则从数据库中重新取得符合条件的25个topic,以此类推…
struct forum {
UInt16 forumID;
string forumName;
string lastPoster; // who is the last one replied in this forum
};
struct topic {
UInt32 topicID;
UInt16 forumID; // topic comes from this forum
string title; // topic title
UInt64 dateOfLastReply; // when last reply to this topic happen
};
struct post {
UInt64 postID;
UInt32 topicID; // post comes from this topic
string title; // post title as of topic
UInt64 dateOfPost; // when this post is created
};
我是这么想的:给topic主数据库(topic id为主键)建两个secondary数据库,分别以forum id和dateOfLastReply为次键并且对次键进行排序,当有需要显示最新的前25个topic时,先从数据库中获取同forumid并按回帖排序的所有topic id,取前25个id再从数据库中获取这些id对应的data显示,当用户翻页时,重复上述动作,使用SQL描述,类似SELECT topicID FROM topic WHERE forumID=xx ORDER BY dateOfLastReply DESC
请问我该如何设计使用BDB才能效率最高?
OTN forum上已经有专家回答了你的问题,请参见http://forums.oracle.com/forums/thread.jspa?threadID=2162345&tstart=0。
如果你觉得他的回答正确或者有帮助,请标识他的答复。
如果你还有其他问题,欢迎继续提问,谢谢。
@tiplip
Bogdan 于Jan 27, 2011 10:04 AM在OTN论坛的回复应该已经回答你的问题了。
简单来说,我们建议你在topic的forumID和dateOfLastReply两个字段建立复合索引(即Secondary Index or Secondary Database)。需要查询最新25个topic时,在Secondary Database上打开一个Cursor,然后将cursor对应到forunID=xx那些记录(通过设置DB_SET_RANGE来实现)。然后通过DB_NEXT循环输出。
注意:在建立Secondary Database时,针对dateOfLastReply字段的比较函数必须是倒序,这样才能把最新的回复排在最前面。