FastDB分析

c++ 接口

fastdb主要的目標之一就是提供一個靈活並且方便的應用語言接口。任何使用過odbc或者類似的sql接口的人會明白我說的是什麼。在fastdb中,一個查詢可以用c++寫成下面的樣子:
 
    dbQuery q; 
    dbCursor<Contract> contracts;
    dbCursor<Supplier> suppliers;
    int price, quantity;
    q = "(price >=",price,"or quantity >=",quantity,") and delivery.year=1999";
    // input price and quantity values
    if (contracts.select(q) != 0) { 
        do { 
            printf("%s\n", suppliers.at(contracts->supplier)->company);
        } while (contracts.next());
    } 

Table

fastdb中的數據保存在表中,這些表對應於c++類,其中表記錄對應於類實例。下面的c++數據類型可以作爲fastdb記錄的原子組件:

Type

Description

Bool

boolean type (true,false)

int1

one byte signed integer (-128..127)

int2

two bytes signed integer (-32768..32767)

int4

four bytes signed integer (-2147483648..2147483647)

int8

eight bytes signed integer (-2**63..2**63-1)

real4

four bytes ANSI floating point type

real8

eight bytes ANSI double precision floating point type

char const*

zero terminated string

dbReference<T>

reference to class T

dbArray<T>

dynamic array of elements of type T

 

除了上表定義的數據類型外,fastdb記錄還可以包括這些元組的嵌套結構。fastdb不支持無符號數據類型以簡化查詢語言,清除由於符號數/無符號數比較產生的錯誤,減少數據庫引擎的大小。

不幸的是c++沒有提供在運行時獲得一個類的元信息(metainformation)的方法(RTTI並不被所有編譯器支持,並且也不能提供足夠的信息)。因此程序員必須明確枚舉包含在數據庫表中的類字段(這也使得在類和表之間的映像更爲靈活)。fastdb提供了一個宏和相關的類的集合來使得這個映像儘可能的靈活。

每一個要在數據庫中使用的c++類或者結構,都包含一個特別的方法來描述其字段。宏TYPE_DESCRIPTOR(field_list)構成了這個方法。這個宏的用括號括起來的單一參數是一個類字段描述符的列表。如果要爲這個類定義一些方法並使之可以用於對應的數據庫,則用宏CLASS_DESCRIPTOR(name, field_list)來代替TYPE_DESCRIPTOR。需要類名來取得成員函數的引用。

下面的宏可以用來構造字段描述符。

 

FIELD(name)

      

指定名字的非索引字段

KEY(name, index_type)

索引字段。index_type必須是HASHED和INDEXED標誌的組合。當指定HASHED標誌的時候,fastdb將爲是用這個字段作爲關鍵字的表創建一個hash表。當指定INDEXED標誌時,fastdb將創建爲使用這個字段作爲關鍵字的表創建一個T_tree(一種特殊的索引).

UDT(name, index_type, comparator)

用戶自定義原始二進制類型。數據庫把這種類型作爲指定大小的字節序列處理。這個字段可以用來查詢(比較下同一類型的查詢參數),可以通過order子句來索引和使用。通過程序員提供的comparator函數來進行比較操作。比較函數接受3個參數:兩個指向待比較的原始二進制對象的指針及其大小。index_type的語義與KEY宏中的一致。

RAWKEY(name, index)

帶有預定義比較算子的原始二進制類型。這個宏只是一個把memcmp作爲比較算子的UDT宏的特例。

RAWFIELD(name)

另一個UDT宏的特例,使用memcmp作爲預定義比較算子,並且沒有索引。 

SUPERCLASS(name)

指定當前類的基類(父親)的信息。

RELATION(reference, inverse_reference)

指定類(表)之間的一對一、一對多或者多對多的關係。reference和inverse_reference字段都必須是引用或者引用數組類型。inverse_reference字段是一個包含了指向當前表的逆引用的引用表。逆引用自動由fastdb更新並用於查詢優化。

OWNER(reference, inverse_reference)

指定類之間的一對一、一對多或者多對多的owner-member關係。當owner記錄被刪除時所有引用的member記錄也會被刪除(層疊式刪除)。如果member記錄要引用owner就必須通過RELATION宏聲明。

METHOD(name)

爲類指定一個方法。該方法必須是無參的實例成員函數,返回bool值、數值、引用或者字符串類型。方法必須在類的所有屬性之後指定。

