Edition-Based Redefinition
- 2021 年 1 月 29 日
- 筆記
Oracle在11g引入了Edition-Based Redefinition(EBR),主要是為了解決在更新資料庫對象,比如PL/SQL程式,視圖等,如果該對象被鎖住了,會導致更新必須等待,如果要使更新立即完成,則需要停止應用的問題。實現方式就是通過創建版本,新的程式碼在子版本實現,通過指定版本來對新舊版本程式碼進行切換。
這個功能主要用在有大量PL/SQL程式的資料庫,例如Oracle EBS資料庫。
EBR通過版本化的方式可實現以下目標:
-
程式碼的改變,包括PL/SQL,表定義等,安裝在子(新)版本中
-
數據的改變只寫到新的表或新的列,父(老)版本不會看到
-
跨版本觸發器將父(老)版本應用所做改變反映到子(新)版本中,反之亦然
EBR依賴於3類新的對象,即edition、editioning view和crossedition trigger。
-
若只改變視圖,同義詞和PL/SQL對象,edition就夠了
-
若表結構和數據的改變並是在後端完成,不涉及終端用戶,則只需edition和editioning view
-
若表結構和數據的改變是由終端用戶發起,則三者都需要
Edition
版本化和非版本化對象
Edition是非Schema對象,不屬於任何用戶,從11gR2開始,每個資料庫都有一個默認版本,即ORA$BASE。
SQL> show edition
EDITION
------------------------------
ORA$BASE
SQL> SELECT SYS_CONTEXT('userenv', 'current_edition_name') from dual;
SYS_CONTEXT('USERENV','CURRENT_EDITION_NAME')
--------------------------------------------------------------------------------
ORA$BASE
SQL> select edition_name,parent_edition_name,usable from dba_editions;
EDITION_NAME PARENT_EDITION_NAME USA
------------------------------ ------------------------------ ---
ORA$BASE YES
可版本化對象
不是所有的模式對象都是可版本化(editionable )的,可版本化的模式對象有:
SYNONYM
VIEW
- All PL/SQL object types:
FUNCTION
LIBRARY
PACKAGE
andPACKAGE BODY
PROCEDURE
TRIGGER
TYPE
andTYPE BODY
除此之外,其他對象,例如表,公有同義詞,都是不支援版本化的。
如果可版本化對象的所有者是editions-enabled ,則該對象是editioned ,否則該對象是potentially editioned。
版本化對象規則
-
A noneditioned object cannot depend on an editioned object.
For example:
- A public synonym cannot refer to an editioned object.
- A function-based index cannot depend on an editioned function.
- A materialized view cannot depend on an editioned view.
- A table cannot have a column of a user-defined data type (collection or Abstract Data Type (ADT)) whose owner is editions-enabled.
- A noneditioned subprogram cannot have a static reference to a subprogram whose owner is editions-enabled.
For the reason for this rule, see “Actualizing Referenced Objects”.
-
An ADT cannot be both editioned and evolved.
For information about type evolution, see Oracle Database Object-Relational Developer’s Guide.
-
An editioned object cannot be the starting or ending point of a
FOREIGN KEY
constraint.The only editioned object that this rule affects is an editioned view. An editioned view can be either an ordinary view or an editioning view.
用戶啟用版本
可以在CREATE USER
或者ALTER USER
語句中使用使用ENABLE EDITIONS
語句來為用戶啟用版本。該操作是不可逆的,一旦為用戶啟用版本,則該用戶現有可版本化對象及後續創建的可版本化對象就自動版本化了。
如果啟用版本的兩個用戶之間存在相關的依賴關係,還需要使用FORCE
關鍵字先為某一個用戶啟用版本。例如用戶A有可版本化的對象a1和a2,用戶B有可版本的對象b1和b2,對象a1依賴對象b1,對象b2依賴對象a2,則為用戶A和用戶B啟用版本步驟如下:
-
Using
FORCE
, enable editions for userA
:ALTER USER A ENABLE EDITIONS FORCE;
Now
a1
anda2
are editioned objects, andb2
(which depends ona2
) is invalid. -
Enable editions for user
B
:ALTER USER B ENABLE EDITIONS;
-
Recompile
b2
, using the appropriateALTER
statement withCOMPILE
. For a PL/SQL object, also specifyREUSE
SETTINGS
.For example, if
b2
is a procedure, use this statement:ALTER PROCEDURE b2 COMPILE REUSE SETTINGS
創建版本
使用CREATE EDITION
語句來創建版本。
繼承對象和實際對象
每個資料庫會話一次只能使用一個版本。創建時,子版本從其父版本繼承資料庫中在父版本中可見的所有已版本化對象。每個繼承的對象在子版本中可見。
例子: creates a procedure named hello
in the edition ora$base
, and then creates the edition e2
as a child of ora$base
. When e2
invokes hello
, it invokes the inherited procedure. Then e2
changes hello
, actualizing it. The procedure hello
in the edition ora$base
remains unchanged, and is no longer visible in e2
. Now when e2
invokes hello
, it invokes the actual procedure.
先創建用戶,授予許可權,啟用版本,切換到新創建的用戶
SQL> create user user1 identified by user1 default tablespace users;
User created.
SQL> grant dba to user1;
Grant succeeded.
SQL> alter user user1 enable editions;
User altered.
SQL> conn user1/user1
Connected.
再執行如下步驟:
-
Create procedure in parent edition:
CREATE OR REPLACE PROCEDURE hello IS BEGIN DBMS_OUTPUT.PUT_LINE('Hello, edition 1.'); END hello; /
-
Invoke procedure in parent edition:
BEGIN hello(); END; /
Result:
Hello, edition 1. PL/SQL procedure successfully completed.
-
Create child edition:
CREATE EDITION e2;
-
Use child edition:
ALTER SESSION SET EDITION = e2;
For information about
ALTER
SESSION
SET
EDITION
, see “Changing Your Session Edition”. -
In child edition, invoke procedure:
BEGIN hello(); END; /
Child edition inherits procedure from parent edition. Child edition invokes inherited procedure. Result:
Hello, edition 1. PL/SQL procedure successfully completed.
-
Change procedure in child edition:
CREATE OR REPLACE PROCEDURE hello IS BEGIN DBMS_OUTPUT.PUT_LINE('Hello, edition 2.'); END hello; /
Child changes only its own copy of procedure. Child’s copy is an actual object.
-
Invoke procedure:
BEGIN hello(); END; /
Child invokes its own copy, the actual procedure:
Hello, edition 2. PL/SQL procedure successfully completed.
-
Return to parent:
ALTER SESSION SET EDITION = ora$base;
-
Invoke procedure and see that it has not changed:
BEGIN hello(); END; /
Result:
Hello, edition 1. PL/SQL procedure successfully completed.
刪除繼承對象
如果子版本的用戶刪除繼承對象,則該對象在子版本中不再可見,但在父版本中仍然可見。
例子: creates a procedure named goodbye
in the edition ora$base
, and then creates edition e2
as a child of ora$base
. After e2
drops goodbye
, it can no longer invoke it, but ora$base
can still invoke it.
先刪除之前創建的版本:
SQL> show edition
EDITION
------------------------------
ORA$BASE
SQL> drop edition e2 cascade;
Edition dropped.
再執行以下步驟:
-
Create procedure in edition
ora$base
:CREATE OR REPLACE PROCEDURE goodbye IS BEGIN DBMS_OUTPUT.PUT_LINE('Good-bye!'); END goodbye; /
-
Invoke procedure:
BEGIN goodbye; END; /
Result:
Good-bye! PL/SQL procedure successfully completed.
-
Create edition
e2
as a child ofora$base
:CREATE EDITION e2;
-
Use edition
e2
:ALTER SESSION SET EDITION = e2;
ALTER
SESSION
SET
EDITION
must be a top-level SQL statement. For more information, see “Changing Your Session Edition”. -
In
e2
, invoke procedure:BEGIN goodbye; END; /
e2
invokes inherited procedure:Good-bye! PL/SQL procedure successfully completed.
-
In
e2
, drop procedure:DROP PROCEDURE goodbye;
-
In
e2
, try to invoke dropped procedure:BEGIN goodbye; END; /
Result:
BEGIN goodbye; END; * ERROR at line 1: ORA-06550: line 1, column 7: PLS-00201: identifier 'GOODBYE' must be declared ORA-06550: line 1, column 7: PL/SQL: Statement ignored
-
Return to parent:
ALTER SESSION SET EDITION = ora$base;
-
In parent, invoke procedure:
BEGIN goodbye; END; /
Result:
Good-bye! PL/SQL procedure successfully completed.
例子: e2
creates a function named goodbye
and then an edition named e3
as a child of e2
. When e3
tries to invoke the procedure goodbye
(which e2
dropped), an error occurs, but e3
successfully invokes the function goodbye
(which e2
created).
-
Return to
e2
:ALTER SESSION SET EDITION = e2;
For information about
ALTER
SESSION
SET
EDITION
, see “Changing Your Session Edition”. -
In
e2
, create function namedgoodbye
:CREATE OR REPLACE FUNCTION goodbye RETURN BOOLEAN IS BEGIN RETURN(TRUE); END goodbye; /
-
Create edition
e3
:CREATE EDITION e3 AS CHILD OF e2;
-
Use edition
e3
:ALTER SESSION SET EDITION = e3;
-
In
e3
, try to invoke proceduregoodbye
:BEGIN goodbye; END; /
Result:
goodbye; * ERROR at line 2: ORA-06550: line 2, column 3: PLS-00221: 'GOODBYE' is not a procedure or is undefined ORA-06550: line 2, column 3: PL/SQL: Statement ignored
-
In
e3
, invoke functiongoodbye
:BEGIN IF goodbye THEN DBMS_OUTPUT.PUT_LINE('Good-bye!'); END IF; END; /
Result:
Good-bye! PL/SQL procedure successfully completed.
版本授權
版本創建者可以使用語句 GRANT
USE
ON
EDITION
將版本的使用許可權授予其他用戶。
如果要授予所有用戶,則可以使用以下兩種方式:
-
Grant the
USE
privilege on the edition toPUBLIC
:GRANT USE ON EDITION edition_name TO PUBLIC
-
Make the edition the database default edition:
ALTER DATABASE DEFAULT EDITION = edition_name
This has the side effect of granting the
USE
privilege onedition_name
toPUBLIC
.
當前版本和會話版本
資料庫會話在任何時間使用的版本為當前版本(current edition),當資料庫會話開始,當前版本就是其會話版本(session edition),改變會話版本,當前版本也會隨之改變。但是,也會有當前版本與會話版本不同的情況。
初始會話版本
當連接資料庫時,可以指定初始會話版本,通過以下SQL查詢可以指定的版本:
SELECT EDITION_NAME FROM ALL_EDITIONS;
那麼如何在連接資料庫的時候指定版本呢,可以通過在service中指定,Oracle推薦使用 srvctl
add
service
或者srvctl
modify
service
命令,通過-t
選項指定服務的默認初始會話版本。
如果連接時沒有指定版本,則會使用資料庫默認版本作為初始會話版本。
修改會話版本
使用 ALTER
SESSION
SET
EDITION
語句修改當前會話版本。
查看當前版本和會話版本
查看當前版本:
SELECT SYS_CONTEXT('USERENV', 'CURRENT_EDITION_NAME') FROM DUAL;
查看會話版本:
SELECT SYS_CONTEXT('USERENV', 'SESSION_EDITION_NAME') FROM DUAL;
退役版本
通過收回edition的use許可權實現。
刪除版本
系統默認的edition不能刪除。
必須沒有人使用此edition時才可刪除。
或者其沒有子edition,或者其子edition中沒有editioned對象時才可刪除。
Editioning view
由於表是不能被版本化的,那麼如果要更改表的結構,如增加欄位,這時就需要使用版本視圖來向用戶提供不同版本下的表結構。
Crossedition Triggers
如果發布版本時還涉及到數據的變化,那麼就需要使用跨版本觸發器。跨版本觸發器分為:
- 正向跨版本觸發器:將父版本表欄位的數據變化同步到子版本相關欄位中,在父版本觸發
- 反向跨版本觸發器:將子版本表欄位的數據變化同步到父版本相關欄位中,在子版本觸發
實戰
使用EBR來進行在執行緒序更新,具體是將HR用戶下的EMPLOYEES表 PHONE_NUMBER 欄位拆分為 COUNTRY_CODE和PHONE_NUMBER_WITHIN_COUNTRY兩個欄位 ,這裡涉及到表結構和數據的變化,需要用到Editioning view和Crossedition Triggers。
準備工作
- 安裝資料庫
- 下載和解壓 ebr.zip
配置環境
使用SYS用戶連接到資料庫:
SQL> conn / as sysdba
Connected.
使用HR用戶連接到資料庫,默認使用的是父版本ORA$BASE:
SQL> conn hr/hr
Connected.
SQL> show edition
EDITION
------------------------------
ORA$BASE
EBR準備
在SYS用戶下為HR用戶啟用版本:
SQL> ALTER USER hr ENABLE EDITIONS;
User altered.
在HR用戶下重命名需要修改的表:
SQL> ALTER TABLE employees RENAME TO employees_;
Table altered.
在HR用戶下為重命名的表創建版本視圖,視圖的名字為表之前的名字:
SQL> CREATE EDITIONING VIEW employees AS
2 SELECT
3 employee_id, first_name, last_name, email, PHONE_NUMBER, hire_date, job_id, salary, commission_pct, manager_id, department_id
4 FROM employees_;
View created.
創建子版本
在SYS用戶下基於當前默認版本Ora$Base創建新的版本post_upgrade:
SQL> CREATE EDITION post_upgrade AS CHILD OF Ora$Base;
Edition created.
使用SYS用戶將版本post_upgrade的USE許可權授予HR用戶:
SQL> GRANT USE ON EDITION post_upgrade TO hr;
Grant succeeded.
使用子版本
在HR用戶下設置當前版本為post_upgrade:
SQL> ALTER SESSION SET EDITION = post_upgrade;
Session altered.
修改表結構
在HR用戶下修改employees_表結構:
SQL> ALTER TABLE employees_ ADD
2 (COUNTRY_CODE VARCHAR2(5),
3 PHONE_NUMBER_WITHIN_COUNTRY VARCHAR2(20));
Table altered.
修改版本視圖
在HR用戶下,在子版本post_upgrade中,修改之前在父版本Ora$Base中創建的版本視圖employees,增加欄位COUNTRY_CODE和PHONE_NUMBER_WITHIN_COUNTRY :
SQL> CREATE OR REPLACE EDITIONING VIEW employees AS
2 SELECT employee_id, first_name, last_name, email, phone_number, hire_date, job_id, salary, commission_pct, manager_id, department_id, phone_number_within_country, country_code
3 FROM employees_;
View created.
創建正向跨版本觸發器
在HR用戶下,在子版本post_upgrade中,執行腳本fwd_ce.sql,具體程式碼為:
-- The procedure called by empl_forward
VARIABLE Warnings VARCHAR2(4000)
begin
:Warnings := $$Plsql_Warnings;
end;
/
--------------------------------------------------------------------------------
alter session set Plsql_Warnings = ' enable:all, disable:06005, disable:06006'
/
create or replace procedure Set_Country_Code_And_Phone_No(
Phone_Number in varchar2,
Country_Code out nocopy varchar2,
Phone_Number_V2 out nocopy varchar2)
is
Char_To_Number_Error exception;
pragma Exception_Init(Char_To_Number_Error, -06502);
Bad_Phone_Number exception;
Nmbr varchar2(30) := Replace(Phone_Number, '.', '-');
function Is_US_Number(Nmbr in varchar2) return boolean is
Len number := Length(Nmbr);
Dash_Pos number := Instr(Nmbr, '-');
n pls_integer;
begin
if Len is null or Len <> 12 then
return false;
end if;
if Dash_Pos is null or Dash_Pos <> 4 then return false; end if;
begin
n := To_Number(Substr(Nmbr, 1, 3));
exception when Char_To_Number_Error then
return false;
end;
Dash_Pos := Instr(Nmbr, '-', 5);
if Dash_Pos is null or Dash_Pos <> 8 then return false; end if;
begin
n := To_Number(Substr(Nmbr, 5, 3));
exception when Char_To_Number_Error then
return false;
end;
begin
n := To_Number(Substr(Nmbr, 9));
exception when Char_To_Number_Error then
return false;
end;
return true;
end Is_US_Number;
begin
if Nmbr like '011-%' then
declare
Dash_Pos number := Instr(Nmbr, '-', 5);
begin
Country_Code := '+'||To_Number(Substr(Nmbr, 5, Dash_Pos-5));
Phone_Number_V2 := Substr(Nmbr, Dash_Pos+1);
exception when Char_To_Number_Error then
raise Bad_Phone_Number;
end;
elsif Is_US_Number(Nmbr) then
Country_Code := '+1';
Phone_Number_V2 := Nmbr;
else
raise Bad_Phone_Number;
end if;
exception when Bad_Phone_Number then
Country_Code := '+0';
Phone_Number_V2 := '000-000-0000';
end Set_Country_Code_And_Phone_No;
/
--------------------------------------------------------------------------------
declare
Stmt constant varchar2(32767) := '
alter session set Plsql_Warnings = '''||:Warnings||'''';
begin
execute immediate Stmt;
end;
/
-- The trigger empl_forward
CREATE OR REPLACE TRIGGER empl_forward
BEFORE INSERT OR UPDATE ON employees_
FOR EACH ROW
FORWARD CROSSEDITION
DISABLE
BEGIN
Set_Country_Code_And_Phone_No(
:New.Phone_Number,
:New.Country_Code,
:New.Phone_Number_Within_Country);
END;
這段程式碼在表employees_ 創建了一個正向跨版本觸發器empl_forward,當在父版本Ora$Base上對錶employees_ 進行DML操作時就會觸發該觸發器,將對父版本的修改傳遞到子版本去。
SQL> @fwd_ce.sql
PL/SQL procedure successfully completed.
Session altered.
SP2-0804: Procedure created with compilation warnings
PL/SQL procedure successfully completed.
12 /
Trigger created.
創建反向擴版本觸發器
在HR用戶下,在子版本post_upgrade中,執行腳本rev_ce.sql,具體程式碼為:
CREATE OR REPLACE TRIGGER empl_reverse
BEFORE INSERT OR UPDATE ON employees_
FOR EACH ROW
REVERSE CROSSEDITION
DISABLE
BEGIN
:NEW.phone_number :=
CASE :NEW.country_code
WHEN '+1' THEN
REPLACE(:NEW.phone_number_within_country, '-', '.')
ELSE
'011.'||LTRIM(:NEW.country_code, '+')||'.'||
REPLACE(:NEW.phone_number_within_country, '-', '.')
END;
END employees_reverse;
/
這段程式碼在表employees_ 創建了一個反向跨版本觸發器empl_reverse,當在子版本post_upgrade上對錶employees_ 進行DML操作時就會觸發該觸發器,將對子版本的修改傳遞到父版本去。
SQL> @rev_ce.sql
Trigger created.
正向操作
在HR用戶下,在子版本post_upgrade中,執行腳本bulk_fwd.sql,具體程式碼為:
ALTER trigger empl_forward enable
/
ALTER trigger empl_reverse enable
/
DECLARE
c number := DBMS_Sql.Open_Cursor();
x number;
BEGIN
DBMS_Sql.Parse(
c => c,
Language_Flag => DBMS_Sql.Native,
Statement => 'update employees set employee_id = employee_id',
Apply_Crossedition_Trigger => 'empl_forward');
x := DBMS_Sql.Execute(c);
DBMS_Sql.Close_Cursor(c);
commit;
end;
/
這段程式碼先啟用前面創建的兩個觸發器,然後執行一個update語句,並觸發正向跨版本觸發器empl_forward。
SQL> @bulk_fwd.sql
Trigger altered.
Trigger altered.
PL/SQL procedure successfully completed.
執行成功後,再執行腳本ver_fwd.sql查看結果,具體程式碼為:
--切換到父版本,執行update會觸發正向跨版本觸發器
ALTER SESSION SET EDITION =ORA$BASE;
SELECT phone_number FROM employees WHERE employee_id=101;
UPDATE employees SET phone_number = '515.123.4444' WHERE employee_id =101;
COMMIT;
ALTER SESSION SET EDITION = post_upgrade;
SELECT employee_id, country_code, phone_number_within_country
FROM employees WHERE employee_id =101;
執行結果如下:
SQL> @ver_fwd.sql
Session altered.
PHONE_NUMBER
--------------------
515.123.4568
1 row updated.
Commit complete.
Session altered.
EMPLOYEE_ID COUNT PHONE_NUMBER_WITHIN_
----------- ----- --------------------
101 +1 515-123-4444
在父版本插入或者更新employees_表之前,empl_forward觸發器使用PHONE_NUMBER欄位的新值更新欄位COUNTRY_CODE和PHONE_NUMBER_WITHIN_COUNTRY。所以在子版本中,查詢COUNTRY_CODE和PHONE_NUMBER_WITHIN_COUNTRY就可以看到更新的值。
反向操作
在HR用戶下,在子版本post_upgrade中,執行腳本ver_rev.sql,具體程式碼為:
--在子版本執行update會觸發反向跨版本觸發器
ALTER SESSION SET EDITION =post_upgrade;
UPDATE employees SET phone_number_within_country = '515.123.4567'
WHERE employee_id =101;
SELECT employee_id, country_code, phone_number_within_country
FROM employees WHERE employee_id=101;
COMMIT;
ALTER SESSION SET EDITION = ORA$BASE;
SELECT employee_id, phone_number
FROM employees WHERE employee_id =101;
執行結果如下:
SQL> @ver_rev.sql
Session altered.
1 row updated.
EMPLOYEE_ID COUNT PHONE_NUMBER_WITHIN_
----------- ----- --------------------
101 +1 515.123.4567
Commit complete.
Session altered.
EMPLOYEE_ID PHONE_NUMBER
----------- --------------------
101 515.123.4567
當在子版本post_upgrade中向表employees_插入數據或者更新PHONE_NUMBER_WITHIN_COUNTRY欄位時,就會觸發empl_reverse觸發器,使用PHONE_NUMBER_WITHIN_COUNTRY這個欄位的新值來更新PHONE_NUMBER欄位。
發布版本
通過回收HR用戶對父版本的許可權,來發布子版本。
--連接到HR用戶,默認使用的還是父版本,查詢的數據也是來自於父版本的版本視圖
SQL> conn hr/hr
Connected.
SQL> show edition
EDITION
------------------------------
ORA$BASE
SQL> select * from employees where employee_id=101;
EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
----------- ---------- ---------- ---------- -------------------- ----------------- ---------- ---------- -------------- ---------- -------------
101 Neena Kochhar NKOCHHAR 515.123.4567 20050921 00:00:00 AD_VP 17000 100 90
--連接到SYS用戶,設置資料庫的默認版本為子版本,然後退役父版本
SQL> conn / as sysdba
Connected.
SQL> ALTER DATABASE DEFAULT EDITION = post_upgrade;
Database altered.
SQL> select grantee,privilege from dba_tab_privs where table_name='ORA$BASE';
GRANTEE PRIVILEGE
------------------------------ ----------------------------------------
PUBLIC USE
SQL> REVOKE USE ON EDITION ora$base FROM PUBLIC;
Revoke succeeded.
--再次連接到HR用戶,默認使用的就是子版本了,查詢的數據也是來自於子版本的版本視圖
SQL> conn hr/hr
Connected.
SQL> show edition
EDITION
------------------------------
POST_UPGRADE
SQL> select * from employees where employee_id=101;
EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID PHONE_NUMBER_WITHIN_ COUNT
----------- ---------- ---------- ---------- -------------------- ----------------- ---------- ---------- -------------- ---------- ------------- -------------------- -----
101 Neena Kochhar NKOCHHAR 515.123.4567 20050921 00:00:00 AD_VP 17000 100 90 515.123.4567 +1
參考:
歡迎關注我的公眾號,好好學習,天天向上。