linux下帐号密码过期

早上看到告警备份失败,检查发现今天并未发起备份任务,查看定时任务时报错

[oracle@ ~]$ crontab -l

Authentication token is no longer valid; new one required
You (oracle) are not allowed to access to (crontab) because of pam configuration.

看提示是oracle用户的认证有问题,不允许其调用crontab任务,可能跟pam配置文件的设置有关。

检查secure日志文件,查看具体报错信息

vim /var/log/secure

su: pam_unix(su-l:session): session opened for user oracle by (uid=0)
su: pam_unix(su-l:session): session closed for user oracle
su: pam_unix(su-l:session): session closed for user oracle
su: pam_unix(su-l:session): session opened for user oracle by root(uid=0)
crontab: pam_unix(crond:account): expired password for user oracle (password aged)

看样子是因为oracle帐号密码超期了。

在/etc/shadow文件中以加密的方式储存了每个linux用户的账户信息,和一些账户的其他熟悉

oracle:$6$w/9VcJHK$h767lV6RlyMu:17974:1:90:7:::

密码的超期信息主要包含6个方面,如/etc/shadow看到的结果,比如90天超期,还剩7天时进行告警等。可以通过修改这个文件来进行属性的变更,但是不建议这样做。而是通过命令的方式来进行配置,命令可以修改密码的超期时间。

列出当前用户的超期信息

[root@ cron]# chage -l oracle
Last password change                    : Mar 19, 2019
Password expires                    : Jun 23, 2019
Password inactive                   : never
Account expires                     : never
Minimum number of days between password change      : 1
Maximum number of days between password change      : 90
Number of days of warning before password expires   : 7

这里可以禁用掉超期限制

[root@ cron]# chage oracle
Changing the aging information for oracle
Enter the new value, or press ENTER for the default

    Minimum Password Age [1]: 0
    Maximum Password Age [90]: 99999
    Last Password Change (YYYY-MM-DD) [2019-03-19]: 
    Password Expiration Warning [7]: 
    Password Inactive [-1]: 
    Account Expiration Date (YYYY-MM-DD) [-1]:

# 重新查看超期信息
[root@ cron]# chage -l oracle
Last password change                    : Mar 19, 2019
Password expires                    : never
Password inactive                   : never
Account expires                     : never
Minimum number of days between password change      : 0
Maximum number of days between password change      : 99999
Number of days of warning before password expires   : 7

# crontab -l查看正常
[oracle@ ~]$ crontab -l
00  17  *  *  2,6  /backup/rman_level0.sh 2>/dev/null
00  17  *  *  1,3,4,5,7  /backup/rman_level1.sh 2>/dev/null

In-Database Archiving

数据库内归档可以将表中的行标记为非活动状态来进行归档,这些非活动的行可以使用压缩来进行优化,但是应用程序看不到这些数据。如果想要看到这些数据的话可以通过在会话级别设置参数。

通过数据库内归档的技术,可以在一个数据库当中保存更多的数据,并维持更长的数据保存周期,并且不会降低应用程序的性能。这些归档数据也可以通过压缩来提高性能,应用升级的过程中,也可以延迟对这些归档数据的修改以此来提高升级效率。

开启数据库内归档

ROW ARCHIVAL子句用来开启数据库内归档,可以通过在CREATE TABLE的时候指定,也可以在创建完毕以后ALTER TABLE进行修改。

drop table t1 purge;

create table t1 (id number,name varchar2(10))
row archival;

# 开启或关闭归档
alter table t1 no row archival;
alter table t1 row archival;

# 查看字段
col column_name for a20
col data_type for a20
SELECT column_id,
 column_name,
 data_type,
 data_length,
 hidden_column
 FROM user_tab_cols
 WHERE table_name = 'T1'
 ORDER BY column_id;

 COLUMN_ID COLUMN_NAME          DATA_TYPE            DATA_LENGTH HID
---------- -------------------- -------------------- ----------- ---
         1 ID                   NUMBER                        22 NO
         2 NAME                 VARCHAR2                      10 NO
           ORA_ARCHIVE_STATE    VARCHAR2                    4000 YES        <<<<====如果开启数据库内归档,则会自动生成这个隐藏字段

# 关闭归档
alter table t1 no row archival;
SELECT column_id,
 column_name,
 data_type,
 data_length,
 hidden_column
 FROM user_tab_cols
 WHERE table_name = 'T1'
 ORDER BY column_id;

 COLUMN_ID COLUMN_NAME          DATA_TYPE            DATA_LENGTH HID
---------- -------------------- -------------------- ----------- ---
         1 ID                   NUMBER                        22 NO
         2 NAME                 VARCHAR2                      10 NO         <<<<==== ORA_ARCHIVE_STATE字段消失

生成一些数据

alter table t1 row archival;

insert into t1 
select level,'xb'||level from dual connect by level <=10;
commit;

col ora_archive_state for a10
select ora_archive_state,id,name from t1;

ORA_ARCHIV         ID NAME
---------- ---------- ----------
0                   1 xb1
0                   2 xb2
0                   3 xb3
0                   4 xb4
0                   5 xb5
0                   6 xb6
0                   7 xb7
0                   8 xb8
0                   9 xb9
0                  10 xb10

归档行数据

归档只是隐藏了行数据,并未实际的删除,这个隐藏参数主要是由ora_archive_state值来进行控制,只要是非’0’值,这行数据就会被隐藏,也就是归档的意思

update t1 
set ora_archive_state='10'
where id <6;
commit;

select ora_archive_state,id,name from t1;

ORA_ARCHIV         ID NAME
---------- ---------- ----------
0                   6 xb6
0                   7 xb7
0                   8 xb8
0                   9 xb9
0                  10 xb10

这时可以发现ID<6的数据都隐藏了,应用程序无法看到,正如前面所说,如果要看到全部数据,可以在会话级别设置参数

alter session set row archival visibility=all;
select ora_archive_state,id,name from t1;

ORA_ARCHIV         ID NAME
---------- ---------- ----------
10                  1 xb1
10                  2 xb2
10                  3 xb3
10                  4 xb4
10                  5 xb5
0                   6 xb6
0                   7 xb7
0                   8 xb8
0                   9 xb9
0                  10 xb10

# 再设置成只看活动的行
alter session set row archival visibility=active;
select ora_archive_state,id,name from t1;

ORA_ARCHIV         ID NAME
---------- ---------- ----------
0                   6 xb6
0                   7 xb7
0                   8 xb8
0                   9 xb9
0                  10 xb10

表之间归档属性传播

当通过CTAS等方式创建表时,行的归档属性是不会从原表传播到目标表的

drop table t2 purge;
create table t2 as select * from t1;

# 查询目标表,由于目标表没有开启归档,所有不包含隐藏列
xb@PDB12C> select id,name,ora_archive_state from t2;
select id,name,ora_archive_state from t2
               *
ERROR at line 1:
ORA-00904: "ORA_ARCHIVE_STATE": invalid identifier


xb@PDB12C> select id,name  from t2;

        ID NAME
---------- --------------------
         1 xb1
         2 xb2
         3 xb3
         4 xb4
         5 xb5
         6 xb6
         7 xb7
         8 xb8
         9 xb9
        10 xb10

开启目标表的归档,查询会发现ora_archive_state值都是0,也就是说表的行都是活动状态

alter session set row archival visibility = all;

alter table t2 row archival;

xb@PDB12C> select id,name,ora_archive_state  from t2;

        ID NAME                 ORA_ARCHIV
---------- -------------------- ----------
         1 xb1                  0
         2 xb2                  0
         3 xb3                  0
         4 xb4                  0
         5 xb5                  0
         6 xb6                  0
         7 xb7                  0
         8 xb8                  0
         9 xb9                  0
        10 xb10                 0

通过dbms_ilm.archivestatename过程来更新ora_archive_state列

update t2
set ora_archive_state=dbms_ilm.archivestatename(1)
where id <= 5;

commit;

xb@PDB12C> select id,name,ora_archive_state  from t2;

        ID NAME                 ORA_ARCHIV
---------- -------------------- ----------
         1 xb1                  1
         2 xb2                  1
         3 xb3                  1
         4 xb4                  1
         5 xb5                  1
         6 xb6                  0
         7 xb7                  0
         8 xb8                  0
         9 xb9                  0
        10 xb10                 0

重新查询原表的数据

xb@PDB12C> select id,name,ora_archive_state  from t1;

        ID NAME                 ORA_ARCHIV
---------- -------------------- ----------
         1 xb1                  10
         2 xb2                  10
         3 xb3                  10
         4 xb4                  10
         5 xb5                  10
         6 xb6                  0
         7 xb7                  0
         8 xb8                  0
         9 xb9                  0
        10 xb10                 0

这个时候,将原表的数据再次插入到目标表当中,看ora_archive_state的状态值是否也会插入过去

insert into t2
select id,name||'_new' from t1;
commit;

xb@PDB12C> select id,name,ora_archive_state  from t2;

        ID NAME                 ORA_ARCHIV
---------- -------------------- ----------
         1 xb1                  1
         2 xb2                  1
         3 xb3                  1
         4 xb4                  1
         5 xb5                  1
         6 xb6                  0
         7 xb7                  0
         8 xb8                  0
         9 xb9                  0
        10 xb10                 0
         1 xb1_new              0
         2 xb2_new              0
         3 xb3_new              0
         4 xb4_new              0
         5 xb5_new              0
         6 xb6_new              0
         7 xb7_new              0
         8 xb8_new              0
         9 xb9_new              0
        10 xb10_new             0

通过上面的结果可以看到ora_archive_state属性值并没有传播到新表当中去,新表中的行默认都仍然是活动状态,原来非活动的5条记录状态保持不变。

Identity Columns

标识列是12c的一个新特性,能自动生成列里的值,一部分功能与mysql中的自增列有点类似,简化了应用程序的开发和其他关系型数据库迁移到oracle。这个特性主要是利用一个默认的序列生成器,通过这个生成器来绝对字段增加或者减少的整数值。

drop table t1 purge;
create table t1
(id number generated by default as identity,
name varchar2(10));

insert into t1 (name) values ('xb');
commit;

select * from t1;

        ID NAME
---------- ----------
         1 xb

对于标识列来说,我们要么指定一个默认的序列去生成字段值或者只对那些插入NULL的情况生成字段值。根据标识列的定义可以有GENERATED ALWAYSGENERATED BY DEFAULT关键字。

GENERATED ALWAYS

oracle使用序列生成器来确定列值,但也可以显示的指定某个值。如果指定了ON NULL关键字那么序列生成器会在你插入NULL值的时候生成一个值给字段。

