Berkeley db管理

Berkeley db管理

1. 打开数据库
首先必须调用db_create()函数初始化DB句柄,然后就可以使用open()方法打开数据库了。默认情况下,如果数据库不存在,DB不会创建。为了覆盖缺省行为,可以在open()调用中指定DB_CREATE标记。
以下代码示范了如何打开数据库:
#include <db.h>
...
DB *dbp;
u_int32_t flags;
int ret;
ret = db_create(&dbp, NULL, 0);
if (ret != 0) {

}
flags = DB_CREATE;
ret = dbp->open(dbp,
NULL,
"my_db.db",
NULL,
DB_BTREE,
flags,
0);
if (ret != 0) {

}
在open参数中,DB_BTREE是DB的一种访问方法,此外还有DB_HASH、DB_HASH、DB_RECNO、DB_QUEUE等访问方法。
DB 存取数据的基本方式是key/value对,DB_BTREE和DB_HASH的key可为任意值,而DB_RECNO和DB_QUEUE的 key只能为逻辑上的数字。DB_BTREE用在记录数据较小的情况,通常能直接保存在内存里,而DB_HASH用在记录数据较大的情况。这里的记录数据 指单条记录。
DB_QUEUE和DB_RECNO用数字来标记每条记录(记录ID),前者逻辑数字不会随着记录的删除发生变化,后者会发生变化。
打开标记出了DB_CREATE外,还可有如下标记的或组合。
– DB_EXCL
独占式创建,如果数据库已经存在则打开失败,仅和DB_CREATE组合使用。
– DB_RDONLY
只读方式打开。
– DB_TRUNCATE
截断磁盘上的数据库文件,清空数据。

2. 关闭数据库
在关闭数据库之前必须确保所有的游标(cursors)已经关闭,关闭数据库时自动把缓存数据写入磁盘。如果需要手工写缓存数据,可调用DB->sync()方法。
以下是示范代码:
#include <db.h>
...
DB *dbp;
...
...
if (dbp != NULL)
dbp->close(dbp, 0);

3. DB管理方法
– DB->get_open_flags() 获取数据库的打开标记
前提是数据库必须已经打开。
#include <db.h>
...
DB *dbp;
u_int32_t open_flags;
dbp->get_open_flags(dbp, &open_flags);

– DB->remove() 删除数据库
如果database参数为NULL,则删除该方法所引用的整个文件。注意,不要删除已经有打开句柄的数据库。
#include <db.h>
...
DB *dbp;
dbp->remove(dbp,
"mydb.db",
NULL,
0);

– DB->rename() 数据库重命名
如果database参数是NULL,则该方法所引用的整个文件被重命名。注意,不要重命名已经有打开句柄的数据库。
#include <db.h>
...
DB *dbp;
dbp->rename(dbp,
"mydb.db",
NULL,
"newdb.db",
0);


4. 错误处理函数
– set_errcall()
当DB发生错误的时候,将调用set_errcall()所指定的回调函数,错误前缀和错误消息发送给该回调函数,错误显示由回调函数处理。
– set_errfile()
设置C库的FILE *来显式错误信息。
– set_errpfx()
设置DB的错误消息前缀
– err()
产生一条错误消息。错误消息将被发送给set_errcall所指定的回调函数,如果没有设置回调函数,则发送给set_errfile()所指定的文件,如果还是没有设置,则发送给标准错误输出(stderr)。
错误消息包含前缀串(由set_errprefix()指定),可选的printf风格的格式化串,错误消息,尾部换行。
– errx()
同err(),除了与错误值关联的DB消息不会追加到错误串。
此外,还可以用db_strerror()直接返回与特定的错误号对应的错误串。
例如,为了把错误消息发送给回调函数,可先定义该回调函数。

void
my_error_handler(const char *error_prefix, char *msg)
{

}
然后注册它。
#include <db.h>
#include <stdio.h>
...
DB *dbp;
int ret;
ret = db_create(&dbp, NULL, 0);
if (ret != 0) {
fprintf(stderr, "%s: %s\n", "my_program",
db_strerror(ret));
return(ret);
}
dbp->set_errcall(dbp, my_error_handler);
dbp->set_errpfx(dbp, "my_example_program");

产生一条错误消息:
ret = dbp->open(dbp,
NULL,
"mydb.db",
NULL,
DB_BTREE,
DB_CREATE,
0);
if (ret != 0) {
dbp->err(dbp, ret,
"Database open failed: %s", "mydb.db");
return(ret);
}

5. DB环境
DB 环境是对1个或者多个数据库的封装,一般用法是先打开DB环境,然后在此环境中打开数据库。这样创建或者打开的数据库是相对于环境HOME目录的。DB环 境对于一般简单的嵌入式数据库用不上,但是对于多数据库文件、多线程多进程支持、事务处理、高可用性(复制)支持、日志子系统等是必要的。
以下代码示范了如何创建/打开DB环境。
#include <db.h>
...
DB_ENV *myEnv;
DB *dbp;
u_int32_t db_flags;
u_int32_t env_flags;
int ret;
ret = db_env_create(&myEnv, 0);
if (ret != 0) {
fprintf(stderr, "Error creating env handle: %s\n", db_strerror(ret));
return -1;
}
env_flags = DB_CREATE;
ret = myEnv->open(myEnv,
"/export1/testEnv",
env_flags,
0);
if (ret != 0) {
fprintf(stderr, "Environment open failed: %s", db_strerror(ret));
return -1;
}

DB环境打开后,就可以在其中创建/打开数据库了。

ret = db_create(&dbp, myEnv, 0);
if (ret != 0) {

}
db_flags = DB_CREATE;
ret = dbp->open(dbp,
NULL,
"my_db.db",
NULL,
DB_BTREE,
db_flags,
0);
if (ret != 0) {

}
在关闭DB环境之前,必须先关闭打开的数据库。

if (dbp != NULL) {
dbp->close(dbp, 0);
}
if (myEnv != NULL) {
myEnv->close(myEnv, 0);
}

6. DB记录
DB记录包含两部分 – 关键字部分(key)和数据部分(data),二者在DB中分别封装为DBT结构。DBT结构有一个void*字段用来存放数据,还有一个字段用来指定数据的长度,因此key和data可以为内存连续的任意长度的数据。
以下代码示范了如何给key和data赋值。
#include <db.h>
#include <string.h>
...
DBT key, data;
float money = 122.45;
char *description = "Grocery bill.";
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
key.data = &money;
key.size = sizeof(float);
data.data = description;
data.size = strlen(description) + 1;

要取回key和data的值,只要把DBT中void *部分返回给相应的变量就可以了。但是上例有点例外,因为浮点数在某些系统中要求内存对齐,为安全起见我们自己提供浮点数的内存,而不使用DB返回的内存。通过使用DB_DBT_USEMEM标记就可以实现。
#include <db.h>
#include <string.h>
...
float money;
DBT key, data;
char *description;
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
key.data = &money;
key.ulen = sizeof(float);
key.flags = DB_DBT_USERMEM;
description = data.data;

7. 读写DB记录和数据的持久化
默认情况下,DB不支持重复记录(同一个key对应到多个data)。DBT->put()和DBT->get()适用于这种情况下的记录读写操作。对于重复记录,可使用游标(Cursor)。
写记录通过DB->put()方法实现,它带有一些标记,其中DB_NOOVERWRITE标记指定不能插入重复记录,如果记录有重复,就算数据库支持重复记录,也会返回DB_KEYEXIST错误。
#include <db.h>
#include <string.h>
...
char *description = "Grocery bill.";
DBT key, data;
DB *my_database;
int ret;
float money;
money = 122.45;
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
key.data = &money;
key.size = sizeof(float);
data.data = description;
data.size = strlen(description) +1;
ret = my_database->put(my_database, NULL, &key, &data, DB_NOOVERWRITE);
if (ret == DB_KEYEXIST) {
my_database->err(my_database, ret,
"Put failed because key %f already exists", money);
}

