首页 > Berkeley DB, David Zhao, 程序设计 > Berkeley DB示例程序详解 (1)

Berkeley DB示例程序详解 (1)

本文通过分析Berkeley DB自带的示例程序来详细阐述了使用Berkeley Db基本功能的方法。这之后还将有更多这类文章,我认为通过学习好的例子和已有代码来学习使用berkeley db是很有效的。

/*
* 这个例子程序是Berkeley DB的示例程序之一(DB/example_cxx/AccessMethod.cpp),
* 它演示了如何使用Berkeley DB的基本功能,包括打开一个数据库,存入若干个
* key/data pair,然后遍历数据库中的数据,最后关闭数据库。
*
* 原始代码中有一些英文注释,但是对于初学者还是不够详细,我没有删除原来
* 的注释,而且添加了针对每一个Berkeley DB操作的更加详细的说明,请参考。
*
* 代码的非关键部分都已删除,所以这里的内容
* 无法直接编译运行。可以直接编译运行的版本我会放到空间的附件中。
*
*
* 用词约定:
* 本文提到的“数据库”是指Berkeley DB的database,相当于关系数据库的一个表。
* 一个数据库当中保存着很多个key/data pair,相当于关系数据库的一个表当中
* 保存着很多条记录。也就是说一个key/data pair相当于关系数据库的一条记录。
* 而数据库环境(DbEnv)是指Berkeley Db的执行环境,相当于一个关系数据库管理系统。
*/

/* 测试用例类声明 */
class AccessExample
{
public:
AccessExample();
void run(bool removeExistingDatabase, const char *fileName);

private:
// no need for copy and assignment
AccessExample(const AccessExample &);
void operator = (const AccessExample &);
};

/*
* 这个例子程序演示了如何使用Berkeley DB的基本功能,包括打开一个数据库,存入
* 若干个key/data pair,然后遍历数据库中的数据,最后关闭数据库。
*/
int
main(int argc, char *argv[])
{
// Use a try block just to report any errors.
// An alternate approach to using exceptions is to
// use error models (see DbEnv::set_error_model()) so
// that error codes are returned for all Berkeley DB methods.
//
try {
AccessExample app;
app.run((bool)(rflag == 1 ? true : false), database);
return (EXIT_SUCCESS);
}
catch (DbException &dbe) {
cerr << “AccessExample: ” << dbe.what() << “n”;
return (EXIT_FAILURE);
}
}

void AccessExample::run(bool removeExistingDatabase, const char *fileName)
{
// Remove the previous database.
if (removeExistingDatabase)
(void)remove(fileName);

// Create the database object.
// There is no environment for this simple example.
// 这里我们没有指定显式的environment,BerkeleyDB会在内部创建一个专门供
// 这个数据库使用的environment. 同时,第二个参数表明,当出现错误后,
// Berkeley DB会抛出异常。
Db db(0, 0);

// 当出现运行错误后,错误信息写入cerr流。
//
// Berkeley DB在调试模式下,可以可选地输出非常丰富的错误信息和其他运行期信息,
// 大大简化了调试过程。比如,你可以让Berkeley DB把错误信息写到一个文件、
// 一个c++ io流中,或者调用用户注册的回调函数由用户自己处理错误信息,
// 以及在错误信息中前缀某些自定义信息,等等。
//
// 关于错误报告和调试的文档:
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/debug/runtime.html
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/am_misc/error.html
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/env/error.html
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/program/errorret.html
db.set_error_stream(&cerr);
// 在每个错误信息前缀 “AccessExample”
db.set_errpfx(“AccessExample”);

// 设置数据库页的size为1024字节。数据库页的设置会在较大程度上影响数据库的性能,
// 这里有关于页设置的说明:
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/am_conf/pagesize.html
db.set_pagesize(1024);       /* Page size: 1K. */

// 设置cache的大小,数据库的页调入内存后,就放在cache当中,也就是说,cache
// 就是存放调入内存的数据库页面的,如果整个数据库中每一个页面都可以调入
// 内存长期存放,那么数据库的速度自然很快,所以,cache确实是越大越好的,
// 当它大于所有数据库页面所占空间后,对性能就没什么影响了。
//
// 设置cache size的指导 :
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
//
// 如果cache填满了,数据库会淘汰不用的页面,若这样的页面有改动,会写回
// 数据库文件。腾出空间后再调入目标页面。这涉及很多磁盘操作,所以数据库
// 操作会突然变慢很多,应用程序的性能就会偶尔
// 发生短暂地下降。为了避免这种性能抖动,你可以在一个单独运行的线程当中,
// 定期淘汰页面腾出cache的空间,保证cache总是有一定的空间可用,
// 这叫做memory trickle。
//
// 方法见此链接:
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
db.set_cachesize(0, 32 * 1024, 0);

// 打开数据库。这个函数的文档 :
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
//
// 这里创建的是一个btree数据库。Berkeley DB支持四种Access Method,也就是
// 数据库文件内部组织数据的方式,包括btree, hash, queue和record number,
// 分别使用DB_BTREE, DB_HASH, DB_QUEUE和DB_RECNO来指定。
//
// 每一种access method都有自己的优点和缺点,适用于某种需求。所以你应该根据自己
// 的应用程序的数据存储需求,数据访问方式来决定使用哪种access method.
// 关于如何选择合适的access method:
// http://www.oracle.com/technology/documentation/berkeley-db/db/ref/am_conf/select.html
db.open(NULL, fileName, NULL, DB_BTREE, DB_CREATE, 0664);

//
// Insert records into the database, where the key is the user
// input and the data is the user input in reverse order.
//
char buf[1024], rbuf[1024];
char *p, *t;
int ret;
u_int32_t len;

// 循环获取用户输入的字符串str, 把str逆序得到str1, 然后存储(str, str1)
// 作为一个key/data pair.
for (;;) {
cout << “input> “;
cout.flush();

cin.getline(buf, sizeof(buf));
if (cin.eof())
break;

if ((len = (u_int32_t)strlen(buf)) <= 0)
continue;
for (t = rbuf, p = buf + (len – 1); p >= buf;)
*t++ = *p–;
*t++ = ‘’;

// Dbt类用于存储用户的一个数据项的信息。一个key/data pair是一次存储
// 操作的单位,相当于关系数据库
// 的一个行(row),key是这行的主键,data是其他各个字段的集合。
//
// Berkeley DB不对data做细分和理解,
// 应用程序自然知道自己存储的数据的结构和意义。
//
// key/data pair中的key和data都是一个数据项,它们各需要一个Dbt对象来描述。
// 由于Berkeley DB存储的是字节串,
// 它不理会数据的更多意义,比如类型等,所关心数据项信息只包括:
// 字节串起始地址,长度,内存管理的flags(约定了在读和写一个key/data pair
// 时,由谁分配和释放Dbt::data所指向的内存),
// 以及用于做分块读取的字段,这个后面再讲。
//
// 这里我们创建的两个对象key和data分别代表要存储的一个key/data pair
// 的key和data。我们把字节串的起始地址和长度传给了它们,Berkeley DB即
// 可得到这两个字节串。
Dbt key(buf, len + 1);
Dbt data(rbuf, len + 1);

// 存储这个key/data pair。DB_NOOVERWRITE 表示如果已经有了这个key,
// 那么不要覆盖那个key/data pair,
// 而是返回错误。Db::put的第四个参数允许你设置若干种flag,来控制插入
// 一个key/data pair的行为。
//
// Db::put的文档:
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
ret = db.put(0, &key, &data, DB_NOOVERWRITE);

// 如上所述,由于不覆盖已有的相同key的key/data pair,如果这样的key
// 真的存在,Db::put就会返回DB_KEYEXIST。
if (ret == DB_KEYEXIST) {
cout << “Key ” << buf << ” already exists.n”;
}
}
cout << “n”;

// We put a try block around this section of code
// to ensure that our database is properly closed
// in the event of an error.
//
try {
// Acquire a cursor for the table.
Dbc *dbcp;
// 创建一个游标来遍历数据库。游标的作用与ODBC/JDBC等的游标的意义相同,
// 它指向一个key/data pair,可以
// 更改、读取、删除它所指向的key/data pair,同时具有游标稳定性–
// 它所指向的key/data pair不会被其他游标修改或者删除。
db.cursor(NULL, &dbcp, 0);

// Walk through the table, printing the key/data pairs.
// 此处我们是要使用游标dbcp遍历整个数据库,所以我们不需要指定key的值,
// key和data都是用来存储返回结果的。
// 并且,在这种默认情况下,用于保存返回结果的内存有Berkeley DB
// 负责分配和释放。
// 你也可以通过指定其他的flag,并且自己分配并且/或者自己释放存储着
// 结果字节串的内存。
//
// 类Dbt的文档:
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
// 里面有所有的flags,以及几种Dbt的内存管理方式。
Dbt key;
Dbt data;

// 循环获取下一条key/data pair。当没有更多的key/data pair时候,
// Dbc::get会返回非0值。一个游标dbcp在创建之初,
// 并不指向任何一条key/data pair,而第一次调用Dbc::get并且传入
// DB_NEXT flag,就会使得dbcp位于第一个key/data pair。
// Dbc::get的文档:
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
//
// key/data pair的顺序是由数据库的存取方式定义的。比如对于btree这种
// 存取方式,key的顺序由它们的大小关系决定。
// 你可以配置key的比较函数来自定义key的比较方式,见文档:
// http://www.oracle.com/technology/documentation/berkeley-db/db/api_cxx/frame.html
while (dbcp->get(&key, &data, DB_NEXT) == 0) {

// 获取Dbt中的数据,也就是字节串的首地址。由于key和data对象
// 使用了默认的flags,所以它们所引用的内存由Berkeley Db负责
// 分配和回收。你也可以使用其他的内存管理方式。
// 详情Dbt的文档。
char *key_string = (char *)key.get_data();
char *data_string = (char *)data.get_data();
cout << key_string << ” : ” << data_string << “n”;
}

// 关闭游标。 一定别忘了做这个,并且尽早关闭游标。
// 这是因为游标稳定性导致游标所引用的
// 页面被锁定,使用同一个数据库的其他进程或者线程无法访问这些页面。
dbcp->close();
}
catch (DbException &dbe) {
cerr << “AccessExample: ” << dbe.what() << “n”;
}

// 关闭数据库。
db.close(0);
}

  1. legendsino
    2009年6月1日18:13 | #1

    请问用Db::put添加key/data时,key/data是必须要用Dbt(void *data, size_t size)构造吗?
    用Dbt()构造,set_flags(DB_DBT_USERMEM)后为什么无法向数据库正常放入数据?
    代码如下:

    char discreption[32]=”aaa”;
    char k[32]=”111″;

    Dbt key,data;

    ///////////////////////////////
    //没有用Dbt(void *data, size_t size)构造
    key.set_data(&k);
    key.set_ulen(32);
    key.set_flags(DB_DBT_USERMEM);
    data.set_data(&discreption);
    data.set_ulen(32);
    data.set_flags(DB_DBT_USERMEM);
    ///////////////////////////////

    if(ret=db_prc->put(NULL,&key,&data,DB_NOOVERWRITE)==0)
    {
    std::cout<err(ret,”DB->put”);
    }

    看了下源代码,还是不明白啊。。

  2. davidzhao
    2009年6月18日10:32 | #2

    这种情况,不需要设置DB_DBT_USERMEM。 事实上,在调用put的时候,dbt对象所指向的内存一定是你自己分配的。 所以,你不应该调用set_ulen,而是调用key/data.set_size(32);

    另外,key.set_data(k)就可以了。

    DB_DBT_USERMEM在读取数据的时候使用。

  3. ayang
    2009年6月19日16:48 | #3

    long dwCount = 0;
    while (dbcp->get(&key, &data, DB_NEXT) == 0) {

    // 获取Dbt中的数据,也就是字节串的首地址。由于key和data对象
    // 使用了默认的flags,所以它们所引用的内存由Berkeley Db负责
    // 分配和回收。你也可以使用其他的内存管理方式。
    // 详情Dbt的文档。
    char *key_string = (char *)key.get_data();
    char *data_string = (char *)data.get_data();
    cout << key_string << ” : ” << data_string << “n”;

    ++ dwCount;
    }
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    在这里我有个问题,为什么我遍历出来的数据要比我实际插入的少呢,如我插入28000条,可这里dwCount却显示为18929, 如果我插入2800000(280w)则只有32768条,我想知道为什么?
    ????

  4. ayang
    2009年6月19日16:54 | #4

    在插入时我用分别用DB_NOOVERWRITE与DB_AUTO_COMMIT进行测试,发现它一样,最后
    遍历出来的记录数还是跟插入时的不一至。

    ret = db.put(0, &key, &data, DB_NOOVERWRITE);

  5. ayang
    2009年6月22日08:43 | #5

    怎么没人回帖呢?

  6. davidzhao
    2009年6月22日10:08 | #6

    你插入的key/data pairs 当中, 有重复的key,后插入的key/data pair会覆盖之前插入的相同key的数据,相当于对它做了更新. 、

    你可以重新创建一个数据库,设置DB_DUP flag (在Db::open之前调用 Db::set_flags(DB_DUP)), 然后再插入数据,就相同了。

  7. davidzhao
    2009年6月22日10:11 | #7

    方法同上。 设置DB_NOOVERWRITE和DB_AUTO_COMMIT不能达到这个目的。这里你只需要Db::put 的flags 是0就可以了。

  8. chaohuang
    2009年6月22日13:40 | #8

    @ayang
    ayang, 抱歉让你久等。再次申明,此网站不做support用!如果你是付费客户,请联系Oracle的销售支持团队;非付费客户,请到OTN的论坛提问。

  9. ayang
    2009年6月22日16:09 | #9

    非常感谢。

  1. 本文目前尚无任何 trackbacks 和 pingbacks.
注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。
Դ