drop table t1 purge;
create table t1
(id number generated by default as identity,
name varchar2(10));

insert into t1 (name) values ('xb');
commit;

xb@PDB12C> insert into t1 values(null,'xb2');
insert into t1 values(null,'xb2')
                      *
ERROR at line 1:
ORA-01400: cannot insert NULL into ("XB"."T1"."ID")

select * from t1;

        ID NAME
---------- ----------
         1 xb

xb@PDB12C> @desc t1
           Name                            Null?    Type
           ------------------------------- -------- ----------------------------
    1      ID                              NOT NULL NUMBER
    2      NAME                                     VARCHAR2(10)

可以看到通过序列生成器生成的字段在创建的时候默认就是NOT NULL属性,因为这里没有对NULL的情况进行指定,所以序列生成器在碰到NULL的情况无法生成值,则会报错

加上ON NULL关键字

drop table t1 purge;
create table t1
(id number generated by default on NULL as identity,
name varchar2(10));

insert into t1 values(1,'xb');
insert into t1 values(9,'xb2');
insert into t1 values(null,'xb3');
commit;

xb@PDB12C> select * from t1;

        ID NAME
---------- ----------
         1 xb
         9 xb2
         1 xb3

这样当插入的字段值为NULL时,生成器则会自动生成一个整数值给字段。通过执行计划也可以看到,在对有序列生成器属性的表插入数据时,其实是访问的序列

xb@PDB12C> insert into t1 values(null,'xb3');

1 row created.

xb@PDB12C> @x
Display execution plan for last statement for this session from library cache...

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  4b1kmg40ra83t, child number 0
-------------------------------------
insert into t1 values(null,'xb3')

Plan hash value: 948190082

---------------------------------------------------------
| Id  | Operation                | Name         | Cost  |
---------------------------------------------------------
|   0 | INSERT STATEMENT         |              |     1 |
|   1 |  LOAD TABLE CONVENTIONAL | T1           |       |
|   2 |   SEQUENCE               | ISEQ$$_90071 |       |
---------------------------------------------------------

   - cpu costing is off (consider enabling it)


16 rows selected.

# 而这个序列也是真实存在的,说明是用户自动创建的
xb@PDB12C> select * from user_sequences;

SEQUENCE_NAME         MIN_VALUE  MAX_VALUE INCREMENT_BY C O CACHE_SIZE LAST_NUMBER S E S K
-------------------- ---------- ---------- ------------ - - ---------- ----------- - - - -
ISEQ$$_90071                  1 1.0000E+28            1 N N         20          21 N N N N

Generated Always

字段无法被用户所更新,所以序列生成器生成的值是列唯一可能的值,如果像之前一样指定某个值或修改都会报错

drop table t2 purge;
create table t2
(id number generated always as identity,
name varchar2(10));

xb@PDB12C> insert into t2 values(1,'xb');
insert into t2 values(1,'xb')
*
ERROR at line 1:
ORA-32795: cannot insert into a generated always identity column

xb@PDB12C> insert into t2 values(null,'xb');
insert into t2 values(null,'xb')
*
ERROR at line 1:
ORA-32795: cannot insert into a generated always identity column

insert into t2(name) values('xb');
insert into t2(name) values('xb2');
insert into t2(name) values('xb3');
commit;

xb@PDB12C> select * from t2;

        ID NAME
---------- ----------
         1 xb
         2 xb2
         3 xb3

xb@PDB12C> update t2 set id=4 where name='xb';
update t2 set id=4 where name='xb'
              *
ERROR at line 1:
ORA-32796: cannot update a generated always identity column

由于是使用的序列,那么也可以用到一些序列中的特性,可以指定不同的初始值和累加值

drop table t3 purge;
create table t3
(id number generated always as identity (start with 2 increment by 2),
name varchar2(10));

insert into t3(name) values('xb');
insert into t3(name) values('xb2');
insert into t3(name) values('xb3');
commit;

xb@PDB12C> select * from t3;

        ID NAME
---------- ----------
         2 xb
         4 xb2
         6 xb3

通过USER_TAB_IDENTITY_COLS等视图可以查询到一些标识列的信息

set linesize 300
col table_name for a10
col COLUMN_NAME for a10
col IDENTITY_OPTIONS for a70
select * from USER_TAB_IDENTITY_COLS;

TABLE_NAME COLUMN_NAM GENERATION SEQUENCE_NAME        IDENTITY_OPTIONS
---------- ---------- ---------- -------------------- ----------------------------------------------------------------------
T3         ID         ALWAYS     ISEQ$$_90075         START WITH: 2, INCREMENT BY: 2, MAX_VALUE: 999999999999999999999999999
                                                      9, MIN_VALUE: 1, CYCLE_FLAG: N, CACHE_SIZE: 20, ORDER_FLAG: N

T1         ID         BY DEFAULT ISEQ$$_90079         START WITH: 1, INCREMENT BY: 1, MAX_VALUE: 999999999999999999999999999
                                                      9, MIN_VALUE: 1, CYCLE_FLAG: N, CACHE_SIZE: 20, ORDER_FLAG: N

T2         ID         ALWAYS     ISEQ$$_90073         START WITH: 1, INCREMENT BY: 1, MAX_VALUE: 999999999999999999999999999
                                                      9, MIN_VALUE: 1, CYCLE_FLAG: N, CACHE_SIZE: 20, ORDER_FLAG: N

这个特性目前也有一些限制条件:

  • 每张表只能由一个标识列
  • 标识列的数据类型必须是数字型,而且不能是用户自定义的类型
  • 标识列中不能有DEFAULT关键字
  • 标识列中,NOT NULLNON DEFERRABLE约束都是隐式存在
  • CTAS语句无法继承字段的标识属性

在线迁移pdb

在线迁移pdb的目的

某些情况下要对cdb进行升级、停机等操作,而其中的部分pdb需要保持对外提供服务,那么就可以将pdb迁移到其他的cdb上运行。在线迁移pdb是目前在不停机或者最短停机时间情况下移动pdb的最快方法,如果采用其他的拔下再插入的方式,那么就需要一定的停机时间。

将pdb在不同的数据中心移动时,或者从私有机房迁移到云上环境时,所有的数据都需要物理的移动。对于那些特别大的pdb来说,这个过程则会消耗较长的时间。而PDB在线迁移则可以完全的消除这些不便之处,在线迁移pdb的过程中,应用依然可以对外提供服务,也不需要改变应用的连接设置等。

在线迁移pdb的实现方式

前提条件:

  • 本地cdb下用户必须有CREATE PLUGGABLE DATABASE权限
  • 远端CDB处于local undo模式和归档模式
  • 本地要有指向远端cdb的数据链,数据链里配置的远端cdb用户必须有PLUGGABLE DATABASESYSDBASYSOPER权限
  • 两个数据库要有相同的字节顺序
  • 如果本地CDB字符集是AL32UTF8,则远端字符集不限。否则两者的字符集必须一致

通过CREATE PLUGGABLE DATABASE语句来将pdb从一个CDB中迁移到其他CDB当中。通过FROM语句来指定当前pdb的位置,RELOCATE语句来指定pdb是被迁移而不是之前说的克隆。在这些操作完成以后,pdb就会被添加到新的CDB当中,同时会将pdb相关的文件也移动过去。

另外在迁移的过程中,被迁移的库是可以处于读写模式下实时提供服务的,在传输和应用redo到新的位置过程中会自动将原pdb静止在旧位置。最后当pdb准备好了,则会将其在新位置上线。当pdb在线迁移完毕,当前指向新位置的的DML和DDL操作会暂停,而查询则不受影响。

必须要创建一个数据链来进行迁移操作,从本地cdb指向远端新位置所在的CDB。

也可以将cdb下的pdb迁移到另一个应用容器下作为容器pdb,也可以将应用容器下的pdb迁移到另一个应用容器下,也可以将一个不包含应用pdb的应用根节点迁移到应用容器下作为应用pdb

pdb迁移后,有两种方式处理连接,AVAILABILITY MAX和AVAILABILITY NORMAL。AVAILABILITY MAX可以将指向旧位置的连接重定向到新位置,而这种情况下又包含下面这些选项

  • 如果系统采用Oracle Internet Directory (Oracle’s LDAP directory service),则连接信息会在一个中心区域进行更新而不是更新每个客户端的配置文件。
  • 如果新位置pdb也采用的同样的监听,则新的连接会在迁移完成后自动路由到新的PDB位置
  • 如果新位置pdb采用不同的监听,则需要配置local_listenerremote_listener参数来使用它们各自的监听进行交叉注册,位置的变换是无缝的,因为pdb服务器的可用性和位置都是通过监听自动注册的。

在线迁移pdb

远端cdb上新建用户,必须要有CREATE PLUGGABLE DATABASE的权限

create user c##xb identified by xb container=all;
grant create session,sysoper, create pluggable database to c##xb container=all;

# 必须本地undo模式和归档模式下
archive log list;

col property_name for a30
col property_value for a30
select property_name,property_value from database_properties where property_name='LOCAL_UNDO_ENABLED';

PROPERTY_NAME                  PROPERTY_VALUE
------------------------------ ------------------------------
LOCAL_UNDO_ENABLED             TRUE

本地cdb新建数据链

drop public database link to_remote;

create public database link to_remote connect to c##xb identified by xb using '(DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = stbyum)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = cdb1)
    )
  )';

# 测试数据链
sys@ORA12C> select * from dual@to_remote;

D
-
X

# 检查cdb的本地undo模式和archive log
col property_name for a30
col property_value for a30
select property_name,property_value from database_properties where property_name='LOCAL_UNDO_ENABLED';

PROPERTY_NAME                  PROPERTY_VALUE
------------------------------ ------------------------------
LOCAL_UNDO_ENABLED             TRUE

archive log list;

在线迁移pdb

sys@ORA12C> create pluggable database pdb3 from pdb3@to_remote relocate;

Pluggable database created.

sys@ORA12C> show pdbs;

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB12C                         READ WRITE NO
         4 PDB3                           MOUNTED

# 这个时候本地新建的pdb3还是mount状态,而远端的pdb3还是读写,可以提供服务
sys@CDB1> show pdbs;

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB3                           READ WRITE NO
sys@CDB1> alter session set container=pdb3;

Session altered.

sys@CDB1> select * from dual;

D
-
X

而当将本地pdb3打开以后,远端的pdb3会自动drop掉

sys@ORA12C> alter pluggable database pdb3 open;

Pluggable database altered.

sys@ORA12C> show pdbs;

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB12C                         READ WRITE NO
         4 PDB3                           READ WRITE NO
sys@ORA12C> select file#,name from v$datafile where con_id=4;

     FILE# NAME
---------- ----------------------------------
       134 /u01/app/oracle/oradata/ORA12C/8C1E38D38CD88F9EE0536992A8C0E93B/datafile/o1_mf_system_gk8xtgs6_.dbf
       135 /u01/app/oracle/oradata/ORA12C/8C1E38D38CD88F9EE0536992A8C0E93B/datafile/o1_mf_sysaux_gk8xtgs7_.dbf
       136 /u01/app/oracle/oradata/ORA12C/8C1E38D38CD88F9EE0536992A8C0E93B/datafile/o1_mf_undotbs1_gk8xtgs8_.dbf
       137 /u01/app/oracle/oradata/ORA12C/8C1E38D38CD88F9EE0536992A8C0E93B/datafile/o1_mf_tbs1_gk8xtgs8_.dbf

# 远端的pdb被清除了
sys@CDB1> show pdbs;

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO

sys@CDB1> select file#,name from v$datafile where con_id=3;

no rows selected

# 删除数据链
drop public database link to_remote;

Proxy PDB

关于proxy pdb

proxy pdb可以让你访问远端cdb像在本地访问一样,类似一个符号链。

可以通过创建一个本地代理pdb来引用一个不同cdb里的pdb,这个被引用的pdb则称为引用pdb。如果要访问引用pdb的数据,那么可以直接访问本地代理pdb即可,简化了一些书写。当不同cdb里的应用容器有相同的应用程序时,可以通过代理pdb来同步应用容器的根节点。

创建代理pdb时都需要创建数据链,都是在本地代理pdb所属的CDB中创建数据链,指向远端的CDB或者引用pdb。

同样也可以在应用容器中创建代理pdb,这样引用pdb则必须是应用根节点或者应用pdb。数据链则要在应用根节点中创建,指向远端的应用根节点或者应用pdb。

  • 代理pdb中的DML和DDL语句都会传输到引用pdb中,结果都会返回代理pdb
  • 当前库是代理pdb时,执行ALTER PLUGGABLE DATABASE和ALTER DATABASE语句都只影响本身,不会传输到引用pdb
  • 当前库是代理pdb所在的cdb时,执行ALTER PLUGGABLE DATABASE语句都只影响本身,不会传输到引用pdb

创建proxy pdb

create pluggable database pdb3_proxy as proxy from pdb3@to_remote;

alter pluggable database pdb3_proxy open;

通过查看proxy pdb的信息,会发现cdb显示的是远端cdb

sys@ORA12C> alter session set container=PDB3_PROXY ;

Session altered.

sys@ORA12C> select name from v$database;
select name from v$database
                 *
ERROR at line 1:
ORA-01017: invalid username/password; logon denied
ORA-02063: preceding line from PROXYPDB$DBLINK


sys@ORA12C> conn sys@ora12c as sysdba
Enter password: 
Connected.

sys@ORA12C> alter session set container=PDB3_PROXY ;

Session altered.

sys@ORA12C> select name from v$database;

NAME
---------
CDB1

查看代理pdb的相关信息

sys@ORA12C> select con_id,name,open_mode,proxy_pdb from v$pdbs;

    CON_ID NAME                           OPEN_MODE  PRO
---------- ------------------------------ ---------- ---
         2 PDB$SEED                       READ ONLY  NO
         3 PDB12C                         READ WRITE NO
         4 PDB4                           READ WRITE NO
         5 APP_ROOT                       READ WRITE NO
         6 APP1                           READ WRITE NO
         7 APP_ROOT$SEED                  READ ONLY  NO
         8 APP2                           READ WRITE NO
         9 F3483665686_3_1                READ WRITE NO
        10 F3483665686_3_2                READ WRITE NO
        11 F3483665686_3_3                READ WRITE NO
        12 PDB3_PROXY                     READ WRITE YES

sys@ORA12C> select pdb_name,status,IS_PROXY_PDB  from cdb_pdbs;

PDB_NAME                       STATUS     IS_
------------------------------ ---------- ---
PDB$SEED                       NORMAL     NO
PDB12C                         NORMAL     NO
PDB4                           NORMAL     NO
APP_ROOT                       NORMAL     NO
APP1                           NORMAL     NO
APP_ROOT$SEED                  NORMAL     NO
APP2                           NORMAL     NO
F3483665686_3_1                NORMAL     NO
F3483665686_3_2                NORMAL     NO
F3483665686_3_3                NORMAL     NO
PDB3_PROXY                     NORMAL     YES

也可以通过V$PROXY_PDB_TARGETS视图来查看代理pdb指向的哪个引用pdb

set linesize 300
col target_host format a20
col target_service format a40
col target_user format a20

select con_id,
       target_port,
       target_host,
       target_service,
       target_user
from   v$proxy_pdb_targets;

    CON_ID TARGET_PORT TARGET_HOST          TARGET_SERVICE                           TARGET_USER
---------- ----------- -------------------- ---------------------------------------- --------------------
        12        1521 stbyum               8c1e38d38cd88f9ee0536992a8c0e93b

根据target_service的名称,可以去远端确定是哪个pdb
sys@CDB1> select name,open_mode from v$pdbs where guid=upper('8c1e38d38cd88f9ee0536992a8c0e93b');

