DB2 中的事務 ID

來源:互聯網
上載者:User
DB2 中的事務 ID
作者:佚名 時間:2005-08-12 17:39 出處:互連網 責編:小漁
              摘要:DB2 中的事務 ID

從 DB2 日誌記錄中檢索工作單元的惟一標識符

層級: 中級

Knut Stolze
DB2 WebSphere Information Integration Development, IBM Germany
2005 年 6 月 20 日

您需要識別運行在 IBM DB2 Universal Database (DB2 UDB) 伺服器上的當前工作單元嗎?Knut Stolze 將向您展示如何完成這一任務,方法是使用預存程序和每個日誌記錄頭中的惟一事務標識符。

簡介
一般情況下,與資料庫伺服器互動的應用程式無需明白資料庫引擎內部的交易處理。應用程式發出一系列 SQL 陳述式(如 INSERT、UPDATE、DELETE、SELECT 或 CREATE TABLE),在事務結束時,運行事務結束語句 COMMIT 或 ROLLBACK。COMMIT 語句結束事務並告知資料庫伺服器去持久儲存所有更改。另一方面,ROLLBACK 使資料庫伺服器撤銷在事務中所做的全部更改。

在這個世界上,不是只有“常規”應用程式。同時還存在著專用應用程式,這樣的應用程式通常具有非常特別的需求。例如,涉及在資料庫系統間相互複製資料的應用程式(如 DB2 Replication [2])需要瞭解事務資訊。當複製的資料改變時,通常需要連同在單個事務中所做的全部更改一起複製下來。因此,需要知道在哪個事務中進行了哪些更改,並且每個事務必須被惟一標識。

DB2 UDB 沒有提供特殊的註冊表或任何其他直接的方法來檢索事務的標識符,對於內部目的這是必需的。在以下幾節中,我將向您介紹如何將 DB2 UDB 的幾種特性組合起來,以產生事務標識符。總的思路是訪問日誌記錄,日誌記錄是 DB2 為確保在萬一系統崩潰、停電或硬碟故障時恢複寶貴資料而撰寫的。DB2 將內部事務 ID 儲存在每個日誌記錄中。執行一些附加設定步驟即可觸發為檢索正確的事務 ID 而編寫特定的日誌記錄。

事務和工作單元 (UOW)
閱讀 DB2 手冊 [1] 時,您通常會看到術語工作單元,縮寫為 UOW。在其他資料庫文獻中,您往往會看到術語事務。這兩個術語指的是同一個概念;它們都表示在事務或工作單元中以原子、一致、隔離和持久的方式執行一組 SQL 陳述式。因此,在本文中,我將作為同義字使用這兩個術語。

訪問事務 ID
DB2 UDB 附帶有很多種用於對資料庫系統本身進行管理的 API。這些 API 的其中之一允許訪問資料庫引擎在操作過程中撰寫的日誌記錄。這些日誌記錄是針對 INSERT、UPDATE、DELETE、REORGANIZE 和您發出的用於修改資料庫表中資料或管理系統本身的很多其他動作而撰寫的。

每個日誌記錄都在其頭部包括一個觸發記錄寫操作的事務的惟一標識符(以 6 個位元組編碼)[3]。此標識符是保證所有資料庫事務的持久性、防止預料中的或意外的操作中斷(如系統故障)所必需的。DB2 自動維護該標識符。

此方法利用儲存在日誌記錄頭中的事務標識符。我們使用日誌頭 API 訪問日誌記錄,並從中提取事務 ID。執行此操作時,請記住:

日誌頭 API 的限制
使用日誌頭 API 意味著必須將資料庫配置參數 LOGRETAIN 設定為 RECOVERY 和/或將參數 USEREXIT 設定為 ON。否則,不可能通過 API 檢索日誌記錄。大多數生產系統已經使用這兩種設定中的一種,而不再使用迴圈日誌記錄。

此外,到 db2ReadLog 的調用被當作其他 SQL 陳述式來處理。而 API 不是動態複合陳述式 中一個受支援的語句。但是,觸發器(和表函數)無法執行任何任意 SQL 操作,並被限制為動態複合陳述式。因此,您無法將調用嵌入到觸發器中已存在的過程 getTransactionId 中。

  • 日誌記錄必須被寫入要為其確定請求標識符的當前事務中。
  • 由於資料庫引擎將單個日誌(可能拆分為若干個檔案)用於所有並發事務,這些並發事務中的很多或全部可能都在更改資料,從而導致同時將內容寫入日誌記錄,因此有必要找到真正屬於當前事務的日誌記錄。

