云和恩墨技术专家
现就职于云和恩墨,为某省电信公司数据库运维服务;在IT行业拥有10年以上的工作经历。擅长 ORACLE 数据库运维管理、shell 脚本开发;长期服务于电信、金融,政府行业;具有丰富的数据库经验。
本文整理自上周四晚云和恩墨大讲堂分享的关于主题:Oracle 12c新特性-多租户的维护管理。
概述
Oracle 12cRelease 1自从2013年发布以来已经有3年多时间了,新的Release版本Oracle 12c R2的发布时间也在翘首以盼。在已发布的12c中有增加了很多新特性,包括多租户、存储优化(heat map,ADO)、内存组件(In-Memory)等等。目前已经有用户开始尝鲜使用12c中的新功能,今天和大家介绍和分享12c中多租户的特点及多租户的维护管理方法。
简单来说,12c中的多租户架构是一个容器数据库(multitenant Container DataBase ,CDB)中包含多个可插拔数据库(PluggableDataBases)。在传统的数据库架构中Oracle实例与数据库的关系是1对1(单实例环境)和1对N(RAC数据库),而在多租户的环境下,实例与数据库的关系为N对1(单实例CDB环境)和N对N(RAC多租户CDB环境)。
下面是多租户的逻辑和物理结构:
从逻辑结构来看,以上图为例,一个CDB中包含有一个根容器(CDB$ROOT),一个种子容器(PDB$SEED),两个PDB分别是hrpdb和salepdb。
根容器(root container ,CDB$ROOT):CDB ROOT中主要保存一些元数据和公共对象。在容器数据库中,PDB有自己独立的SYSTEM和SYSAUX表空间保存自己数据库中的元数据信息。
种子容器(seed pdb,PDB$SEED):是系统提供用来创建PDB的模板。无法在seed容器中添加或修改对象。在CDB启动之后,SEED PDB的状态是READ ONLY的状态。
PDB(Pluggabledatabases):一个CDB中可以包含0~N个PDB,PDB是提供应用服务的数据库,在上图中包含salespdb和hrpdb两个PDB,正常情况下是由salespdb和hrpdb对外提供服务,CDB$ROOT只是用来对数据库进行管理,与Unix/Linux系统中的root用户类似。新建的CDB中不包含PDB,需要单独去创建。
从物理结构来看,在一个CDB容器中,包含有:
共用的控制文件;
共用的redo日志文件;
一个或多个临时表空间(默认情况下,CDB和PDB共用CDB$ROOT中的临时表空间,但PDB也可以创建自己的临时表空间);
共用一个UNDO表空间;
各自独立的SYSTEM和SYSAUX表空间(即每个PDB会有各自SYSTEM和SYSAUX表空间);
PDB各自应用表空间数据文件。
PDB的创建有下面几种方法:
从PDB$SEED中创建PDB;
通过克隆一个现有的PDB数据库或非CDB数据库(non-CDB)来创建一个新的PDB;
使用XML元数据描述文件将数据库插入到CDB中;
使用DBMS_PDB方式;
a.通过seed新建一个PDB数据库
创建一个名字为salespdb的PDB,其应用用户默认表空间为sales,数据文件保存路径在/disk1/oracle/dbs/salespdb。创建的SQL语句如下:
CREATE PLUGGABLE DATABASE salespdb ADMIN USERsalesadm IDENTIFIED BY password
STORAGE(MAXSIZE 2G MAX_SHARED_TEMP_SIZE 100M)
DEFAULT TABLESPACEsales
DATAFILE'/disk1/oracle/dbs/salespdb/sales01.dbf' SIZE 250M AUTOEXTEND ON
PATH_PREFIX= '/disk1/oracle/dbs/salespdb/'
FILE_NAME_CONVERT = ('/disk1/oracle/dbs/pdbseed/','/disk1/oracle/dbs/salespdb/');
在上面的创建语句中,通过STORAGE子句中的MAXSIZE和MAX_SHARED_TEMP_SIZE来限制PDB可以分配空间大小和从CDB中分配的临时表空间大小。我们可以指定大小,也可以设置为UNLIMITED.
通过PATH_PREFIX和FILE_NAME_CONVERT子句来指定数据文件的名称。
也可以通过下面的命令来创建:
CREATE PLUGGABLE DATABASE salespdb ADMIN USERsalesadm IDENTIFIED BY password FILE_NAME_CONVERT = ('/disk1/oracle/dbs/pdbseed/','/disk1/oracle/dbs/salespdb/');
b.通过克隆一个现有的PDB数据库或非CDB数据库(non-CDB)来创建一个新的PDB
通过现有的pdb1创建新的PDB pdb2:
CREATE PLUGGABLE DATABASE pdb2 FROM pdb1
FILE_NAME_CONVERT = ('/disk1/oracle/pdb1/', '/disk2/oracle/pdb2/');
通过non-cdb创建pdb
CREATE PLUGGABLE DATABASE pdb2 FROM pdb1
(1)首先需要将non-cdb置为只读
(2)创建连接指向non-cdb的dblink
(3)创建pdb
CREATEPLUGGABLE DATABASE pdb2 FROM mydb@mydb_link FILE_NAME_CONVERT =('/disk1/oracle/non-cdb1/', '/disk2/oracle/pdb2/‘);
CREATEPLUGGABLE DATABASE pdb2 FROM NON$CDB@mydb_link FILE_NAME_CONVERT =('/disk1/oracle/non-cdb1/', '/disk2/oracle/pdb2/‘);
上面这两个语句是等效的。
(4)在pdb上运行@$ORACLE_HOME/rdbms/admin/noncdb_to_pdb.sql脚本
c.使用XML元数据描述文件将数据库插入到CDB中
这个创建PDB的方式其实等于是使用XML描述文件对PDB做迁移。即使用XML描述文件的方式将PDB从一个CDB迁移到另一个CDB中。在网络不通的情况下可以使用,如果网络能正常连接的情况下,可以参考使用dblink进行迁移。
具体的创建过程如下:
首先生成XML描述文件
exec dbms_pdb.DESCRIBE('/home/oracle/pdbenmo3_describe.xml','PDBENMO3');
使用keepdatafile的方式drop掉 PDB
drop pluggable database PDBENMO3 keep datafiles;
在数据库中验证XML描述文件是否可以插入到目标的CDB环境中
DECLARE
compatible CONSTANT VARCHAR2(3) :=
CASE DBMS_PDB.CHECK_PLUG_COMPATIBILITY(
pdb_descr_file => '/home/oracle/pdbenmo3_describe.xml',
pdb_name => 'PDBENMO3')
WHEN TRUE THEN 'YES'
ELSE 'NO'
END;
BEGIN
DBMS_OUTPUT.PUT_LINE(compatible);
END;
/
将PDBPROD3中的数据文件目录改名mv为PDBTEST01(数据文件中存在PDBENMO3也改为PDBTEST01);
将数据库改名插入到CDB中,使用NOCOPY的方式:
CREATE PLUGGABLE DATABASE PDBTEST01 USING'/home/oracle/pdbenmo3_describe.xml'
SOURCE_FILE_NAME_CONVERT= ('PDBENMO3', 'PDBTEST01')
NOCOPY;
d.使用DBMS_PDB方式
通常来说,我们将Non-CDB数据库迁移到PDB有下面几种情况和方法:
(1)通过克隆的方式进行迁移,即第二种方式中使用dblink方式进行创建,这种方式需要PDB和Non-CDB数据库的版本都要高于12.1.0.2,如果没有12.1.0.2,则需要将数据库升级到12.1.0.2之后的版本
(2)通过DBMS_PDB的方式生成XML元数据文件进行迁移。这种方式要求Non-CDB数据库的版本高于12c。该节我们主要介绍这种方法
(3)使用数据泵EXPDP/IMPDP的方式。这种方式要求Non-CDB数据库的版本高于11.2.0.3,在使用该方式的时候,expdp数据时,需要添加VERSION参数version=12。如果Non-CDB数据库版本低于11.2.0.3,则可以使用传输表空间的方式进行迁移
(4)使用GoldenGate或类似于Goldengate的工具进行迁移
我们看一下使用DBMS_PDB的方式将Non-CDB转化为PDB对方式,步骤如下:
(1)将Non-CDB数据库置为只读
(2)生成XML描述文件
exec dbms_pdb.describe('/home/oracle/prod4_describe.xml');
(3)关闭Non-CDB数据库
(4)在CDB数据库中使用XML描述文件创建PDB。
CREATE PLUGGABLE DATABASE ncdb USING'/home/oracle/prod4_describe.xml' COPY FILE_NAME_CONVERT =('/u01/app/oracle/oradata/PROD4','/u01/app/oracle/oradata/PRODCDB/ncdb');
创建完成之后不要立即打开PDB,还需要执行noncdb_to_pdb.sql脚本
(5)执行noncdb_to_pdb.sql脚本
alter session set container=NCDB;
--NCDB为新建的PDB名称
@?/rdbms/admin/noncdb_to_pdb.sql
(6) 确认没有报错后,打开数据库
alter pluggable database NCDB open;
对于PDB的删除,有两种情况:保留数据文件和不保留数据文件
对于不保留数据文件的情况,直接使用drop pluggable database including datafiles即可
drop pluggable database pdb_name includingdatafiles;
对于需要保留数据文件的情况,则在删除前需要生成描述文件将数据库unplug,然后再进行删除:
alter pluggable database PDBENMO2 unplug into'/home/oracle/pdbenmo2_describe.xml';
drop pluggable database PDBENMO2 keep datafiles;
以上是对多租户数据库的创建和删除的维护操作,接下来我们分享一下CDB和PDB的启动停止维护内容
CDB的启动和停止
CDB实例的启动和停止与传统的Non-CDB数据库启动一样,同样经历nomount,mount,open几个阶段,在各个阶段中读取的数据库文件也相同。默认情况下,当CDB启动之后,CDB$ROOT是自动打开的,而CDB$SEED无法打开,其状态为READ ONLY状态。其余PDB的状态默认情况下也都是MOUNT,如果需要设置PDB随CDB启动而打开,简单设置一下即可。
在12.1.0.1版本中,如果需要PDB随CDB启动而打开,需要通过创建触发器来实现。
CREATEOR REPLACE TRIGGER open_pdbs
AFTER STARTUP ON DATABASE
BEGIN
EXECUTE IMMEDIATE 'ALTER PLUGGABLE DATABASE ALLOPEN';
END open_pdbs;
/
在12.1.0.2版本中,可以保存PDB的状态,当下次CDB重启的时候,会将PDB启动到保存的状态下。因此我们可以将PDB的状态保存为open,当下次CDB启动之后,直接打开PDB。
其实现方法如下
alter pluggable database pdbenmo3 save state;
保存的状态通过dba_pdb_saved_states视图查看
删除保存的状态:
alter pluggable database pdbenmo3 discard state;
PDB的打开和关闭
打开PDB时,需要以SYSOPER或SYSDBA身份连接到CDB$ROOT,然后发出ALTERPLUGGABLE DATABASE OPEN语句,可以指定一个或多个PDB来打开。
alter pluggable database pdbenmo3 open;
alter pluggable database all open;
alter pluggable database all except pdbenmo3 open;
使用show pdb 或查询v$pdbs视图查看打开状态
也可以使用sysdba身份连接到PDB中打开PDB。这种情况下,不必命名要打开的PDB。
启动和关闭PDB:
关闭PDB时,以SYSOPER或SYSDBA身份连接到CDB$ROOT,然后发出alterpluggable database close,制定一个或多个PDB名称进行关闭
表空间管理
在多租户数据库环境中,对数据库表空间的管理与传统的非多租户数据库管理基本相同。无论是对用户表空间还是临时表空间,唯一需要注意的是,在对表空间数据文件进行维护的时候,需要确认对象是属于哪个PDB或者是属于CDB$ROOT。
查看当前PDB的方法:
SQL> show pdbs
CON_IDCON_NAME OPEN MODE RESTRICTED
---------- ---------------------------------------- ----------
2 PDB$SEED READ ONLY NO
3 PDBENMO1 MOUNTED
4 PDBENMO2 READ WRITE NO
SQL> show pdbs
CON_IDCON_NAME OPEN MODE RESTRICTED
---------- ---------------------------------------- ----------
2 PDB$SEED READ ONLY NO
3 PDBENMO1 MOUNTED
4 PDBENMO2 READ WRITE NO
或者查看dba_pdbs视图
切换PDB
alter session set container=pdb_name; —切换到某个PDB
alter session set container=cdb$root; —切换到cdb root
或者可以使用sqlplus或conn的方式直接连到相应的pdb数据库中
用户管理
相比Oracle 11g之前版本中的用户管理,多租户数据库中数据库的管理有两种:本地用户和公共用户。本地用户即为某个PDB中的数据库用户,是仅存在某个PDB中的数据库用户,其管理方法与传统的非CDB环境相同。相比本地用户,公共用户是在多个PDB中具有相同用户名和验证身份的用户。
公共用户的名称不能与所有PDB中任何本地用户名称相同。公共用户是在根和每个现有和将来的PDB中具有相同身份的数据库用户。
本地用户的用户信息保存在属主PDB中的SYSTEM表空间中,而公共用户的用户信息在CDB$ROOT和各个PDB中的SYSTEM表空间中都有保存。
公共用户创建需要在CDB$ROOT根容器下创建:
SQL> alter session set container=cdb$root;
Session altered.
common_user_prefix参数设定要求公共参数需要用C##开头,可以调整修改
SQL> show parameter common_user_prefix
NAME TYPE VALUE
------------------------------------ -----------------------------------------
common_user_prefix string C##
创建公共用户
SQL> create user C##user1 identified by oracle
User created.
查看公共用户,分别在CDB$ROOT和各个PDB中存在:
SQL> select username,con_id from cdb_users whereusername like '%USER1%';
USERNAME CON_ID
---------------- ----------
C##USER1 1
C##USER1 3
C##USER1 4
管理角色和权限
与公共用户和本地用户类似,在创建角色role的时候,也会有公共角色和本地角色的区别,当在根容器中创建角色时,该角色为公共角色,而在具体某一个PDB中创建角色时,该角色为本地角色。
公共角色可以授予公共用户,也可以授予本地用户。同样,在某一个PDB中,可以给一个公共用户授予公共角色,也可以给它赋予本地角色。所以,公共用户在不同的PDB中的权限可能是不同的。
可以在根容器CDB$ROOT下给所有的公共用户授予公共权限,如果需要赋予一个公共权限,则在赋权SQL命令后面需要加上container=all子句。如下:
--创建公共角色role
SQL> create role c##role1;
Role created.
--当前公共用户C##USER1没有被赋予任何角色权限
SQL> select grantee,granted_role,con_id from cdb_role_privs where grantee like '%C##USER1%';
no rows selected
--给公共用户赋予公共角色role1
SQL> grant c##role1 to c##user1 container=all;
Grant succeeded.
--赋权成功
SQL> select grantee,granted_role,con_id from cdb_role_privs where grantee like '%C##USER1%';
GRANTEE GRANTED_ROLE CON_ID
------------------------------ ------------------------------ ----------
C##USER1 C##ROLE1 3
C##USER1 C##ROLE1 4
C##USER1 C##ROLE1 1
同样对于公共权限也一样
--当前公共用户C##USER1没有给赋予任何系统权限
SQL> select grantee,privilege,con_id from cdb_sys_privs where grantee like '%C##USER1%';
no rows selected
--用户C##USER1赋予create session权限但不加container=all子句
SQL> grant create session to C##USER1;
Grant succeeded.
--使用container=all子句给用户C##USER1赋予create table权限
SQL> grant create table to C##USER1 container=all;
Grant succeeded.
--查看用户的权限,可以看到如果使用container=all子句,则授予的是公共权限。否则是本地权限
SQL> select grantee,privilege,con_id from cdb_sys_privs where grantee like '%C##USER1%';
GRANTEE PRIVILEGE CON_ID
------------------------------ ---------------------------------------- ----------
C##USER1 CREATE SESSION 1
C##USER1 CREATE TABLE 1
C##USER1 CREATE TABLE 3
C##USER1 CREATE TABLE 4
在CDB多租户环境中各PDB的资源分配
对于在多租户数据库的维护中,有时候考虑到多个PDB之间可能会存在CPU,内存等资源的竞争,如何去规划和限制这些PDB的资源分配,以避免PDB之间相互影响。
在传统以前的数据库中,Oracle一直提供了资源管理器resource manager工具对各个schema之间的资源进行分配和限制。在Oracle 12c的多租户环境中,我们同样可以使用resource manager对多租户中各个PDB的资源进行规划和分配。
下面是一个规划的示例:
为CDB数据库PRODCDB中的PDBENMO1,PDBENMO2,PDBENMO3配置资源管理器,
PDBENMO1 得到5 份共享CDB 资源
PDBENMO2 得到3 份共享CDB 资源
PDBENMO3 得到2 份共享CDB 资源
没有PDB 可以得到多于80%的可用cpu 时间
没有PDB 可以得到多于30%的parallel_servers_target
DECLARE
spfileValue VARCHAR2(1000);
execText VARCHAR2(1000);
scopeValue VARCHAR2(30) := 'MEMORY';
planName VARCHAR2(100) :='DAYTIMEP';
BEGIN
dbms_resource_manager.clear_pending_area();
dbms_resource_manager.create_pending_area();
dbms_resource_manager.create_cdb_plan( plan => 'PLAN_P', comment => '');
dbms_resource_manager.create_cdb_plan_directive(
plan => 'PLAN_P',
pluggable_database => 'PDBENMO1',
comment => '',
shares => 5,
utilization_limit => 80,
parallel_server_limit => 30 );
dbms_resource_manager.create_cdb_plan_directive(
plan => 'PLAN_P',
pluggable_database => 'PDBENMO2',
comment => '',
shares => 4,
utilization_limit => 80,
parallel_server_limit => 30 );
dbms_resource_manager.create_cdb_plan_directive(
plan => 'PLAN_P',
pluggable_database => 'PDBENMO3',
comment => '',
shares => 1,
utilization_limit => 80,
parallel_server_limit => 30 );
dbms_resource_manager.submit_pending_area();
END;
/
如上,可以使用dbms_resource_manager资源管理器包对CDB中各个PDB分配的资源进行规划和限制。
部署的数据库对硬件的资源消耗并不大,系统压力比较小
具有实例和存储空间的开销,因此同一台服务器上不能同时部署很多套数据库,不同的应用分散在不同的服务器上
每一套数据库的维护不是很复杂,但是维护的数据库量比较大
不需要大量的时间来对数据库进行升级或打补丁
对于以上这种情况,我们可以考虑使用Oracle 12c多租户的功能,将很多个数据库集中到CDB中进行维护管理。
多租户数据库的优点:
(1)在集中管理的平台中操作多个数据库,其成本更低:
—实例开销较低
—存储成本较低
(2)减少DBA的重复维护工作量并保证维护的安全性
—对应用来说,数据库的连接并没有改变,不会涉及应用程序的更改
—对DBA数据库管理员来说,减少了数据库的维护量
—数据库维护更方便,CDB/PDB的维护工作与传统的维护方式没有太大改变,相比在升级和打补丁方面更方便
—新建PDB和迁移比较快速
(3)不同的PDB之间相互隔离,不会相互干扰影响
往期大讲堂精彩主题回顾
20160107期:李真旭 - 动手为王 - 整合迁移与数据恢复实践
20160113期:敬勇 - 一条执行时间小于1秒的 SQL 引发的性能问题
20160114期:郑保卫 - 索引优化策略及实战
20160310期:李华 - 故障分析 | library cache latch 竞争案例分享
20160317期:于远 - 一条让人欲罢不能的 SQL给我的启发
20160324期:皇甫晓飞 - 合理使用存储过程提高运维效率
20160331期:戴明明 - 基于PCIE 闪存卡的 Oracle 数据库使用(一)
20160331期:戴明明 - 基于PCIE 闪存卡的 Oracle 数据库使用(二)
20160407期:怀晓明 - 细致入微 | 让 SQL 优化再多飞一会儿
20160414期:李建国 - 从容灾迈向双活案例分享
20160421期:盖国强 - Oracle 数据库的架构演进和我的学习之路
20160428期:黄嵩 - 一次 truncate 核心表衍生的信息系统安全思考
20160505期:陈龙 - 探究 Oracle 高水位对数据库性能影响
20160519期:李轶楠 - 意料之外的 RAC 宕机罪犯 - 子游标
20160526期:程昌明 - 通过 Oracle 日志文件了解 CRS 的启动过程
20160602期:李真旭 - 数套 ASM RAC 的恢复案例
20160616期:李翔宇 - 轻轻揭开 b*tree 索引结构的神秘面纱
20160623期:黄宸宁 - 一次特殊的 ORA-04030 故障处理
20160630期:钟时荣 - SQL性能突然降低引起的业务办理缓慢案例一则
20160707期:易欣 - TX - row lock contention 的一些场景
20160714期:巩飞 - Oracle 实用技巧之不知道密码情况下 dblink 的迁移
20160721期:李磊 - 深入剖析 ORA-04031 的前世今生
20160728期:杨俊 - 炎炎夏日-恩墨小王子带你玩转“数据卫士”Data Guard
20160804期:彭文元 - 中间件 BES 连接池的配置和问题诊断方法
20160811期:尹涛- 由 DRM 引起的 ORA-00481错误