[轉(zhuǎn)載]開源嵌入式數(shù)據(jù)庫Berkeley DB
開源嵌入式數(shù)據(jù)庫Berkeley DB
作者:肖文鵬 發(fā)文時(shí)間:2004.04.09
像MySQL這類基于C/S結(jié)構(gòu)的關(guān)系型數(shù)據(jù)庫系統(tǒng)雖然代表著目前數(shù)據(jù)庫應(yīng)用的主流,但卻并不能滿足所有應(yīng)用場(chǎng)合的需要。有時(shí)我們需要的可能只是一個(gè)簡(jiǎn)單的基于磁盤文件的數(shù)據(jù)庫系統(tǒng)。這樣不僅可以避免安裝龐大的數(shù)據(jù)庫服務(wù)器,而且還可以簡(jiǎn)化數(shù)據(jù)庫應(yīng)用程序的設(shè)計(jì)。Berkeley DB正是基于這樣的思想提出來的。
Berkeley DB簡(jiǎn)介
Berkeley DB是一個(gè)開放源代碼的內(nèi)嵌式數(shù)據(jù)庫管理系統(tǒng),能夠?yàn)閼?yīng)用程序提供高性能的數(shù)據(jù)管理服務(wù)。應(yīng)用它程序員只需要調(diào)用一些簡(jiǎn)單的API就可以完成對(duì)數(shù)據(jù)的訪問和管理。與常用的數(shù)據(jù)庫管理系統(tǒng)(如MySQL和Oracle等)有所不同,在Berkeley DB中并沒有數(shù)據(jù)庫服務(wù)器的概念。應(yīng)用程序不需要事先同數(shù)據(jù)庫服務(wù)建立起網(wǎng)絡(luò)連接,而是通過內(nèi)嵌在程序中的Berkeley DB函數(shù)庫來完成對(duì)數(shù)據(jù)的保存、查詢、修改和刪除等操作。
Berkeley DB為許多編程語言提供了實(shí)用的API接口,包括C、C++、Java、Perl、Tcl、Python和PHP等。所有同數(shù)據(jù)庫相關(guān)的操作都由Berkeley DB函數(shù)庫負(fù)責(zé)統(tǒng)一完成。這樣無論是系統(tǒng)中的多個(gè)進(jìn)程,或者是相同進(jìn)程中的多個(gè)線程,都可以在同一時(shí)間調(diào)用訪問數(shù)據(jù)庫的函數(shù)。而底層的數(shù)據(jù)加鎖、事務(wù)日志和存儲(chǔ)管理等都在Berkeley DB函數(shù)庫中實(shí)現(xiàn)。它們對(duì)應(yīng)用程序來講是完全透明的。俗話說:“麻雀雖小五臟俱全。”Berkeley DB函數(shù)庫本身雖然只有300KB左右,但卻能夠用來管理多達(dá)256TB的數(shù)據(jù),并且在許多方面的性能還能夠同商業(yè)級(jí)的數(shù)據(jù)庫系統(tǒng)相抗衡。就拿對(duì)數(shù)據(jù)的并發(fā)操作來說,Berkeley DB能夠很輕松地應(yīng)付幾千個(gè)用戶同時(shí)訪問同一個(gè)數(shù)據(jù)庫的情況。此外,如果想在資源受限的嵌入式系統(tǒng)上進(jìn)行數(shù)據(jù)庫管理,Berkeley DB可能就是惟一正確的選擇了。
Berkeley DB作為一種嵌入式數(shù)據(jù)庫系統(tǒng)在許多方面有著獨(dú)特的優(yōu)勢(shì)。首先,由于其應(yīng)用程序和數(shù)據(jù)庫管理系統(tǒng)運(yùn)行在相同的進(jìn)程空間當(dāng)中,進(jìn)行數(shù)據(jù)操作時(shí)可以避免繁瑣的進(jìn)程間通信,因此耗費(fèi)在通信上的開銷自然也就降低到了極低程度。其次,Berkeley DB使用簡(jiǎn)單的函數(shù)調(diào)用接口來完成所有的數(shù)據(jù)庫操作,而不是在數(shù)據(jù)庫系統(tǒng)中經(jīng)常用到的SQL語言。這樣就避免了對(duì)結(jié)構(gòu)化查詢語言進(jìn)行解析和處理所需的開銷。
基本概念
Berkeley DB簡(jiǎn)化了數(shù)據(jù)庫的操作模式,同時(shí)引入了一些新的基本概念,從而使得訪問和管理數(shù)據(jù)庫變得相對(duì)簡(jiǎn)單起來。在使用Berkeley DB提供的函數(shù)庫編寫數(shù)據(jù)庫應(yīng)用程序之前,有必要先了解以下這些基本概念。
關(guān)鍵字和數(shù)據(jù)
關(guān)鍵字(Key)和數(shù)據(jù)(Data)是Berkeley DB用來進(jìn)行數(shù)據(jù)庫管理的基礎(chǔ),由這兩者構(gòu)成的Key/Data對(duì)(見表1)組成了數(shù)據(jù)庫中的一個(gè)基本結(jié)構(gòu)單元,而整個(gè)數(shù)據(jù)庫實(shí)際上就是由許多這樣的結(jié)構(gòu)單元所構(gòu)成的。通過使用這種方式,開發(fā)人員在使用Berkeley DB提供的API來訪問數(shù)據(jù)庫時(shí),只需提供關(guān)鍵字就能夠訪問到相應(yīng)的數(shù)據(jù)。
Key Data?
sport football
Fruit orange
Drink beer
表1 Key/Data對(duì)
如果想將第一行中的“sport”和“football”保存到Berkeley DB數(shù)據(jù)庫中,可以調(diào)用Berkeley DB函數(shù)庫提供的數(shù)據(jù)保存接口。此時(shí)“sport”和“football”將分別當(dāng)成關(guān)鍵字和數(shù)據(jù)來看待。之后如果需要從數(shù)據(jù)庫中檢索出該數(shù)據(jù),可以用“sport”作為關(guān)鍵字進(jìn)行查詢。此時(shí)Berkeley DB提供的接口函數(shù)會(huì)返回與之對(duì)應(yīng)的數(shù)據(jù)“football”。
關(guān)鍵字和數(shù)據(jù)在Berkeley DB中都是用一個(gè)名為DBT的簡(jiǎn)單結(jié)構(gòu)來表示的。實(shí)際上兩者都可以是任意長(zhǎng)度的二進(jìn)制數(shù)據(jù),而DBT的作用主要是保存相應(yīng)的內(nèi)存地址及其長(zhǎng)度,其結(jié)構(gòu)如下所示:
typedef struct {
? void *data;
? u_int32_t size;
? u_int32_t ulen;
? u_int32_t dlen;
? u_int32_t doff;
? u_int32_t flags;
} DBT;
?
在使用Berkeley DB進(jìn)行數(shù)據(jù)管理時(shí),缺省情況下是一個(gè)關(guān)鍵字對(duì)應(yīng)于一個(gè)數(shù)據(jù),但事實(shí)上也可以將數(shù)據(jù)庫配置成一個(gè)關(guān)鍵字對(duì)應(yīng)于多個(gè)數(shù)據(jù)。
對(duì)象句柄
在Berkeley DB函數(shù)庫定義的大多數(shù)函數(shù)都遵循同樣的調(diào)用原則:首先創(chuàng)建某個(gè)結(jié)構(gòu),然后再調(diào)用該結(jié)構(gòu)中的某些方法。從程序設(shè)計(jì)的角度來講,這一點(diǎn)同面向?qū)ο蟮脑O(shè)計(jì)原則是非常類似的,即先創(chuàng)建某個(gè)對(duì)象的一個(gè)實(shí)例,然后再調(diào)用該實(shí)例的某些方法。正因如此,Berkeley DB引入了對(duì)象句柄的概念來表示實(shí)例化后的結(jié)構(gòu),并且將結(jié)構(gòu)中的成員函數(shù)稱為該句柄的方法。
對(duì)象句柄的引入使得程序員能夠完全憑借面向?qū)ο蟮乃枷耄瑏硗瓿蓪?duì)Berkeley DB數(shù)據(jù)庫的訪問和操作,即使當(dāng)前使用的是像C這樣的結(jié)構(gòu)化語言。例如,對(duì)于打開數(shù)據(jù)庫的操作來說,可以調(diào)用DB的對(duì)象句柄所提供的open函數(shù),其原型如下所示:
int DB->open(DB *db, DB_TXN *txnid, const char *file,
const char *database, DBTYPE type, u_int32_t flags, int mode);
?
錯(cuò)誤處理
對(duì)于任何一個(gè)函數(shù)庫來說,如何對(duì)錯(cuò)誤進(jìn)行統(tǒng)一的處理都是需要考慮的問題。Berkeley DB提供的所有函數(shù)都遵循同樣的錯(cuò)誤處理原則,即函數(shù)成功執(zhí)行后返回零,否則的話則返回非零值。
對(duì)于系統(tǒng)錯(cuò)誤(如磁盤空間不足和訪問權(quán)限不夠等),返回的是一個(gè)標(biāo)準(zhǔn)的值;而對(duì)于非系統(tǒng)錯(cuò)誤,返回的則是一個(gè)特定的錯(cuò)誤編碼。例如,如果在數(shù)據(jù)庫中沒有與某個(gè)特定關(guān)鍵字所對(duì)應(yīng)的數(shù)據(jù),那么在通過該關(guān)鍵字檢索數(shù)據(jù)時(shí)就會(huì)出現(xiàn)錯(cuò)誤。此時(shí)函數(shù)的返回值將是DB_NOTFOUND,表示所請(qǐng)求的關(guān)鍵字并沒有在數(shù)據(jù)庫中出現(xiàn)。所有標(biāo)準(zhǔn)的errno值都是大于零的,而由Berkeley DB定義的特殊錯(cuò)誤編碼則都是小于零的。
要求程序員記住所有的錯(cuò)誤代號(hào)既不現(xiàn)實(shí)也沒有什么實(shí)際意義,因?yàn)锽erkeley DB提供了相應(yīng)的函數(shù)來獲得錯(cuò)誤代號(hào)所對(duì)應(yīng)的錯(cuò)誤描述。一旦有錯(cuò)誤發(fā)生,只需首先調(diào)用db_strerror()函數(shù)來獲得錯(cuò)誤描述信息,然后再調(diào)用DB->err()或DB->errx()就可以很輕松地輸出格式化后的錯(cuò)誤信息。? 開源嵌入式數(shù)據(jù)庫Berkeley DB(2)?
作者:肖文鵬 發(fā)文時(shí)間:2004.04.09
接上一篇:開源的嵌入式數(shù)據(jù)庫Berkeley DB(1)
?
應(yīng)用統(tǒng)一的編程接口
使用Berkeley DB提供的函數(shù)來進(jìn)行數(shù)據(jù)庫的訪問和管理并不復(fù)雜,在大多數(shù)場(chǎng)合下只需按照統(tǒng)一的接口標(biāo)準(zhǔn)進(jìn)行調(diào)用就可以完成最基本的操作。
打開數(shù)據(jù)庫
打開數(shù)據(jù)庫通常要分兩步進(jìn)行:首先調(diào)用db_create()函數(shù)來創(chuàng)建DB結(jié)構(gòu)的一個(gè)實(shí)例,然后再調(diào)用DB->open()函數(shù)來完成真正的打開操作。Berkeley DB將所有對(duì)數(shù)據(jù)庫的操作都封裝在名為DB的結(jié)構(gòu)中。db_create()函數(shù)的作用就是創(chuàng)建一個(gè)該結(jié)構(gòu),其原型如下所示:
typedef struct__db DB;
int db_create(DB **dbp, DB_ENV *dbenv, u_int32_t flags);
?
將磁盤上保存的文件作為數(shù)據(jù)庫打開是由DB->open()函數(shù)來完成的,其原型如下所示:
int DB->open(DB *db, DB_TXN *txnid, const char *file,
const char *database, DBTYPE type, u_int32_t flags, int mode);
?
下面這段代碼示范了如何創(chuàng)建DB對(duì)象句柄及如何打開數(shù)據(jù)庫文件:
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <db.h>
#define DATABASE "demo.db"
/* 以下程序代碼的程序頭同此*/
int main()
{ DB *dbp;
int ret;
if ((ret = db_create(&dbp, NULL, 0)) != 0) {
? fprintf(stderr, "db_create: %s\n", db_strerror(ret));
? exit (1);
}
if ((ret = dbp->open(dbp, NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
? dbp->err(dbp, ret, "%s", DATABASE);
? exit (1);
}
}
?
代碼首先調(diào)用db_create()函數(shù)來創(chuàng)建一個(gè)DB對(duì)象句柄。變量dbp在調(diào)用成功后將成為數(shù)據(jù)庫句柄,通過它可以完成對(duì)底層數(shù)據(jù)庫的配置或訪問。接下去調(diào)用DB->open()函數(shù)打開數(shù)據(jù)庫文件,參數(shù)“DATABASE”指明對(duì)應(yīng)的磁盤文件名為demo.db;參數(shù)“DB_BTREE”表示數(shù)據(jù)庫底層使用的數(shù)據(jù)結(jié)構(gòu)是B樹;而參數(shù)“DB_CREATE”和“0664”則表明當(dāng)數(shù)據(jù)庫文件不存在時(shí)創(chuàng)建一個(gè)新的數(shù)據(jù)庫文件,并且將該文件的屬性值設(shè)置為0664。
錯(cuò)誤處理是在打開數(shù)據(jù)庫時(shí)必須的例行檢查,這可以通過調(diào)用DB->err()函數(shù)來完成。其中參數(shù)“ret”是在調(diào)用Berkeley DB函數(shù)后返回的錯(cuò)誤代碼,其余參數(shù)則用于顯示結(jié)構(gòu)化的錯(cuò)誤信息。
添加數(shù)據(jù)
向Berkeley DB數(shù)據(jù)庫中添加數(shù)據(jù)可以通過調(diào)用DB->put()函數(shù)來完成,其原型如下所示:
int DB->put(DB *db, DB_TXN *txnid, DBT *key, DBT *data, u_int32_t flags);
?
下面這段代碼示范了如何向數(shù)據(jù)庫中添加新的數(shù)據(jù):
int main()
{ DB *dbp;
DBT key, data;
int ret;
if ((ret = db_create(&dbp, NULL, 0)) != 0) {
? fprintf(stderr, "db_create: %s\n", db_strerror(ret));
? exit (1);
}
if ((ret = dbp->open(dbp,
? NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
? dbp->err(dbp, ret, "%s", DATABASE);
? exit (1);
}
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
key.data = "sport";
key.size = sizeof("sport");
data.data = "football";
data.size = sizeof("football");
if ((ret = dbp->put(dbp, NULL, &key, &data, 0)) == 0)
? printf("db: %s: key stored.\n", (char *)key.data);
else
? dbp->err(dbp, ret, "DB->put");
}
?
代碼首先聲明了兩個(gè)DBT結(jié)構(gòu)變量,并分別用字符串“sport”和“football”進(jìn)行填充。它們隨后作為關(guān)鍵字和數(shù)據(jù)傳遞給用來添加數(shù)據(jù)的DB->put()函數(shù)。DBT結(jié)構(gòu)幾乎會(huì)在所有同數(shù)據(jù)訪問相關(guān)的函數(shù)中被用到。
在向數(shù)據(jù)庫中添加數(shù)據(jù)時(shí),如果給定的關(guān)鍵字已經(jīng)存在,大多數(shù)應(yīng)用會(huì)對(duì)于已經(jīng)存在的數(shù)據(jù)采用覆蓋原則。也就是說,如果數(shù)據(jù)庫中已經(jīng)保存了一個(gè)“sport/basketball”對(duì),再次調(diào)用DB->put()函數(shù)添加一個(gè)“sport/football”對(duì),那么先前保存的那些數(shù)據(jù)將會(huì)被覆蓋。但Berkeley DB允許在調(diào)用DB->put()函數(shù)時(shí)指定參數(shù)“DB_NOOVERWRITE”,聲明不對(duì)數(shù)據(jù)庫中已經(jīng)存在的數(shù)據(jù)進(jìn)行覆蓋,其代碼如下:
if ((ret = dbp->put(dbp, NULL, &key, &data, DB_NOOVERWRITE)) == 0)
printf("db: %s: key stored.\n", (char *)key.data);
else
dbp->err(dbp, ret, "DB->put");
?
一旦給出“DB_NOOVERWRITE”標(biāo)記,如果DB->put()函數(shù)在執(zhí)行過程中發(fā)現(xiàn)給出的關(guān)鍵字在數(shù)據(jù)庫中已經(jīng)存在了,就無法成功地把該Key/Data對(duì)添加到數(shù)據(jù)庫中,于是將返回錯(cuò)誤代號(hào)“DB_KEYEXIST”。
檢索數(shù)據(jù)
從Berkeley DB數(shù)據(jù)庫中檢索數(shù)據(jù)可以通過調(diào)用DB->get()函數(shù)來完成,其原型如下所示:
int DB->get(DB *db, DB_TXN *txnid, DBT *key, DBT *data, u_int32_t flags);
?
下面這段代碼示范了如何從數(shù)據(jù)庫中檢索出所需的數(shù)據(jù):
int main()
{ DB *dbp;
DBT key, data;
int ret;
if ((ret = db_create(&dbp, NULL, 0)) != 0) {
? fprintf(stderr, "db_create: %s\n", db_strerror(ret));
? exit (1);
}
if ((ret = dbp->open(dbp,
? NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
? dbp->err(dbp, ret, "%s", DATABASE);
? exit (1);
}
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
key.data = "sport";
key.size = sizeof("sport");
if ((ret = dbp->get(dbp, NULL, &key, &data, 0)) == 0)
? printf("db: %s: key retrieved: data was %s.\n",
?? (char *)key.data, (char *)data.data);
else
? dbp->err(dbp, ret, "DB->get");
}
?
代碼同樣聲明了兩個(gè)DBT結(jié)構(gòu)變量,并且調(diào)用memset()函數(shù)對(duì)它們的內(nèi)容清空。雖然Berkeley DB并不強(qiáng)制要求在進(jìn)行數(shù)據(jù)操作之前先清空它們,但出于提高代碼質(zhì)量考慮還是建議先進(jìn)行清空操作。在進(jìn)行數(shù)據(jù)檢索時(shí),對(duì)DB->get()函數(shù)的返回值進(jìn)行處理是必不可少的,因?yàn)樗鼣y帶著檢索操作是否成功完成等信息。下面列出的是DB->get()函數(shù)的返回值:
◆ 0 函數(shù)調(diào)用成功,指定的關(guān)鍵字被找到;
◆ DB_NOTFOUND 函數(shù)調(diào)用成功,但指定的關(guān)鍵字未被找到;
◆大于0 函數(shù)調(diào)用失敗,可能出現(xiàn)了系統(tǒng)錯(cuò)誤。
刪除數(shù)據(jù)
從Berkeley DB數(shù)據(jù)庫中刪除數(shù)據(jù)可以通過調(diào)用DB->del()函數(shù)來完成,其原型如下所示:
int DB->del(DB *db, DB_TXN *txnid, DBT *key, u_int32_t flags);
?
下面這段代碼示范了如何從數(shù)據(jù)庫中刪除數(shù)據(jù):
int main()
{ DB *dbp;
DBT key;
int ret;
if ((ret = db_create(&dbp, NULL, 0)) != 0) {
? fprintf(stderr, "db_create: %s\n", db_strerror(ret));
? exit (1);
}
if ((ret = dbp->open(dbp,
? NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
? dbp->err(dbp, ret, "%s", DATABASE);
? exit (1);
}
memset(&key, 0, sizeof(key));
key.data = "sport";
key.size = sizeof("sport");
if ((ret = dbp->del(dbp, NULL, &key, 0)) == 0)
? printf("db: %s: key was deleted.\n", (char *)key.data);
else
? dbp->err(dbp, ret, "DB->del");
}
?
刪除數(shù)據(jù)只需給出相應(yīng)的關(guān)鍵字,不用指明與之對(duì)應(yīng)的數(shù)據(jù)。
關(guān)閉數(shù)據(jù)庫
對(duì)于一次完整的數(shù)據(jù)庫操作過程來說,關(guān)閉數(shù)據(jù)庫是不可或缺的一個(gè)環(huán)節(jié)。這是因?yàn)锽erkeley DB需要依賴于系統(tǒng)底層的緩沖機(jī)制,也就是說只有在數(shù)據(jù)庫正常關(guān)閉的時(shí)候,修改后的數(shù)據(jù)才有可能全部寫到磁盤上,同時(shí)它所占用的資源也才能真正被全部釋放。關(guān)閉數(shù)據(jù)庫的操作是通過調(diào)用DB->close()函數(shù)來完成的,其原型如下所示:
int DB->close(DB *db, u_int32_t flags);
?
下面這段代碼示范了如何在需要的時(shí)候關(guān)閉數(shù)據(jù)庫:
int main()
{ DB *dbp;
DBT key, data;
int ret, t_ret;
if ((ret = db_create(&dbp, NULL, 0)) != 0) {
? fprintf(stderr, "db_create: %s\n", db_strerror(ret));
? exit (1);
}
if ((ret = dbp->open(dbp,
? NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
? dbp->err(dbp, ret, "%s", DATABASE);
? goto err;
}
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
key.data = "sport";
key.size = sizeof("sport");
if ((ret = dbp->get(dbp, NULL, &key, &data, 0)) == 0)
? printf("db: %s: key retrieved: data was %s.\n",
?? (char *)key.data, (char *)data.data);
else
? dbp->err(dbp, ret, "DB->get");
if ((t_ret = dbp->close(dbp, 0)) != 0 && ret == 0)
? ret = t_ret;
exit(ret);
}
?
小結(jié)
Berkeley DB這個(gè)嵌入式數(shù)據(jù)庫系統(tǒng)使用非常簡(jiǎn)單。它沒有數(shù)據(jù)庫服務(wù)器的概念,也不需要復(fù)雜的SQL語句,所有對(duì)數(shù)據(jù)的操作和管理都可以通過函數(shù)調(diào)用來完成,非常適合于那些需要對(duì)數(shù)據(jù)進(jìn)行簡(jiǎn)單管理的應(yīng)用場(chǎng)合。
?
?
posted on 2006-12-14 10:22
Sword.Hell blog 閱讀(353)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
使用Berkeley DB