FastDB 很难得的一篇分析

来源:互联网 发布:php 积分商城 编辑:程序博客网 时间:2024/05/19 02:04

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类来派生出自定义的子类,并重定义缺省的错误处理。

原创粉丝点击