BerkeleyDB

db資料庫中,可以調用berkeley

像mysql 這類基於c/s結構的關係型資料庫系統雖然代表著目前資料庫套用的主流,但卻並不能滿足所有套用場合的需要。有時我們需要的可能只是一個簡單的基於磁碟文 件的資料庫系統。這樣不僅可以避免安裝龐大的資料庫伺服器,而且還可以簡化資料庫應用程式的設計。BERKELEY db正是基於這樣的思想提出來的。

Berkeley DB簡介

berkeley db是一個開放原始碼的內嵌式資料庫管理系統,能夠為應用程式提供高性能的數據管理服務。套用它程式設計師只需要調用一些簡單的api就可以完成對數據的訪問 和管理。與常用的資料庫管理系統(如mysql和oracle等)有所不同,在berkeley db中並沒有資料庫伺服器的概念。應用程式不需要事先同資料庫服務建立起網路連線,而是通過內嵌在程式中的berkeley DB函式庫來完成對數據的保存、查詢、修改和刪除等操作。
berkeley db為許多程式語言提供了實用的api接口,包括c、c++、java、perl、tcl、python和php等。所有同資料庫相關的操作都由 berkeley db函式館負責統一完成。這樣無論是系統中的多個進程,或者是相同進程中的多個執行緒,都可以在同一時間調用訪問資料庫的函式。而底層的數據加鎖、事務日誌 和存儲管理等都在berkeley db函式館中實現。它們對應用程式來講是完全透明的。俗話說:“麻雀雖小五臟俱全。”berkeley db函式館本身雖然只有300kb左右,但卻能夠用來管理多達256tb的數據,並且在許多方面的性能還能夠同商業級的資料庫系統相抗衡。就拿對數據的並 發操作來說,berkeley db能夠很輕鬆地應付幾千個用戶同時訪問同一個資料庫的情況。此外,如果想在資源受限的嵌入式系統上進行資料庫管理,berkeley db可能就是惟一正確的選擇了。
berkeley db作為一種嵌入式資料庫系統在許多方面有著獨特的優勢。首先,由於其應用程式和資料庫管理系統運行在相同的進程空間當中,進行數據操作時可以避免繁瑣的 進程間通信,因此耗費在通信上的開銷自然也就降低到了極低程度。其次,berkeley db使用簡單的函式調用接口來完成所有的資料庫操作,而不是在資料庫系統中經常用到的sql語言。這樣就避免了對結構化查詢語言進行解析和處理所需的開 銷。

基本概念

berkeley db簡化了資料庫的操作模式,同時引入了一些新的基本概念,從而使得訪問和管理資料庫變得相對簡單起來。在使用berkeley db提供的函式館編寫資料庫應用程式之前,有必要先了解以下這些基本概念。
關鍵字和數據
關 鍵字(key)和數據(data)是berkeley db用來進行資料庫管理的基礎,由這兩者構成的key/data對(見表1)組成了資料庫中的一個基本結構單元,而整個資料庫實際上就是由許多這樣的結構 單元所構成的。通過使用這種方式,開發人員在使用berkeley db提供的api來訪問資料庫時,只需提供關鍵字就能夠訪問到相應的數據。
key data
sport football
fruit orange
drink beer
表1 key/data對
如果想將第一行中的 “sport”和“football”保存到berkeley db資料庫中,可以調用berkeley db函式館提供的數據保存接口。此時“sport”和“football”將分別當成關鍵字和數據來看待。之後如果需要從資料庫中檢索出該數據,可以用 “sport”作為關鍵字進行查詢。此時berkeley db提供的接口函式會返回與之對應的數據“football”。
關鍵字和數據在berkeley db中都是用一個名為dbt的簡單結構來表示的。實際上兩者都可以是任意長度的二進制數據,而dbt的作用主要是保存相應的記憶體地址及其長度,其結構如下所示:
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進行數據管理時,預設情況下是一個關鍵字對應於一個數據,但事實上也可以將資料庫配置成一個關鍵字對應於多個數據。

對象句柄

在berkeley db函式館定義的大多數函式都遵循同樣的調用原則:首先創建某個結構,然後再調用該結構中的某些方法。從程式設計的角度來講,這一點同面向對象的設計原則 是非常類似的,即先創建某個對象的一個實例,然後再調用該實例的某些方法。正因如此,berkeley db引入了對象句柄的概念來表示實例化後的結構,並且將結構中的成員函式稱為該句柄的方法。
對象句柄的引入使得程式設計師能夠完全憑藉面向對象的思想,來完成對berkeley db資料庫的訪問和操作,即使當前使用的是像c這樣的結構化語言。例如,對於打開資料庫的操作來說,可以調用db的對象句柄所提供的open函式,其原型如下所示:
int db->open(db *db, db_txn *txnid, const char *file,
const char *database, DBTYPE type, u_int32_t flags, int mode);

錯誤處理

對於任何一個函式館來說,如何對錯誤進行統一的處理都是需要考慮的問題。berkeley db提供的所有函式都遵循同樣的錯誤處理原則,即函式成功執行後返回零,否則的話則返回非零值。
對 於系統錯誤(如磁碟空間不足和訪問許可權不夠等),返回的是一個標準的值;而對於非系統錯誤,返回的則是一個特定的錯誤編碼。例如,如果在資料庫中沒有與某 個特定關鍵字所對應的數據,那么在通過該關鍵字檢索數據時就會出現錯誤。此時函式的返回值將是db_notfound,表示所請求的關鍵字並沒有在資料庫 中出現。所有標準的errno值都是大於零的,而由berkeley db定義的特殊錯誤編碼則都是小於零的。
要求程式設計師記住所有的 錯誤代號既不現實也沒有什麼實際意義,因為berkeley db提供了相應的函式來獲得錯誤代號所對應的錯誤描述。一旦有錯誤發生,只需首先調用db_strerror()函式來獲得錯誤描述信息,然後再調用db ->err()或db->errx()就可以很輕鬆地輸出格式化後的錯誤信息。

套用統一的編程接口

使用berkeley db提供的函式來進行資料庫的訪問和管理並不複雜,在大多數場合下只需按照統一的接口標準進行調用就可以完成最基本的操作。

打開資料庫

打 開資料庫通常要分兩步進行:首先調用db_create()函式來創建db結構的一個實例,然後再調用db->open()函式來完成真正的打開操 作。berkeley db將所有對資料庫的操作都封裝在名為db的結構中。db_create()函式的作用就是創建一個該結構,其原型如下所示:
typedef struct__db db;
int db_create(db **DBP, db_env *dbenv, u_int32_t flags);
將磁碟上保存的檔案作為資料庫打開是由db->open()函式來完成的,其原型如下所示:
int db->open(db *db, db_txn *txnid, const char *file,
const char *database, dbtype type, u_int32_t flags, int mode);
下面這段代碼示範了如何創建db對象句柄及如何打開資料庫檔案:
#include
#include
#include
#include
#include
#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);
}
}
代 碼首先調用db_create()函式來創建一個db對象句柄。變數dbp在調用成功後將成為資料庫句柄,通過它可以完成對底層資料庫的配置或訪問。接下 去調用db->open()函式打開資料庫檔案,參數“database”指明對應的磁碟檔案名稱為demo.db;參數“db_btree”表示數 據庫底層使用的數據結構是b樹;而參數“db_create”和“0664”則表明當資料庫檔案不存在時創建一個新的資料庫檔案,並且將該檔案的屬性值設 置為0664。
錯誤處理是在打開資料庫時必須的例行檢查,這可以通過調用db->err()函式來完成。其中參數“ret”是在調用berkeley db函式後返回的錯誤代碼,其餘參數則用於顯示結構化的錯誤信息。

添加數據

向berkeley db資料庫中添加數據可以通過調用db->put()函式來完成,其原型如下所示:
int db->put(db *db, db_txn *txnid, dbt *key, dbt *data, u_int32_t flags);
下面這段代碼示範了如何向資料庫中添加新的數據:
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");
}
代碼首先聲明了兩個dbt結構變數,並分別用字元串“sport”和“football”進行填充。它們隨後作為關鍵字和數據傳遞給用來添加數據的db->put()函式。dbt結構幾乎會在所有同數據訪問相關的函式中被用到。
在 向資料庫中添加數據時,如果給定的關鍵字已經存在,大多數套用會對於已經存在的數據採用覆蓋原則。也就是說,如果資料庫中已經保存了一個 “sport/basketball”對,再次調用db->put()函式添加一個“sport/football”對,那么先前保存的那些數據將 會被覆蓋。但berkeley db允許在調用db->put()函式時指定參數“db_nooverwrite”,聲明不對資料庫中已經存在的數據進行覆蓋,其代碼如下:
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”標記,如果db->put()函式在執行過程中發現給出的關鍵字在資料庫中已經存在了,就無法成功地把該key/data對添加到資料庫中,於是將返回錯誤代號“db_keyexist”。

檢索數據

