BerkeleyDB 多索引查詢


由於性能原因,我們打算將關系型數據庫轉移到內存數據庫中;在內存數據庫產品的選型中,我們確定的候選對象有Redis和Berkeley DB;

Redis查詢效率不錯,並且支持豐富的數據存儲結構,但不支持多索引,這樣對於比較復雜的sql移植可能會造成數據膨脹;Berkeley DB只支持簡單的Key/Value, 但支持多索引查詢,對我們目前的應用來說,移植起來更有優勢;

 

下面我們看看,如何為DB建立二級索引;

還是用例子來說明:

一張表中記錄學生的信息;每個學生有個唯一的ID,這個id通常就是表的主鍵;

現在,我們希望通過學生的last_name來查詢,這就需要建立二級索引;

注:用詞約定:

* 本文提到的“數據庫”是指Berkeley DB的database,相當於關系數據庫的一個表。

作為SQL的常用表:

CREATE TABLE students(student_id CHAR(4) NOT NULL,lastname CHAR(15),

firstname CHAR(15), PRIMARY KEY(student_id)); CREATE INDEX lname ON students(lastname);

在Berkeley DB中,就是定義為如下結構:

struct student_record {
    char student_id[4];
    char last_name[15];
    char first_name[15];
};

void second()
{
    DB *dbp, *sdbp;
    int ret;

    /* 創建/打開第一個數據庫*/
    if ((ret = db_create(&dbp, dbenv, 0)) != 0)
        handle_error(ret);
    if ((ret = dbp->open(dbp, NULL,
        "students.db", NULL, DB_BTREE, DB_CREATE, 0600)) != 0)
        handle_error(ret);
    /* 打開第二個數據庫,注意,需要申明這個庫支持重復記錄,因為學生的last_name不是唯一的,是可能重復的*/  
    if ((ret = db_create(&sdbp, dbenv, 0)) != 0)
        handle_error(ret);
    if ((ret = sdbp->set_flags(sdbp, DB_DUP | DB_DUPSORT)) != 0)
        handle_error(ret);
    if ((ret = sdbp->open(sdbp, NULL,
        "lastname.db", NULL, DB_BTREE, DB_CREATE, 0600)) != 0)
        handle_error(ret);

    /* 將二級個庫關聯到第一個庫上. 注:getname是提取key函數*/
    if ((ret = dbp->associate(dbp, NULL, sdbp, getname, 0)) != 0)
        handle_error(ret);
}

/*
* getname -- 從第一個庫的鍵值對中提取第二個庫的key(即 last name)
*/
int getname(DB *secondary, const DBT *pkey, const DBT *pdata, DBT *skey)
{
    /*
     * 這里第二個key是數據的簡單結構,所以並不需要做其它的工作,直接返回就完事。
     *  如果第二個key是需要從復雜記錄中提取出來再組建,這個用戶函數可能需要做分配空間和copy數據的工作;在這種情況下,對於第二個鍵的DBT結構需要設置 DB_DBT_APPMALLOC 標志位;*/
    memset(skey, 0, sizeof(DBT));
    skey->data = ((struct student_record *)pdata->data)->last_name;
    skey->size = sizeof(((struct student_record *)pdata->data)->last_name);
    return (0);
}

 

插入數據

從開發者的角度來看,插入數據與第二個索引數據庫無關,直接操作第一個數據庫中即可:

struct student_record s;
DBT data, key;
memset(&key, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
memset(&s, 0, sizeof(struct student_record));
key.data = "WC42";
key.size = 4;
memcpy(&s.student_id, "WC42", sizeof(s.student_id));
memcpy(&s.last_name, "Churchill      ", sizeof(s.last_name));
memcpy(&s.first_name, "Winston        ", sizeof(s.first_name));
data.data = &s;
data.size = sizeof(s);
if ((ret = dbp->put(dbp, txn, &key, &data, 0)) != 0)
    handle_error(ret);

 

刪除數據

刪除數據可以通過第一個索引(student_id)來刪除,也可以通過第二個索引(last_name)來刪除,無論使用哪個索引刪除,被刪除的都是第一個庫中的真實數據;

eg: 使用第一個索引刪除:

BT key;
memset(&key, 0, sizeof(DBT));
key.data = "WC42";
key.size = 4;
if ((ret = dbp->del(dbp, txn, &key, 0)) != 0)
    handle_error(ret);

 

eg:使用二級個索引刪除:

 

DBT skey;
memset(&skey, 0, sizeof(DBT));
skey.data = "Churchill      ";
skey.size = 15;
if ((ret = sdbp->del(sdbp, txn, &skey, 0)) != 0)
    handle_error(ret);

 

這里需要注意的是,第二個索引並非唯一性索引,所以可能對應多條數據,執行刪除操作,將刪除所有對應的數據;

 

查詢數據

使用第一個索引查詢數據,使用DB->get();

使用第二個索引查詢數據,可使用DB->pget() 或者 DB->pget()

兩者的區別就是,如果使用DB->pget() ,則會將查詢到的數據對應的第一個索引key同時返回;(DBC->pget()也是這樣)

這里給出兩者的函數原型:

#include <db_cxx.h>
int Db::get(DbTxn *txnid, Dbt *key, Dbt *data, u_int32_t flags);
int Db::pget(DbTxn *txnid, Dbt *key, Dbt *pkey, Dbt *data, u_int32_t flags); 
pkey即第一索引的key;

eg:
DBT data, pkey, skey;
memset(&skey, 0, sizeof(DBT));
memset(&pkey, 0, sizeof(DBT));
memset(&data, 0, sizeof(DBT));
skey.data = "Churchill      ";
skey.size = 15;
if ((ret = sdbp->pget(sdbp, txn, &skey, &pkey, &data, 0)) != 0)
    handle_error(ret);

 

錯誤處理

在DS或CDS上更新二級索引時,可能會產生以下錯誤:

• 0

• DB_BUFFER_SMALL

• DB_NOTFOUND

• DB_KEYEMPTY

• DB_KEYEXIST

為了防止這些錯誤,在索引更新后,最好立刻刪除這個二級索引,然后重建;

注意:DB_RUNRECOVERY 和 DB_PAGE_NOTFOUND屬於嚴重級錯誤,一般不會發生;

如果Berkeley DB返回了這類錯誤,需要首先檢查數據庫的完整性(使用DB->verify()),確認沒問題后再重建索引;

 

總結

一旦調用DB->associate() 將兩個索引庫關聯起來,二級索引就成為第一數據庫的另一個入口;

所有的更新操作都會影響與其關聯的索引庫;

在二級索引上,游標的操作函數都可正常使用;

需要指出的是,對於插入操作,BDB禁止通過二級索引來插入數據,因為那樣的話,就沒有方法為第一數據庫指明主索引。應用程序,應該在第一個數據庫上使用DB->put() or DBC->put()來插入數據;

可以對建立任意多個二級索引,BDB中對這方面沒有限制;只要內存大小允許,以及文件描述符夠用,理論上對於一個數據庫可以建立任意多個二級索引;當然,索引不是越多越好,在數據更新時,索引的更新也是不小的代價;所以,設計階段,對於索引的建立,需要精心的設計一二;

如果發現二級索引失效了,應該通過調用DB->remove()將其刪除,同時,再調用一次DB->associate() 方法來生成新的索引;

如果二級索引庫不再需要了,需要先關閉數據庫句柄,DB->close(),再將其刪除:DB->remove();

關閉主索引庫句柄時,會自動關閉所以與其關聯的二級索引句柄;

 

更多參考

《Reference Guide for Berkeley DB》

http://docs.oracle.com/cd/E17076_03/html/index.html

 

Posted by: 大CC | 26SEP,2013

博客:blog.me115.com

微博:新浪微博


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM