SQL 开发人员 Oracle Berkeley DB 指南

SQL 开发人员 Oracle Berkeley DB 指南

作者:Margo Seltzer

Oracle Berkeley DB 中常用 SQL 函数使用指南。

常常有人问 Oracle Berkeley DB “我如何在 Berkeley DB 中进行 <SQL 查询>?”因此,我们此处将介绍如何在 Oracle Berkeley DB 中实施众多您喜爱的 SQL 功能。不是所有的 SQL 应用程序都应该在 Oracle Berkeley DB 实施( Oracle Berkeley DB 是一个开放源的嵌入式数据库引擎,提供了快速、可靠、本地的持久性,无需管理),但如果您有一系列相对固定的查询且很关心性能,Berkeley DB 将是很好的选择。

让我们从头开始 (很好的起步点)。我们从 ABC 开始学阅读,在 Berkeley DB 中我们从术语开始。

这里为坚定的 SQL 程序员提供了一个小型“翻译指南”:

SQL 术语 Oracle Berkeley DB 对应词汇
数据库 环境
数据库
字节组/行 键/数据对
主索引
次索引 次数据库

选择一个应用程序域 — 传统员工数据库,经过一定的简化。我们进一步假定您需要所有 Berkeley DB 的全部额外特性:并发、事务、可恢复性等。

创建数据库

在 SQL 中,您可以执行以下命令

CREATE DATABASE personnel

在 Berkeley DB 中, 您想要创建一个放置所有应用程序数据的环境。在代码中,您将通过一个环境句柄来引用环境,该句柄类型为 DB_ENV。您将使用这一句柄来操作此环境。现在,将一些精妙的错误处理过程放在一边,来集中讨论 API。

	DB_ENV *dbenv;
	int ret;
	
	/* Create the handle. */
	DB_ASSERT(db_env_create(&dbenv, 0) == 0);
	
	/*
	 * If you wanted to configure the environment, you would do that here.
	 * Configuraition might include things like setting a cache size,
	 * specifying error handling functions, specifying (different)
 	 * directories in which to place your log and/or data files, setting
 	 * parameters to describe how many locks you'd need, etc.
	 */
	
	/* Now, open the handle. */
	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 中,查询通常由单独的服务器处理,该服务器由数据库管理员配置以在您的系统上正常(或不正常)工作。因为 Berkeley DB 嵌入到了您的应用程序中,因此该应用程序可以执行许多配置工作。但这确实与数据库调优有关,我们将另文详述。

现在创建了数据库,接下来创建一些表。在 Berkeley DB 中, 表由类型为 DB * 的句柄引用 。对于应用程序中的每个表,通常会打开一个句柄,然后在一或多个线程中使用该句柄。

因此,在 SQL 中可能是

CREATE TABLE employee
	(primary key empid int(8), last_name varchar(20), first_name varchar(15),
	salary numeric(10, 2) salary, street varchar (20), city varchar (15),
	state char(2), zip int(5))

在我们讨论实施这一过程的 Berkeley DB 代码前,要记住在 SQL 中,数据库负责实施和解释数据模式,这很重要。在 Berkeley DB 中,这一解释由应用程序完成。在分析数据操作语言 (DML) 时这将变得更加有趣,但现在其很明显,因为在创建员工表时,Berkeley DB 只知道主键,不知道数据库中的不同域。

首先,您需要创建一个数据库句柄来代表创建的的表。(我们再次略过错误处理。)

	DB *dbp;
	DB_ENV *dbenv;
	
	
	/* Let's assume we've used the code from above to set dbenv. */
	ASSERT(db_create(&dbp, dbenv, 0) == 0);
	
	/*
	 * Like with the environment, tables can also be configured. You
	 * can specify things like comparison functions, page-size, etc.
	 * That would all go here.
	 */
	
	/* Now, we'll actually open/create the primary table. */
	
	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)。