從berkeley db資料庫中檢索數據可以通過調用db->get()函式來完成,其原型如下所示:
int db->get(db *db, db_txn *txnid, dbt *key, dbt *data, u_int32_t flags);
下面這段代碼示範了如何從資料庫中檢索出所需的數據:
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");
}
代 碼同樣聲明了兩個dbt結構變數,並且調用memset()函式對它們的內容清空。雖然berkeley db並不強制要求在進行數據操作之前先清空它們,但出於提高代碼質量考慮還是建議先進行清空操作。在進行數據檢索時,對db->get()函式的返 回值進行處理是必不可少的,因為它攜帶著檢索操作是否成功完成等信息。下面列出的是db->get()函式的返回值:
◆ 0 函式調用成功,指定的關鍵字被找到;
◆ db_notfound 函式調用成功,但指定的關鍵字未被找到;
◆大於0 函式調用失敗,可能出現了系統錯誤。

刪除數據

從berkeley db資料庫中刪除數據可以通過調用db->del()函式來完成,其原型如下所示:
int db->del(db *db, db_txn *txnid, dbt *key, u_int32_t flags);
下面這段代碼示範了如何從資料庫中刪除數據:
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");
}
刪除數據只需給出相應的關鍵字,不用指明與之對應的數據。

關閉資料庫

對 於一次完整的資料庫操作過程來說,關閉資料庫是不可或缺的一個環節。這是因為berkeley db需要依賴於系統底層的緩衝機制,也就是說只有在資料庫正常關閉的時候,修改後的數據才有可能全部寫到磁碟上,同時它所占用的資源也才能真正被全部釋 放。關閉資料庫的操作是通過調用db->close()函式來完成的,其原型如下所示:
int db->close(db *db, u_int32_t flags);
下面這段代碼示範了如何在需要的時候關閉資料庫:
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);
}

小結