儘管只有原子字段可以被索引,但可以爲一個結構指定一個索引類型。只有當該索引類型在該結構的索引mask中指定時纔會爲該結構的成員創建。這就允許程序員可以根據該結構在記錄中的角色來設置或者取消索引。

下面的例子說明了頭文件中類型描述符的創建過程:
class dbDateTime { 
    int4 stamp;
  public:
 
    int year() { 
        return localtime((time_t*)&stamp)->tm_year + 1900;
    }
    ...
 
    CLASS_DESCRIPTOR(dbDateTime, 
                    (KEY(stamp,INDEXED|HASHED), 
                     METHOD(year), METHOD(month), METHOD(day),
                     METHOD(dayOfYear), METHOD(dayOfWeek),
                     METHOD(hour), METHOD(minute), METHOD(second)));
};    
 
class Detail { 
  public:
    char const* name;
    char const* material;
    char const* color;
    real4       weight;
 
    dbArray< dbReference<Contract> > contracts;
 
    TYPE_DESCRIPTOR((KEY(name, INDEXED|HASHED), 
                    KEY(material, HASHED), 
                    KEY(color, HASHED),
                    KEY(weight, INDEXED),
                    RELATION(contracts, detail)));
};
 
class Contract { 
  public:
    dbDateTime            delivery;
    int4                  quantity;
    int8                  price;
    dbReference<Detail>   detail;
    dbReference<Supplier> supplier;
 
    TYPE_DESCRIPTOR((KEY(delivery, HASHED|INDEXED), 
                    KEY(quantity, INDEXED), 
                    KEY(price, INDEXED),
                    RELATION(detail, contracts),
                    RELATION(supplier, contracts)));
};
所有數據庫中使用的類都要定義類型描述符。除了定義類型描述符外,還必須在C++類和數據庫表之間建立一個映像。宏REGISTER(name)就做這個工作。與TYPE_DESCRIPTOR宏不同的是,REGISTER宏應該在實現文件中使用而不是在頭文件中。該宏構造一個與類相連的表的描述符。如果你要在一個應用中使用多個數據庫,那麼就可能使用REGISTER_IN(name,database)宏在一個具體數據庫中註冊一個表。該宏的database參數應該是一個指向dbDatabase對象的指針。你可以像下面這樣註冊數據庫的表:
REGISTER(Detail);
REGISTER(Supplier);
REGISTER(Contract);
表(以及對應的類)在每一時刻只能對應於一個數據庫。當你打開一個數據庫,fastdb向數據庫中導入所有在應用中定義的類。如果一個同名的類在數據庫中已經存在,則會比較描述符在數據庫中的類與描述符在應用中的類,如果兩個類的定義不同,則fastdb試圖將該錶轉換成新的格式。數值類型之間的任何轉換(整形到實型,實型到整形,擴展或者截斷)都是允許的。增加字段也很容易,但是隻有對空表纔可以刪除字段(以避免偶然的數據崩潰).
裝載所有的類描述符後,fastdb就檢查在應用程序的類描述符中指定的索引是否存在於數據庫中、創建新的索引並且刪除不再使用的索引。只有在不超過一個應用程序訪問數據庫是纔可以進行表的重構以及增加/刪除索引。所以只有第一個與數據庫關聯的應用程序可以進行表的轉換,所有其餘的應用只能向數據庫中增加新類。
有一個特殊的內部表Metatable,該表包含了數據庫中所有其他表的信息。C++程序員不需要訪問這個表,因爲數據庫表的結構是由C++類指定的。但在交互SQL程序中,還是有必要檢查這個表來獲取記錄字段的信息。

從版本2.30開始,fastdb支持自增字段(有數據自動賦值的值唯一的字段).要使用自增字段必須:

1.帶上-DAUTOINCREMENT_SUPPROT標誌重新編譯fastdb和你的應用程序。(在fastdb makefile中的DEFS變量中增加這個標誌)

注意:不帶該標記編譯的fastdb創建的數據庫文件與帶標記編譯的fastdb創建的數據庫文件不兼容。

2.如果你要使用初始值非0的計數器,則必須給dbTableDescriptor::initialAutoincrementCount賦個值。該變量由所有的表共享,因此所有的表都有一個共同初始值的自增計數器。

3.自增字段必須是int4類型,並且必須用AUTOINCREMENT標誌聲明

        class Record {

 
             int4 rid;
             char const* name;
             ...
       
             TYPE_DESCRIPTOR((KEY(rid, AUTOINCREMENT|INDEXED), FIELD(name), ...));
        }

