常常有人問 Oracle Berkeley DB “我如何在 Berkeley DB 中進行 ?"因此,我們此處將介紹如何在 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) 應用程序必須檢查循環內范圍的終點。