读记录通过DB->get()方法实现,如果数据库支持重复记录,它只返回第一个与key匹配的记录。当然也可以用一批get调用来获取重复的记录,需要在调用DB->get()时指定DB_MULTIPLE标记。
默认情况DB->get()返回与提供的key匹配的记录,如果需要记录既和key匹配,也和data匹配,可指定DB_GET_BOTH标记。如果没有匹配的记录,本方法返回DB_NOTFOUND。
#include <db.h>
#include <string.h>
...
DBT key, data;
DB *my_database;
float money;
char *description;
money = 122.45;
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
key.data = &money;
key.ulen = sizeof(float);
key.flags = DB_DBT_USERMEM;
my_database->get(my_database, NULL, &key, &data, 0);
description = data.data;

删除记录可调用DB->del()方法,如果记录有重复,则删除所有的与key匹配的记录。要删除单条记录,可考虑用游标。要删除数据库的所有记录,可调用DB->truncate()方法。
#include <db.h>
#include <string.h>
...
DBT key;
DB *my_database;
float money = 122.45;

memset(&key, 0, sizeof(DBT));
key.data = &money;
key.size = sizeof(float);
my_database->del(my_database, NULL, &key, 0);

默认情况下数据的修改被缓存起来,另一个打开数据库的进程不会立即看到修改后的数据,在数据库关闭时数据自动保存到磁盘。可以调用Db->sync()强制保存缓存的数据,也可采用事务数据库防止缓存数据的丢失。
如果不希望数据库关闭时保存缓存数据,可在DB->close()中指定DB_NOSYNC标记。
如果系统崩溃而且没有使用事务数据库,可调用DB->verify()验证数据,如果验证失败,可用db_dump命令(使用-R或-r参数控制db_dump的恢复程度)尽可能恢复丢失的数据。

8. DB游标
游标使用DBC结构管理,打开游标可调用DB->cursor()方法。
#include <db.h>
...
DB *my_database;
DBC *cursorp;
my_database->cursor(my_database, NULL, &cursorp, 0);

关闭有掉调用DBC->c_close()方法,注意在关闭数据库之前必须关闭所有的游标,否则后果不可预料。
#include <db.h>
...
DB *my_database;
DBC *cursorp;
if (cursorp != NULL)
cursorp->c_close(cursorp);
if (my_database != NULL)
my_database->close(my_database, 0);

为了在数据库中遍历记录,可调用DBC->c_get()方法,指定DB_NEXT标记。
#include <db.h>
#include <string.h>
...
DB *my_database;
DBC *cursorp;
DBT key, data;
int ret;
my_database->cursor(my_database, NULL, &cursorp, 0);
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
while ((ret = cursorp->c_get(cursorp, &key, &data, DB_NEXT)) == 0) {

}
if (ret != DB_NOTFOUND) {

}
if (cursorp != NULL)
cursorp->c_close(cursorp);
if (my_database != NULL)
my_database->close(my_database, 0);

要从数据库的尾部到头部遍历记录,可使用DB_PREV代替DB_NEXT标记。
#include <db.h>
#include <string.h>
...
DB *my_database;
DBC *cursorp;
DBT key, data;
int ret;
my_database->cursor(my_database, NULL, &cursorp, 0);
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
while ((ret = cursorp->c_get(cursorp, &key,
&data, DB_PREV)) == 0) {

}
if (ret != DB_NOTFOUND) {

}
// Cursors must be closed
if (cursorp != NULL)
cursorp->c_close(cursorp);
if (my_database != NULL)
my_database->close(my_database, 0);