4.當要在數據庫中插入帶有自增字段的記錄是不需要爲自增字段賦值(將會被忽略)。當插入成功後,該字段將被賦給一個唯一的值(這樣確保在數據庫中從未使用過).

        Record rec;

       // no rec.rid should be specified
       rec.name = "John Smith";
       insert(rec);
       // rec.rid now assigned unique value
       int newRecordId  = rec.rid; // and can be used to reference this record

5.當記錄被刪除該值將不會再使用,當事務中止時,表的自增計數器也將回滾。

Query

query類用於兩個目的:

1.構造一個查詢並綁定查詢參數

2.作爲已編譯的查詢的緩存

fastdb提供重載的c++運算符'='和','來構造帶參數的查詢語句。參數可以在被使用的地方直接指定,消除了在參數佔位符和c變量之間的任何映像,在下面的查詢示例中,參數price和quantity的指針保存在查詢中,因此該查詢可以用不同的參數執行多次。c++函數重載使之可以自動的確定參數的類型,不需要程序員提供額外信息(從而減少了bug的可能性).

 dbQuery q;

        int price, quantity;
        q = "price >=",price,"or quantity >=",quantity;
由於char *可以用來指定一個查詢的分片(fraction)(例如"price >=")和一個字符串類型的參數,fastdb使用了一個特別的規則來解決這個模糊性。該規則基於這樣一個假定即沒有理由把一個查詢文本分解成兩個字符串如("price",">=")或者指定多於一個的參數序列("color=",color,color).因此fastdb假定第一個字符串是該查詢文本的一個分片並且隨之轉換到操作數模式。在操作數模式中,fastdb認爲char * 參數是一個查詢參數然後切換回到查詢文本模式,依此類推。也可以不用這個“句法糖”(syntax sugar)而是顯示的通過dbQuery::append(dbQueryElement::ElementType type, void const* ptr)方法來構造查詢元素。在向查詢添加元素之前,必須通過dbQuery::reset()方法來重置查詢('operator='自動作了這個事情)。
不能使用c++數值常量來作爲查詢參數,因爲參數是通過引用來訪問的。但可以使用字符串常量,因爲字符串時傳值的。有兩種方法在一個查詢中指定字符串參數:使用一個字符串緩衝或一個指向字符串指針的指針
     dbQuery q;
     char* type;
     char name[256];
     q = "name=",name,"and type=",&type;
 
     scanf("%s", name);
     type = "A";     
     cursor.select(q);
     ...
     scanf("%s", name);
     type = "B";     
     cursor.select(q);
     ...
查詢變量既不能作爲一個參數傳給一個函數也不能賦給另一個變量。當fastdb編譯查詢時,會把編譯樹存到該對象中。下一次使用該查詢時,不需要再次編譯並且可以使用已編譯好的樹。這樣節省了一些編譯查詢的時間。
fastdb提供了兩個方法來集成數據庫中的用戶自定義類型。第一種方法-定義類方法-已經討論過,另一個方法只處理查詢構造。程序員要定義方法,該方法並不作確實的運算,而是返回一個表達式(根據預先定義的數據庫類型),該方法來執行必要的查詢。最好通過例子來說明這點。fastdb沒有內置的日期時間類型,而是使用一個普通的c++類dbDateTime。該類定義了方法用來在有序列表中指定日期時間字段和使用通常的運算符來比較兩個日期。
class dbDateTime { 
    int4 stamp;
  public:
    ...
    dbQueryExpression operator == (char const* field) { 
        dbQueryExpression expr;
        expr = dbComponent(field,"stamp"),"=",stamp;
        return expr;
    }
    dbQueryExpression operator != (char const* field) { 
        dbQueryExpression expr;
        expr = dbComponent(field,"stamp"),"<>",stamp;
        return expr;
    }
    dbQueryExpression operator < (char const* field) { 
        dbQueryExpression expr;
        expr = dbComponent(field,"stamp"),">",stamp;
        return expr;
    }
    dbQueryExpression operator <= (char const* field) { 
        dbQueryExpression expr;
        expr = dbComponent(field,"stamp"),">=",stamp;
        return expr;
    }
    dbQueryExpression operator > (char const* field) { 
        dbQueryExpression expr;
        expr = dbComponent(field,"stamp"),"<",stamp;
        return expr;
    }
    dbQueryExpression operator >= (char const* field) { 
        dbQueryExpression expr;
        expr = dbComponent(field,"stamp"),"<=",stamp;
        return expr;
    }
    friend dbQueryExpression between(char const* field, dbDateTime& from,
                                    dbDateTime& till)
    { 
        dbQueryExpression expr;
        expr=dbComponent(field,"stamp"),"between",from.stamp,"and",till.stamp;
        return expr;
    }
 
