您好,登錄后才能下訂單哦!
嘉年華聽了恩墨學(xué)院的一個主題:《重現(xiàn)ORA-01555 細說Oracle 12c Undo數(shù)據(jù)管理》,呂星昊老師介紹了UNDO的概念以及ORA-1555的產(chǎn)生,并介紹了12c以來Oracle的UNDO相關(guān)的新特性。
其中介紹了Oracle如何使用UNDO來實現(xiàn)多版本一致性讀,使用了OPEN CURSOR的方式非常巧妙地在很少量數(shù)據(jù)的情況下構(gòu)造出可重現(xiàn)的案例。不過這個案例存在一點小的瑕疵,因為如果一不小心,很可能會導(dǎo)致結(jié)果與預(yù)期不符,這是因為這里有一個例外存在。
我們先來模擬一下UNDO構(gòu)造一致性讀的情況,對于Oracle而言,默認的隔離級別是READ COMMIT,也就是說一個會話只能看到其他會話已經(jīng)提交的修改,未提交的修改或者在當(dāng)前會話查詢發(fā)起之后提交的修改都是不可見的。
再介紹一下OPEN CURSOR,Oracle中當(dāng)一個游標(biāo)被打開,其結(jié)果集就已經(jīng)確定了,也就是說這個游標(biāo)會根據(jù)OPEN CURSOR這個時間點對應(yīng)的SCN來構(gòu)造一致性查詢。但是OPEN CURSOR時,對應(yīng)的SQL并不會被執(zhí)行,在后續(xù)FETCH的時候(對于SQLPLUS而言PRINT命令會觸發(fā)FETCH),SQL才真正被執(zhí)行。使用這種辦法可以模擬一個大的查詢,OPEN CURSOR相當(dāng)于大的查詢的開始時間,其早于其他會話的修改提交時間,而FETCH的時間相當(dāng)于大查詢讀取到這條記錄的時間,而該時間晚于其他會話提交的時間:
SQL> SET SQLP 'SQL1> '
SQL1> CREATE TABLE T_UNDO (ID NUMBER, NAME VARCHAR2(30));
Table created.
SQL1> INSERT INTO T_UNDO SELECT ROWNUM, OBJECT_NAME FROM DBA_OBJECTS;
96920 rows created.
SQL1> COMMIT;
Commit complete.
SQL1> CREATE INDEX IND_UNDO_ID ON T_UNDO(ID);
Index created.
SQL1> SELECT NAME FROM T_UNDO WHERE ID = 1119;
NAME
------------------------------------------------------------
I_EXTERNAL_LOCATION1$
SQL1> VAR C REFCURSOR
SQL1> EXEC OPEN :C FOR SELECT NAME FROM T_UNDO WHERE ID = 1119;
PL/SQL procedure successfully completed.
在第一個會話已經(jīng)構(gòu)造了一個查詢,下面在會話2對這條ID為1119的記錄進行修改并提交:
SQL> SET SQLP 'SQL2> '
SQL2> UPDATE T_UNDO SET NAME = 'UPDATED' WHERE ID = 1119;
1 row updated.
SQL2> COMMIT;
Commit complete.
在會話3上執(zhí)行查詢,這時會看到會話2修改提交后的結(jié)果:
SQL> SET SQLP 'SQL3> '
SQL3> SELECT NAME FROM T_UNDO WHERE ID = 1119;
NAME
------------------------------------------------------------
UPDATED
回到會話1,對CURSOR變量執(zhí)行PRINT,檢查得到的結(jié)果:
SQL1> PRINT :C
NAME
------------------------------------------------------------
I_EXTERNAL_LOCATION1$
到目前為止,所有都是預(yù)期之內(nèi)的結(jié)果,Oracle會利用UNDO來存儲UPDATE的前鏡像,當(dāng)查詢發(fā)現(xiàn)需要訪問的數(shù)據(jù)塊SCN大于會話發(fā)起的SCN,而需要通過UNDO中存儲的前鏡像來構(gòu)造一致性讀,找到會話需要讀取的修改前的數(shù)據(jù)。
那么例外來自哪里呢,在這個例子中,我們給ID列上創(chuàng)建了一個索引,如果這不是一個普通的索引,而是一個主鍵,那么效果如何呢:
SQL1> DROP INDEX IND_UNDO_ID;
Index dropped.
SQL1> ALTER TABLE T_UNDO ADD PRIMARY KEY (ID);
Table altered.
SQL1> SELECT NAME FROM T_UNDO WHERE ID = 1118;
NAME
------------------------------------------------------------
EXTERNAL_LOCATION$
SQL1> EXEC OPEN :C FOR SELECT NAME FROM T_UNDO WHERE ID = 1118;
PL/SQL procedure successfully completed.
會話2修改ID為1118的記錄:
SQL2> UPDATE T_UNDO SET NAME = 'UPDATED WITH PK' WHERE ID = 1118;
1 row updated.
SQL2> COMMIT;
Commit complete.
會話3檢查確認修改結(jié)果:
SQL3> SELECT NAME FROM T_UNDO WHERE ID = 1118;
NAME
---------------
UPDATED WITH PK
再次回到會話1,PRINT游標(biāo)變量:
SQL1> PRINT :C
NAME
------------------------------------------------------------
UPDATED WITH PK
可以看到例外產(chǎn)生了,一致性讀的結(jié)果被破壞了,居然可以查詢到發(fā)生在游標(biāo)打開之后提交的修改。
導(dǎo)致這個例外的原因來自于一個隱含函數(shù)_row_cr:
Oracle11g以后,這個隱含參數(shù)默認值修改為TRUE,這使得Oracle對于基于主鍵的訪問不再采用默認的一致性讀方案。當(dāng)然Oracle做出這種修改的目的是為了提高性能,而且僅對于單行訪問生效,而大部分情況下單行訪問的效率非常高,因此對于一致性破壞的影響并不明顯。到18C為止,該參數(shù)仍然為TRUE。
如果關(guān)閉該參數(shù):
SQL1> ALTER SYSTEM SET "_row_cr" = FALSE;
System altered.
SQL1> SELECT NAME FROM T_UNDO WHERE ID = 1117;
NAME
------------------------------------------------------------
I_EXTERNAL_TAB1$
SQL1> EXEC OPEN :C FOR SELECT NAME FROM T_UNDO WHERE ID = 1117;
PL/SQL procedure successfully completed.
會話2進行修改:
SQL2> UPDATE T_UNDO SET NAME = 'UPDATED NO ROW CR' WHERE ID = 1117;
1 row updated.
SQL2> COMMIT;
Commit complete.
檢查結(jié)果:
SQL3> SELECT NAME FROM T_UNDO WHERE ID = 1117;
NAME
------------------
UPDATED NO ROW CR
回到會話1檢查結(jié)果:
SQL1> PRINT :C
NAME
------------------------------------------------------------
I_EXTERNAL_TAB1$
Oracle恢復(fù)默認的讀一致性隔離級別。
雖然Oracle認為這種優(yōu)化只是針對主鍵或唯一索引等行級訪問生效,造成數(shù)據(jù)一致性破壞的可能性很小,但是建議對于一致性要求較高的行業(yè)尤其是金融相關(guān)行業(yè)還是將該特性關(guān)閉,避免因此造成的一致性問題。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。