9. 使用游标查找记录
使 用DBC->c_get()可查找与key或者key和data匹配的记录,如果数据库支持已序多重记录集,还可执行部分匹配 (partial matches)。参数key和data会被填充找到的DB记录,游标指向找到的记录位置。如果查找失败,游标状态保持不变,返回 DB_NOTFOUND。
DBC->c_get()可带如下的标记:
– DB_SET
移动游标到第一条匹配key的数据库记录。
– DB_SET_RANGE
和DB_SET的Cursor.getSearchKey()类似,除非使用BTree的访问方法,在BTree的访问方法下,游标移到第一条大于等于key的记录位置,这种比较由你提供比较函数,或者缺省按字典顺序。
比如数据库存储了如下key:
Alabama
Alaska
Arizona
如果查找的key为Al,则游标移到Alabama,key为Alas游标移到Alaska,key为Ar移动到最后一条记录。
– DB_GET_BOTH
移动游标到第一条匹配key和data的记录位置。
– DB_GET_BOTH_RANGE
和DB_SET_RANGE类似,先匹配key,然后匹配data。
假设数据库存储了如下的key/data对,
Alabama/Athens
Alabama/Florence
Alaska/Anchorage
Alaska/Fairbanks
Arizona/Avondale
Arizona/Florence
则查找的key/data和查找结果关系如下
待查的key 待查的data 游标位置
—————————————
Al Fl Alabama/Florence
Ar Fl Arizona/Florence
Al Fa Alaska/Fairbanks
Al A Alabama/Athens
假设有一个数据库存储了美国的州(key)和城市(data)的关系,则如下代码片断可定位游标的位置,然后打印key/data对:
#include <db.h>
#include <string.h>
...
DBC *cursorp;
DBT key, data;
DB *dbp;
int ret;
char *search_data = "Fa";
char *search_key = "Al";
dbp->cursor(dbp, NULL, &cursorp, 0);
key.data = search_key;
key.size = strlen(search_key) + 1;
data.data = search_data;
data.size = strlen(search_data) + 1;
ret = cursorp->c_get(cursorp, &key, &data, DB_GET_BOTH_RANGE);
if (!ret) {

} else {

}
if (cursorp != NULL)
cursorp->c_close(cursorp);
if (dbp != NULL)
dbp->close(dbp, 0);


10. 使用游标处理重复记录
所谓重复记录是指不同的data有相同的key,它们共享同一个key。只有BTree和Hash访问方法支持重复记录,重复记录可通过游标读取,以下是DBC->c_get()相关的标记:
– DB_NEXT, DB_PREV
取数据库中的下一条/上一条记录,不管它是否重复。
– DB_GET_BOTH_RANGE
定位游标到一条特定的记录,不管它是否重复。
– DB_NEXT_NODUP, DB_PREV_NODUP
获取下一条/上一条不重复的记录。
– DB_NEXT_DUP
获取和当前记录分享同一个key的下一条记录。如果没有则返回DB_NOTFOUND。
比如,下面的代码片断定位游标到某条记录,然后显式它以及和它重复的记录。
#include <db.h>
#include <string.h>
...
DB *dbp;
DBC *cursorp;
DBT key, data;
int ret;
char *search_key = "Al";

dbp->cursor(dbp, NULL, &cursorp, 0);
key.data = search_key;
key.size = strlen(search_key) + 1;
ret = cursorp->c_get(cursorp, &key, &data, DB_SET);
while (ret != DB_NOTFOUND) {
printf("key: %s, data: %s\n", (char *)key.data, (char *)data.data);
ret = cursorp->c_get(cursorp, &key, &data, DB_NEXT_DUP);
}
if (cursorp != NULL)
cursorp->c_close(cursorp);
if (dbp != NULL)
dbp->close(dbp, 0);

