BerkeleyDB(簡稱爲BDB)是一種以key-value爲結構的嵌入式數據庫引擎:html
DB的設計思想是簡單、小巧、可靠、高性能。若是說一些主流數據庫系統是大而全的話,那麼DB就可稱爲小而精。DB提供了一系列應用程序接口(API),調用自己很簡單,應用程序和DB所提供的庫在一塊兒編譯成爲可執行程序。這種方式從兩方面極大提升了DB的效率。第一:DB庫和應用程序運行在同一個地址空間,沒有客戶端程序和數據庫服務器之間昂貴的網絡通信開銷,也沒有本地主機進程之間的通信;第二:不須要對SQL代碼解碼,對數據的訪問直截了當。git
DB對須要管理的數據見解很簡單,DB數據庫包含若干條記錄,每個記錄由關鍵字和數據(KEY/VALUE)構成。數據能夠是簡單的數據類型,也能夠是複雜的數據類型,例如C語言中結構。DB對數據類型不作任何解釋, 徹底由程序員自行處理,典型的C語言指針的"自由"風格。若是把記錄當作一個有n個字段的表,那麼第1個字段爲表的主鍵,第2--n個字段對應了其它數據。DB應用程序一般使用多個DB數據庫,從某種意義上看,也就是關係數據庫中的多個表。DB庫很是緊湊,不超過500K,但能夠管理大至256T的數據量。程序員
DB的設計充分體現了UNIX的基於工具的哲學,即若干簡單工具的組合能夠實現強大的功能。DB的每個基礎功能模塊都被設計爲獨立的,也即意味着其使用領域並不侷限於DB自己。例如加鎖子系統能夠用於非DB應用程序的通用操做,內存共享緩衝池子系統能夠用於在內存中基於頁面的文件緩衝。算法
BDB能夠分爲幾個子系統:數據庫
BDB的每個基礎功能模塊都被設計爲獨立的,也即意味着其使用領域並不侷限於BDB自己,例如加鎖子系統能夠用於非BDB應用程序的通用操做,內存共享緩衝池子系統能夠用於在內存中基於頁面的文件緩衝。編程
BDB庫的安裝方法:從官網下載、解壓後執行下面的命令緩存
cd build_unix ../dist/configure make make install
DB缺省把庫和頭文件安裝在目錄 /usr/local/BerkeleyDB.6.1/ 下,使用下面的命令就可正確編譯程序:服務器
gcc test.c -I/usr/local/BerkeleyDB.6.1/include/ -L/usr/local/BerkeleyDB.6.1/lib/ -ldb -lpthread
下面是一個BDB API使用的例子:網絡
#include <db.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <pthread.h> typedef struct customer { int c_id; char name[10]; char address[20]; int age; } CUSTOMER; /* 數據結構DBT在使用前,應首先初始化,不然編譯可經過但運行時報參數錯誤 */ void init_DBT(DBT * key, DBT * data) { memset(key, 0, sizeof(DBT)); memset(data, 0, sizeof(DBT)); } int main(void) { DB_ENV *dbenv; DB *dbp; DBT key, data; int ret = 0; int key_cust_c_id = 1; CUSTOMER cust = {1, "chenqi", "beijing", 30}; /* initialize env handler */ if (ret = db_env_create(&dbenv, 0)) { printf("db_env_create ERROR: %s\n", db_strerror(ret)); goto failed; } u_int32_t flags = DB_CREATE | DB_INIT_MPOOL | DB_INIT_CDB | DB_THREAD;; if (ret = dbenv->open(dbenv, "/data0/bdb_test", flags, 0)) { printf("dbenv->open ERROR: %s\n", db_strerror(ret)); goto failed; } /* initialize db handler */ if (ret = db_create(&dbp, dbenv, 0)) { printf("db_create ERROR: %s\n", db_strerror(ret)); goto failed; } flags = DB_CREATE | DB_THREAD; if (ret = dbp->open(dbp, NULL, "single.db", NULL, DB_BTREE, flags, 0664)) { printf("dbp->open ERROR: %s\n", db_strerror(ret)); goto failed; } /* write record */ /* initialize DBT */ init_DBT(&key, &data); key.data = &key_cust_c_id; key.size = sizeof(key_cust_c_id); data.data = &cust; data.size = sizeof(CUSTOMER); if (ret = dbp->put(dbp, NULL, &key, &data, DB_NOOVERWRITE)) { printf("dbp->put ERROR: %s\n", db_strerror(ret)); goto failed; } /* flush to disk */ dbp->sync(dbp, 0); /* get record */ init_DBT(&key, &data); key.data = &key_cust_c_id; key.size = sizeof(key_cust_c_id); data.flags = DB_DBT_MALLOC; if (ret = dbp->get(dbp, NULL, &key, &data, 0)) { printf("dbp->get ERROR: %s\n", db_strerror(ret)); goto failed; } CUSTOMER *info = data.data; printf("id = %d\nname=%s\naddress=%s\nage=%d\n", info->c_id, info->name, info->address, info->age); /* free */ free(data.data); if(dbp) { dbp->close(dbp, 0); } if (dbenv) { dbenv->close(dbenv, 0); } return 0; failed: if(dbp) { dbp->close(dbp, 0); } if (dbenv) { dbenv->close(dbenv, 0); } return -1; }
上面的例子中使用了不少BDB庫中的API,在下面會再具體介紹它們。 數據結構
訪問方法對應了數據在硬盤上的存儲格式和操做方法。在編寫應用程序時,選擇合適的算法可能會在運算速度上提升1個甚至多個數量級。大多數數據庫都選用B+樹算法,DB也不例外,同時還支持HASH算法、Recno算法和Queue算法。接下來,咱們將討論這些算法的特色以及如何根據須要存儲數據的特色進行選擇。
說明:
BTree和Hash的key和value都支持任意複雜類型,而且也容許存在key重複的記錄;
Queue和Recno的key只能是邏輯序列號,二者基本上都是創建在Btree算法之上,提供存儲有序數據的接口。前者的序列號是不可變的,後者的序列號能夠是可變,也能夠是不變;
可變,指的是當記錄被刪除或者插入時,編號改變;不變,指的是無論數據庫如何操做,編號都不改變。在Queue算法中編號總被不變的。在Recno算法中編號是可變的,即當記錄被刪除或者插入時,數據庫裏的其餘記錄的編號也可能會改變。
另外,Queue的value爲定長結構,而Recno的value能夠爲定長,也能夠爲變長結構;
對算法的選擇首先要看關鍵字的類型,若是爲複雜類型,則只能選擇BTree或HASH算法,若是關鍵字爲邏輯記錄號,則應該選擇Recno或Queue算法。
當工做集key有序時,BTree算法比較合適;若是工做集比較大且基本上關鍵字爲隨機分佈時,選擇HASH算法。
Queue算法只能存儲定長的記錄,在高的併發處理狀況下,Queue算法效率較高;若是是其它狀況,則選擇Recno算法,Recno算法把數據存儲爲flat text file。
Access Method |
Description |
Choosing Occasion |
BTree |
關鍵字有序存儲,而且其結構能隨數據的插入和刪除進行動態調整。爲了代碼的簡單,Berkeley DB沒有實現對關鍵字的前綴碼壓縮。B+樹支持對數據查詢、插入、刪除的常數級速度。關鍵字能夠爲任意的數據結構。 |
一、 當Key爲複雜類型時。 二、 當Key有序時。 |
Hash |
DB中實際使用的是擴展線性HASH算法(extended linear hashing),能夠根據HASH表的增加進行適當的調整。關鍵字能夠爲任意的數據結構。 |
一、 當Key爲複雜類型。 二、 當數據較大且key隨機分佈時。
|
Recno |
要求每個記錄都有一個邏輯紀錄號,邏輯紀錄號由算法自己生成。至關於關係數據庫中的自動增加字段。Recho創建在B+樹算法之上,提供了一個存儲有序數據的接口。記錄的長度能夠爲定長或不定長。 |
一、 當key爲邏輯記錄號時。 二、 當非高併發的狀況下。 |
Queue |
和Recno方式接近, 只不過記錄的長度爲定長。數據以定長記錄方式存儲在隊列中,插入操做把記錄插入到隊列的尾部,相比之下插入速度是最快的。 |
一、當key爲邏輯記錄號時。 二、定長記錄。 三、 高併發的狀況下。 |
數據庫環境句柄結構DB_ENV:環境在DB中屬於高級特性,本質上看,環境是多個數據庫的包裝器。當一個或多個數據庫在環境中打開後,環境能夠爲這些數據庫提供多種子系統服務,例如多線/進程處理支持、事務處理支持、高性能支持、日誌恢復支持等。
數據庫句柄結構DB:包含了若干描述數據庫屬性的參數,如數據庫訪問方法類型、邏輯頁面大小、數據庫名稱等;同時,DB結構中包含了大量的數據庫處理函數指針,大多數形式爲 (*dosomething)(DB *, arg1, arg2, …),其中最重要的有open、close、put、get等函數。
數據庫記錄結構DBT:DB中的記錄由關鍵字和數據構成,關鍵字和數據都用結構DBT表示。實際上徹底能夠把關鍵字當作特殊的數據。結構中最重要的兩個字段是 void * data和u_int32_t size,分別對應數據自己和數據的長度。
數據庫遊標結構DBC:遊標(cursor)是數據庫應用中常見概念,其本質上就是一個關於特定記錄的遍歷器。注意到DB支持多重記錄(duplicate records),即多條記錄有相同關鍵字,在對多重記錄的處理中,使用遊標是最容易的方式。
DB中核心數據結構在使用前都要初始化,隨後能夠調用結構中的函數(指針)完成各類操做,最後必須關閉數據結構。從設計思想的層面上看,這種設計方法是利用面向過程語言實現面對對象編程的一個典範。
DB_ENV *dbenv; // 環境句柄 DB *dbp; // 數據庫句柄 DBT key, value; // 紀錄結構 DBC *cur; // 遊標結構
數據庫每條記錄包含兩個DBT結構,一個是key,一個是value。
typedef struct { void *data; // 數據buf u_int32_t size; // 數據大小 u_int32_t ulen; // u_int32_t dlen; // 數據長度 u_int32_t doff; // 數據開始處 u_int32_t flags; } DBT;
DB_ENV *dbenv; db_env_create(&dbenv, 0); // 建立數據庫環境句柄 dbenv->open(dbenv, path, flags, 0); // 打開數據庫環境, path是環境的目錄路徑, flag參數參考下面介紹 dbenv->close(dbenv, 0); // 關閉數據庫環境 dbenv->err(dbenv, ret, formart, ...); // 錯誤調試
DB_CREATE // 打開的環境不存在的話就建立它 DB_THREAD // 支持線程 DB_INIT_MPOOL // 初始化內存中的cache DB_INIT_CDB
BDB 環境的使用例子:
/* 定義一個環境變量,並建立 */ DB_ENV *dbenv; db_env_create(&dbenv, 0); /* 在環境打開以前,可調用形式爲dbenv->set_XXX()的若干函數設置環境 */ /* 通知DB使用Rijndael加密算法(參考資料>)對數據進行處理 */ dbenv->set_encrypt(dbenv, "encrypt_string", DB_ENCRYPT_AES); /* 設置DB的緩存爲5M */ dbenv->set_cachesize(dbenv, 0, 5 * 1024 * 1024, 0); /* 設置DB查找數據庫文件的目錄 */ dbenv->set_data_dir(dbenv, "/usr/javer/work_db"); /* 設置出錯時的回調函數 */ dbenv->set_errcall(dbenv, callback); /* 將錯誤信息寫到指定文件 */ dbenv->set_errfile(dbenv, file); /* 打開數據庫環境,注意後四個標誌分別指示DB啓動日誌、加鎖、緩存、事務處理子系統 */ dbenv->open(dbenv,home,DB_CREATE|DB_INIT_LOG|DB_INIT_LOCK| DB_INIT_MPOOL |DB_INIT_TXN, 0); /* 在環境打開後,則能夠打開若干個數據庫,全部數據庫的處理都在環境的控制和保護中。 注意db_create函數的第二個參數是環境變量 */ db_create(&dbp1, dbenv, 0); dbp1->open(dbp1, ……); db_create(&dbp2, dbenv, 0); dbp1->open(dbp2, ……); /* do something with the database */ /* 最後首先關閉打開的數據庫,再關閉環境 */ dbp2->close(dbp2, 0); dbp1->close(dbp1, 0); dbenv->close(dbenv, 0);
DB* dbp; db_create(&dbp, dbenv, 0); // 獲取數據庫句柄 dbp->open(dbp, NULL, filename, NULL, DB_BTREE, flags, 0); dbp->close(&dbp, 0); // 在關閉數據庫前,先關閉全部打開的遊標 dbp->sync(dbp, 0) // 刷新cache,同步到磁盤,close操做會隱含調用該過程 dbp->remove(dbp, filename, NULL, 0) // 移除數據庫,不要移除已打開的數據庫 dbp->rename(dbp, oldname, NULL, newname, 0) // 數據庫重命名,不要重命名已打開的數據庫 dbp->put(dbp, NULL, &key, &data, DB_NOOVERWRITE); // DB_NOOVERWRITE不容許重寫已存在的key dbp->get(dbp, NULL, &key, &data, flags); // 若是存在key重複的記錄,只返回第一個,或者使用遊標 dbp->del(dbp, NULL, &key, 0); // 刪除指定key的記錄 dbp->truncate(dbp, NULL, u_int32_t* count, 0); // 刪除全部記錄,count中返回被刪除的記錄個數 dbp->get_open_flags(dbp, &open_flags); // 獲取打開的flags,僅對已打開的數據庫纔有意義 dbp->set_flags(dbp, flags); // 設置打開的flags
DB_CREATE //若是打開的數據庫不存在,就建立它;不指定這個標誌,若是數據庫不存在,打開失敗! DB_EXC //與DB_CREATE一塊兒使用,若是打開的數據庫已經存在,則打開失敗;不存在,則建立它; DB_RDONLY //只讀的方式打開,隨後的任何寫操做都會失敗; DB_TRUNCATE //清空對應的數據庫磁盤文件; DB_DUPSORT //
get方法返回DB_NOTFOUND時表示沒有匹配記錄,其最後一個參數flags:
DB_GET_BOTH // get方法默認只匹配key,該flag將返回key和data都匹配的第一條記錄 DB_MULTIPLE // get方法默認只返回匹配的第一條記錄,該flag返回全部匹配記錄
使用get方法時,data參數是DBT結構,該DBT的flags參數能夠定義爲:
DB_DBT_USERMEM // 使用本身的內存存儲檢索的data DB_DBT_MALLOC // 使用DB分配的內存,用完後要手動free
DB提供的內存對齊方式可能不符合用戶數據結構的需求,因此儘可能使用咱們本身的內存。
用DB_DBT_USERMEM方式改寫前面的例子:
/* get record */ CUSTOMER info; init_DBT(&key, &data); key.data = &key_cust_c_id; key.size = sizeof(key_cust_c_id); data.data = &info; data.ulen = sizeof(CUSTOMER); data.flags = DB_DBT_USERMEM; if (ret = dbp->get(dbp, NULL, &key, &data, 0)) { printf("dbp->get ERROR: %s\n", db_strerror(ret)); goto failed; } printf("id = %d\nname=%s\naddress=%s\nage=%d\n", info.c_id, info.name, info.address, info.age);
db_strerror(errno) // 將錯誤編碼映射成一個字符串 dbp->set_errfile(dbp, FILE*) // 設置錯誤文件 dbp->set_errcall(dbp, void(*)(const DB_ENV *dbenv, const char* err_pfx, const char* msg)) // 定義錯誤處理的回調函數 dbp->set_errpfx(dbp, format...) // 加上錯誤消息前綴 dbp->err(dbp, ret, format...) // 生成錯誤消息,並按優先級發給set_errcall定義的錯誤處理回調函數、set_errfile定義的文件、stderr; dbp->errx(dbp, format...) // 與dbp->err相似,但沒有返回值ret這個額外參數
錯誤消息由一個前綴(由set_errpfx定義)、消息自己(由err或errx定義)和一個換行符組成。
DBC *cur;
dbp->cursor(dbp, NULL, &cur, 0); // 初始化遊標對象 cur->close(cur); // 關閉遊標 cur->get(cur, &key, &data, flags); // 迭代記錄,當沒有可迭代的記錄時,返回DB_NOTFOUND cur->put(cur, &key, &data, flags); cur->del(cur, 0); // 刪除遊標指向的記錄
DB_NEXT // 從第一條紀錄遍歷到最後一條紀錄; DB_PREV // 逆序遍歷,從最後一條紀錄開始;
DB_SET // 移動遊標到鍵值等於給定值的第一條紀錄; DB_SET_RANGE // 若是數據庫使用BTREE的算法,移動遊標到鍵值大於或等於給定值的紀錄集合; DB_GET_BOTH // 移動遊標到鍵值和數據項均等於給定值的第一條記錄; DB_GET_BOTH_RAGNE // 移動遊標到鍵值等於給定值,數據項大於或等於給定值的紀錄集合; DB_NEXT_DUP // 獲取下一個key重複的記錄; DB_PREV_DUP // 獲取上一個key重複的記錄; DB_NEXT_NODUP // 獲取下一個key不重複的記錄; DB_PREV_NODUP // 獲取上一個key不重複的記錄;
DB_NODUPDATA // 若是插入的key已存在,返回DB_KEYEXIST,若是不存在,則記錄的插入順序由其在數據庫的插入順序決定; DB_KEYFIRST // 在key重複的集合裏面放在第一個位置; DB_KEYLAST // 在key重複的集合裏面放在最後一個位置; DB_CURRENT // replace
創建secondary database以後,若是在primary database中新增或刪除記錄,會觸發對secondary database的更新。
注意:咱們不能直接更新secondary database,任何寫secondary database的操做都會失敗,secondary database的變動須要經過修改primary database實現。但這裏有一個例外,容許在secondary database中刪除記錄。
primary_dbp->associate(primary_dbp, NULL, second_dbp, key_creator, 0); int key_creator(DB* dbp, const DBT* pkey, const DBT* pdata, DBT* skey);
一個例子:
DB *dbp, *sdbp; /* Primary and secondary DB handles */ u_int32_t flags; /* Primary database open flags */ int ret; /* Function return value */ typedef struct vendor { char name[MAXFIELD]; /* Vendor name */ char street[MAXFIELD]; /* Street name and number */ char city[MAXFIELD]; /* City */ char state[3]; /* Two-digit US state code */ char zipcode[6]; /* US zipcode */ char phone_number[13]; /* Vendor phone number */ char sales_rep[MAXFIELD]; /* Name of sales representative */ char sales_rep_phone[MAXFIELD]; /* Sales rep's phone number */ } VENDOR; /* Primary */ ret = db_create(&dbp, NULL, 0); if (ret != 0) { /* Error handling goes here */ } /* Secondary */ ret = db_create(&sdbp, NULL, 0); if (ret != 0) { /* Error handling goes here */ } /* Usually we want to support duplicates for secondary databases */ ret = sdbp->set_flags(sdbp, DB_DUPSORT); if (ret != 0) { /* Error handling goes here */ } /* Database open flags */ flags = DB_CREATE; /* If the database does not exist, create it.*/ /* open the primary database */ ret = dbp->open(dbp, NULL, "my_db.db", NULL, DB_BTREE, flags, 0); if (ret != 0) { /* Error handling goes here */ } /* open the secondary database */ ret = sdbp->open(sdbp, NULL, "my_secdb.db", NULL, DB_BTREE, flags, 0); if (ret != 0) { /* Error handling goes here */ } /* Callback used for key creation. Not defined in this example. See the next section. */ int get_sales_rep(DB *sdbp, /* secondary db handle */ const DBT *pkey, /* primary db record's key */ const DBT *pdata, /* primary db record's data */ DBT *skey) /* secondary db record's key */ { VENDOR *vendor;
/* First, extract the structure contained in the primary's data */ vendor = pdata->data;
/* Now set the secondary key's data to be the representative's name */ memset(skey, 0, sizeof(DBT)); skey->data = vendor->sales_rep; skey->size = strlen(vendor->sales_rep) + 1;
/* Return 0 to indicate that the record can be created/updated. */ return (0); } /* Now associate the secondary to the primary */ dbp->associate(dbp, NULL, sdbp, get_sales_rep, 0);
DB* dbp; dbp->set_pagesize() // 設置page size dbp->stat() // 查看page size
DB能夠將那些常常訪問到記錄cache 到內存裏面,從而加快讀寫速度。
dbp->set_cachesize(dbp, gbytes, bytes, ncache); // 經過數據庫句柄設置cache大小 dbenv->set_cachesize(dbp, gbytes, bytes, ncache); // 經過環境句柄設置cache大小(全局)
from:https://www.cnblogs.com/chenny7/p/4864547.html