0:Berkeley DB的子系统
1:存取管理子系统
为数据库创建和读写提供支持,它不包含事务
2:内存池管理子系统
对DB使用的共享缓冲区进行管理,
3:事务子系统
提供事务管理功能
4:锁子系统
存取管理子系统用锁子系统同步对数据库的读写,事务子系统通过锁子系统实现多个事务的并发控制
5:日志子系统
用来支持事务子系统对数据的恢复,保证数据的一致性
应用程序直接调用的是存取管理子系统和事务子系统,这两个系统调用下层的内存池,锁,日志子系统
如果程序只需要支持多线程多进程并发但不需要事务,则可以只用锁子系统,如果程序单用户访问,不需要事务,则可以不用事务和锁子系统,这样会获得最高的性能
1:4种访问存取方式
1:访问方式应该和存取方式对应
HASH和BTree:当记录号不是数据存储的主键时,采用这两种访问方式,对于小数据量,两者性能没有区别,当数据量增大,BTree需要更多的附加信息来完成操作,这会导致IO繁忙,所以大数据Hash性能更好,由于BTree的key是依次放置的,如果连续访问顺序的数据,可能BTree的性能更好
Queue和Recno:插入时key为空,插入后会将key返回
Queue:定长,记录号是固定的
Recno:不定长,记录号可以选择固定和不固定
2:BTree、Queue、Recno算法可以记录编号,编号基于1,获得方式为DB->get()或者用游标,指定标志DB_SET_RECNO,返回的DBT data是一个db_recno_t类型,db_recno_t是32位无符号数据,这表明最多存储40多亿条数据
3:BTree启用编号会影响性能,BTree的编号是不固定的,在Queue中当达到最大编号时编号变为1,返回错误EFBIG,在Recno中编号达到最大时返回错误
2:环境
1:支持多个数据库文件在同一环境中
2:为多进程多线程访问提供支持,在一个环境中,可以共享一些内存和锁
3:提供事务支持
4:复制支持
5:支持日志系统
3:异常
try
{
}
catch(DbException &e)
{
e.get_errno(); //error no
e.what(); //message
}
catch(std::exception &e)
{
}
可以通过DB_CXX_NO_EXCEPTIONS关闭异常,程序中通过返回值处理错误
4:返回值
函数调用成功返回0,错误返回非0
5:基本操作
Db db(NULL,0); //环境,是否抛出异常
db.open(NULL,"test.db",NULL,DB_BTREE,DB_CREATE,0);
string str="life";
int nLife=1000;
Dbt key,value;
key.set_data((void*)(str.c_str()));
key.set_size(str.length());
value.set_data(&nLife);
value.set_size(sizeof(int));
if(db.put(NULL,&key,&value,DB_NOOVERWRITE)==DB_KEYEXIST)
{
return 0;
}
db.close(0);
6:打开标志
DB_CREATE,DB_EXCL,DB_RDONLY,DB_TRUNCATE
7:管理
//获得打开标志
Db db(NULL,0);
...
u_int32_t flags;
db.get_open_flags(&flags);
//删除数据库
Db db(NULL,0);
db.remove("mydb.db",NULL,0);
//重命名数据库
Db db(NULL,0);
db.rename("old.db",NULL,"new.db",0);
8:错误处理
Db db(NULL,0);
db.set_error_stream()
db.set_errcall() //设置一个函数指针,在异常发生时回调此函数
db.set_errfile() //异常发生时,将异常消息写入一个文件
db.set_errpfx() //异常发生时,错误消息的前缀
db.err() //在catch()中使用,构建一个错误消息,此消息将根据上面几个函数的
//调用发送给回调函数,或者写入文件,或者给C++ ostream,如果都没有设置
//将会发送C++标准异常,然后在exception &e中通过e.what()写出
9:环境
u_int32_t evnFlags=DB_CREATE|DB_INIT_MPOOL; //必须初始化内存
string str="/xx/xxx"; //必须指定路径
DbEvn evn(0);
evn.open(str.c_str(),evnFlags,0);
Db db(&evn,0);
...
evn.close(0);
10:Records
1:获取数据
Dbt t1(123,sizeof(int));
string str=(char *)t1.get_data();
2:数据库默认不支持重复键,写重复键默认为覆盖值,设置DB_NOOVERWRITE为不准覆盖值
int ret=db.put(NULL,&key,&data,DB_NOOVERWRITE);
if(ret==DB_KEYEXIST)
{
}
3:删除Records
db.del(NULL,&key,0);
11:持久化数据
事务能够保证数据被写回磁盘,如果不想用事务,则可以调用
db.sync()
事务开销巨大,在一个没有使用事务的数据库关闭时默认执行此函数,如果在调用Db::close()指定了DB_NOSYNC则不执行写回操作
如果你没有使用事务你的程序或者系统蹦了,你应该销毁此数据库或者调用Db::verify(),如果verify()没有把数据库调整正确,你应该使用命令行db_dump -r或者-R去拯救你的数据库
12:游标
//创建
Dbc *cur;
Db db(NULL,0);
db.cursor(NULL,&cur,0);
//正向遍历
Dbt key,data;
while(cur->get(&key,&data,DB_NEXT)==0)
{
}
//反向遍历
while(cur->get(&key,&data,DB_PREV)==0)
{
}
//查找特定数据,可以通过key,或者data,或者key和data的组合,或者部分查找
cur->get(&key,&data,DB_GET);
//当查找一个数据时,如果没有找到,会返回DB_NOTFOUND
//相关标志
//SET只关注key,Get会同时关注key和data
DB_SET //游标跳转到key相同的第一条记录
DB_SET_RANGE //只能在BTree模式下使用,跳转到Key大于或等于的第一条记录,如果自己不提供比较函数,则会使用默认比较函数
DB_GET_BOTH
DB_GET_BOTH_RANGE
//关闭
cur->close();
13:游标访问Duplicate Records
1:Duplicate只会出现在BTree和Hash中,如果使用Db::del()删除一个key,这个set将会被全部删除,如果只想删除其中一个就应该使用游标
2:参数
DB_NEXT_NODUP
DB_PREV_NODUP
DB_NEXT_DUP //没有返回DB_NOFOUND
14:使用游标插入数据
使用cur->put();
1:DB_NODUPDATA
如果数据库中已经存在相同的key,插入操作返回DB_KEYEXIST
如果为数据库提供了比较函数,则会插入到相应位置,如果没有提供,对于BTree会使用默认排序函数
想要使用此标志必须在创建数据库的时指定DB_DUPSORT
2:DB_KEYFIRST
如果数据库不支持Duplicate,插入操作会以默认的方式进行
如果数据库支持Duplicate并且提供了排序函数,会插入到其应该的位置
如果数据库支持Duplicate并且没有提供排序函数和已经存在了一个key值的记录,则他会插到此记录最前面
3:DB_KEYLAST
和DB_KEYFIRST基本一样,唯一的差别是如果没有提供排序函数,则会将Record插入末尾
15:使用游标删除数据和覆盖数据
cur->del(0);
cur->put(&key,&value,DB_CURRENT); //你不能覆盖key,如果你指定了key,其实是无效的
如果数据库支持重复记录,你覆盖的记录是重复记录中的一条,只有你覆盖的数据和以前的数据排序是一样的,这次覆盖才会成功;特别的,如果你使用的是默认排序算法
如果你提供了排序算法,并且这个排序算法使用了data,想要执行覆盖重复记录很可能失败,替代办法是执行删除然后创建记录的操作
16:Secondary Databases
1:将第一个数据库key查询出的value通过fun函数计算后得到新的值作为第二个数据库的key,提供的函数返回0则代表插入此记录
Db dbPrimary(NULL,0);
Db bdSecondary(NULL,0);
dbPrimary.open(...);
bdSecondary.open(...);
dbPrimary.associate(NULL,&bdSecondary,fun,0);
2:关闭时应该先关闭二级数据库,再关闭一级数据库
3:如果想将fun计算后的值为多个,将这多个值作为key进行查询,则使用如下方式:
skey.set_flags(DB_DBT_MULTIPLE|DB_DBT_APPMALLOC);
skey.set_data(...); //一个DBT的数组,数据中的元素内容必须是唯一的
skey.set_size(2); //数组元素个数
4:获取Secondary Database的数据
如果一个数据库被associate到primary数据库,则当调用get()时,返回的是primary的value,如果使用pget(),返回的是primary的key和value
5:通过del()删除一个二级数据库的记录会导致primary数据库相应的记录被删除,如果你的二级数据库有支持dumplicate key,删除一个key会导致整个key相同的记录被删除
6:可以使用游标遍历二级数据库,返回的value是primary数据库的value
7:二级数据库不能增加,修改数据,但能删除数据
17:Join cursor
步骤:
1:创建多个二级数据库,附加到同一个一级数据库
2:为每个二级数据库创建一个游标
3:创建一个游标指针数组,放二级数据库的游标指针,最后一个数组元素为空
4:创建一个新的游标,调用一级数据库Db::join();
5:使用,删除
18:配置
1:page size
Db::set_pagesize();512bytes到64kb,一般设置为系统页面大小,此选项受四个方面的影响
(1):overflow页,当记录超出了页大小,会将超出的部分保存在overflow页中,由于overflow页是额外的页面,这会影响性能
(2):DB锁定是以此设置为单位,如果设置的太大,锁定的数据会更多,会影响并发性能
(3):DB页大小是系统页面大小的整数倍效率会最高
(4):使用事务时,页大小会影响数据库错误的恢复
2:chche size
Db::set_cachesize()或者Evn::set_cachesize(),你的缓存大小必须是2的平方,根据程序测试获得最佳的值
3:BTree configuration
(1):Db::set_bt_compare() 设置排序函数
(2):Db::set_dup_compare() 支持重复记录的排序函数,如果不提供此函数,put()操作会将记录插入到重复记录集的末尾,或者在put()是制定:DB_AFTER,DB_BEFORE,DB_KEYFIRST,DB_KEYLAST
(3):配置数据库支持重复记录:Db::set_flags():
a:DB_DUP 数据库支持不排序的重复记录
b:DB_DUPSORT 数据库支持排序的重复记录
4:几种情况下你可能会提供自己的排序函数而不是用默认的
1:对于你的本地语言,使用特殊的排序准则
2:如果你用int作为key,berkeley DB实际上都认为key是string,int是4个字节的string,当你传入一个key进行比较时,在小端口计算机系统上,由于高字节在前,会进行一些无谓的比较导致性能损失
3:为了性能考虑,你可能不想比较所有的字符,比如:只比较前五个字符,如果相等,就认为他们是相等的
19:杂项
1:BTree
Db::set_bt_compress(dbp,NULL,NULL), 后面两个NULL参数表示使用BDB自带的缺省压缩和解压缩算法,该函数需要在DB被打开之前调用
2:Hash
1. DB->set_h_ffactor()设置填充因子,用于确定每个hash bucket中可能容纳key/value的一个近似值,以决定hash table应该在什么时候增长或缩短.请参照以下设置方式
(pagesize - 32) / (average_key_size + average_data_size + 8)
2. DB->set_h_hash(),设置自定义的hash方法
3. DB->set_h_nelem(),用于设定hash table中可能容纳的element数量,如果设置的比较接近真实情况,将更好的避免由于hash bucket动态增长而带来的性能损失,设置该值时也需要考虑设置fill factor
3:Queue
DB->set_re_len(),确定记录长度,短于该长度,用DB->set_re_pad()中的的字符填充,长于改长度产生错误
20:分区
1. 只用BTree和Hash两种方式可以支持partitioning。
2. DB->set_partition(),DB->set_partition_dirs(), 这两个函数用于对DB进行partition,他们必须在数据库第一次被open之前调用,一旦partition成功之后,其partition的scheme将不能再改变。其中后面的函数主要用于指定partition文件所在的home directory。分区策略是,可以给partition指定其所包含的key的值,也可以通过回调函数的方式,通过回调函数的返回值来确定该键应该存放的partition。
a--f to go on partition 0
g--p to go on partition 1
q--z to go on partition 2
如果打算使用数组的方式进行partition,set_partition中回调函数参数为NULL。
1 void partition_exampe_with_array() {
2 DBT partKey[2];
3 int ret;
4
5 memset(partKeys,0,sizeof(DBT) * 2);
6 partKeys[0].data = "g";
7 partKeys[0].size = sizeof("g") - 1;
8 partKeys[1].data = "q";
9 partKeys[1].size = sizeof("q") - 1;
10
11 dbp->set_partition(dbp,3,partKeys,NULL);
12 }
如果打算使用回调的方式进行partition,set_partition中分区提示数组参数为NULL。
1 void partition_example_with_callback() {
2 dbp->set_partition(dbp,3,NULL,db_partition_fn);
3 }
4
5 //参数key实际存放的partition No = ret % number_of_partitions
6 u_int32_t db_partition_fn(DB* db,DBT* key) {
7 char* key_data;
8 u_int32_t ret;
9
10 key_data = (char*)key->data;
11 //根据key_data的值进行自定义运算,从而判定该key应该存放的partition。
12 ret = 0;
13 return ret;
14 }
3. DB->set_partition_dirs(),该函数必须在数据库创建和打开之前设定,一旦成功设定并打开DB,该值将不可变。推荐设置和partition相同数量的dirs。dir可以通过绝对和相对路径支出,可以位于不同的磁盘。如果DB的打开是基于Environment的,在设置之前,需要保证所设定目录已经存在于DB_ENV->add_data_dir()的列表中。