空間站

          北極心空

            BlogJava :: 首頁(yè) :: 聯(lián)系 :: 聚合  :: 管理
            15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks

          developerWorks 中國(guó)  >  Information Management  >

          如何用代理鍵實(shí)現(xiàn) DB2 UDB 主鍵

          考察生成惟一序列數(shù)字的幾種選擇

          developerWorks
          文檔選項(xiàng)
          將此頁(yè)作為電子郵件發(fā)送

          將此頁(yè)作為電子郵件發(fā)送

          未顯示需要 JavaScript 的文檔選項(xiàng)



          級(jí)別: 初級(jí)

          Jason Zhang (jasonz@ca.ibm.com), 軟件開(kāi)發(fā)人員, IBM

          2004 年 9 月 01 日

          如何將代理鍵(surrogate key)用作 DB2 UDB 中的主鍵?代理鍵是生成惟一序列號(hào)的一種有效方法。從本文中可以了解三種實(shí)現(xiàn):傳統(tǒng)方法,使用鍵管理器,以及使用新的 DB2 UDB 特性。

          簡(jiǎn)介

          使用代理鍵解決方案是為了發(fā)現(xiàn)一種生成惟一序列號(hào)的有效方法。本文描述了三種實(shí)現(xiàn):

          • 使用傳統(tǒng)方法。
          • 使用鍵管理器。
          • 使用 DB2 UDB 特性。

          代理鍵也叫 內(nèi)鍵(internal key)。當(dāng)創(chuàng)建一個(gè)表時(shí),可以添加一個(gè)額外的列作為代理鍵。這個(gè)列應(yīng)該是 NOT NULL,并且沒(méi)有商業(yè)意義。可以將該代理列指定為主鍵列。例如可以有一個(gè)數(shù)字代理列。代理鍵的值從某一個(gè)數(shù)字開(kāi)始,例如 "1",以這個(gè)數(shù)字作為該列在表中第一行的值,之后的每一行中該列的值都按 1 遞增。

          例如,如果我們有表 EMPLOYEE:

          CREATE TABLE EMPLOYEE (	FIRSTNAME		CHAR(64),
                                  LASTNAME		CHAR(64),
                                  SALARY 			DECIMAL(10, 2))
                                  

          那么可以添加一個(gè)代理鍵列 SERIALNUMBER,并將其指定為主鍵列。這樣,這個(gè)表的定義就變?yōu)椋?/p>
          CREATE TABLE EMPLOYEE (	SERIALNUMBER	BIGINT NOT NULL,
                                  FIRSTNAME	CHAR(64),
                                  LASTNAME	CHAR(64),
                                  SALARY 		DECIMAL(10, 2),
                                  PRIMARY KEY (SERIALNUMBER))
                                  

          那么,怎樣將惟一的值賦給每一行的 SERIALNUMBER 列呢?首先需要為代理鍵生成惟一的值。下面我將討論三種可行的解決方案。





          回頁(yè)首


          使用傳統(tǒng)方法

          解決方案的思想

          傳統(tǒng)方法是使用簡(jiǎn)單的 SQL 或觸發(fā)器生成惟一的值。

          示例

          以表 EMPLOYEE 為例。您可以在 INSERT 語(yǔ)句中實(shí)現(xiàn)代理鍵生成函數(shù):

          INSERT INTO EMPLOYEE (SERIALNUMBER, FIRSTNAME, LASTNAME,
                                  SALARY) VALUES ((SELECT MAX(SERIALNUMBER) FROM EMPLOYEE)+1,
                                  ‘John’, ‘Smith’, 999.99)
                                  

          SQL 語(yǔ)句 " (SELECT MAX(SERIALNUMBER) FROM EMPLOYEE)+1 " 將找出最大的 SERIALNUMBER 并將其加 1,這樣新行就有一個(gè)惟一的 SERIALNUMBER。

          這樣做存在的一個(gè)問(wèn)題是,當(dāng)將第一行插入表中時(shí),可能會(huì)得到如下錯(cuò)誤:

          SQL0407N  Assignment of a NULL value to a NOT NULL.
                                  SQLSTATE=23502.
                                  

          得到上述錯(cuò)誤的原因是,當(dāng)表為空時(shí)," SELECT MAX(SERIALNUMBER) FROM EMPLOYEE " 的返回為 NULL。因此,我們必須使用 COALESCE() 來(lái)處理這個(gè)問(wèn)題:

          INSERT INTO EMPLOYEE (SERIALNUMBER, FIRSTNAME, LASTNAME,
                                  SALARY) VALUES (COALESCE((SELECT MAX(SERIALNUMBER)
                                  FROM EMPLOYEE), 0)+1, ‘John’, ‘Smith’, 999.99)
                                  

          另一種傳統(tǒng)方法是使用觸發(fā)器來(lái)生成代理鍵:

          CREATE TRIGGER AUTOSURROGATEKEY NO CASCADE
                                  BEFORE INSERT ON EMPLOYEE
                                  REFERENCING NEW AS N
                                  FOR EACH ROW
                                  MODE DB2SQL BEGIN ATOMIC
                                  SET N.SERIALNUMBER = COALESCE((SELECT MAX(SERIALNUMBER)
                                  FROM EMPLOYEE), 0)+1;
                                  END
                                  

          優(yōu)點(diǎn)及問(wèn)題

          傳統(tǒng)方法易于理解,而且容易在所有系統(tǒng)上實(shí)現(xiàn)。但是,這種實(shí)現(xiàn)實(shí)際上會(huì)導(dǎo)致事務(wù)處理系統(tǒng)中出現(xiàn)并發(fā)問(wèn)題。因?yàn)樵搶?shí)現(xiàn)只允許一次執(zhí)行一條 INSERT 操作。

          因此,在獲得最大的 SERIALNUMBER 之前," SELECT MAX(SERIALNUMBER) FROM EMPLOYEE " 必須等待其他所有事務(wù)完成對(duì)表 EMPLOYEE 的 INSERT 或 UPDATE 操作。例如,如果有兩個(gè)事務(wù)正在對(duì) EMPLOYEE 表進(jìn)行 INSERT 操作,那么其中有一個(gè)事務(wù)會(huì)被另一個(gè)事務(wù)阻塞。顯然,這種“逐次插入”的解決方案不適合多用戶(hù)的事務(wù)處理系統(tǒng)。





          回頁(yè)首


          使用鍵管理器

          解決方案的思想

          很多大型的應(yīng)用程序使用鍵管理器方法維護(hù)所有表的代理鍵。鍵管理器可以是一個(gè)助手類(lèi)。每當(dāng)需要向表插入一個(gè)行時(shí),便可以調(diào)用鍵管理器生成新的鍵值,然后將獲得的鍵值插入新行。

          示例

          首先,需要?jiǎng)?chuàng)建表 KEYS 來(lái)記錄每個(gè)表的當(dāng)前代理鍵值。鍵管理器類(lèi)將使用該表生成新鍵值。

          CREATE TABLE KEYS (	TABLENAME		CHAR(256),
                                  COLNUMNAME		CHAR(256),
                                  SURROGATEKEYVALUE	BIGINT,
                                  INCREMENT		BIGINT,
                                  PRIMARY KEY(TABLENAME, COLNUMNAME));
                                  

          第二,將新表(例如表 EMPLOYEE)注冊(cè)到表 KEYS 中。

          INSERT INTO KEYS (TABLENAME, COLUMNNAME, SURROGATEKEYVALUE,
                                  INCREMENT) VALUES (‘EMPLOYEE’, ‘SERIALNUMBER’, 0, 1);
                                  

          第三,編寫(xiě) KeyManger 類(lèi)來(lái)維護(hù)每個(gè)已注冊(cè)表的代理鍵。KeyManager 將提供兩個(gè)方法:

          /**
                                  *Intialize the KeyManger
                                  */
                                  KeyManager.singleton();
                                  /**
                                  *Return the unique surrogate key value according to the input table
                                  *name and column name.
                                  */
                                  KeyManager. GetSurrogateKey(String tableName, String columnName);
                                  

          要查看更詳細(xì)的 KeyManger 的代碼,請(qǐng)參考附錄。

          第四,調(diào)用 KeyManger 來(lái)獲得主鍵值:

          …
                                  KeyManager km = KeyManager.singleton();
                                  Long surrogateKey = km.getSurrogateKey("EMPLOYEE", "SERIALNUMBER");
                                  …
                                  

          優(yōu)點(diǎn)和問(wèn)題

          顯然,鍵管理器是模塊化設(shè)計(jì)的一個(gè)很好的例子。鍵管理器封裝了代理鍵生成函數(shù)。這種實(shí)現(xiàn)也易于定制。您可以在 KEYS 表中為 SURROGATEKEYVALUE 或 INCREMENT 指定不同的值,以得到不同的代理鍵。而且,這種實(shí)現(xiàn)可以在大多數(shù)數(shù)據(jù)庫(kù)系統(tǒng)上實(shí)施。但是,為了進(jìn)行維護(hù),需要一個(gè)單獨(dú)的表和編寫(xiě)代碼。所以,這種方法更適合于大型的跨數(shù)據(jù)庫(kù)系統(tǒng)。





          回頁(yè)首


          使用 DB2 UDB 特性

          DB2 UDB 提供了三種方法來(lái)生成惟一值。您可以使用這些方法來(lái)實(shí)現(xiàn)代理鍵。

          • DB2 UDB Version 6.1 中的 GENERATE_UNIQUE() SQL 函數(shù)。
          • DB2 UDB Version 7.2 中 CREATE TABLE 語(yǔ)句的 IDENTITY 選項(xiàng)。
          • DB2 UDB Version 7.2 中的 SEQUENCE 對(duì)象。

          GENERATE_UNIQUE()

          解決方案的思想

          GENERATE_UNIQUE() 最初是在 DB2 UDB Version 6.1 中提供的一個(gè) SQL 函數(shù)。該函數(shù)返回當(dāng)前系統(tǒng)時(shí)間戳。我們可以使用該函數(shù)為代理鍵列生成惟一值。

          示例

          CREATE TABLE EMPLOYEE (	SERIALNUMBER	CHAR(13) FOR BIT
                                  DATA NOT NULL,
                                  FIRSTNAME		CHAR(64),
                                  LASTNAME		CHAR(64),
                                  SALARY 			DECIMAL(10, 2),
                                  PRIMARY KEY (SERIALNUMBER))
                                  

          然后可以用下面的 SQL 語(yǔ)句插入一行:

          INSERT INTO EMPLOYEE (SERIALNUMBER, FIRSTNAME, LASTNAME,
                                  SALARY) VALUES(GENERATE_UNIQUE(), ‘John’, ‘Smith’, 999.99)
                                  

          優(yōu)點(diǎn)和問(wèn)題

          這里需要清楚兩件事情。

          首先,當(dāng)多個(gè)事務(wù)在同一時(shí)刻插入行時(shí),GENERATE_UNIQUE() 可能會(huì)返回相同的時(shí)間戳。在這種情況下,GENERATE_UNIQUE() 不能為每個(gè)事務(wù)生成一個(gè)惟一的返回值,因而這種方法不適合有大量事務(wù)的系統(tǒng)。

          第二,一旦系統(tǒng)時(shí)鐘需要向后調(diào)整,那么 GENERATE_UNIQUE() 將可能返回重復(fù)的值。

          由于上述限制,我決不會(huì)在生產(chǎn)系統(tǒng)中使用 GENERATE_UNIQUE()。但是,當(dāng)您需要在有限的時(shí)間內(nèi)完成一個(gè)原型時(shí),這也許是一種選擇。

          CREATE TABLE 語(yǔ)句中的 IDENTITY 選項(xiàng)

          解決方案的思想

          IDENTITY 是 DB2 UDB Version 7.1 和后期版本提供的 CREATE TABLE 語(yǔ)句中的一個(gè)選項(xiàng)。在創(chuàng)建表時(shí),可以將某個(gè)列指定為 IDENTITY 列。對(duì)于每條 INSERT 語(yǔ)句,DB2 將負(fù)責(zé)為其中的這一列生成一個(gè)惟一的值。

          示例

          CREATE TABLE EMPLOYEE (	SERIALNUMBER	BIGINT NOT NULL
                                  GENERATED ALWAYS AS IDENTITY
                                  (START WITH 1, INCREMENT BY 1),
                                  FIRSTNAME	CHAR(64),
                                  LASTNAME	CHAR(64),
                                  SALARY 		DECIMAL(10, 2),
                                  PRIMARY KEY (SERIALNUMBER))
                                  

          然后可以用下面的語(yǔ)句插入一行:

          INSERT INTO EMPLOYEE (FIRSTNAME, LASTNAME, SALARY) VALUES
                                  ( ‘John’, ‘Smith’, 999.99)
                                  

          INSERT 語(yǔ)句不需要指定 SERIALNUMBER 列的值。DB2 UDB 將根據(jù)列的定義自動(dòng)生成惟一值,即 "GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)"。

          優(yōu)點(diǎn)和問(wèn)題

          IDENTITY 函數(shù)在大多數(shù)情況下是代理鍵函數(shù)的一個(gè)好的解決方案。DB2 import 和 export 實(shí)用程序也支持 IDENTITY 選項(xiàng)。

          然而,在某種情況下,這種解決方案不大方便。在運(yùn)行 INSERT 語(yǔ)句之后,應(yīng)用程序?qū)⒂肋h(yuǎn)都不知道放入了主鍵列中的是什么值。如果應(yīng)用程序必須繼續(xù)向子表插入一個(gè)行,那么它就不得不對(duì)父表運(yùn)行一條 SELECT 語(yǔ)句,以得到主鍵值。不過(guò),如果這一點(diǎn)對(duì)于您的系統(tǒng)不成問(wèn)題的話,那么使用 IDENTITY 選項(xiàng)是一個(gè)好主意。

          SEQUENCE 對(duì)象

          解決方案的思想

          SEQUENCE 對(duì)象是在 DB2 UDB Version 7.2 中引入的一個(gè)特性。用戶(hù)可以在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè) SEQUENCE 對(duì)象,就像創(chuàng)建表對(duì)象或視圖對(duì)象一樣,然后從 SEQUENCE 中請(qǐng)求值。DB2 保證用戶(hù)每次可以得到一個(gè)惟一的序列值。

          示例

          您可以在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè) SEQUENCE 對(duì)象:

          CREATE SEQUENCE EMPSERIAL
                                  AS BIGINT
                                  START WITH 1
                                  INCREMENT BY 1
                                  

          如果有一個(gè)如下所示的 EMPLOYEE 表:

          CREATE TABLE EMPLOYEE (	SERIALNUMBER	BIGINT NOT NULL,
                                  FIRSTNAME		CHAR(64),
                                  LASTNAME		CHAR(64),
                                  SALARY 		DECIMAL(10, 2),
                                  PRIMARY KEY (SERIALNUMBER))
                                  

          那么可以用下面的語(yǔ)句插入一個(gè)行:

          INSERT INTO EMPLOYEE ( SERIALNUMBER, FIRSTNAME, LASTNAME,
                                  SALARY) VALUES(NEXTVAL FOR EMPSERIAL, 'John', 'Smith', 99.99)
                                  

          在這里使用 " NEXTVAL FOR EMPSERIAL " 從 SEQUENCE 中獲得惟一值。

          您可以使用 " PREVVAL FOR EMPSERIAL " 獲得當(dāng)前連接會(huì)話中最近生成的序列值。應(yīng)用程序就可以知道放入主鍵列中的是什么值,從而繼續(xù)向子表插入一個(gè)行。這里,“在當(dāng)前連接會(huì)話中”這一點(diǎn)很重要,這意味著 "PREVVAL" 將只返回在相同連接會(huì)話中生成的值。

          例如,考慮這樣的情況:有兩個(gè)應(yīng)用程序連接到數(shù)據(jù)庫(kù),并按照如下順序運(yùn)行下面的 SQL 語(yǔ)句。

          (假設(shè) SEQUENCE " EMPSERIAL " 的當(dāng)前值是 3)。

          應(yīng)用程序 1:
          INSERT INTO EMPLOYEE ( SERIALNUMBER, FIRSTNAME, LASTNAME,
                                  SALARY) VALUES(NEXTVAL FOR EMPSERIAL, 'Martin', 'Wong', 1000.00)

          從 EMPSERIAL 生成的 " NEXTVAL " 是 4。

          應(yīng)用程序 2:
          INSERT INTO EMPLOYEE ( SERIALNUMBER, FIRSTNAME, LASTNAME,
                                  SALARY) VALUES(NEXTVAL FOR EMPSERIAL, 'Patrick', 'Chan', 99.99)
                                  

          從 EMPSERIAL 生成的 " NEXTVAL " 是 5。

          應(yīng)用程序 1:
          SELECT PREVVAL FOR EMPSERIAL FROM EMPLOYEE
                                  

          " PREVVAL " 將返回 4,而不是 5。

          而且, PREVVALNEXTVAL 的值不會(huì)受事務(wù)回滾的影響。

          例如,假設(shè) SEQUENCE " EMPSERIAL " 的當(dāng)前值是 30。某個(gè)應(yīng)用程序開(kāi)始了一個(gè)事務(wù):

          INSERT INTO EMPLOYEE ( SERIALNUMBER, FIRSTNAME, LASTNAME,
                                  SALARY) VALUES(NEXTVAL FOR EMPSERIAL, 'William', 'Chen', 99.99)
                                  

          執(zhí)行 ROLLBACK 操作。

          然后,如果運(yùn)行:

          SELECT PREVVAL FOR EMPSERIAL FROM EMPLOYEE
                                  

          則 " PREVVAL " 將返回 31,而不是 30。

          優(yōu)點(diǎn)和問(wèn)題

          SEQUENCE 是最近 DB2 UDB 為生成惟一值而實(shí)現(xiàn)的函數(shù)。它還有一個(gè)緩存函數(shù),用于提高性能(要了解詳細(xì)信息,請(qǐng)參閱 IBM DB2 UDB SQL Reference)。該函數(shù)比 IDENTITY 函數(shù)更靈活,因?yàn)樗菙?shù)據(jù)庫(kù)中的一個(gè)獨(dú)立對(duì)象。必要時(shí)候,可以通過(guò)運(yùn)行 ALTER SEQUENCE 語(yǔ)句更改其設(shè)置。

          如果系統(tǒng)只在 DB2 UDB 上運(yùn)行,那么 SEQUENCE 也許是最好的解決方案,因?yàn)樗子谑褂茫也幌矜I管理器那樣需要額外的代碼,并且可以隨需求的變化很輕易對(duì)其進(jìn)行更改。





          回頁(yè)首


          結(jié)束語(yǔ)

          本文描述了實(shí)現(xiàn)作為主鍵的代理鍵的三種方法。文中主要討論了如何為代理鍵生成惟一的序列值。

          傳統(tǒng)方法適合于簡(jiǎn)單的、單用戶(hù)(非并發(fā))系統(tǒng)。對(duì)于實(shí)現(xiàn)對(duì)于大型系統(tǒng)和跨平臺(tái)系統(tǒng),鍵管理器是一個(gè)好選擇。但是,如果項(xiàng)目只在 DB2 UDB 上運(yùn)行的話,可以考慮 DB2 UDB 提供的特性。IDENTITY 和 SEQUENCE 函數(shù)提供了一種容易的、靈活的解決方案。

          在創(chuàng)建 IDENTITY 列和 SEQUENCE 對(duì)象時(shí),可以使用很多選擇。請(qǐng)參閱 IBM DB2 UDB Administration GuideIBM DB2 UDB SQL Reference,以獲得完整的細(xì)節(jié)。





          回頁(yè)首


          附錄

          import java.sql.Connection;
                                  import java.sql.DriverManager;
                                  import java.sql.PreparedStatement;
                                  import java.sql.ResultSet;
                                  import java.sql.SQLException;
                                  public class KeyManager {
                                  private static KeyManager	singleton;
                                  private PreparedStatement getKeyStmt = null;
                                  private PreparedStatement updateKeyStmt = null;
                                  static final String db2Driver = "COM.ibm.db2.jdbc.app.DB2Driver";
                                  static final String db2UrlPfx = "jdbc:db2:";
                                  public KeyManager Singleton() throws ClassNotFoundException,
                                  SQLException{
                                  if (singleton == null) {
                                  if (singleton == null)
                                  singleton = new KeyManager();
                                  }
                                  return singleton;
                                  }
                                  private KeyManager() throws ClassNotFoundException, SQLException{
                                  Class.forName(db2Driver);
                                  Connection connection = DriverManager.getConnection(db2UrlPfx+ "dbName",
                                  "userName", "password");
                                  getKeyStmt = connection.prepareStatement("SELECT SURROGATEKEYVALUE
                                  FROM KEYS WHERE TABLENAME = ? AND COLUMNNAME = ?");
                                  updateKeyStmt = connection.prepareStatement("UPDATE KEYS SET
                                  SURROGATEKEYVALUE = SURROGATEKEYVALUE + INCREMENT WHERE
                                  TABLENAME = ? AND COLUMNNAME = ?");
                                  }
                                  public Long getSurrogateKey(String tableName, String columnName)
                                  throws SQLException{
                                  Long keyValue = null;
                                  getKeyStmt.setString(1, tableName);
                                  getKeyStmt.setString(2, columnName);
                                  updateKeyStmt.setString(1, tableName);
                                  updateKeyStmt.setString(2, columnName);
                                  updateKeyStmt.execute();
                                  ResultSet rs = getKeyStmt.executeQuery();
                                  if (rs.next() == true) {
                                  keyValue = new Long(rs.getLong(1));
                                  }
                                  return keyValue;
                                  }
                                  }
                                  



          參考資料


          posted on 2007-07-19 17:29 蘆葦 閱讀(955) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): IBM
          主站蜘蛛池模板: 察哈| 丹棱县| 麦盖提县| 新郑市| 双鸭山市| 天长市| 文水县| 抚宁县| 广南县| 中卫市| 桦川县| 安吉县| 龙岩市| 缙云县| 姜堰市| 铁力市| 嘉兴市| 延寿县| 汕尾市| 临泉县| 尚义县| 克拉玛依市| 临颍县| 七台河市| 米泉市| 黎平县| 虹口区| 皮山县| 厦门市| 张家川| 德清县| 察隅县| 闻喜县| 文昌市| 沿河| 乐东| 历史| 邢台市| 固始县| 壤塘县| 蒲江县|