NAME       OPEN_MODE
---------- ----------
PDB3       READ WRITE

proxy pdb数据传输

验证在代理pdb上创建数据,看是否会影响引用pdb

conn sys/oracle@pdb3_proxy as sysdba
create table t1 (id number);

insert into t1 values(1);
commit;

# 查看引用pdb
conn sys/oracle@pdb3 as sysdba
sys@PDB3> select * from t1;

        ID
----------
         1

看如果反过来的情况怎么样

sys@PDB3> insert into t1 values(2);

1 row created.

sys@PDB3> commit;

Commit complete.

# 查看proxy pdb
sys@PDB3_PROXY> select * from t1;

        ID
----------
         1
         2

可以看到无论是在哪边进行ddl、dml的操作,得到的结果都是一样。

那试一下ALTER PLUGGABLE DATABASE的操作

# 关闭proxy pdb
sys@PDB3_PROXY> alter pluggable database close immediate;

Pluggable database altered.

sys@ORA12C> select open_mode from v$pdbs where name='PDB3_PROXY';

OPEN_MODE
----------
MOUNTED

# 而引用pdb状态仍然是读写
sys@PDB3> show pdbs;

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         3 PDB3                           READ WRITE NO

这样就验证了之前的结论。

代理pdb并不是将全部的数据文件同步到本地,而只有一些系统表空间

# 在代理pdb端新建表空间
sys@PDB3_PROXY> create  tablespace tbs1 datafile size 10m;

Tablespace created.

sys@ORA12C> select file#,name from v$datafile where con_id=12;

     FILE# NAME
---------- ------------------------------------------------------------------------------------------------------------------------
       128 /u01/app/oracle/oradata/ORA12C/8C1E5E9B7B4A86A6E0536892A8C0D8F8/datafile/o1_mf_system_gk34lm03_.dbf
       129 /u01/app/oracle/oradata/ORA12C/8C1E5E9B7B4A86A6E0536892A8C0D8F8/datafile/o1_mf_sysaux_gk34lm14_.dbf
       130 /u01/app/oracle/oradata/ORA12C/8C1E5E9B7B4A86A6E0536892A8C0D8F8/datafile/o1_mf_undotbs1_gk34lm15_.dbf

# 引用pdb端
sys@CDB1> select file#,name from v$datafile where con_id=3;

     FILE# NAME
---------- ------------------------------------------------------------------------------------------------------------------------
        17 /u01/app/oracle/oradata/CDB1/8C1E38D38CD88F9EE0536992A8C0E93B/datafile/o1_mf_system_gk33yoco_.dbf
        18 /u01/app/oracle/oradata/CDB1/8C1E38D38CD88F9EE0536992A8C0E93B/datafile/o1_mf_sysaux_gk33yodn_.dbf
        19 /u01/app/oracle/oradata/CDB1/8C1E38D38CD88F9EE0536992A8C0E93B/datafile/o1_mf_undotbs1_gk33yodn_.dbf
        20 /u01/app/oracle/oradata/CDB1/8C1E38D38CD88F9EE0536992A8C0E93B/datafile/o1_mf_tbs1_gk3x1g2l_.dbf

说明除了其他表空间外,其他的数据文件都是存放在引用pdb端的,代理pdb都是通过引用pdb端来访问。

当proxy pdb创建完毕,数据链就可以删除了

drop public database link to_remote;
drop user c##xb cascade ;

proxy pdb和监听

这时代理pdb是直接与引用pdb进行通信,代理pdb通常是采用默认值。

  • 监听端口:1521
    如果引用pdb不是使用的默认端口,那么必须通过PORT选项来指定,可以在创建的时候指定,也可以创建完毕以后修改代理pdb属性
  • 监听主机名称: 包含引用pdb的CDB所在的主机名称
    如果不是默认值,则需要通过HOST选项来指定,可以在创建的时候指定,也可以创建完毕以后修改代理pdb属性
create pluggable database pdb3_proxy as proxy from pdb3@to_remote PORT=1522 HOST='testyum';

-- 修改远端host
alter pluggable database containers host='stbyum';
alter pluggable database containers host rest;

-- 修改远端端口
alter pluggable database containers port='1522';
alter pluggable database containers port rest;

Application Container

顾名思义,表示应用容器。与之前的cdb不同的是,它是作为CDB中的一个pdb存在的,在应用容器下面还可以创建新一级的pdb给应用程序使用。这个应用容器中的程序是可以同步到挂在它下面的一个或多个相同业务模块的pdb当中去。

关于应用容器

应用容器是一个可选的、用户创建的CDB组件,用于存储一个或多个应用端的数据和字典信息。一个CDB可以包含0个或多个应用容器。

比如你可以在一个应用容器中创建多个与销售相关的pdb,这些pdb可以共享一个由一组公共表和表定义的应用端。

创建PDB时只需要指定AS APPLICATION CONTAINER语句就可以创建出一个应用容器的根节点,这时是没有pdb的,需要连接到应用容器根节点以后创建相应的pdb。

管理应用容器

根节点

每个应用容器只有一个应用根节点,是这个应用容器中所有pdb的父节点。