兩個問題可一起解決。我們使用 DB2 的內建函數 GENERATE_UNIQUE 來產生惟一值。將該值插入表中,觸發日誌記錄的寫操作。下一步將讀取所有新日誌記錄(自從上一次 INSERT 操作以來的記錄),直到找到包含那個惟一值的日誌記錄為止。它就是我們要尋找的日誌記錄,並從中提取出事務 ID。最後,插入操作被撤銷,以便該表不累積到期資料。整個處理過程 1 所示。

圖 1. 從資料庫日誌提取事務標識符的邏輯

實現預存程序
上面介紹的整個邏輯被封裝在單個預存程序中。這樣,所有應用程式都有一種簡單、標準化的方式來檢索事務 ID。任何到 DB2 API 的直接調用都不是必需的。預存程序將事務 ID 作為字串(即類型 CHAR(12) 的值)返回。

在編譯預存程序之前,您需要建立一個名為 TA_ID_FORCE_LOGWRITE 的表。該表在過程內訪問。該表本身結構非常簡單,只有一個列,函數 GENERATE_UNIQUE 產生的惟一值儲存在其中。使用清單 1 所示的 SQL 陳述式建立該表:

清單 1. 建立表 TA_ID_FORCE_LOGWRITE

            CREATE TABLE ta_id_force_logwrite (            unique_val VARCHAR(18) FOR BIT DATA NOT NULL,            CONSTRAINT taid_pk PRIMARY KEY (unique_val)            )@            

該過程以 C++ 實現,用於訪問非同步讀取日誌 API [4],它使用嵌入式 SQL 來執行必需的 SQL 操作。儲存點用於復原在過程中執行的 INSERT 操作,以觸發日誌記錄的寫操作。該過程中的 SQL 陳述式可以充分利用受 DB2 UDB Version 8.2 支援的 SQL 功能。

因此,我們產生惟一值,將其插入表中,並在單個語句中將它檢索到預存程序代碼調用。此操作在以下清單的斜體 部分中完成。如果需要在 DB2 的早先版本中使用這一過程,則必須將此邏輯拆分為多個獨立的 SQL 陳述式。

DB2 日誌是使用 API db2ReadLog 讀取的。一開始,系統將調用該 API 來確定當前的記錄序號 (LSN)。完成此步驟可以避免查詢調用此過程前撰寫的日誌記錄。畢竟,我們感興趣的只是單個日誌記錄:INSERT 操作所撰寫的那個。

在 INSERT 操作之後,所有新的日誌記錄都會被檢索。對於每個記錄,我們都要檢查它是否是針對 INSERT 操作而撰寫的。如果是的,並且所插入的資料包含 INSERT 語句過程中使用的惟一值,那麼我們就找到了所需的日誌記錄,可以返回正確的事務標識符了。

在離開該過程之前,我們需要將處理復原到開始設定的儲存點。這樣,超出該過程的範圍的任何資料修改都不會保留。

該過程的完整代碼如清單 2 所示。在清單 2 中,到日誌 API 的調用被設定為粗體,SQL 陳述式則顯示為斜體。其他部分僅涉及到參數的準備。

