http://www.cnblogs.com/stephen-liu74/archive/2011/04/20/2023049.html
一、通用配置
1. 选择合适的access method(BTree、Hash、Queue or RecNo)
2. (DB->set_pagesize)选择合适的pagesize,处于性能考虑,最好和os的block size匹配,除非每个key/value的数据很大,如果pagesize过小,会导致overflow page的问。
3. DB->set_cachesize
4. DB->set_lorder设置字节序。
5. DB->set_flag(DB_DUP)设置BTree的存储是否支持duplicate keys,缺省情况不支持重复键。DB->get()只是返回重复键中的第一个键值对,重复键数据的获取只能通过DBC-> get(), 重复键的插入缺省行为是append方式,如果打算自定义需要通过DBC->put(),同时制定DB_AFTER, DB_BEFORE, DB_KEYFIRST or DB_KEYLAST.
6. DB->set_flag(DB_DUPSORT)设置data为自动排序模式,这将极大的提高searching和join的效率。DB->set_dup_compare可以设置data的自定义排序方法。如果该表示已经设置,BDB则不允许生成duplicate data,即key/value都是相同的。
二、BTree配置
1. DB->set_bt_compare, 用户可以自定义key的比较函数
1 int compare_int(DB* dbp, const DBT *a, const DBT *b) {
2 int ai, bi;
3 memcpy(&ai,a->data,sizeof(int));
4 memcpy(&bi,b->data,sizeof(int));
5 return ai - bi;
6 }
7
8 //比较前5个字符的比较函数
9 int compare_dbt(DB* dbp,const DBT *a, const DBT *b) {
10 int len;
11 u_char *p1, *p2;
12 for (p1 = a->data, p2 = b->data, len = 5; len--; ++p1,++p2) {
13 if (*p1 != *p2)
14 return (long)*p1 - (long)*p2;
15 }
16 return 0;
17 }
2. DB->set_bt_prefix,用于前缀比较,其比较规则应和完全比较的规则一致,应用该方法主要便于BDB中的page每次只是存储能够区分任何两个键的差别,从而使每页可以存储更多的数据,提高查询的效率,该函数返回任意两个键比较之后的最小可识别的字符数量,如果两个keys在比较时,比较长度直到长度较小的结尾时仍然相等,则返回较小key的长度加一。
1 u_int32_t compare_prefix(DB* dbp, const DBT *a, const DBT *b) {
2 size_t cnt, len;
3 u_int8_t *p1,*p2;
4 cnt = 1;
5 len = a->size > b->size ? b->size : a->size;
6 for (p1 = a->data, p2 = b->data; len--; ++p1,++p2,++cnt) {
7 if (*p1 != *p2)
8 return cnt;
9 }
10
11 if (a->size < b->size)
12 return a->size + 1;
13 if (b->size < a->size)
14 return b->size + 1;
15 return b->size;
16 }
3. DB->set_bt_minkey, 用于设置DB中每个page上可容纳的key/value的最小数量,参考如下公式:
maximum_size = page_size / (minimum_keys * 2)
之所以minimum_key乘以2,是因为key和value在page中需要占用两个slot,此例中,如果page_size = 8k, minimum_keys = 2,那么任何key或value超过2k的项都将存入overflow page。
4. DB->set_bt_compress(dbp,NULL,NULL), 后面两个NULL参数表示使用BDB自带的缺省压缩和解压缩算法,该函数需要在DB被打开之前调用。
三、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。
四、Recno和Queue配置
1. DB->set_re_len(),用于设置fixed-length记录的长度。短于该长度的数据,将用DB->set_re_pad中设置的字符进行尾部填充,长于该值的data,将会导致一个错误发生。
五、DB Partitioning
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()的列表中。
六、Secondary Indexes
1. 可以通过给key/value中value的部分信息建立索引,作为secondary db,其key为期望的索引数据,value为primary db的key。
1 struct student_record {
2 char student_id[4];
3 char last_name[15];
4 char first_name[15];
5 };
6
7 void example_of_secondary() {
8 DB *dbp, *sdbp;
9 int ret;
10
11 //open/create primary
12 if ((ret = db_create(&dbp,dbenv,0))
13 handle_error(ret);
14 if ((ret = dbp->dbp,NULL,"student.db",NULL,DB_BTREE,DB_CREATE,0600)) != 0)
15 handle_error(ret);
16
17 //open/create secondary
18 if ((ret = db_create(&sdbp,dbenv,0)) != 0)
19 handle_error(ret);
20 if ((ret = sdbp->set_flags(sdbp,DB_DUP|DB_DUPSORT)) != 0)
21 handle_error(ret);
22 if ((ret = sdbp->open(sdbp,NULL,"lastname.db",NULL,DB_BTREE,DB_CREATE,0600)) != 0)
23 handle_error(ret);
24 //如果基于已有数据recreate secondary index,需要在associate方法中传入DB_CREATE标志。
25 if ((ret = dbp->associate(dbp,NULL,sdbp,getname,0)) != 0)
26 handle_error(ret);
27 }
28
29 int getname(DB* secondary,const DBT* pkey, const DBT* pdata,DBT* skey) {
30 /*如果在构造skey的数据时,需要自定义组合某些数据信息,此时需要用户函数自行分配内存并copy数据到该缓冲,同时也需要给skey的flag设置DB_DBT_APPMALLOC*/
31 memset(skey,0,sizeof(DBT));
32 skey->data = ((struct student_record*)pdata->data)->last_name);
33 skey->size = sizeof(((struct student_record*)pdata->data)->last_name);
34 return 0;
35 }
2. 如果删除primary中的数据,相关的secondary数据也会自动删除,反之亦然。通过DB->pget和DBC->pget()可以通过secondary的查询获取primary中key和value。但是不能直接在secondary DB中直接插入数据。
七、DB Cursor
1. DB_CONSUME标志:Read-and-Delete, 这个标志只能用于Queue类型的DB,头部记录将被返回并删除。
2. DB->join: 相等性连接。请参照以下示例:
1 //DB_Personnel { key = SSN data = record contain name, address, phone number,job titile }
2 //DB_Lastname { key = lastname data = SSN }
3 //DB_jobs { key = job title data = SSN }
4
5 void example() {
6 DBC *name_curs,*job_curs,*join_curs;
7 DBC *carray[3];
8 DBT key,data;
9 int ret, tret;
10
11 name_curs = NULL;
12 job_curs = NULL;
13 memset(&key,0,sizeof(key));
14 memset(&data,0,sizeof(data));
15
16 if ((ret = name_db->cursor(name_db,txn,&name_curs,0)) != 0)
17 goto err;
18
19 key.data = "smith";
20 key.size = sizeof("simth");
21 if ((ret = name_curs->get(name_curs,&key,&data,DB_SET)) != 0)
22 goto err;
23
24 if ((ret = job_db->cursor(job_db,txn,&job_curs,0)) != 0)
25 goto err;
26
27 key.data = "manager";
28 key.size = sizeof("manager");
29 if ((ret = job_curs->get(job_curs,&key,&data,DB_SET)) != 0)
30 goto err;
31
32 carray[0] = name_curs;
33 carray[1] = job_curs;
34 carray[2] = NULL;
35
36 if ((ret = pers_db->join(pers_db,carray,&join_curs,0)) != 0)
37 goto err;
38
39 while ((ret = join_curs->get(join_curs,&key,&data,0)) == 0) {
40 //TODO Process record returned in key/data
41 }
42
43 if (ret == DB_NOTFOUND)
44 return = 0;
45
46 err:
47 if (join_curs != NULL && (tret = join_curs->close(join_curs) != 0 && ret == 0)
48 ret = tret;
49 if (name_curs != NULL && (tret = name_curs->close(name_curs) != 0 && ret == 0)
50 ret = tret;
51 if (job_curs != NULL && (tret = job_curs->close(job_curs)) != && ret == 0)
52 ret = tret;
53 return ret;
54 }
八、Bulk获取和更新
1. DB_MULTIPLE和DB_MULTIPLE_KEY分别对应于获取所有指定key的duplicate data和多个keys/values
DB_MULTIPLE_INIT用于最初的初始化调用。
DB_MULTIPLE_NEXT: 该宏总是和DB_MULTIPLE标志绑定使用。
DB_MULTIPLE_KEY_NEXT: 该宏总是和DB_MULTIPLE_KEY标志绑定使用,同时要求底层db的类型为BTree or Hash。
DB_MULTIPLE_RECNO_NEXT: 该宏总是和DB_MULTIPLE_KEY标志绑定使用,同时要求底层db的类型为Queue or Recno。
在调用DB->get() or DBC->get()的时候,将DBT data参数data字段指向buffer,ulen表示buffer的长度,flags字段需要设置为DB_DBT_USERMEM
1 #define BUFFER_LENGTH (5*1024*1024)
2 int rec_display(DB *dbp) {
3 DBC *dbcp;
4 DBT key,data;
5 size_t retklen,retdlen;
6 char *retkey,*retdata;
7 int ret,t_ret;
8 void *p;
9
10 memset(&key,0,sizeof(key));
11 memset(&data,0,sizeof(data));
12
13 if ((data.data = malloc(BUFFER_LENGTH)) == NULL)
14 return errno;
15 data.ulen = BUFFER_LENGTH;
16 data.flags = DB_DBT_USERMEM;
17
18 if ((ret = dbp->cursor(dbp,NULL,&dbcp,0)) != 0) {
19 dbp->err(dbp,ret,"DB->cursor");
20 free(data.data);
21 return ret;
22 }
23
24 for (;;) {
25 if ((ret = dbcp->get(dbcp,&key,&data,DB_MULTIPLE_KEY | DB_NEXT)) != 0) {
26 if (ret != DB_NOTFOUND)
27 dbp->err(dbp,ret,"DBcursor->get");
28 break;
29 }
30
31 for (DB_MULTIPLE_INIT(p,&data);;) {
32 DB_MULTIPLE_KEY_NEXT(p,&data,retkey,retklen,retdata,retdlen);
33 if (p == NULL)
34 break;
35 printf("Key: %.*s, Data: %.*s\n",(int)retklen,retkey,retdlen,retdata);
36 }
37
38 if ((t_ret = dbcp->close(dbcp)) != 0) {
39 dbp->err(dbp,ret,"DBcursor->close");
40 if (ret == 0)
41 ret = t_ret;
42 }
43
44 free(data.data);
45 return ret;
46 }
47 }
九、部分记录读取和存储
1. 在进行数据操作的时候需要为DB->put() or DB->get()的DBT data参数的flags字段设定DB_DBT_PARTIAL标志。同时还要指定doff(偏移量)和dlen(长度)。如"ABCDEFGHIJ",如果doff=3 and dlen=4,其所操作的字符串为"DEFG"。在进行数据部分替换的时候,DB->put()将使用data的data字段和size字段表示的数据替换其doff字段和dlen字段所包含的数据,如果size大于ulen,该key的data item将增大,否则data item将缩小。
2. 在使用DB->get() or DBC->get()获取数据的时候,其返回的data.data指针指向实际包含的数据,由于该指针所指向的地址在缺省情况下是有BDB内部自行分配的,因此当有任何对BDB的handle操作时,该地址将变为无效,这其中包括多线程中的争用。如果打算避免此类问题的发生,需要自行分配内存空间,同时将DBT data的flags标志设定DB_DBT_USERMEM即可。
十、 错误支持
1. DB->db_strerror(error_ret): 其功能和用法类似于Ansi C中的strerror,只是该函数不仅能够处理system errors,也能够处理BDB特定的errno。
2. DB->db_setpfx(), 为后面调用DB->db_err()和DB->db_errx()函数输出错误信息时,提供一致性的前缀。
3. DB->db_err(), 其用法类似printf,只是还会为第二个参数中给出的错误返回值打印出其错误消息。
4. DB->db_errx(), 其用法类似printf,但是比db_err()更简单。
1 #define DATABASE "access.db"
2 void errorExampe() {
3 int ret;
4 (void)dbp->set_errpfx(dbp, program_name);
5
6 if ((ret = dbp->open(dbp,NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
7 dbp->err(dbp, ret, "%s", DATABASE);
8 dbp->errx(dbp, "contact your system administrator: session ID was %d", session_id);
9 return 1;
10 }
11 }
输出结果:
my_app: access.db: Permission denied.
my_app: contact your system administrator: session ID was 14
十一、 Little Endian对Key排序的影响
假设用户存放254到257这4个数字以Little Endian的方式put到DB中,其内存布局为:
254 fe 0 0 0
255 ff 0 0 0
256 0 1 0 0
257 1 1 0 0
如果将他们视为缺省的lexicographical排序方式,其排序结果如下:
256 0 1 0 0
257 1 1 0 0
254 fe 0 0 0
255 ff 0 0 0
如果在Big Endian的体系结构中,他们的内存布局为:
254 0 0 0 fe
255 0 0 0 ff
256 0 0 1 0
257 0 0 1 1
结论:推荐使用Big Endian的方式存储整型数据,以便使Tree更加紧凑。
对于数据库文件本身,其byte order是相对独立的,因为在多个拥有不同byte order的machine中copy这些数据文件是没有问题,字节序的处理细节均需交给程序本身来完成。log文件的字节序是有依赖的,因为只能在拥有相同byte order的主机之间进行copy。
十二、Env相关的设置
1. 环境的目录信息设置。
dbenv->set_lg_dir(dbenv,"logdir");
dbenv->set_data_dir(dbenv,"datadir");
dbenv->set_tmp_dir(dbenv,"temp");
dbenv->open(dbenv,"/a/database",flags,mode);
以上代码将日志文件存放于/a/database/logdir,数据文件存放于/a/database/datadir, 临时文件存放于/a/database/temp.
十三、事物
1. DB_TXN_WRITE_NOSYNC and DB_TXN_NOSYNC标志,将会让事物的commit操作避免了同步的disk IO,从而极大的提高了事物的性能,也同时降低了死锁的风险。
2. 当有多个简单的事物操作同时发生时,如DB->put(),是不会有死锁产生的,因为他们同一时间只是持有一把锁,复杂的事物由于会在很长的时间内同时持有多把锁,因此,如果同时存在多个复杂的事物将会极大的提高死锁的几率。如果出现该类情况,可以考虑将复杂的事物划分为多个子嵌套事物,以便缓解死锁带来的问题。
3. 数据隔离性:下面的事例用于表示多个线程同时从DB中读取同一条记录,并且将其data加一后回写。如果此时没有事物的隔离性保护,由于是并发的在同一条记录进行操作,因而可能会产生race condition,最终会导致错误的结果。
1 int add_color(DB_ENV* dbenv,DB* dbp,char* color, int increment) {
2 DBT key, data;
3 DB_TXN* tid;
4 int fail,original,ret,t_ret;
5 char buf64;
6
7 memset(&key,0,sizeof(key));
8 key.data = color
9 key.size = strlen(color);
10 memset(&data,0,sizeof(data));
11 //设置该标志用于提示BDB负责分配内存(based on malloc function),由caller决定何时通过free释放该内存,
12 //对于多线程同时操作的情况,应该使用该方式,从而避免在BDB的内部多个线程共享同一地址空间。该标志还可以带来
13 //一定的效率提升,由于只是进行了一次内存分配,caller即可以自由的使用,并根据自己的情况决定何时释放,相比于
14 //传统的local memory方式,数据持有者将节省了一次内存copy的工作。
15 data.flags = DB_DBT_MALLOC;
16
17 for (fail = 0;;) {
18 if ((ret = dbenv->txn_begin(dbenv,NULL,&tid,0)) != 0) {
19 dbenv->err(dbenv,ret,"DB_ENV->txn_begin");
20 exit(1);
21 }
22
23 //DB_RMW标志表示在读取数据的时候即直接获取写锁,因为该代码逻辑是获取指定key的数据,加一后回写,
24 //因而最终还是有写操作放生在同一记录上,使用该标志可以降低从读锁到写锁升级过程中而带来的死锁可能。
25 switch (ret = dbp->get(dbp,tid,&key,&data,DB_RMW)) {
26 case 0:
27 original = atoi(data.data);
28 break;
29 case DB_LOCK_DEADLOCK:
30 default:
31 if ((t_ret = tid->abort(tid)) != 0) {
32 dbenv->err(dbenv,t_ret,"DB_TXN->abort");
33 exit(1);
34 }
35 if (fail++ == MAXIMUM_RETRY)
36 return ret;
37 continue;
38 case DB_NOTFOUND:
39 original = 0;
40 break;
41 }
42 if (data.data != NULL)
43 free(data.data);
44
45 snprintf(buf,sizeof(buf),"%d",original + increment);
46 data.data = buf;
47 data.size = strlen(buf) + 1;
48
49 switch (ret = dbp->put(dbp,tid,&key,&data,0)) {
50 case 0:
51 if ((ret = tid->commit(tid,0)) != 0) {
52 dbenv->err(dbenv,t_ret,"DB_TXN->commit");
53 exit(1);
54 }
55 return 0;
56 case DB_LOCK_DEADLOCK:
57 default:
58 if ((t_ret = tid->abort(tid)) != 0) {
59 dbenv->err(dbenv,t_ret,"DB_TXN->abort");
60 exit(1);
61 }
62 if (fail++ == MAXIMUM_RETRY)
63 return ret;
64 break;
65 }
66 }
67 }
4. 死锁检测通过两种方式进行设置,其一是在dbenv初始化的时候调用该函数 dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT),也可以通过一个独立的线程定时的执行dbenv->lock_detect()方法,前者将会使每次出现conflict的时候都会执行死锁检测,因此会影响并发的性能,后者是在一个单独的线程中定时的完成,所以效率更好。
5. checkpoint,定时的执行dbenv->txn_checkpoint()函数,可以将日志文件中最近更新的数据(上一次执行该函数之后)直接flush到database文件中去。执行checkpoint操作的频率越高,DB_RECOVERY的操作完成的越快。
6. 数据备份,主要分为标准备份和热备份两种。
标准备份的步骤:
1) 提交或者放弃所有当前的事物;
2) 停止所有写操作,读操作可以进行,知道备份完毕;
3) 在该DBENV上强制执行一个checkponit操作
4) 运行db_archive工具加-s选项,列出所有当前可用的数据库文件,copy他们到备份介质,推荐将database files放到单独的目录,这样可以直接copy该子目录。
5) 运行db_archive工具加-l选项,列出所有当前可用的日志文件,copy最后一个日志文件到备份介质。
热备份的步骤:
1) 在环境中设置DB_HOTBACKUP_IN_PROGRESS标志,其将影响DB_TXN_BULK的行为,致使Bulk操作可以产生日志数据。
2) 完成标准备份中的第四步骤,确保你使用的copy工具,可以对database page进行原子性copy。
3) 归档所有的日志文件,将其copy到备份介质,鉴于此,推荐将日志文件存放到环境主目录下的一个单独子目录。注意,一定要保证copy的顺序是先copy数据文件,再copy日志文件,特别是当数据文件和日志文件存放于同一目录下时。
4) 恢复DB_HOTBACKUP_IN_PROGRESS.
5) 为了缩短日志的copy时间,可以在hotbackup之前,通过db_archive工具识别出不在使用的日志文件,将其移出当前的目录。
7. 日志移除
1) db_archive -d 将移除所有不在使用的日志文件。
2) 调用DB_ENV->log_archive()函数,同时将flags参数设置为DB_ARCH_REMOVE,其效果同1)。
3) 调用DB_ENV->log_set_config()函数,同时将flags参数设置为DB_LOG_AUTO_REMOVE,这样BDB将自动移除所有不在使用中的日志文件,应用程序同样也没有机会copy这些日志文件到备份介质。
十四、 Replication
1. 所有被复制的数据库文件必须位于DB_ENV的主目录下面,或者是位于DB_ENV->set_data_dir()中指定的目录中,不能位于其子目录内。
2. 环境初始化必须包含DB_INIT_REP和DB_THREAD两个标志。
3. 该结构的DB_ENV必须是支持事务的。
4. 调用DB_ENV->repmgr_set_local_site()、DB_ENV->repmgr_set_ack_policy()和DB_ENV->repmgr_add_remote_site(), 以便初始化复制的通讯层,最后调用DB_ENV->repmgr_start()方法启动复制管理器。
5. 其他和Replicatio Manager方式相关的函数:
1) DB_ENV->rep_set_nsites() 设置Replication Group中BDB的数量,以便用于master失败后的election。
2) DB_ENV->rep_set_priotiry() 设置优先级,该值越高,在竞选master的过程中,成为master的可能性越高。
3) DB_ENV->rep_set_timeout() 设置master和slaves之间的heartbeat超时,如果指定时间内没有收到master的HeartBeat,election将被启动。