    friend dbQueryExpression ascent(char const* field) { 
        dbQueryExpression expr;
        expr=dbComponent(field,"stamp");
        return expr;
    }    
    friend dbQueryExpression descent(char const* field) { 
        dbQueryExpression expr;
        expr=dbComponent(field,"stamp"),"desc";
        return expr;
    }    
};

所有這些方法接受參數作爲一個記錄的字段的名字,該名字用來構造一個記錄組件的全名。使用類dbComponent來作這個事情,該類把結構字段的名字和結構組件的名字組合成一個用'.'符號分隔的複合名字。類dbQueryExpression用來收集表達式項,表達式自動的用圓括號括起來,消除了運算符優先級引起的衝突。

假定一個記錄包含了一個字段dbDateTime類型的字段delivery,可以如下構造查詢:

 dbDateTime from, till;

        q1 = between("delivery", from, till),"order by",ascent("delivery");
        q2 = till >= "delivery"; 

除了這些方法外,一些類指定方法也可以用這種方法定義,擂如一個區域類型的overlaps方法。這種方法的好處是數據庫引擎可以使用預定義的類型並且可以使用索引和其他的一些優化方法來執行查詢。另一方面,這些類的實現的封裝已保留,因此程序員在一個類的表示改變時不應該重寫所有的查詢。

下面這些c++類型可以用作查詢參數:

int1

bool

int2

char const*

int4

char **

int8

char const**

real4

dbReference<T>

real8

dbArray< dbReference<T> >

Cursor

遊標用來訪問選擇語句返回的記錄。fastdb提供了有類型的遊標,也就是說,與具體表相關的遊標。fastdb有兩種遊標:只讀遊標和用於更新的遊標。fastdb中的遊標用c++模板類dbCursor<T>來表示,其中T爲與數據庫表相關的C++類的的名字。遊標類型必須在構造遊標的時候指定。缺省創建一個只讀遊標。要創建一個用於更新的遊標,必須給構造函數傳遞一個dbCursorForUpdate參數。

執行一個查詢要麼通過遊標select(dbQuery &q)方法,要麼通過select()方法,後者可以迭代表中的所有記錄。兩個方法都返回中選的記錄的數量,並且把當前位置置於第一個記錄(如果有的話)。遊標可以前滾或者後滾。next(),prev(),first(),last()方法可以用來改變遊標的當前位置。如果由於沒有(更多)的記錄存在而使得操作無法進行,這些方法將返回NULL,並且遊標的位置不改變。

一個類T的遊標包含一個類T的實例,用來獲得當前的記錄。這就是爲什麼表類必須要有一個缺省構造函數(無參數的構造函數)而沒有副作用。fastdb優化了從數據庫中獲取記錄,只從對象的固定部分複製記錄。字符串本身並不複製,而是使相應的字段直接指向數據庫中。數組也是如此:他們的組件在數據庫中的表示根在應用程序中的表示是一樣的(標量類型的數組或者標量組件的嵌套結構的數組).

應用程序不能直接改變數據庫中的字符串和數據元素。當一個數組方法需要更新一個數組體時,先在內存中創建一個副本然後更新這個副本。如果程序員要更新字符串字段,他應該給這個指針賦一個新值,而不是直接修改數據庫裏的字符串。對於字符串元素建議使用char const * 而不是char *,以使編譯器可以檢查對字符串的非法使用。

遊標類提供了get()方法來獲得指向當前記錄(保存在遊標中)的指針。重載的運算符' ->'也可以用來訪問當前記錄的元素。如果一個遊標一更新方式打開,就可以改變當前的記錄並且用update()方法保存到數據庫中或者被刪除掉。如果當前記錄被刪除,下一個記錄變爲當前記錄。如果沒有下一個記錄,則前一個記錄(如果有的話)變爲當前。removeAll()方法刪除表中的所有記錄。而removeAllSelected方法只刪除遊標選擇的所有記錄。