berkeley db這個嵌入式資料庫系統使用非常簡單。它沒有資料庫伺服器的概念,也不需要複雜的sql語句,所有對數據的操作和管理都可以通過函式調用來完成,非常適合於那些需要對數據進行簡單管理的套用場合
include <db.h>
#include <string.h>
#include <stdlib.h>
#define MAXBUFFER 300
#define MAXSTRING 100
void getdata(void);
/* Declare our struct */
struct example_structure {
float myFloat;
int myInt;
char *myString;
};
typedef struct example_structure EXAMPLE_STRUCTURE;
char SAMPLE_KEY[] = "eins";
char * Database = "./sample.db";
DBT sampleKEY;
/*
* Program to illustrate marshalling and unmarshalling structures.
*
* Marshalling is the process of moving the contents of a structure&#039;s
* fields into a single contiguous memory location. This is done so that
* the structure can be stored in a Berkeley DB database.
*
* Unmarshalling is performed to take data retrieved from a Berkeley DB database
* and place it back into the structure so that it can be used by the
* application.
*/
int
main(void)
{
DBT sampleDBT; /* The Berkeley DB data structure that we use to store data
* in a database. Typically there are two of these for
* every database record, one for a key and one for the
* data. In this case, we need only one DBT for
* illustration purposes.
*/
DB *dbp;
char *SerialNumber;
int ret;
EXAMPLE_STRUCTURE myStruct; /* The structure we want to store */
int buffer_length; /* The amount of data stored in our buffer */
char buffer[MAXBUFFER]; /* The buffer itself */
char *bufferPtr; /* A pointer into the buffer */
memset(&sampleKEY, 0, sizeof(DBT));
memset(&sampleDBT, 0, sizeof(DBT));
/* First, we fill in our structure&#039;s data fields */
myStruct.myFloat = 3.04;
myStruct.myInt = 200;
/* malloc space for the string */
myStruct.myString = (char *)malloc(MAXSTRING * sizeof(char));
/* Copy a string into that space. */
strcpy(myStruct.myString, "My example string.");
/*
* In order to store the data for this structure, we must make sure that
* all its data is lined up in a single contiguous block of memory -- that
* is, in a single buffer. We also need to know how much data was put into
* that buffer. To do this, we copy the structure&#039;s data into the buffer.
* This is the actual marshalling process.
*
* Note that the order we use to copy the data is not important, except
* that we have to make sure that we unmarshall in the same order. Here
* we mix things up a bit in order to illustrate the concept.
*
* Notice that we keep track of how much data we&#039;ve placed in the buffer as
* we go. Also, take care to copy each new bit of data to the end of the
* buffer, so as to not overwrite any data previously placed there.
*/
/* Initialize the buffer */
memset(&buffer, 0, MAXBUFFER);
/* Copy the struct&#039;s int into the buffer */
bufferPtr = &buffer[0];
memcpy(bufferPtr, &(myStruct.myInt), sizeof(int));
buffer_length = sizeof(int);
/* Copy the struct&#039;s string into the buffer */
bufferPtr = &buffer[buffer_length];
memcpy(bufferPtr, myStruct.myString,
strlen(myStruct.myString) + 1);
buffer_length += (strlen(myStruct.myString) + 1);
/* Copy the struct&#039;s float into the buffer */
bufferPtr = &buffer[buffer_length];
memcpy(bufferPtr, &(myStruct.myFloat), sizeof(float));
buffer_length += sizeof(float);
/*
* We now have a buffer that contains all our data. We also have the size of
* the data contained in that buffer. We can use this buffer with our DBT:
*/
sampleDBT.data = buffer;
sampleDBT.size = buffer_length;
sampleKEY.data = SAMPLE_KEY;
sampleKEY.size = sizeof(SAMPLE_KEY);
if ((ret = db_create(&dbp, NULL, 0)) != 0)
{
fprintf(stderr, "db_create: %s\n", db_strerror(ret));
return(ret);
}
if ((ret = dbp->open(dbp,
NULL, Database, NULL, DB_BTREE, DB_CREATE, 0664)) != 0)
{
dbp->err(dbp, ret, "%s", Database);
return(ret);
}
if ((ret = dbp->put(dbp, NULL, &sampleKEY, &sampleDBT, 0)) != 0)
{
dbp->err(dbp, ret, "%s", Database);
fprintf(stderr, "db_create: %s\n", db_strerror(ret));
return(ret);
}
//ret = dbp->close(dbp, 0);
/*
* We can now pass the data DBT to a DB->put() or DBC->c_put() call for
* storage in the database. We won&#039;t show that here.
*/
/*
* Upon retrieval from the database (again, we don&#039;t show the actual
* database get() activity), the DBT&#039;s data field is pointing to
* a void * buffer that contains exactly the data that we marshalled into
* our buffer above. We now need to only unmarshall that data. To do this,
* we have to remember the order in which we originally marshalled the
* data.
*/
/*
* Note that we&#039;re reusing the same DBT for unmarshalling process as we used
* to marshall the data. In real-world usage, the two would be different
* as they would almost certainly be declared in different scopes.
*/
/*
* Now we can print everything out to prove that the marshalling process
* worked
*/
printf("Original structure:\n");
printf("\tmyInt: %i, \tmyFloat: %f\n", myStruct.myInt, myStruct.myFloat);
printf("\tmyString: %s\n", myStruct.myString);
ret = dbp->close(dbp, 0);
getdata();
return(0);
}
void getdata(void)
{
char *bufferPtr; /* A pointer into the buffer */
EXAMPLE_STRUCTURE newStruct; /* The structure we want to place data into */
DBT sampleDBT;
DB *dbp;
int ret;
memset(&sampleDBT, 0, sizeof(DBT));
if ((ret = db_create(&dbp, NULL, 0)) != 0)
{
fprintf(stderr, "db_create: %s\n", db_strerror(ret));
}
if ((ret = dbp->open(dbp,
NULL, Database, NULL, DB_BTREE, DB_CREATE, 0664)) != 0)
{
dbp->err(dbp, ret, "%s", Database);
}
if ((ret = dbp->get(dbp, NULL, &sampleKEY, &sampleDBT, 0)) != 0)
{
dbp->err(dbp, ret, "%s", Database);
return;
}
bufferPtr = sampleDBT.data;
/* First, find the int (the first bit of data that we stored) */
newStruct.myInt = *((int *)bufferPtr);
bufferPtr += sizeof(int);
/* Next, the string */
newStruct.myString = (char *)bufferPtr;
bufferPtr += (strlen(newStruct.myString) + 1);
/* And finally, the float */
newStruct.myFloat = *((float *)bufferPtr);
printf("\nNew structure (after marshalling and unmarshalling:\n");
printf("\tmyInt: %i, \tmyFloat: %f\n", newStruct.myInt, newStruct.myFloat);
printf("\tmyString: %s\n", newStruct.myString);
/* Cleanup */
ret = dbp->close(dbp, 0);
}
output:
Original structure:
myInt: 200, myFloat: 3.040000
myString: My example string.
New structure (after marshalling and unmarshalling:
myInt: 200, myFloat: 3.040000
myString: My example string.
Press enter to continue...
I did not understand why you would us a u_int32_t for the buffer, and of course you have to either flush the output or close the database connection otherwise nothing is written.
Regards
Friedrich

相關詞條

相關搜尋

熱門詞條

聯絡我們