清單 2. 預存程序代碼

            #include <string.h> // memset(), memcpy(), strncpy()            #include <stdio.h> // sprintf()            #include <sqludf.h>            #include <db2ApiDf.h>            #if defined(__cplusplus)            extern "C"            #endif            int SQL_API_FN getTransactionId(            SQLUDF_VARCHAR *taId,            SQLUDF_NULLIND *taId_ind,            SQLUDF_TRAIL_ARGS)            {            SQL_API_RC rc = SQL_RC_OK;            struct sqlca sqlca;            db2ReadLogInfoStruct logInfo;            db2ReadLogStruct logData;            SQLU_LSN startLsn;            SQLU_LSN endLsn;            char buffer[64 * 1024] = { '/0' }; // for log record data            EXEC SQL BEGIN DECLARE SECTION;            char uniqueVal[13] = { '/0' };            EXEC SQL END DECLARE SECTION;            // we assume NULL return            *taId_ind = -1;            /*            * Step 1: Set a savepoint to be able to undo the data modifications            */            EXEC SQL SAVEPOINT get_transaction_id ON ROLLBACK RETAIN CURSORS;            /*            * Step 2: Query the DB2 Log to get the start LSN            */            memset(&sqlca, 0x00, sizeof sqlca);            memset(&logInfo, 0x00, sizeof logInfo);            memset(&logData, 0x00, sizeof logData);            logData.iCallerAction = DB2READLOG_QUERY;            logData.piStartLSN = NULL;            logData.piEndLSN = NULL;            logData.poLogBuffer = NULL;            logData.iLogBufferSize = 0;            logData.iFilterOption = DB2READLOG_FILTER_OFF;            logData.poReadLogInfo = &logInfo;            rc = db2ReadLog(db2Version810, &logData, &sqlca);            if (rc < 0) {            memcpy(SQLUDF_STATE, "38TA0", SQLUDF_SQLSTATE_LEN);            strncpy(SQLUDF_MSGTX, "Could not query log for last LSN",            SQLUDF_MSGTEXT_LEN);            goto exit;            }            else if (sqlca.sqlcode) {            memcpy(SQLUDF_STATE, "38TA1", SQLUDF_SQLSTATE_LEN);            snprintf(SQLUDF_MSGTX, SQLUDF_MSGTEXT_LEN, "SQL error while "            "reading log records.  SQLCODE = %d, SQLSTATE=%s",            sqlca.sqlcode, sqlca.sqlstate);            goto exit;            }            memcpy(&startLsn, &logInfo.nextStartLSN, sizeof startLsn);            /*            * Step 3: Force a log record to be written            *            * Insert a unique value into our table, which triggers a log record to be            * written.  The same value is also returned right away so that we can use            * it to search through the new log records.            */            EXEC SQL SELECT value INTO :uniqueVal FROM NEW TABLE ( INSERT INTO ta_id_force_logwrite VALUES ( GENERATE_UNIQUE() ) ) AS t(value);            if (sqlca.sqlcode) {            memcpy(SQLUDF_STATE, "38TA2", SQLUDF_SQLSTATE_LEN);            snprintf(SQLUDF_MSGTX, SQLUDF_MSGTEXT_LEN, "SQL error while "            "triggering log record.  SQLCODE = %d, SQLSTATE=%s",            sqlca.sqlcode, sqlca.sqlstate);            goto exit;            }            /*            * Step 4: Search through the new log records to find our INSERT            */            while (true) {            char *ptr = NULL;            char *transactionId = NULL;            sqlint32 recordLength = 0;            memset(&sqlca, 0x00, sizeof sqlca);            memset(&logInfo, 0x00, sizeof logInfo);            memset(&logData, 0x00, sizeof logData);            memset(&endLsn, 0xFF, sizeof endLsn);            logData.iCallerAction = DB2READLOG_READ_SINGLE;            logData.piStartLSN = &startLsn;            logData.piEndLSN = &endLsn;            logData.poLogBuffer = buffer;            logData.iLogBufferSize = sizeof buffer;            logData.iFilterOption = DB2READLOG_FILTER_OFF;            logData.poReadLogInfo = &logInfo;            rc = db2ReadLog(db2Version810, &logData, &sqlca);            if (rc < 0) {            memcpy(SQLUDF_STATE, "38TA3", SQLUDF_SQLSTATE_LEN);            sprintf(SQLUDF_MSGTX, "Could not read log record.  rc = %d",            (int)rc);            goto exit;            }            else if (sqlca.sqlcode == SQLU_RLOG_READ_TO_CURRENT) {            memcpy(SQLUDF_STATE, "38TA4", SQLUDF_SQLSTATE_LEN);            strncpy(SQLUDF_MSGTX, "Last log record reached prematurely.",            SQLUDF_MSGTEXT_LEN);            goto exit;            }            else if (sqlca.sqlcode) {            memcpy(SQLUDF_STATE, "38TA5", SQLUDF_SQLSTATE_LEN);            snprintf(SQLUDF_MSGTX, SQLUDF_MSGTEXT_LEN, "SQL error while "            "reading log records.  SQLCODE = %d, SQLSTATE=%s",            sqlca.sqlcode, sqlca.sqlstate);            goto exit;            }            if (logInfo.logBytesWritten < 20) {            memcpy(SQLUDF_STATE, "38TA6", SQLUDF_SQLSTATE_LEN);            strncpy(SQLUDF_MSGTX, "Log Manager Header of record too small.",            SQLUDF_MSGTEXT_LEN);            goto exit;            }            memcpy(&startLsn, &logInfo.nextStartLSN, sizeof startLsn);            // the data in the buffer starts with the LSN, followed by the Log            // Manager Header; skip the LSN            ptr = buffer;            ptr += sizeof(SQLU_LSN);            // get the length of the log record (plus LSN)            recordLength = *(sqlint32 *)ptr + sizeof(SQLU_LSN);            ptr += 4;            // verify that this is a "Normal" log record            if (*(sqlint16 *)ptr != 0x004E) {            continue;            }            ptr += 2;            // skip behind the Log Manager Header (to the DMS Log Record Header);            // (we do not have "Compensation" records here and "Propagatable"            // doesn't occur either)            ptr += 2 + // flags            6; // LSN of previous record in same transaction            // remember the location of the transaction id            transactionId = ptr;            ptr += 6;            // now we are at the beginning of the DML Log Record Header            if (ptr - buffer + 18 + 4 > recordLength) {            continue;            }            // check that the "Function identifier" in the DMS header indicates an            // "INSERT" log record            ptr += 1;            if (*(unsigned char *)ptr != 118) {            continue;            }            // skip to the record data            ptr += 5 + // remainder of DMS Log Record Header            2 + // padding            4 + // RID            2 + // record Length            2 + // free space            2; // record offset            // the record contains data if the 1st byte of the record header (the            // record type) is 0x00 or 0x10, or if the bit 0x04 is set            if (*ptr != 0x00 && *ptr != 0x10 && (*ptr & 0x04) == 0) {            continue;            }            ptr += 4;            // we reached the record data and the unique value can be found after            // the record length            ptr += 1 + // record type            1 + // reserved            2 + // length of fixed length data            4; // RID            // that's where the unique value should be            // once we found the unique value, extract the transaction ID and            // convert it to a string            if (memcmp(ptr, uniqueVal, 13) == 0) {            int i = 0;            char *result = taId;            for (i = 0; i < 6; i++) {            sprintf(result, "%02hhx", ptr[i]);            result += 2;            }            *result = '/0';            *taId_ind = 0;            break; // found the correct log record            }            }            exit:            EXEC SQL ROLLBACK TO SAVEPOINT get_transaction_id;            return SQLZ_DISCONNECT_PROC;            }            