注意,您尚未指定具体由什么组成主键 (索引) 或数据字段好似存储于该表中。这都将是应用程序的任务,稍后接触到插入、选择和更新部分的内容时这将变得更清楚。

现在,我们来看看如果在 employee id 上有一个主索引同时对姓使用一个次索引时将会出现什么情况。

您应该使用上述 SQL 查询并执行。

CREATE INDEX lname ON  employee (last_name)

在 Berkeley DB 中,次索引就像是表。然后您可以联合表,使一个表成为另一个表的次索引。为实施这一功能,需要更深入地了解应用程序要使用的数据表示。

假设应用程序要使用 C 结构以在我们的员工表中包含字节组。您可以按下面所示定义结构:

typedef struct _emp_data {
	char	lname[20];
	char	fname[15];
	float	salary;
	char	street[20];
	char	city[15];
	char	state[2];
	int	zip;
} emp_data;

假设员工 ID 为简单的整数:

typedef int emp_key;

在 Berkeley DB 中,操作键或数据项时,您使用 DBT 结构。DBT 包含了不透明的字节串,以指针和长度来表示。指针由 DBT 的数据字段引用,长度存储于 DBT 的大小字段中。如果希望操纵表示一个员工的键/数据对,您需要将一个 DBT 用于 emp_key,另一个用于 emp_data。

	DBT	key_dbt, data_dbt;
	emp_key	ekey;
	emp_data	edata;
	
	memset(&key_dbt, 0, sizeof(key_dbt));
	memset(&data_dbt, 0, sizeof(data_dbt));
	
	/*
	 * Now make the key and data DBT's reference the key and data
	 * variables.
	 */
	key_dbt.data = &ekey;
	key_dbt.size = sizeof(ekey);
	
	data_dbt.data = &edata;
	data_dbt.size = sizeof(edata);
	

这里我们可以了解到 SQL 中的字节组由键/数据对表示,应用程序负责了解如何解释这些对。

有了这些作为背景后,我们返回到次索引的讨论。因为 Berkeley DB 不了解键/数据对中数据元素的模式和结构,所以需要应用程序的帮助来找到我们用作次索引的字段。应用程序以回调函数方式提供帮助。回调函数接受键/数据对输入并返回引用了用作次键的值的 DBT。

所以要创建 last_name 的次索引,必须编写一个回调函数,接受键/数据对输入并返回引用了数据项的 last_name 字段的 DBT。