11. 使用游标插入记录
使用游标插入记录的时候,游标位于新插入的记录上。注意游标插入记录不能获取事务性保护,要想事务性地保护数据库,直接用DB句柄插入。以下是DBC->c_put()相关的标记。
– DB_NODUPDATA
如果key已经存在返回DB_KEYEXIST错误。该标记只支持已序重复数据的数据库插入,比如BTree或者Hash。
– DB_KEYFIRST
如果数据库支持重复数据,且key已经存在,则新记录插在最前面。
– DB_KEYLAST
如果数据库支持重复数据,且key已经存在,则新记录插在最后面。
比如:
#include <db.h>
#include <string.h>
...
DB *dbp;
DBC *cursorp;
DBT data1, data2, data3;
DBT key1, key2;
char *key1str = "My first string";
char *data1str = "My first data";
char *key2str = "A second string";
char *data2str = "My second data";
char *data3str = "My third data";
int ret;
key1.data = key1str;
key1.size = strlen(key1str) + 1;
data1.data = data1str;
data1.size = strlen(data1str) + 1;
key2.data = key2str;
key2.size = strlen(key2str) + 1;
data2.data = data2str;
data2.size = strlen(data2str) + 1;
data3.data = data3str;
data3.size = strlen(data3str) + 1;
dbp->cursor(dbp, NULL, &cursorp, 0);
ret = cursorp->c_put(cursorp, &key1,
&data1, DB_KEYFIRST);
ret = cursorp->c_put(cursorp, &key2,
&data2, DB_KEYFIRST);
ret = cursorp->c_put(cursorp, &key2,
&data3, DB_KEYFIRST); 


12. 使用游标删除记录
调用DBC->c_del()方法。
#include <db.h>
#include <string.h>
...
DB *dbp;
DBC *cursorp;
DBT key, data;
char *key1str = "My first string";
int ret;
key.data = key1str;
key.size = strlen(key1str) + 1;

dbp->cursor(dbp, NULL, &cursorp, 0);
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
while ((ret = cursorp->c_get(cursorp, &key,
&data, DB_SET)) == 0) {
cursorp->c_del(cursorp, 0);
}
if (cursorp != NULL)
cursorp->c_close(cursorp);
if (dbp != NULL)
dbp->close(dbp, 0);

13. 使用游标替换记录
调用DBC->c_put()方法,指定DB_CURRENT标记。
#include <db.h>
#include <string.h>
...
DB *dbp;
DBC *cursorp;
DBT key, data;
char *key1str = "My first string";
char *replacement_data = "replace me";
int ret;
key.data = key1str;
key.size = strlen(key1str) + 1;

dbp->cursor(dbp, NULL, &cursorp, 0);
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
ret = cursorp->c_get(cursorp, &key, &data, DB_SET);
if (ret == 0) {
data.data = replacement_data;
data.size = strlen(replacement_data) + 1;
cursorp->c_put(cursorp, &key, &data, DB_CURRENT);
}
if (cursorp != NULL)
cursorp->c_close(cursorp);
if (dbp != NULL)
dbp->close(dbp, 0);

注意,如果你想替换多条重复记录集的记录,而且没有使用定制的排序函数,最好的做法是先删除记录,然后插入新记录。

14. 从属数据库
假 设你有一个用户数据库DBUser,key为用户ID,data为用户的相关信息,比如name,age,address等。你可以根据用户ID 很快找出这个用户信息来。但是有一天老板要求根据用户的name信息找这个用户,用户的ID是未知的。根据目前所学的东西,只能遍历整个数据库,一条一条 记录去比对,这是很费力不讨好的事情。
从属数据库是因这个需求产生的。你可以根据DBUser库的name信息建立从属数据库,这样就相当于为DBUser建立了基于name的索引。查询从属数据库的name信息,很快就可以找到DBUser库里对应的记录。
使用从属数据库的基本步骤是:创建数据库,打开它,和主数据库建立关联。此外还需要提供一个回调函数,用来根据主数据库的记录建立从属数据库的关键字。
关闭从属数据库的时候,确保关闭了主数据库,这在多线程的时候非常重要。
以下代码示范了如何打开从属数据库:
#include <db.h>
...
DB *dbp, *sdbp;
u_int32_t flags;
int ret;
ret = db_create(&dbp, NULL, 0);
if (ret != 0) {

}
ret = db_create(&sdbp, NULL, 0);
if (ret != 0) {

}
ret = sdbp->set_flags(sdbp, DB_DUPSORT);
if (ret != 0) {

}
flags = DB_CREATE;
ret = dbp->open(dbp,
NULL,
"my_db.db",
NULL,
DB_BTREE,
flags,
0);
if (ret != 0) {

}
ret = sdbp->open(sdbp,
NULL,
"my_secdb.db",
NULL,
DB_BTREE,
flags,
0);
if (ret != 0) {

}
dbp->associate(dbp,
NULL,
sdbp,
get_sales_rep,
0);