當更新記錄後,數據庫的大小可能會增加。從而虛擬存儲器的數據庫區域需要進行擴展。該重映射的後果之一就是,該區域的基地址可能發生變化,然後應用程序中保存的所有數據庫指針都變得無效。當數據庫區域重映象時fastdb自動更新所有打開的遊標中的當前記錄。因此,當一個數據庫更新時,程序員應該通過遊標的->方法來訪問記錄字段,而不應該使用指針變量。

當前選擇使用的內存可以通過reset()方法來釋放。該方法自動的由select()、dbDatabase::commit()、dbDatabase::rollback()方法以及遊標的銷燬(destructor)函數調用,因此大多數情況下不需要顯式調用reset()方法。

遊標也可以通過引用來訪問記錄。at(dbReference<T> const& ref)方法把遊標指向引用所指的記錄。在這種情況下,選擇將只包含一個記錄,而next(),prev()方法將總是返回NULL。由於遊標和引用在fastdb重視嚴格類型化的,所有必須的檢查可以有編譯器靜態的進行而不需要動態類型檢查。運行時唯一要進行的檢查是對空引用的檢查。遊標中當前記錄的對象標識符可以用currentId()方法獲得。

可以限制select語句返回的記錄的數目。遊標類有兩個方法setSelectionLimit(size_t lim)和unsetSelectionLimit()用來設置/取消查詢返回的記錄數的限制。在某些情況下,程序員可能只需要幾個記錄或者頭幾個記錄,從而查詢的執行時間和消耗的內存大小可以通過限制選擇的大小來降低。但如果你指定了被選記錄的排序方式,只選擇k個記錄的查詢並不返回關鍵字最小的頭k個記錄,而是返回任意k個記錄然後排序。

於是所有數據庫數據的操作都可以通過遊標來進行,唯一的例外是插入操作,fastDB提供了一個重載的插入函數:
        template<class T>
        dbReference<T> insert(T const& record);

該函數將在表的末尾插入一個記錄然後返回該對象的引用。fastdb中插入的順序是嚴格指定的因而應用程序可以使用表中記錄排序方式的假定。因爲應用程序大量使用引用在對象之間導航,有必要提供一個根對象,從這個對象開始進行引用遍歷。這樣一個根對象的一個可取候選者就是表中的第一個記錄(也是表中最老的記錄).該記錄可以通過不帶參數執行select()方法來訪問。遊標中的當前記錄就是表中的第一條記錄

fastdb的c++API爲引用類型定義了一個特殊的null變量,可以用null變量與引用比較或者賦給一個引用:
        void update(dbReference<Contract> c) {
            if (c != null) { 
                dbCursor<Contract> contract(dbCursorForUpdate);
               contract.at(c);
               contract->supplier = null;
            }
        }
查詢參數通常跟c++變量綁定。大多數情況下這是方便而且靈活的機制。但在多線程應用中,無法保證同一查詢會在同一時刻不被另一線程以不同的參數執行。一個解決的方法是使用同步原語(臨界區或者mutex)來排除查詢的併發執行。但這種方法會導致性能退化。fastdb可以並行操作讀操作而提高了整體系統吞吐量。另一個解決方法是使用延遲參數綁定。如下所示:
dbQuery q;
 
struct QueryParams { 
    int         salary;
    int         age;
    int         rank;
};
 
void open()
{
    QueryParams* params = (QueryParams*)NULL;
    q = "salary > ", params->salary, "and age < ", params->age, "and rank =", params->rank;
}
 
void find(int salary, int age, int rank) 
{ 
    QueryParams params;
    params.salary = salary;
    params.age = age;
    params.rank = rank;
    dbCursor<Person> cusor;
    if (cursor.select(q, &params) > 0) { 
        do { 
            cout << cursor->name << NL;
        } while (cursor.next());
    }
}
在這個例子中open函數只爲結構中的字段偏移綁定查詢變量。然後再find函數中,指向帶有參數的結構的真實的指針傳遞給select結構。find函數可以被多個線程併發執行而只有一個編譯好的查詢被所有這些線程使用。這種機制從版本2.25開始使用。

Database

dbDatabase類控制應用與數據庫的交互,進行數據庫併發訪問的同步,事務處理,內存分配,出錯處理...

dbDatabase對象的構造函數允許程序員制定一些數據庫參數。
    dbDatabase(dbAccessType type = dbAllAccess,
               size_t dbInitSize = dbDefaultInitDatabaseSize,
               size_t dbExtensionQuantum = dbDefaultExtensionQuantum,
               size_t dbInitIndexSize = dbDefaultInitIndexSize,
               int nThreads = 1);