int
lname_callback(DB *dbp, const DBT *key, const DBT *data, DBT *skey)
{
	emp_data	*edata;
	
	/*
	 * We know that the opaque byte-string represented by the data DBT
	 * represents one of our emp_data structures, so let's cast it
	 * to one of those so that we can manipulate it.
	 */
	
	edata = data->data;
	
	skey->data = edata->lname;
	skey->size = strlen((edata->lname);
	
	return (0);
}

写完回调函数后,可以指定次索引。次索引只是一个表,所以先创建一个表:

	DB *sdbp;
	
	ASSERT(db_create(&sdbp, dbenv, 0) == 0);
	
	/* Configure sdbp. */
	
	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 中丢弃索引和删除表一样,您也能在 Berkeley DB 中完成这些操作。在 SQL 中,您可以执行以下命令

DROP TABLE employee

或者

DROP INDEX lname

在 SQL 中丢弃一个表将丢弃与之关联的所有索引,在 Berkeley DB 中,您必须显式完成此任务。幸运的是,在 Berkeley DB 中丢弃表或索引是同样的操作。

移除表前,必须关闭表上的所有数据库句柄。关闭表容易;假设我们要丢弃员工数据库的次索引。先关闭次索引:

	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 中执行

DROP DATABASE personnel

这一命令在 Berkeley DB 中也有对应的命令。

首先,必须关闭环境:

	ASSERT(dbenv->close(dbenv, 0) == 0);

与关闭表句柄一样,当关闭环境句柄后,将不能使用该句柄。所以,为了丢弃表,您需要创建一个新句柄,然后使用该句柄移除数据库(环境)。

	ASSERT(db_env_create(&dbenv, 0) == 0);
	ASSERT(dbenv->remove(dbenv,  "my_databases/personnel", 0) == 0);

至此,我们完成了 SQL 的 DDL 到 Berkeley DB 的转换。接下来,我们将讨论如何完成 SQL DML 到 Berkeley DB 的转换。

在 Berkeley DB 中执行 SQL DML 操作

我们已经介绍了 SQL 的 DDL 和其在 Berkeley DB 中的实现,现在要将数据添加到数据库,讨论 SQL 的插入、更新和删除。

在 SQL 中使用插入语句将数据插入表:

INSERT INTO employees VALUES  (00010002, "mouse", "mickey", 1000000.00,
	"Main Street", "Disney Land",  "CA", 98765);

SQL 插入都变成了数据库或游标句柄的 Berkeley DB“put”方法;我们先讨论数据库,然后再探讨游标。

假设您已经打开了一个表,有一个数据库句柄 dbp 引用了员工表。现在,雇佣 Mickey Mouse。

	DB *dbp;
	DBT key_dbt, data_dbt;
	emp_data edata;
	emp_key ekey;
	
	/* Put the value into the employee key. */
	ekey = 00010002;
	
	/* Initialize an emp_data structure. */
	strcpy(edata.lname, "Mouse");
	strcpy(edata.fname, "Mickey");
	edata.salary = 1000000.00;
	strcpy(edata.street, "Main Street");
	strcpy(edata.city, "Disney Land");
	strcpy(edata.state, "CA");
	edata.zip = 98765;
	
	/* Initialize DBTs */
	memset(&key_dbt, 0, sizeof(key_dbt));
	memset(&data_dbt, 0, sizeof(data_dbt));
	
	/* Now, assign key and data values to DBTs. */
	key->data = &ekey;
	key->size = sizeof(ekey);
	data->data = &edata;
	data->size = sizeof(edata);
	
	/* Finally, put the data into the database. */
	ASSERT(dbp->put(dbp, NULL, &key_dbt, &data_dbt, DB_AUTO_COMMIT) == 0);

请注意,如果已经有将任何次索引与员工表相关联(如在 SQL 中),则在插入时将自动对其进行更新。

现在,假设表中有些数据,您希望对这些数据进行更改。例如,要给 Mickey 涨工资!有多种完成方法。

第一个方法与上面的插入代码一样 — 如果您在一个表上使用 PUT 方法并且该键已存在 (且表不允许单键有相同的数值), PUT 将使用新版本代替旧版本。因此,以下步骤将使用新记录替换 Mickey 的记录,薪水将为 $2,000,000,而不是 $1,000,000。

	/* Put the value into the employee key. */
	ekey = 00010002;
	
	/* Initialize an emp_data structure. */
	strcpy(edata.lname, "Mouse");
	strcpy(edata.fname, "Mickey");
	edata.salary = 2000000.00;
	strcpy(edata.street, "Main Street");
	strcpy(edata.city, "Disney Land");
	strcpy(edata.state, "CA");
	edata.zip = 98765;
	
	/* Initialize DBTs */
	memset(&key_dbt, 0, sizeof(key_dbt));
	memset(&data_dbt, 0, sizeof(data_dbt));
	
	/* Now, assign key and data values to DBTs. */
	key->data = &ekey;
	key->size = sizeof(ekey);
	data->data = &edata;
	data->size = sizeof(edata);
	
	/* Finally, put the data into the database. */
	ASSERT(dbp->put(dbp, NULL, &key_dbt, &data_dbt, DB_AUTO_COMMIT) == 0);

请注意,该方法较麻烦,为完成它,您需要了解数据库中所有其他域的值。因此,不同于

UPDATE employees  SET salary = 2000000 WHERE empid = 000100002

其中,您只需知晓 employee ID,而现在需要知晓所有信息。难道在 Berkeley DB 没有可用方法吗?答是是有。如果确切地知道要替换的数据项字节,您可以使用与等同于更新命令的方法。

要使用此方法,您需要引入游标概念。游标表示表中的某个位置。它让您遍历表并保留当前项的情况,然后再对其操作。

在 Berkeley DB 中创建游标很简单 — 它是数据库句柄的方法:

	DBC	*dbc;
	DB	*dbp;
	
	ASSERT(dbp->cursor(dbp, NULL, 0) == 0);

有游标后,我们需要将其定位于 Mickey 的记录,以便能对其进行更新。这等同于 SQL 语句的 WHERE 部分。

	DBT	key_dbt, data_dbt;
	emp_data	*edata;
	emp_key		ekey;
	
	/* We'd like to look up Mickey's key. */
	emp_key = 0010002;
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = &emp_key;
	key_dbt.size = sizeof(emp_key);
	
	/*
	 * We want the data returned, so we don't need to initialize the
	 * employee data data structure.
	 */
	memset(&data_dbt, 0, sizeof(data_dbt));
	
	/* Now, set the cursor to the record with the key emp_key. */
	dbc->c_get(dbc, &key_dbt, &data_dbt, DB_SET);

接下来我们就可以更改薪水了 (处理子句的“SET salary=2000000”部分)

	/* Change the salary. */
	edata = data_dbt->data;
	edata.salary = 2000000;

最后,应用 SQL 语句的 UPDATE 部分:

	dbc->c_put(dbc, &key_dbt, &data_dbt, DB_CURRENT);

在本例中,您事先不知道 Mickey 的记录内容,因此需要检索然后再更新。

或者,甚至无需检索记录。DBT 上的 DB_DBT_PARTIAL 标记值指示您在获取/插入记录的一部分,所以 Berkeley DB 可以忽略除该部分外的所有内容。

再试一次:

	emp_data edata;
	float salary;
	
	/* We'd like to look up Mickey's key. */
	emp_key = 0010002;
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = &emp_key;
	key_dbt.size = sizeof(emp_key);

不检索整个记录,不检索任何东西 — 即执行 PARTIAL 获取,指定您只需要 0 字节的数据项。

	/* We don't want the data, we just want to position the cursor. */
	memset(&data_dbt, 0, sizeof(data_dbt));
	data_dbt->flags = DB_DBT_PARTIAL;
	data_dbt->dlen = 0;
	/* Position the cursor on Mickey's record */
	dbc->c_get(dbc, &key_dbt, &data_dbt, DB_SET);
	/*
	 * Now, prepare for a partial put. Note that the DBT has already
	 * been initialized for partial operations.  We need to specify
	 * where in the data item we wish to place the new bytes and
	 * how many bytes we'd like to replace.
	 */
	salary = 2000000.00;
	/* The DBT contains just the salary information. */
	data_dbt->data = &salary;
	data_dbt->size = sizeof(salary);
	/*
	 * dlen and doff tell Berkeley DB where to place this information
	 * in the record.  dlen indicates how many bytes we are replacing --
	 * in this case we're replacing the length of the salary field in
	 * the structure (sizeof(emp_data.salary)).  doff indicates where
	 * in the data record we will place these new bytes -- we need to
	 * compute the offset of the salary field.
	 */
	data_dbt->dlen = sizeof(emp_data.salary);
	data_dbt->doff = ((char *)&edata.salary - (char *)&edata);
	/* Now, put the record back with the new data. */
	dbc->c_put(dbc, &key_dbt, &data_dbt, DB_CURRENT);

数据检索

了解如何向表插入数据后,现在学习如何检索它。让我们从最简单的方法开始:根据其主键查找值

	SELECT * FROM employees   WHERE id=0010002

您已经知道如何使用游标来完成此任务:

	DBT		key_dbt, data_dbt;
	emp_data	*edata;
	emp_key	ekey;
	/* We'd like to look up Mickey's key. */
	emp_key = 0010002;
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = &emp_key;
	key_dbt.size = sizeof(emp_key);
	/*
	 * We want the data returned, so we don't need to initialize the
	 * employee data data structure.
	 */
	memset(&data_dbt, 0, sizeof(data_dbt));
	/* Now, set the cursor to the record with the key emp_key. */
	dbc->c_get(dbc, &key_dbt, &data_dbt, DB_SET);

使用上面的游标操作,因为我们稍后要更新该记录。假如只想检索记录,则甚至不需要游标。所需要的是 dbp 句柄的 get 方法:

	DBT		key_dbt, data_dbt;
	emp_data	*edata;
	emp_key	ekey;
	/* We'd like to look up Mickey's key. */
	emp_key = 0010002;
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = &emp_key;
	key_dbt.size = sizeof(emp_key);
	/*
	 * We want the data returned, so we don't need to initialize the
	 * employee data data structure.
	 */
	memset(&data_dbt, 0, sizeof(data_dbt));
	/* Now, use the dbp method. */
	dbp->get(dbp, NULL, &key_dbt, &data_dbt, 0);

因此,这与上面的 SELECT 表达式一样。

到目前为止,我们都是根据主键来查找记录。如果不知道主键,该怎么办?下面提供了几种方法:

  • 使用次键值查找记录。
  • 遍历共享同一键的项目。
  • 遍历整个数据库。

下面详述以上方法。

使用次键

与在 SQL 中一样,根据次键检索和使用主键非常类似。

事实上,SQL 查询看起来相同,除了 where 子句:

SELECT * FROM employees  WHERE last_name = "Mouse"

Berkeley DB 调用与其对等的主键调用类似。

它使用的不是主键示例中的 dbp,而是使用 sdbp 根据次键来进行查找:

	DBT	key_dbt, data_dbt;
	emp_data	*edata;
	/* We'd like to look up by Mickey's last name. */
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = "Mouse";
	key_dbt.size = strlen((char *)key_dbt.data);
	/*
	 * We want the data returned, so we don't need to initialize the
	 * employee data data structure.
	 */
	memset(&data_dbt, 0, sizeof(data_dbt));
	/* Now, call the get method. */
	sdbp->get(sdbp, NULL, &key_dbt, &data_dbt, 0);

data_dbt 中返回的内容非常有趣。返回的是主数据库中的数据 — 即在数据 DBT 中返回了同样的东西,不管您是使用主键还是次键查找。

但是,您会发现按次键查找时,得到的结果与按主键检索或 SQL 语句得到的结果有所不同。主键丢失,因为没有位置来返回它。所以,上面的代码实际上实施

	SELECT last_name, first_name, salary, street, city, state, zip FROM
	employees  WHERE  last_name="Mouse"

如果您需要主键,该如何做?答案是使用 dbp->pget 或 dbc->pget 方法。这两个方法与 get 方法一样,只是它们专门设计用于您需要返回主键时的次索引查询。因此,在这一情形中,结果中会包括主键、次键和数据元素:

	DBT	key_dbt, pkey_dbt, data_dbt;
	emp_data	*edata;
	/* We'd like to look up by Mickey's last name. */
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = "Mouse";
	key_dbt.size = strlen((char *)key_dbt.data);
	/* Set up the dbt into which to return the primary. */
	memset(&pkey_dbt, 0, sizeof(pkey_dbt));
	/*
	 * We want the data returned, so we don't need to initialize the
	 * employee data data structure.
	 */
	memset(&data_dbt, 0, sizeof(data_dbt));
	/* Now, get the record and the primary key. */
	sdbp->pget(sdbp, NULL, &key_dbt, &pkey_dbt, &data_dbt, 0);

该代码等同于 SQL 次选择:

	SELECT * FROM employees  WHERE last_name="Mouse"

遍历多个记录

到现在为止,我们仅返回了一个记录。SQL 允许您返回多个记录 (换言之,姓为 Mouse 的所有员工)。如何在 Berkeley DB 中实现此目的?

让我们考虑两种情形。第一个情形,按照键来查找项目。第二个情形,搜索数据库,按照不带键的字段查找项目。

假设您希望查找所有姓 Mouse 的员工(假设有多个)。这意味着已经创建了 last_name 次索引,从而允许重复。打开数据库前,需要对它进行配置以支持重复:

	sdbp->set_flags(sdbp, DB_DUP);
	
	ASSERT(sdbp->open(sdbp, NULL, "emp_lname.db", NULL, DB_BTREE,
		DB_AUTO_COMMIT | DB_CREATE | DB_THREAD, 0644) == 0);

现在,按次索引检索时,您可能想使用游标来完成。开始时使用前面使用的代码,可以添加一个循环以遍历共享同一次键的项目:

	DBT	key_dbt, data_dbt;
	DBC	*sdc;
	emp_data	*edata;
	/* We'd like to look up by Mickey's last name. */
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = "Mouse";
	key_dbt.size = strlen((char *)key_dbt.data);
	/*
	 * We want the data and primary key returned, so we need only
	 * initialize the DBTs for them to be returned.
	 */
	memset(&data_dbt, 0, sizeof(data_dbt));
	memset(&pkey_dbt, 0, sizeof(pkey_dbt));
	/* Now, create a cursor. */
	sdbp->cursor(sdbp, NULL, &sdbc, 0);
	/* Now loop over all items with the specified key. */
	for (ret = sdbc->pget(sdbc, &key_dbt, &pkey_dbt, &data_dbt, DB_SET);
	    ret == 0:
	    ret = sdbc->pget(sdbc, &key_dbt, &pkey_dbt, &data_dbt, DB_NEXT_DUP) {
	    /* Do per-record processing in here. */
	}

通过使用游标查找带指定键的第一个项目,完成游标的初始化,然后遍历数据库中带同一键的所有项目。

另一个可能的根据键进行遍历的形式为查询形式,如

SELECT * FROM employees  WHERE id >= 1000000 AND id < 2000000

再次使用游标来遍历,但这一次您想创建一个起始和结束点。Berkeley DB 使得起始点的建立非常简单,结束点由应用程序来完成。

	DBT	key_dbt, data_dbt;
	DBC	*dc;
	emp_key		ekey;
	/* Set the starting point. */
	memset(&key_dbt, 0, sizeof(key_dbt));
	ekey = 1000000;
	key_dbt.data = &ekey;
	key_dbt.size = sizeof(ekey);
	key_dbt.flags = DB_DBT_USERMEM;
	key_dbt.ulen = sizeof(ekey);
	memset(&data_dbt, 0, sizeof(data_dbt));
	/* Now, create a cursor. */
	dbp->cursor(dbp, NULL, &dbc, 0);
	/* Now loop over items starting with the low key. */
	for (ret = dbc->get(dbc, &key_dbt, &data_dbt, DB_SET_RANGE);
	    ret == 0:
	    ret = dbc->get(dbc, &key_dbt, &data_dbt, DB_NEXT)) {
	    /* Check if we are still in the range. */
	    if (ekey >= 2000000)
	    	break;
	
	    /* Do per-record processing in here. */
	}

要注意两点:1) 以 DB_SET_RANGE 标记作为循环的开始,这会将游标定位于第一个大于或等于指定键的项目; 2) 应用程序必须检查循环内范围的终点。

还要注意您在 key_dbt 中设置了 DB_DBT_USERMEM 标记,指示检索的键应置于用户指定的内存中。这可以让您使用 ekey 变量来检查该键。

在选择部分内容的最后,我们来讨论一个查询,该查询将返回一个或多个其评判标准不是带键的字段的项。假设

SELECT * FROM  employees  WHERE state=ca

因为 state 字段上没有键,您只能遍历整个数据库。即意味着执行一个简单的游标遍历循环。

	DBC *dbc;
	DBT key_dbt, data_dbt;
	emp_data	*edata;
	dbp->cursor(dbp, &key_dbt, &data_dbt, &dbc, 0);
	memset(&key_dbt, 0, sizeof(key_dbt));
	memset(&data_dbt, 0, sizeof(data_dbt));
	for (ret = dbc->get(dbc, &key_dbt, &data_dbt, DB_FIRST);
		ret == 0;
		ret = dbc->get(dbc, &key_dbt, &data_dbt, DB_NEXT)) {
		/* See if the state field is "ca". */
		edata = data_dbt->data;
		if (strcmp(edata->state, "ca") == 0)
			/* Keep this record. */
	}

这似乎没有效率,如果没有字段的索引,您别无它法,而且,实际上这就是当您指定一个匹配不带键的字段的查询时 SQL 数据库内部的工作过程。

移除数据

您现在已经了解了如何插入和更改数据以及如何对其进行检索。最后要讨论的是如何移除数据。基本上,有两种不同的方法可用于从数据中删除字节组:如果您知道要移除的项的键(且该项不是该键的众多重复项之一),则可以使用键式删除。如果不知道键,您可以遍历和使用游标删除。从简单情形开始,解雇 Mickey Mouse。

DELETE FROM employees  WHERE id= 0010002

这看起来像检索,但我们将使用 del 方法。

	DBT key_dbt;
	emp_key	ekey;
	ekey = 0010002;
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = &ekey;
	key_dbt.size = sizeof(ekey);
	dbp->del(dbp, NULL, &key_dbt, 0);

您可以选择解雇所有姓 Mouse 的员工。可以用同一方法,因为您有姓的次索引:

DELETE FROM employees  WHERE last_name = "Mouse"
	DBT key_dbt;
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = "Mouse";
	key_dbt.size = strlen(key_dbt.data);
	dbp->del(dbp, NULL, &key_dbt, 0);

但这有点过于无情。假如您不想解雇 Mickey,而只想解雇 Minnie Mouse。有可以方便地解雇 Minnie 的方法吗?换言之,如何完成这一任务:

DELETE FROM  employees  where last_name =  "Mouse" AND first_name = "Minnie"

毫不为奇,您要使用游标来遍历数据项目,然后选择要删除的项目。

	DBT key_dbt, data_dbt;
	DBC *sdbc;
	sdbp->cursor(sdbp, NULL, &sdbc, 0);
	memset(&key_dbt, 0, sizeof(key_dbt));
	key_dbt.data = "Mouse";
	key_dbt.size = strlen(key_dbt.data);
	for (ret = sdbc->get(sdbc, &key_dbt, &data_dbt, DB_SET);
	    ret == 0;
	    ret = sdbc->get(sdbc, &key_dbt, &data_dbt, DB_NEXT_DUP)) {
		edata = data_dbt->data;
		if (strcmp(edata->first_name, "Minnie") == 0) {
			/* OK, this is a record we want to delete. */
			sdbc->del(sdbc, 0);
		}
	}
	dbp->del(dbp, NULL, &key_dbt, 0);

现在您应该已经大致了解了如何创建 Oracle Berkeley DB 函数来执行基本的 SQL 命令。Berkeley DB 也有众多的选项和配置,可以为您提供更为精妙的功能。此处只探讨一个话题:如何将数据库操作纳入事务中。

管理事务

先了解事务在(多数) SQL 实施中是如何工作的。当在 SQL 中执行 DML 语句时,它将成为当前事务的一部分。随后的每个语句都将作为该事务的一部分运行。当 SQL 会话结束或应用程序执行 COMMIT 语句时,当前事务将进行提交操作。任何事后时候都可同工作执行 ROLLBACK 语句中断事务。

许多 SQL 实施还包括 AUTOCOMMIT 特性,该特性将每个 DML 语句都视为其自己的事务。当启用了 AUTOCOMMIT 模式后,以下次序

	statement 1
	COMMIT
	statement 2
	COMMIT
	statement 3
	COMMIT

等同于

	statement 1
	statement 2
	statement 3

Berkeley DB 也允许您将数据库操作封装入事务中。与 SQL 不同,您可以不用事务运行 Berkeley DB。事实上,除非显示请求事务,否则运行时不会用到它们。因此,如何告知 Berkeley DB 您要使用事务呢?

请记住,在打开环境时您可以指定一些标记:

	DB_ASSERT(dbenv->open(dbenv,  "my_databases/personnel",
		DB_CREATE | DB_INIT_LOCK |  DB_INIT_MPOOL | DB_INIT_TXN | DB_THREAD,
		0644);

那些标记将针对您的应用程序配置 Berkeley DB。在本例中,您通过指定 DB_INIT_TXN 标记启用了事务。如果省略标记,则应用程序将无需通过事务运行。

Berkeley DB 提供了与 SQL 的 AUTOCOMMIT 类似的特性。 您可以配置整个数据库 (环境) 始终使用环境句柄的 set_flags 方法自动提交:

	dbenv->set_flags(dbenv, DB_AUTOCOMMIT, 1);

或者,也可以在一个打开的数据库上指定 DB_AUTOCOMMIT,致使您不会向其显式传递事务的所有后续操作将在事务中运行。

假如您不想自动提交,希望应用程序可以将操作组合成为一个逻辑事务。例如,您想添加 Mickey Mouse 并指定他为经理。

INSERT INTO employees VALUES (00010002, "mouse", "mickey", 1000000.00,
	"Main Street", "Disney Land", "CA", 98765);
INSERT INTO manages(00000001, 000100002)
COMMIT

(上面表明 id=00000001 的员工将管理 Mickey。)

假设您知道如何执行数据操作,因此我们集中讨论如何指定事务。

首先,必须显式开始一个事务 (与在 SQL 中不同)。创建事务是一个环境操作,因此它是环境句柄的一个方法。该方法将创建一个事务句柄 (DB_TXN)。

	DB_TXN *txn;
	dbenv->txn_begin(dbenv, NULL, &txn, 0);

具备事务句柄后,将其传入任何您希望加入事务中的数据库操作:

	emp_dbp->put(emp_dbp, txn, &key, &data, 0);
	man_dbp->put(man_dbp, txn, &key, &data, 0);

然后,您可以调用事务句柄的适当方法来提交或中断事务。

提交事务:

      txn->commit(txn, 0);

中断事务:

      txn->abort(txn);

两个方法都为破坏性方法,使得事务句柄不可用。

与 SQL 不同, Berkeley DB 也以通过事务来保护 DDL 操作。因此,您可以将 DB_TXN 句柄传送到操作,如 dbenv->dbremove、dbenv->dbrename 和 dbp->open(¡  DB_CREATE ¡ )。在这些情形中,DDL 操作将在指定事务的环境中执行,这意味着与其它事务一样可以对其进行提交或中止。

结论

Oracle Berkeley DB 提供的功能与 SQL 数据库的相同,但以完全不同的程序包提供。您可编写程序调用 API,并且整个数据库被直接“嵌入”到应用程序中;即它们运行于同一地址空间。这通常会将性能提升一个数量级。为取得这一成绩将增加应用程序的负担。当应用程序要求非常高的性能或应用程序操纵的数据本身不是关系型时,这通常是最有用的。


Margo Seltzer 是 Oracle 的 Berkeley DB 组中的架构师。她是 Berkeley DB 的创始人之一,与他人共同创建了 Sleepycat Software。她还是赫雪尔史密夫计算机教授和哈佛工程和应用科学学院的教授。其研究领域包括文件系统、数据库和事务处理系统。

出处:http://www.oracle.com/technology/global/cn/pub/articles/seltzer-berkeleydb-sql.html

发表评论