关闭主从数据库的代码如下:

if (sdbp != NULL)
sdbp->close(sdbp, 0);
if (dbp != NULL)
dbp->close(dbp, 0);

15. 从属数据库的操作
我们必须提供一个回调函数,为从属数据库建立key。key的建立可依据主数据库记录的key或者data,主要看应用需要。
假设主数据库存储了如下的数据:
typedef struct vendor {
char name[MAXFIELD];
char street[MAXFIELD];
char city[MAXFIELD];
char state[3];
char zipcode[6];
char phone_number[13];
char sales_rep[MAXFIELD];
char sales_rep_phone[MAXFIELD];
} VENDOR;

你想基于主数据库的sales_rep来查询主数据库,可这样写回调函数:
#include <db.h>
...
int
get_sales_rep(DB *sdbp,
const DBT *pkey,
const DBT *pdata,
DBT *skey)
{
VENDOR *vendor;

vendor = pdata->data;

memset(skey, 0, sizeof(DBT));
skey->data = vendor->sales_rep;
skey->size = strlen(vendor->sales_rep) + 1;

return (0);
}

如果回调函数返回DB_DONOTINDEX或者其他非0值,则记录不会被索引。
还需要通过associate()建议关联:
dbp->associate(dbp,
NULL,
sdbp,
get_sales_rep,
0);

读从属数据库和读主数据库没什么区别,不同在于DB->get()或者DB->pget()返回的是主数据库的key和data。
#include <db.h>
#include <string.h>
...
DB *my_secondary_database;
DBT key;
DBT pkey, pdata;
char *search_name = "John Doe";

memset(&key, 0, sizeof(DBT));
memset(&pkey, 0, sizeof(DBT));
memset(&pdata, 0, sizeof(DBT));
key.data = search_name;
key.size = strlen(search_name) + 1;
my_secondary_database->get(my_secondary_database, NULL,
&key, &pdata, 0);
my_secondary_database->pget(my_secondary_database, NULL,
&key, &pkey, &pdata, 0);

一般我们不用直接修改从属数据库,而是让DB管理。也可直接删除从属数据库的记录,这时主数据库对应的记录会被删除。比如:
#include <db.h>
#include <string.h>
...
DB *dbp, *sdbp;
DBT key;
int ret;
char *search_name = "John Doe";
ret = db_create(&dbp, NULL, 0);
if (ret != 0) {

}
ret = db_create(&sdbp, NULL, 0);
if (ret != 0) {

}
ret = sdbp->set_flags(sdbp, DB_DUPSORT);
if (ret != 0) {

}
ret = dbp->open(dbp,
NULL,
"my_db.db",
NULL,
DB_BTREE,
0,
0);
if (ret != 0) {

}
ret = sdbp->open(sdbp,
NULL,
"my_secdb.db",
NULL,
DB_BTREE,
0,
0);
if (ret != 0) {

}
dbp->associate(dbp,
NULL,
sdbp,
get_sales_rep,
0);
memset(&key, 0, sizeof(DBT));
key.data = search_name;
key.size = strlen(search_name) + 1;
sdbp->del(sdbp, NULL, &key, 0);

从 属数据库也可以使用游标进行查询和遍历,但是不能使用DB_GET_BOTH和相关的标记调用DB->c_get(),应该使用DB-> c_pget()。而且在那种情况下,主库的key和从库的key必须都和DB->c_pget()给予的key参数匹配,才返回主库的结果。
比如以下代码在从库里查询人名,然后删除所有主库和从库使用那个名字的记录。
#include <db.h>
#include <string.h>
...
DB *sdbp;
DBC *cursorp;
DBT key, data;
char *search_name = "John Doe";