可以使用位於 sqllib/samples/c/ 目錄中的 bldrtn 指令碼來編譯這一預存程序。該指令碼將產生一個共用庫,共用庫會被複製到 sqllib/function 目錄中。此操作完成後,您即可按照清單 3 所示將過程註冊到資料庫中。完成這最後一個步驟後,才能開始使用該過程。

清單 3. 將過程註冊到資料庫中

            CREATE PROCEDURE getTransactionId ( OUT transactionId CHAR(12) )            SPECIFIC getTaId            DYNAMIC RESULT SETS 0            MODIFIES SQL DATA            NOT DETERMINISTIC            NEW SAVEPOINT LEVEL            LANGUAGE C            EXTERNAL NAME 'transaction-id!getTransactionId'            FENCED THREADSAFE            NO EXTERNAL ACTION            PARAMETER STYLE SQL            PROGRAM TYPE SUB            NO DBINFO@            

測試過程
最後一步是檢查過程的功能是否正確。清單 4 顯示一些在 DB2 命令列上執行過而自動認可關閉的 SQL 陳述式。最前面的情境說明仍然啟用迴圈日誌記錄時會出現的錯誤。在此之後,您將看到進行某些資料修改後,對預存程序的各種調用的結果。當然,只有在執行了 COMMIT 或 ROLLBACK 後並且當前事務觸發了日誌記錄的寫操作時,DB2 才分配新的事務 ID。

清單 4. 測試過程

            $ db2 -c- -td@            db2 => CALL getTransactionId(?)@            SQL0443N  Routine "*ACTIONID" (specific name "") has returned an error            SQLSTATE with diagnostic text "SQL error while reading log records.  SQLCODE =            -2651, SQLS".  SQLSTATE=38TA1            db2 => ? sql2651@            SQL2651N The log records associated with the database can not be            asynchronously read.            Explanation:            The asynchronous read log API was used against a connected            database which does not have LOG RETAIN or USER EXITS ON.  Only            databases which are forward recoverable may have their associated            logs read.            User Response:            Update the database configuration for the database, identified to            the asynchronous read log API, turning LOG RETAIN and/or USER            EXITS ON.            db2 => UPDATE DATABASE CONFIGURATION USING LOGRETAIN ON@            db2 => BACKUP DATABASE sample TO /dev/null@            Backup successful. The timestamp for this backup image is : 20050305214103            db2 => TERMINATE@            $ db2stop force && db2start && db2 -c- -td@            db2 => CONNECT TO sample@            db2 => CALL getTransactionId(?)@            Value of output parameters            --------------------------            Parameter Name  : TRANSACTIONID            Parameter Value : 200503052054            Return Status = 0            db2 => COMMIT@            DB20000I  The SQL command completed successfully.            db2 => CALL getTransactionId(?)@            Value of output parameters            --------------------------            Parameter Name  : TRANSACTIONID            Parameter Value : 200503052054            Return Status = 0            db2 => CREATE TABLE t ( a INT )@            DB20000I  The SQL command completed successfully.            db2 => CALL getTransactionId(?)@            Value of output parameters            --------------------------            Parameter Name  : TRANSACTIONID            Parameter Value : 200503052055            Return Status = 0            db2 => ROLLBACK@            DB20000I  The SQL command completed successfully.            db2 => CALL getTransactionId(?)@            Value of output parameters            --------------------------            Parameter Name  : TRANSACTIONID            Parameter Value : 200503052056            Return Status = 0            

結束語
為什麼 DBA 想具有識別當前工作單元的能力?原因不一而足,對複製過程的管理就是一個例子。使用此處介紹的技巧,可以充分利用 DB2 UDB 的獨特功能,同時進一步瞭解資料庫的後台操作。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.