支持下面的數據庫訪問類型:

Access type

Description

dbDatabase::dbReadOnly

Read only mode

dbDatabase::dbAllAccess

Normal mode

dbDatabase::dbConcurrentRead

Read only mode in which application can access the database concurrently with application updating the same database in dbConcurrentUpdate mode

dbDatabase::dbConcurrentUpdate

Mode to be used in conjunction with dbConcurrentRead to perform updates in the database without blocking read applications for a long time

當數據庫已只讀方式打開時,不能向數據庫添加新的類定義,不能改變已有的類的定義和索引。

在數據庫主要用只讀模式訪問而更新不應該長時間堵塞讀操作的情況下應該同時使用dbConcurrentUpdatedbConcurrentRead模式。在這種模式下更新數據庫可以與讀訪問併發執行(讀將不會看到改變的數據直到事務提交)。只有在更新事務提交時,纔會設置排他鎖然後在當前對象索引自增改變(incremental change)之後馬上釋放掉。

於是你可以使用dbConcurrentRead模式啓動一個或多個應用而其讀事務將併發執行。也可以使用dbConcurrentUpdate模式啓動一個或多個應用。所有這些應用的事務將通過一個全局的mutex來同步。因此這些事務(甚至是隻讀)將排他性的執行。但是dbConcurrentUpdate模式的應用的事務可以與dbConcurrentRead模式的應用的事務併發運行。請參閱testconc.cpp例子,裏邊說明了這些模式的使用方法。

注意!不要把dbConcurrentUpdatedbConcurrentRead模式與其他模式混合使用,也不要在一個進程中同時使用他們(因此不能啓動兩個線程其中一個用dbConcurrentUpdate模式打開數據庫另一個用dbConcurrentRead模式)。在dbConcurrentUpdate模式下不要使用dbDatabase::precommit方法。

dbInitSize參數指定了數據庫文件的初始大小。數據庫文件按照需要增長;設置初始大小只是爲了減少重新分配空間(會佔用很多時間)的次數。在當前fastdb數據庫的實現中該大小在每次擴展時至少加倍。該參數的缺省值爲4兆字節。

dbExtensionQuantum指定了內存分配位圖的擴展量子。簡單的說,這個參數的值指定了再不需要試圖重用被釋放的對象的空間時分配多少連續內存空間。缺省值爲4MB.詳細情況參見Memory allocation小節。

dbInitIndexSize參數指定了初始的索引大小。fastdb中的所有對象都通過一個對象索引訪問。該對象索引有兩個副本:當前的和已提交的。對象索引按照需要重新分配,設置一個初始值只是爲了減少(或者增加)重新分配的次數。該參數的缺省值是64K個對象標識符。

最後一個參數nThreads控制並行查詢的層次。如果大於1,則fastdb啓動一些查詢的並行執行(包括對結果排序).在這種情況下,fastdb引擎將派生指定數目的並行線程。通常爲該參數指定超過系統中在線CPU數目的值是沒有意義的。也可以爲該參數傳遞0值。在這種情況下,fastdb將自動偵測系統中在線cpu的數目。在任何時候都可以用dbDatabase::setConcurrency來指定線程數。

dbDatabase類包含一個靜態字段dbParallelScanThreshold,該字段指定了在使用並行查詢後表中記錄數的一個閾值,缺省爲1000。

可以用open(char const* databaseName, char const* fileName = NULL, unsigned waitLockTimeout = INFINITE)方法來打開數據庫。如果文件名參數省略,則通過數據庫名家一個後綴“.fdb"來創建一個文件。數據庫名應該是由除了‘\’之外的任意符號組成的標識符。最後一個參數waitLockTimeout可以設置用來防止當工作於該數據庫的所有活動進程中的某些進程崩潰時把所有的進程鎖住。如果崩潰的進程鎖住了數據庫,則其他的進程都將無法繼續執行。爲了防止這種情況,可以指定一個等待該鎖的最大延遲,當該延遲過期後,系統將試圖進行恢復並繼續執行活動的進程。如果數據庫成功打開open方法返回true,否則返回false。在後面這種情況,數據庫的handleError方法將帶上DatabaseOpenError錯誤碼被調用。一個數據庫會話可以用close方法中止,該方法隱含的提交當前事務。