sdbp->cursor(sdbp, NULL, &cursorp, 0);
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
key.data = search_name;
key.size = strlen(search_name) + 1;
while (cursorp->c_get(cursorp, &key, &data, DB_SET) == 0)
cursorp->c_del(cursorp, 0);


16. 数据库联接
假设你有一个汽车数据库,保存了汽车的颜色、厂商、价格、样式等信息。为了便于检索,分别为颜色、厂商、价格建立了从属数据库。如果要查询符合指定颜色和指定厂商、价格的汽车,就需要数据库联接了。
数据库联接的建立过程如下:
– 打开每一个从库的游标,这些从库和同样的主库关联着
– 把每个游标分别定位到符合单项条件的记录,比如颜色定位到红色,厂商定位到红旗
– 建立游标数组,把上面的每个游标放进去
– 获取联接游标。调用DB->join()并把游标数组传入。
– 遍历匹配的记录,直到返回值非0
– 关闭联接游标
– 关闭所有其他的游标
例如:
#include <db.h>
#include <string.h>
...
DB *automotiveDB;
DB *automotiveColorDB;
DB *automotiveMakeDB;
DB *automotiveTypeDB;
DBC *color_curs, *make_curs, *type_curs, *join_curs;
DBC *carray[3];
DBT key, data;
int ret;
char *the_color = "red";
char *the_type = "minivan";
char *the_make = "Toyota";

color_curs = NULL;
make_curs = NULL;
type_curs = NULL;
join_curs = NULL;
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
if (( ret =
automotiveColorDB->cursor(automotiveColorDB, NULL,
&color_curs, 0)) != 0) {

}
if (( ret =
automotiveMakeDB->cursor(automotiveMakeDB, NULL,
&make_curs, 0)) != 0) {

}
if (( ret =
automotiveTypeDB->cursor(automotiveTypeDB, NULL,
&type_curs, 0)) != 0) {

}
key.data = the_color;
key.size = strlen(the_color) + 1;
if ((ret = color_curs->c_get(color_curs, &key, &data, DB_SET)) != 0)

key.data = the_make;
key.size = strlen(the_make) + 1;
if ((ret = make_curs->c_get(make_curs, &key, &data, DB_SET)) != 0)

key.data = the_type;
key.size = strlen(the_type) + 1;
if ((ret = type_curs->c_get(type_curs, &key, &data, DB_SET)) != 0)

carray[0] = color_curs;
carray[1] = make_curs;
carray[2] = type_curs;
if ((ret = automotiveDB->join(automotiveDB, carray, &join_curs, 0)) != 0)

while ((ret = join_curs->c_get(join_curs, &key, &data, 0)) == 0) {

}
if (ret == DB_NOTFOUND) {

}

17. 配置数据库 – 内存页大小
首先我们需要考虑的是内存页的大小,取值范围在512字节 ~ 64K之间。可通过创建数据库时调用DB->set_pagesize()设置,取值必须为2的指数次方。
当一页内存不足以存储一条记录时,剩下的数据会存在另外一页,这称为内存页溢出。太多的溢出内存页会导致性能下降,溢出页数可通过DB->stat()查看,也可通过db_stat命令行检查。对于BTree访问方法,一页内存理想的情况下能存4条记录。
DB是基于内存页加锁的,这意味着如果一页内存保存的记录数太多,对记录的读写就会导致频繁的加锁/解锁发生,同样会降低性能。一个取巧的做法是先选择合适的页面大小,如果有太多的竞争锁事件发生,再减少页面尺寸。
可用DB_ENV->lock_stat()方法查看锁冲突,或者使用命令行工具db_stat。由于冲突而无法获取锁的数目保存在st_nconflicts字段里。
页大小还会影响数据移入移出磁盘的I/O效率,尤其是缓冲内存不足以容纳整个工作数据集的时候更是如此。
通常对于页的大小,首先要考虑和文件系统的块大小一致,如果页不足以容纳4条记录(对于BTree),再考虑增加页大小。这时页应该越大越好,直到竞争锁事件频繁发生为止。

