Berkeley DB 中常用 SQL 函数使用指南.docx
《Berkeley DB 中常用 SQL 函数使用指南.docx》由会员分享,可在线阅读,更多相关《Berkeley DB 中常用 SQL 函数使用指南.docx(24页珍藏版)》请在冰豆网上搜索。
BerkeleyDB中常用SQL函数使用指南
BerkeleyDB中常用SQL函数使用指南
常常有人问OracleBerkeleyDB“我如何在BerkeleyDB中进行?
"因此,我们此处将介绍如何在OracleBerkeleyDB中实施众多您喜爱的SQL功能。
不是所有的SQL应用程序都应该在OracleBerkeleyDB实施(OracleBerkeleyDB是一个开放源的嵌入式数据库引擎,提供了快速、可靠、本地的持久性,无需管理),但如果您有一系列相对固定的查询且很关心性能,BerkeleyDB将是很好的选择。
让我们从头开始(很好的起步点)。
我们从ABC开始学阅读,在BerkeleyDB中我们从术语开始。
这里为坚定的SQL程序员提供了一个小型“翻译指南”:
SQL术语OracleBerkeleyDB对应词汇
数据库环境
表数据库
字节组/行键/数据对
主索引键
次索引次数据库
选择一个应用程序域—传统员工数据库,经过一定的简化。
我们进一步假定您需要所有BerkeleyDB的全部额外特性:
并发、事务、可恢复性等。
创建数据库
在SQL中,您可以执行以下命令
CREATEDATABASEpersonnel
在BerkeleyDB中,您想要创建一个放置所有应用程序数据的环境。
在代码中,您将通过一个环境句柄来引用环境,该句柄类型为DB_ENV。
您将使用这一句柄来操作此环境。
现在,将一些精妙的错误处理过程放在一边,来集中讨论API。
DB_ENV*dbenv;
intret;
/*Createthehandle.*/
DB_ASSERT(db_env_create(&dbenv,0)==0);
/*
*Ifyouwantedtoconfiguretheenvironment,youwoulddothathere.
*Configuraitionmightincludethingslikesettingacachesize,
*specifyingerrorhandlingfunctions,specifying(different)
*directoriesinwhichtoplaceyourlogand/ordatafiles,setting
*parameterstodescribehowmanylocksyou'dneed,etc.
*/
/*Now,openthehandle.*/
DB_ASSERT(dbenv->open(dbenv,"my_databases/personnel",
DB_CREATE|DB_INIT_LOCK|DB_INIT_MPOOL|DB_INIT_TXN|DB_THREAD,0644);
您现在创建和打开了一个环境。
需要注意几项事情:
*开始前必须有my_databases/personnel目录。
*open调用的最后一个参数就是作为此环境的一部分为您创建的文件的模式。
*此处指定的标记将允许您创建环境
(DB_CREATE),使用锁定(DB_INIT_LOCK);有一个共享的内存缓存池(DB_INIT_MPOOL);使用事务(DB_INIT_TXN);并同时在不同的控制线程中使用得到的环境句柄(DB_THREAD)。
在SQL中,查询通常由单独的服务器处理,该服务器由数据库管理员配置以在您的系统上正常(或不正常)工作。
因为BerkeleyDB嵌入到了您的应用程序中,因此该应用程序可以执行许多配置工作。
但这确实与数据库调优有关,我们将另文详述。
现在创建了数据库,接下来创建一些表。
在BerkeleyDB中,表由类型为DB*的句柄引用。
对于应用程序中的每个表,通常会打开一个句柄,然后在一或多个线程中使用该句柄。
因此,在SQL中可能是
CREATETABLEemployee
(primarykeyempidint(8),last_namevarchar(20),first_namevarchar(15),
salarynumeric(10,2)salary,streetvarchar(20),cityvarchar(15),
statechar
(2),zipint(5))
在我们讨论实施这一过程的BerkeleyDB代码前,要记住在SQL中,数据库负责实施和解释数据模式,这很重要。
在BerkeleyDB中,这一解释由应用程序完成。
在分析数据操作语言(DML)时这将变得更加有趣,但现在其很明显,因为在创建员工表时,BerkeleyDB只知道主键,不知道数据库中的不同域。
首先,您需要创建一个数据库句柄来代表创建的的表。
(我们再次略过错误处理。
)
DB*dbp;
DB_ENV*dbenv;
/*Let'sassumewe'veusedthecodefromabovetosetdbenv.*/
ASSERT(db_create(&dbp,dbenv,0)==0);
/*
*Likewiththeenvironment,tablescanalsobeconfigured.You
*canspecifythingslikecomparisonfunctions,page-size,etc.
*Thatwouldallgohere.
*/
/*Now,we'llactuallyopen/createtheprimarytable.*/
ASSERT(dbp->open(dbp,NULL,"employee.db",NULL,DB_BTREE,
DB_AUTO_COMMIT|DB_CREATE|DB_THREAD,0644)==0).
这一调用将使用B-树作为主索引结构创建表。
该表将在my_databases/personnel目录中物化,使用名称employee.db。
文件将包含一个表并具有由最后的参数(0644)指定的文件系统权限。
指定的标记在事务中创建表,允许以后的事务操作
(DB_AUTO_COMMIT);允许表不存在时创建表(DB_CREATE);并指定可由多个控制线程同时使用得到的句柄(DB_THREAD)。
注意,您尚未指定具体由什么组成主键(索引)或数据字段好似存储于该表中。
这都将是应用程序的任务,稍后接触到插入、选择和更新部分的内容时这将变得更清楚。
现在,我们来看看如果在employeeid上有一个主索引同时对姓使用一个次索引时将会出现什么情况。
您应该使用上述SQL查询并执行。
CREATEINDEXlnameONemployee(last_name)
在BerkeleyDB中,次索引就像是表。
然后您可以联合表,使一个表成为另一个表的次索引。
为实施这一功能,需要更深入地了解应用程序要使用的数据表示。
假设应用程序要使用C结构以在我们的员工表中包含字节组。
您可以按下面所示定义结构:
typedefstruct_emp_data{
charlname[20];
charfname[15];
floatsalary;
charstreet[20];
charcity[15];
charstate[2];
intzip;
}emp_data;
假设员工ID为简单的整数:
typedefintemp_key;
在BerkeleyDB中,操作键或数据项时,您使用DBT结构。
DBT包含了不透明的字节串,以指针和长度来表示。
指针由DBT的数据字段引用,长度存储于DBT的大小字段中。
如果希望操纵表示一个员工的键/数据对,您需要将一个DBT用于emp_key,另一个用于emp_data。
DBTkey_dbt,data_dbt;
emp_keyekey;
emp_dataedata;
memset(&key_dbt,0,sizeof(key_dbt));
memset(&data_dbt,0,sizeof(data_dbt));
/*
*NowmakethekeyanddataDBT'sreferencethekeyanddata
*variables.
*/
key_dbt.data=&ekey;
key_dbt.size=sizeof(ekey);
data_dbt.data=&edata;
data_dbt.size=sizeof(edata);
这里我们可以了解到SQL中的字节组由键/数据对表示,应用程序负责了解如何解释这些对。
有了这些作为背景后,我们返回到次索引的讨论。
因为BerkeleyDB不了解键/数据对中数据元素的模式和结构,所以需要应用程序的帮助来找到我们用作次索引的字段。
应用程序以回调函数方式提供帮助。
回调函数接受键/数据对输入并返回引用了用作次键的值的DBT。
所以要创建last_name的次索引,必须编写一个回调函数,接受键/数据对输入并返回引用了数据项的last_name字段的DBT。
int
lname_callback(DB*dbp,constDBT*key,constDBT*data,DBT*skey)
{
emp_data*edata;
/*
*Weknowthattheopaquebyte-stringrepresentedbythedataDBT
*representsoneofouremp_datastructures,solet'scastit
*tooneofthosesothatwecanmanipulateit.
*/
edata=data->data;
skey->data=edata->lname;
skey->size=strlen((edata->lname);
return(0);
}
写完回调函数后,可以指定次索引。
次索引只是一个表,所以先创建一个表:
DB*sdbp;
ASSERT(db_create(&sdbp,dbenv,0)==0);
/*Configuresdbp.*/
ASSERT(sdbp->open(sdbp,NULL,"emp_lname.db",NULL,DB_BTREE,
DB_AUTO_COMMIT|DB_CREATE|DB_THREAD,0644)==0);
再次使用B树结构对姓进行索引,保留以前使用的所有标记和模式。
最后,您必须将次索引表与主表(员工表)相关联。
请记住,dbp是员工表的句柄,sdbp是次索引表的句柄。
ASSERT(dbp->associate(dbp,NULL,sdbp,lname_callback,flags)==0);
注意事项:
*可以了创建任意多的次索引。
重要的问题是次索引降低了插入的速度(因为您必须为每个次索引创建索引项),但如果使用次键值(如,姓)来查询和选择字节组,它们将大大改进查询的性能。
*在任何时候更新主表时,只要打开并关联了次索引,次索引将始终保持最新。
但如果忘记打开并关联次索引,然后更改基表,您会发现次索引不是最新的。
应当避免这一情况。
DDL中的最后两个操作是丢弃命令:
丢弃索引、表和数据库。
如同在SQL中丢弃索引和删除表一样,您也能在BerkeleyDB中完成这些操作。
在SQL中,您可以执行以下命令
DROPTABLEemployee
或者
DROPINDEXlname
在SQL中丢弃一个表将丢弃与之关联的所有索引,在BerkeleyDB中,您必须显式完成此任务。
幸运的是,在BerkeleyDB中丢弃表或索引是同样的操作。
移除表前,必须关闭表上的所有数据库句柄。
关闭表容易;假设我们要丢弃员工数据库的次索引。
先关闭次索引:
sdbp->close(sdbp,0)
在发出数据库句柄的关闭命令后,不能再使用句柄。
关闭次索引表后,您可以使用dbenv句柄的dbremove方法将其移除:
DB_ENV*dbenv;
ASSERT(dbenv->dbremove(dbenv,
NULL,"emp_lname.db",NULL,DB_AUTO_COMMIT)==0);
可以使用同一调用顺序(closing和dbremoving)来丢弃表。
假设不想丢弃表,只想更改其名称。
您也可完成这一操作。
与移除一样,首先要关闭表句柄:
dbp->close(dbp,0);
现在您可以更改表名称:
DB_ENV*dbenv;
ASSERT(dbenv->dbrename(dbenv,
NULL,"employee.db",NULL,"newemp.db",DB_AUTO_COMMIT)==0);
最后,您可能想销毁数据库。
在SQL中执行
DROPDATABASEpersonnel
这一命令在BerkeleyDB中也有对应的命令。
首先,必须关闭环境:
ASSERT(dbenv->close(dbenv,0)==0);
与关闭表句柄一样,当关闭环境句柄后,将不能使用该句柄。
所以,为了丢弃表,您需要创建一个新句柄,然后使用该句柄移除数据库(环境)。
ASSERT(db_env_create(&dbenv,0)==0);
ASSERT(dbenv->remove(dbenv,"my_databases/personnel",0)==0);
至此,我们完成了SQL的DDL到BerkeleyDB的转换。
接下来,我们将讨论如何完成SQLDML到BerkeleyDB的转换。
在BerkeleyDB中执行SQLDML操作
我们已经介绍了SQL的DDL和其在BerkeleyDB中的实现,现在要将数据添加到数据库,讨论SQL的插入、更新和删除。
在SQL中使用插入语句将数据插入表:
INSERTINTOemployeesVALUES(00010002,"mouse","mickey",1000000.00,
"MainStreet","DisneyLand","CA",98765);
SQL插入都变成了数据库或游标句柄的BerkeleyDB“put”方法;我们先讨论数据库,然后再探讨游标。
假设您已经打开了一个表,有一个数据库句柄dbp引用了员工表。
现在,雇佣MickeyMouse。
DB*dbp;
DBTkey_dbt,data_dbt;
emp_dataedata;
emp_keyekey;
/*Putthevalueintotheemployeekey.*/
ekey=00010002;
/*Initializeanemp_datastructure.*/
strcpy(edata.lname,"Mouse");
strcpy(edata.fname,"Mickey");
edata.salary=1000000.00;
strcpy(edata.street,"MainStreet");
strcpy(edata.city,"DisneyLand");
strcpy(edata.state,"CA");
edata.zip=98765;
/*InitializeDBTs*/
memset(&key_dbt,0,sizeof(key_dbt));
memset(&data_dbt,0,sizeof(data_dbt));
/*Now,assignkeyanddatavaluestoDBTs.*/
key->data=&ekey;
key->size=sizeof(ekey);
data->data=&edata;
data->size=sizeof(edata);
/*Finally,putthedataintothedatabase.*/
ASSERT(dbp->put(dbp,NULL,&key_dbt,&data_dbt,DB_AUTO_COMMIT)==0);
请注意,如果已经有将任何次索引与员工表相关联(如在SQL中),则在插入时将自动对其进行更新。
现在,假设表中有些数据,您希望对这些数据进行更改。
例如,要给Mickey涨工资!
有多种完成方法。
第一个方法与上面的插入代码一样—如果您在一个表上使用PUT方法并且该键已存在(且表不允许单键有相同的数值),PUT将使用新版本代替旧版本。
因此,以下步骤将使用新记录替换Mickey的记录,薪水将为$2,000,000,而不是$1,000,000。
/*Putthevalueintotheemployeekey.*/
ekey=00010002;
/*Initializeanemp_datastructure.*/
strcpy(edata.lname,"Mouse");
strcpy(edata.fname,"Mickey");
edata.salary=2000000.00;
strcpy(edata.street,"MainStreet");
strcpy(edata.city,"DisneyLand");
strcpy(edata.state,"CA");
edata.zip=98765;
/*InitializeDBTs*/
memset(&key_dbt,0,sizeof(key_dbt));
memset(&data_dbt,0,sizeof(data_dbt));
/*Now,assignkeyanddatavaluestoDBTs.*/
key->data=&ekey;
key->size=sizeof(ekey);
data->data=&edata;
data->size=sizeof(edata);
/*Finally,putthedataintothedatabase.*/
ASSERT(dbp->put(dbp,NULL,&key_dbt,&data_dbt,DB_AUTO_COMMIT)==0);
请注意,该方法较麻烦,为完成它,您需要了解数据库中所有其他域的值。
因此,不同于
UPDATEemployeesSETsalary=2000000WHEREempid=000100002
其中,您只需知晓employeeID,而现在需要知晓所有信息。
难道在BerkeleyDB没有可用方法吗?
答是是有。
如果确切地知道要替换的数据项字节,您可以使用与等同于更新命令的方法。
要使用此方法,您需要引入游标概念。
游标表示表中的某个位置。
它让您遍历表并保留当前项的情况,然后再对其操作。
在BerkeleyDB中创建游标很简单—它是数据库句柄的方法:
DBC*dbc;
DB*dbp;
ASSERT(dbp->cursor(dbp,NULL,0)==0);
有游标后,我们需要将其定位于Mickey的记录,以便能对其进行更新。
这等同于SQL语句的WHERE部分。
DBTkey_dbt,data_dbt;
emp_data*edata;
emp_keyekey;
/*We'dliketolookupMickey'skey.*/
emp_key=0010002;
memset(&key_dbt,0,sizeof(key_dbt));
key_dbt.data=&emp_key;
key_dbt.size=sizeof(emp_key);
/*
*Wewantthedatareturned,sowedon'tneedtoinitializethe
*employeedatadatastructure.
*/
memset(&data_dbt,0,sizeof(data_dbt));
/*Now,setthecursortotherecordwiththekeyemp_key.*/
dbc->c_get(dbc,&key_dbt,&data_dbt,DB_SET);
接下来我们就可以更改薪水了(处理子句的“SETsalary=2000000”部分)
/*Changethesalary.*/
edata=data_dbt->data;
edata.salary=2000000;
最后,应用SQL语句的UPDATE部分:
dbc->c_put(dbc,&key_dbt,&data_dbt,DB_CURRENT);
在本例中,您事先不知道Mickey的记录内容,因此需要检索然后再更新。
或者,甚至无需检索记录。
DBT上的DB_DBT_PARTIAL标记值指示您在获取/插入记录的一部分,所以BerkeleyDB可以忽略除该部分外的所有内容。
再试一次:
emp_dataedata;
floatsalary;
/*We'dliketolookupMickey'skey.*/
emp_key=0010002;
memset(&key_dbt,0,sizeof(key_dbt));
key_dbt.data=&emp_key;
key_dbt.size=sizeof(emp_key);
不检索整个记录,不检索任何东西—即执行PARTIAL获取,指定您只需要0字节的数据项。
/*Wedon'twantthedata,wejustwanttopositionthecursor.*/
memset(&data_dbt,0,sizeof(data_dbt));
data_dbt->flags=