本文主要是介绍Edition-Based Redefinition,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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$BASESQL> SELECT SYS_CONTEXT('userenv', 'current_edition_name') from dual;SYS_CONTEXT('USERENV','CURRENT_EDITION_NAME')
--------------------------------------------------------------------------------
ORA$BASESQL> 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 ISBEGINDBMS_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 ISBEGINDBMS_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 editionEDITION
------------------------------
ORA$BASESQL> drop edition e2 cascade;Edition dropped.
再执行以下步骤:
-
Create procedure in edition
ora$base
:CREATE OR REPLACE PROCEDURE goodbye ISBEGINDBMS_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 goodbyeRETURN BOOLEAN IS BEGINRETURN(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
:BEGINgoodbye; 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
:BEGINIF goodbye THENDBMS_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 editionEDITION
------------------------------
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 AS2 SELECT3 employee_id, first_name, last_name, email, PHONE_NUMBER, hire_date, job_id, salary, commission_pct, manager_id, department_id4 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_ ADD2 (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 AS2 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_code3 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)
isChar_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 isLen number := Length(Nmbr);Dash_Pos number := Instr(Nmbr, '-');n pls_integer;beginif Len is null or Len <> 12 thenreturn false;end if;if Dash_Pos is null or Dash_Pos <> 4 then return false; end if;beginn := To_Number(Substr(Nmbr, 1, 3));exception when Char_To_Number_Error thenreturn false;end;Dash_Pos := Instr(Nmbr, '-', 5);if Dash_Pos is null or Dash_Pos <> 8 then return false; end if;beginn := To_Number(Substr(Nmbr, 5, 3));exception when Char_To_Number_Error thenreturn false;end;beginn := To_Number(Substr(Nmbr, 9));exception when Char_To_Number_Error thenreturn false;end;return true;end Is_US_Number;
beginif Nmbr like '011-%' thendeclareDash_Pos number := Instr(Nmbr, '-', 5);beginCountry_Code := '+'||To_Number(Substr(Nmbr, 5, Dash_Pos-5));Phone_Number_V2 := Substr(Nmbr, Dash_Pos+1);exception when Char_To_Number_Error thenraise Bad_Phone_Number;end;elsif Is_US_Number(Nmbr) thenCountry_Code := '+1';Phone_Number_V2 := Nmbr;elseraise Bad_Phone_Number;end if;
exception when Bad_Phone_Number thenCountry_Code := '+0';Phone_Number_V2 := '000-000-0000';
end Set_Country_Code_And_Phone_No;
/--------------------------------------------------------------------------------
declareStmt constant varchar2(32767) := 'alter session set Plsql_Warnings = '''||:Warnings||'''';
beginexecute immediate Stmt;
end;
/-- The trigger empl_forwardCREATE 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 warningsPL/SQL procedure successfully completed.12 /Trigger created.
创建反向扩版本触发器
在HR用户下,在子版本post_upgrade中,执行脚本rev_ce.sql,具体代码为:
CREATE OR REPLACE TRIGGER empl_reverseBEFORE INSERT OR UPDATE ON employees_FOR EACH ROWREVERSE CROSSEDITIONDISABLE
BEGIN:NEW.phone_number :=CASE :NEW.country_codeWHEN '+1' THENREPLACE(: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.sqlTrigger created.
正向操作
在HR用户下,在子版本post_upgrade中,执行脚本bulk_fwd.sql,具体代码为:
ALTER trigger empl_forward enable
/
ALTER trigger empl_reverse enable
/
DECLAREc number := DBMS_Sql.Open_Cursor();x number;
BEGINDBMS_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.sqlTrigger 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.sqlSession altered.PHONE_NUMBER
--------------------
515.123.45681 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.sqlSession altered.1 row updated.EMPLOYEE_ID COUNT PHONE_NUMBER_WITHIN_
----------- ----- --------------------101 +1 515.123.4567Commit 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 editionEDITION
------------------------------
ORA$BASESQL> 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 USESQL> REVOKE USE ON EDITION ora$base FROM PUBLIC;Revoke succeeded.--再次连接到HR用户,默认使用的就是子版本了,查询的数据也是来自于子版本的版本视图
SQL> conn hr/hr
Connected.SQL> show editionEDITION
------------------------------
POST_UPGRADESQL> 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
参考:
-
Edition-based redefinition
-
Performing Online Application Upgrade Using the Edition-Based Redefinition Feature
-
Edition-Based Redefinition白皮书笔记
欢迎关注我的公众号,好好学习,天天向上。
这篇关于Edition-Based Redefinition的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!