创建的那一刻应用容器的根节点属性就确定了且无法更改,根节点与CDB根节点和标准的PDB都有区别,因为它能储存用户创建的公用对象。应用公用对象可以被所有应用容器下的应用pdb访问,但是这些对象无法被CDB根节点、其他应用根节点和所有不属于这个应用根节点下的pdb所访问。

create pluggable database app_root as application container admin user xb identified by xb;
alter pluggable database app_root open;

alter session set container=app_root;
col name format a20
col root format a10
select con_id, name, application_root as root, 
       application_pdb as pdb
from   v$containers;

    CON_ID NAME                 ROOT       PDB
---------- -------------------- ---------- ---
         5 APP_ROOT             YES        NO

对应用容器根节点的操作与常规pdb类似,都可以克隆、删除等。

应用容器pdb

创建完根节点以后,要登录到根节点才能创建相关应用pdb。可以通过种子pdb,克隆pdb或者插入一个已经拔下的pdb的方式来创建应用pdb。与CDB根节点下的pdb操作一样,在应用容器中你也可以克隆、拔下或者删除一个应用pdb。但是应用pdb必须属于应用根节点下。

alter session set container=app_root;
create pluggable database app1 admin user xb1 identified by xb;
alter pluggable database app1 open;

当创建完一个新的应用pdb后,需要将应用根节点的数据同步到pdb当中来

alter session set container=app1;
alter pluggable database application all sync;

可以通过在应用根节点或者CDB根节点来删除应用pdb

alter pluggable database close immediate;
alter session set container=cdb$root;
drop pluggable database app1 including datafiles;

应用种子pdb

应用种子pdb是可选的、在应用容器中由用户创建的pdb。可以让你很快速的创建应用pdb,在应用容器中的角色与CDB$root中的seed角色一样。

# 创建种子pdb
alter session set container=app_root;
create pluggable database as seed admin user seed_xb identified by xb;
alter pluggable database app_root$seed open;

# 刷新种子pdb
alter session set container=app_root$seed;
alter pluggable database application all sync;

# 将种子pdb置于只读
alter pluggable database close immediate;
alter pluggable database open read only;

也可以通过其他应用pdb来创建种子pdb

alter session set container=app_root;
alter pluggable database app_root$seed close immediate;
drop pluggable database app_root$seed including datafiles;

create pluggable database as seed from app1;
alter pluggable database app_root$seed open;
alter pluggable database app_root$seed close immediate;
alter pluggable database app_root$seed open read only;

通过应用根节点来创建种子pdb

create pluggable database as seed from app_root;
alter pluggable database app_root$seed open;

# 种子pdb打开时有告警
sys@ORA12C> 
Warning: PDB altered with errors.

sys@ORA12C> select message,time from pdb_plug_in_violations;

MESSAGE                                                                                              TIME
---------------------------------------------------------------------------------------------------- ----------------------------------------------------
Non-Application PDB plugged in as an Application PDB, requires pdb_to_apppdb.sql be run.             20-JUN-19 01.37.32.388687 PM

# 提示要运行pdb_to_apppdb.sql
alter session set container=app_root$seed;
@$ORACLE_HOME/rdbms/admin/pdb_to_apppdb.sql

# 重新打开种子pdb
alter pluggable database app_root$seed close immediate;
alter pluggable database app_root$seed open read only;

应用公共对象

应用公共对象是创建在应用根节点里的应用的公共对象,访问它的pdb要么是数据关联或者是元数据关联。

对于数据关联的对象,应用pdb共享一组数据。比如一个销售应用容器当中有一个销售应用程序,版本是1.0,包含了一个数据关联的销售表。在这个情况下,虽然这个表是存储在应用根节点的,但是可以被这个根节点下的所有pdb所访问。

对于元数据关联的对象,应用pdb则只共享元数据,但是各自包含不同的数据。比如是之前那张销售表sales,这时每个pdb下有这样一张sales表,表定义完全一样,但是存储的是完全不同的数据。

创建应用公共对象

对象类型 共享内容 元数据存储 数据存储
数据关联 数据 应用根节点 应用根节点
扩展数据关联 扩展数据 应用根节点 应用根节点和应用pdb
元数据关联 元数据 应用根节点 应用pdb

有两种方式来决定对象的共享属性,DEFAULT_SHARING参数和SHARING语句。

PARAMETER_NAME                                               TYPE        VALUE
------------------------------------------------------------ ----------- --------------------------
default_sharing                                              string      METADATA

默认都是元数据共享。

元数据关联

因为只能在应用程序的安装、升级或打补丁时才能创建和修改公共对象,所以先创建应用程序。

# 创建应用
alter session set container=app_root;
alter pluggable database application sales_app begin install '1.0';

# 创建表空间和用户
create tablespace tbs_sales datafile size 10m autoextend on;
create user sales_xb identified by xb container=all;
grant dba to sales_xb;
alter user sales_xb default tablespace tbs_sales;

# 创建元数据关联表
drop table sales_xb.sales purge;
create table sales_xb.sales SHARING=METADATA
(id       number,
 amount    number);

insert into sales_xb.sales values(1,100);
commit; 

# 结束应用的创建
alter pluggable database application sales_app  end install '1.0';

现在可以通过DBA_APPLICATIONS视图查看刚创建的应用

col app_name format a20
col app_version format a10

select app_name,
       app_version,
       app_status
from   dba_applications
where  app_name = 'SALES_APP';

APP_NAME             APP_VERSIO APP_STATUS
-------------------- ---------- ------------
SALES_APP            1.0        NORMAL