在一個多線程的應用中,每一個要訪問數據庫的線程都應該首先與數據庫粘附(attach). dbDatabase::attach()方法分配線程指定數據然後把線程與數據庫粘附起來。該方法自動由open()方法調用。因此沒有理由爲打開數據的線程調用attach()方法。當該線程工作完畢,應當調用dbDatabase::detach() 方法。close方法自動調用detach()方法。detach()方法隱含提交當前事務。一個已經分離的線程試圖訪問數據庫時將產生斷言錯誤(assertion failure)。

fastdb可以並行的編譯和執行查詢,在多處理器系統中顯著的提高了性能。但不能併發更新數據庫(這是爲了儘可能少的日誌事務(log-less transaction)機制和0等待恢復的代價).當一個應用程序試圖改變數據庫(打開一個更新遊標或者在表中插入新記錄)時,首先就以排他方式鎖住數據庫,禁止其他應用程序訪問數據庫,即使是隻讀的查詢。這樣來避免鎖住數據庫應用程序過長的時間,改變事務應當儘可能的短。在該事務中不能進行堵塞操作(如等待用戶的輸入).

在數據庫層只使用共享鎖和排它鎖使得fastdb幾乎清除鎖開銷從而優化無衝突操作的執行速度。但是如果多個應用同時更新數據庫的不同部分,則fastdb使用的方法將非常低效。這就是爲什麼fastdb主要適用於單應用程序數據局訪問模式或者多應用讀優勢(read-dominated)訪問模式模型。

在多線程應用中游標對象應該只由一個線程使用。如果在你的應用中有超過一個的線程,則在每個線程中使用局部遊標變量。在線程間有可能共享查詢變量,但要注意查詢參數。查詢要麼沒有參數,要麼使用相關的參數綁定形式。

數據庫對象由所有的線程共享,使用線程專有數據來進行查詢的同步代價最小的並行編譯和執行。需要同步的全局的東西很少:符號表,樹結點池…..。但是掃描、解析和執行查詢可以不需要任何的同步來進行,如果有多處理器系統的高層併發機制。

一個數據庫事務由第一個選擇或者插入操作開始。如果使用用於更新的遊標,則數據庫以排他方式鎖住,禁止其他應用和線程訪問數據庫。如果使用只讀遊標,這數據庫以共享模式鎖住,防止其他的應用或者線程改變數據庫,但允許併發讀請求的執行。一個事務必須顯示終止,要麼通過dbDatabase::commit()方法提交該事務對數據庫的所有更改,或者通過dbDatabase::rollback()方法來取消事務所作的所有更改。dbDatabase::close()方法自動提交當前事務。

如果你使用只讀遊標來執行選擇從而啓動一個事務然後又適用更新遊標來對數據庫作某些改變,則數據庫將首先以共享模式鎖住,然後鎖變成排他模式。如果該數據被多個應用訪問這種情況可能會造成死鎖。想象一下應用A啓動了一個讀事務而應用B也啓動了一個讀事務。二者都擁有數據庫的共享鎖。如果二者都試圖把它們的鎖改變爲排他模式,他們將永遠被互相堵塞(另外一個進程的共享鎖存在時不能授予排它鎖)。爲了避免這種情況,在事務開始就試着使用更新遊標,或者顯示的使用dbDatabase::lock()方法。關於fastdb中事務實現的信息可以參見《事務》這一節。

        
可以使用lock()方法來顯示的鎖住數據庫。鎖通常是自動進行的。只有很少的情況下你才需要使用這個方法。它將以排他方式鎖住數據庫知道當前事務結束。
 
可以用dbDatabase::backup(char const* file)方法來備份數據庫。備份操作將以共享模式鎖住數據然後從內存向指定的文件刷新數據庫的映像。因爲使用了影子對象索引,數據庫文件總是處於一致狀態,因此從備份恢復至需要把備份文件改一下名字(如果備份被放到磁帶,則首先要把文件恢復到磁盤).
dbDatabase類也負責處理一些應用的錯誤,如編譯查詢時的句法錯誤,執行查詢時的索引越界或者空引用訪問。由一個虛方法dbDatabase::handleError來處理這些錯誤。
 
        virtual void handleError(dbErrorClass error, 
                                 char const*  msg = NULL, 
                                 int          arg = 0);

 

程序員可以從dbDatabase類來派生出自定義的子類,並重定義缺省的錯誤處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章