18. 配置数据库 – 选择缓存大小
缓 存的大小和磁盘I/O的次数息息相关,也是需要考虑的因素。可通过DB->set_cachesize()或者DB_ENV-> set_cachesize()改变缓存的大小,设置值必须为2的指数次方。幸运的是缓存是在打开数据库设置的,因此修改更方便些。
最好的决定缓存大小的方法是先决定一个值,然后在产品环境跑应用。观察磁盘I/O的次数,如果应用的性能较差,富余的内存较多,磁盘I/O次数较多,可增加缓存值。
可使用db_stat命令和-m选项查看缓存的效率,百分比越接近100%效果越好。

19. BTree配置
BTree默认按照字典顺序对key进行排序,可调用DB->set_bt_compare()设置自己的排序函数。排序函数的原型是:
int (*function)(DB *db, const DBT *key1, const DBT *key2)
如果key1 > key2,返回正数;key1 = key2返回0;否则返回负数。
例如一个对整数key的排序函数是:
int
compare_int(DB *dbp, const DBT *a, const DBT *b)
{
int ai, bi;

memcpy(&ai, a->data, sizeof(int));
memcpy(&bi, b->data, sizeof(int));
return (ai - bi);
}

其设置代码如下:
#include <db.h>
#include <string.h>
...
DB *dbp;
int ret;
ret = db_create(&dbp, NULL, 0);
if (ret != 0) {
fprintf(stderr, "%s: %s\n", "my_program",
db_strerror(ret));
return(-1);
}
dbp->set_bt_compare(dbp, compare_int);


BTree支持重复记录,几条记录共享同一个key。对于重复记录,有已序重复记录和无序重复记录之分。一般我们使用已序重复记录。
如果是已序重复记录,可调用DB->set_dup_compare()设置重复记录的排序函数。排序函数的原型同key的排序函数,不过这里是对data部分进行排序。
对于支持无序重复记录的数据库,在插入数据时,如果是调用DB->put()方法,则记录插在重复记录集的结尾。如果是调用游标方法,这要看DBC->c_put()方法的标记:
– DB_AFTER
DBC->c_put()的data参数被作为重复值插入,key是游标当前所指记录的key,DBC->c_put()传入的key参数被忽略。重复记录插在游标所指记录的后面。对于已序重复记录忽略本标记。
– DB_BEFORE
同上,除了新记录插在游标位置的前面。
– DB_KEYFIRST
如果DBC->c_put()参数里的key已经存在,数据库支持无序重复记录,新记录插在重复记录列表的最前面。
– DB_KEYLAST
同上,除了新记录插在重复记录列表的最后面。
在创建数据库的时候,可以调用DB->set_flags()指定数据库是否支持重复记录:DB_DUP标记无序重复记录,DB_DUPSORT标记已序重复记录。
以下代码演示了如何创建支持重复记录的数据库:
#include <db.h>
...
DB *dbp;
FILE *error_file_pointer;
int ret;
char *program_name = "my_prog";
char *file_name = "mydb.db";

ret = db_create(&dbp, NULL, 0);
if (ret != 0) {
fprintf(error_file_pointer, "%s: %s\n", program_name,
db_strerror(ret));
return(ret);
}
dbp->set_errfile(dbp, error_file_pointer);
dbp->set_errpfx(dbp, program_name);
ret = dbp->set_flags(dbp, DB_DUPSORT);
if (ret != 0) {
dbp->err(dbp, ret, "Attempt to set DUPSORT flag failed.");
dbp->close(dbp, 0);
return(ret);
}
ret = dbp->open(dbp,
NULL,
file_name,
NULL,
DB_BTREE,
DB_CREATE,
0);
if (ret != 0) {
dbp->err(dbp, ret, "Database '%s' open failed.", file_name);
dbp->close(dbp, 0);
return(ret);
}

发表评论