现在就可以通过ALTER PLUGGABLE DATABASE APPLICATION ... SYNC语句将应用程序的定义信息同步到应用的pdb当中去,每个应用的pdb会有一个与sales_xb.sales表一样元数据的表,但是数据是各自存在每个pdb自己当中的。

这里总共建了两个应用pdb,分别是pdb1和pdb2

sys@ORA12C> show pdbs;

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         5 APP_ROOT                       READ WRITE NO
         6 APP1                           READ WRITE NO
         7 APP_ROOT$SEED                  READ ONLY  NO
         8 APP2                           READ WRITE NO

同步到APP1当中

alter session set container=app1;
select * from sales_xb.sales;

ERROR at line 1:
ORA-00942: table or view does not exist

# 刷新数据
alter pluggable database application sales_app sync;

sys@ORA12C> select * from sales_xb.sales;

        ID     AMOUNT
---------- ----------
         1        100

这样应用程序就同步到了app1当中,可以通过视图DBA_APP_PDB_STATUS查询到应用程序与pdb的关系状态

alter session set container=app_root;
select c.name,
       a.con_uid,
       a.app_name,
       a.app_version,
       a.app_status
from   dba_app_pdb_status a
       join v$containers c on c.con_uid = a.con_uid
where  a.app_name = 'SALES_APP';

NAME                    CON_UID APP_NAME             APP_VERSIO APP_STATUS
-------------------- ---------- -------------------- ---------- ------------
APP1                 3232047153 SALES_APP            1.0        NORMAL

数据关联

这里升级应用程序从1.0到2.0,然后创建数据关联的公用表

# 升级应用程序
alter pluggable database application sales_app begin upgrade '1.0' to '2.0';

drop table sales_xb.sales2 purge;
create table sales_xb.sales2 SHARING=DATA
(id       number,
 amount    number);

insert into sales_xb.sales2 values(2,100);
commit; 

alter pluggable database application sales_app end upgrade to '2.0';

同步APP2的pdb

alter session set container=app2;

alter pluggable database application sales_app sync;
select * from sales_xb.sales2;

        ID     AMOUNT
---------- ----------
         2        100

alter session set container=app_root;
select c.name,
       a.con_uid,
       a.app_name,
       a.app_version,
       a.app_status
from   dba_app_pdb_status a
       join v$containers c on c.con_uid = a.con_uid
where  a.app_name = 'SALES_APP';

NAME                    CON_UID APP_NAME             APP_VERSIO APP_STATUS
-------------------- ---------- -------------------- ---------- ------------
APP1                 3232047153 SALES_APP            1.0        NORMAL
APP2                  827740620 SALES_APP            2.0        NORMAL

扩展数据关联

新建扩展数据关联的公用表,然后同步到app1当中

alter pluggable database application sales_app begin upgrade '2.0' to '3.0';

drop table sales_xb.sales3 purge;
create table sales_xb.sales3 SHARING=EXTENDED DATA
(id       number,
 amount    number);

insert into sales_xb.sales3 values(3,100);
commit; 

alter pluggable database application sales_app end upgrade to '3.0';


alter session set container=app1;

alter pluggable database application sales_app sync;
select * from sales_xb.sales3;

        ID     AMOUNT
---------- ----------
         3        100

alter session set container=app_root;
select c.name,
       a.con_uid,
       a.app_name,
       a.app_version,
       a.app_status
from   dba_app_pdb_status a
       join v$containers c on c.con_uid = a.con_uid
where  a.app_name = 'SALES_APP';

NAME                    CON_UID APP_NAME             APP_VERSIO APP_STATUS
-------------------- ---------- -------------------- ---------- ------------
APP2                  827740620 SALES_APP            2.0        NORMAL
APP1                 3232047153 SALES_APP            3.0        NORMAL

接下来验证一下不同共享模式下数据的读写情况

# sales -> metadata
# sales2 -> data
# sales3 -> extended data
alter session set container=app1;
insert into sales_xb.sales values(1,200);
insert into sales_xb.sales2 values(2,200);
insert into sales_xb.sales3 values(3,200);
commit;

# 在插入sales2时出现报错 ORA-65097: DML into a data link table is outside an application action。

说明对于数据关联的表是无法在应用pdb级别进行dml操作的,而对于metadata和extended data来说,每个pdb只共享了元数据或部分数据,允许每个pdb有自己独立的数据存在。

应用程序卸载

alter session set container=app_root;

alter pluggable database application sales_app begin uninstall;
drop user sales_xb cascade;
drop tablespace tbs_sales including contents and datafiles;

alter pluggable database application sales_app end uninstall;

同步所有的pdb

alter session set container=app1;
alter pluggable database application sales_app sync;

alter session set container=app2;
alter pluggable database application sales_app sync;

alter session set container=app_root;
select c.name,
       a.con_uid,
       a.app_name,
       a.app_version,
       a.app_status
from   dba_app_pdb_status a
       join v$containers c on c.con_uid = a.con_uid
where  a.app_name = 'SALES_APP';

NAME                    CON_UID APP_NAME             APP_VERSIO APP_STATUS
-------------------- ---------- -------------------- ---------- ------------
APP1                 3232047153 SALES_APP            3.0        UNINSTALLED
APP2                  827740620 SALES_APP            3.0        UNINSTALLED