BerkeleyDB学习笔记(一)
一、通用配置
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的比较函数
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的长度加一。
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。
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。
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。
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: 相等性连接。请参照以下示例:
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
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 }