编辑手记:备份重于一切,我们必需知道,系统总是要崩溃的,没有有效的备份只是等哪一天死!今天你备份了吗?我们一起来回顾Oracle的物理备份,本文摘自《循序渐进Oracle》
逻辑备份:Oracle的逻辑备份与恢复
正文:
物理备份是指针对Oracle的文件进行的备份,这与逻辑备份针对数据的备份不同。在物理备份中,数据库使用的重要文件都需要进行针对性的备份,这些文件包括数据文件(DATA FILE)、控制文件(CONTROL FILE)、联机日志文件(REDO LOG)、归档日志文件(ARCHIVE LOG)和参数文件及口令文件等(可选)。
需要注意的是,临时文件因为不存储永久数据,所以可以不必备份,在恢复后可以重新创建临时表空间的临时文件。根据备份方式的不同,物理备份又可以分为冷备份和热备份。
冷备份
冷备份是指关闭数据库的备份,又称脱机备份或一致性备份,在冷备份开始前数据库必须彻底关闭。关闭操作必须用带有normal、transaction、immediate选项的shutdown来执行。
冷备份通常适用于业务具有阶段性的企业,比如白天运行、夜间可以停机维护的企业,冷备份操作简单,但是需要关闭数据库,对于需要24×7提供服务的企业是不适用的。
在进行冷备份之前通常需要做一些准备工作,例如,确定数据文件的名称及路径、确定日志文件的名称及路径等。很多数据库出于空间或负载分担的考虑,会将文件分散存储到不同的硬盘或分区,所以在备份时一定要注意包含所有的需要备份文件。我曾经见过有的公司在恢复时才发现冷备份漏掉了部分文件,这种错误在工作中是应该避免的。
以下几个查询在备份之前应当执行,以确认数据库文件及存储路径:
SQL> select name from v$datafile;
NAME
-----------------------------------------------------
/opt/oracle/oradata/eygle/system01.dbf
/opt/oracle/oradata/eygle/undotbs01.dbf
/opt/oracle/oradata/eygle/sysaux01.dbf
/data1/oradata/systemfile/eygle01.dbf
SQL> select member from v$logfile;
MEMBER
---------------------------------------------------
/opt/oracle/oradata/eygle/redo01.log
/opt/oracle/oradata/eygle/redo02.log
/opt/oracle/oradata/eygle/redo03.log
SQL> select name from v$controlfile;
NAME
---------------------------------------------------
/opt/oracle/oradata/eygle/control01.ctl
/opt/oracle/oradata/eygle/control02.ctl
/opt/oracle/oradata/eygle/control03.ctl
冷备份的通常步骤是:
正常关闭数据库;
备份所有重要的文件到备份目录;
完成备份后启动数据库。
为了恢复方便,冷备份应该包含所有的数据文件、控制文件和日志文件,这样当需要采用冷备份进行恢复时,只需要将所有文件恢复到原有位置,就可以启动数据库了。
热备份
由于冷备份需要关闭数据库,所以已经很少有企业能够采用这种方式进行备份了,当数据库运行在归档模式下时,Oracle允许用户对数据库进行联机热备份。
热备份又可以简单的分为两种:用户管理的热备份(user-managed backup and recovery,)和Oracle管理的热备份(Recovery Manager-RMAN)。
用户管理的热备份是指用户通过将表空间置于热备份模式下,然后通过操作系统工具对文件进行复制备份,备份完成后再结束表空间的备份模式。
Oracle管理的热备份通常指通过RMAN对数据库进行联机热备份,RMAN执行的热备份不需要将表空间置于热备模式,从而可以减少对于数据库的影响获得性能提升。另外RMAN的备份信息可以通过控制文件或者额外的目录数据库进行管理,功能强大但是相对复杂。
下面分别来介绍一下用户管理的备份和RMAN
1.用户管理的热备份
用户管理的热备份通常包含以下几个步骤:
(1)在备份之前需要显示的发出Begin Backup的命令;
(2)在操作系统拷贝备份文件(包括数据文件、控制文件等);
(3)发出end backup命令通知数据库完成备份;
(4)备份归档日志文件。
常见的备份过程如下,这里以一个表空间的备份为例:
alter tablespace system begin backup;
host copy E:\ORACLE\ORADATA\EYGLE\SYSTEM01.DBF e:\oracle\orabak\SYSTEM01.DBF
alter tablespace system end backup;
当备份被激活时,可以通过v$backup视图来检查表空间的备份情况:
FILE# STATUS CHANGE# TIME
---------- ------------------ ---------- ---------
1 NOT ACTIVE 0
2 NOT ACTIVE 0
3 NOT ACTIVE 0
4 NOT ACTIVE 0
SQL> alter tablespace system begin backup;
Tablespace altered.
SQL> select * from v$backup;
FILE# STATUS CHANGE# TIME
----- ------------------ ---------- -------------------
1 ACTIVE 1.8995E+10 2007-02-12 15:54:52
2 NOT ACTIVE 0
3 NOT ACTIVE 0
4 NOT ACTIVE 0
需要注意的是,当表空间置于热备模式下,表空间数据文件头的检查点会被冻结,当热备份完成,发出end backup命令后,表空间数据文件检查点被重新同步,恢复更新:
SQL> alter tablespace system end backup;
Tablespace altered.
SQL> select * from v$backup;
FILE# STATUS CHANGE# TIME
-------- ------------------ ---------- -------------------
1 NOT ACTIVE 1.8995E+10 2007-02-12 15:54:52
2 NOT ACTIVE 0
3 NOT ACTIVE 0
4 NOT ACTIVE 0
备份的起止时间会在警告日志文件中记录:
Mon Feb 12 15:54:52 2007
alter tablespace system begin backup
Mon Feb 12 15:54:52 2007
Completed: alter tablespace system begin backup
Mon Feb 12 16:13:00 2007
alter tablespace system end backup
Mon Feb 12 16:13:00 2007
Completed: alter tablespace system end backup
这里需要提醒的是,如果遗忘了end backup命令将会导致数据库问题,所以使用这种方式备份时需要确认备份正确完成。
2、额外Redo的生成
在使用Begin Backup开始备份时,数据库会产生了比平常更多的日志,也就会生成更多的归档。这是因为在热备份期间,Oracle为了解决SPLIT BLOCK的问题,需要在日志文件中记录修改的行所在的数据块的前镜像(image),而不仅仅是修改信息。
为了理解这段话,还需要简单介绍一下SPLIT BLOCK的概念。
我们知道,Oracle的数据块是由多个操作系统块组成。通常UNIX文件系统使用512bytes的数据块,而Oracle使用8kB的db_block_size。当热备份数据文件的时候,使用文件系统的命令工具(通常是cp工具)拷贝文件,并且使用文件系统的blocksize读取数据文件。
在这种情况下,可能出现如下状况:当拷贝数据文件的同时,数据库正好向数据文件写数据。这就使得拷贝的文件中包含这样的database block,它的一部分OS block来自于数据库向数据文件(这个db block)写操作之前,另一部分来自于写操作之后。对于数据库来说,这个databaseblock本身并不一致,而是一个分裂块(SPLIT BLOCK)。这样的分裂块在恢复时并不可用(会提示corrupted block)。
所以,在热备状态下,对于变更的数据,Oracle需要在日志中记录整个变化的数据块的前镜像。这样如果在恢复的过程中,数据文件中出现分裂块,Oracle就可以通过日志文件中的数据块的前镜像覆盖备份,以完成恢复。
来看一下测试,首先通过SYS用户连接数据库,确认SCOTT用户连接信息及日志信息:
创建一个视图用于简化查询:
CREATE OR REPLACE VIEW redo_size
AS
SELECT VALUE
FROM v$mystat, v$statname
WHERE v$mystat.statistic# = v$statname.statistic#
AND v$statname.NAME = 'redo size';
然后看一下正常情况下的日志生成:
SQL> select * from redo_size;
VALUE
----------
11972
SQL> update emp set sal=10 where empno=7788;
1 row updated.
SQL> commit;
Commit complete.
SQL> select * from redo_size;
VALUE
----------
12484
SQL> select 12484 -11972 from dual;
12484-11972
-----------
512
正常的更新操作,大约生成了512 byte的日志。然后用SYS用户,将EMP表所在的SYSTEM表空间置于热备份模式:
SQL> alter tablespace system begin backup;
Tablespace altered.
使用SCOTT用户进行同样操作:
SQL> select * from redo_size;
VALUE
----------
8788
SQL> update emp set sal=10 where empno=7788;
1 row updated.
SQL> commit;
Commit complete.
SQL> select * from redo_size;
VALUE
----------
17516
SQL> select 17516 - 8788 from dual;
17516-8788
----------
8728
注意到这一次,生成了8728 byte的重做,这个日志量较上次正常模式下大约多出了一个Block的数量。然后用SYS用户转储日志文件,看一下多出来的日志到底是什么内容:
SQL> @gettrcname
TRACE_FILE_NAME
----------------------------------------------------------------------------
/opt/oracle9/admin/testora9/udump/testora9_ora_4692.trc
SQL> ALTER SYSTEM DUMP LOGFILE '/opt/oracle9/oradata/testora9/redo01.log';
System altered.
检查相关信息,注意到较常规状态下,增加了“Log block image redo entry”部分,这一部分就是在热备份时产生的额外日志信息:
REDO RECORD -Thread:1 RBA: 0x000085.00000020.0010 LEN: 0x2018 VLD: 0x01
SCN: 0x0000.0f8b260c SUBSCN: 103/19/2006 20:24:14
CHANGE #1 TYP:3 CLS:1 AFN:1 DBA:0x0040a482SCN:0x0000.0f8b2471SEQ: 1 OP:18.1
Log block image redo entry
Dump of memory from0x0000000103DB7020 to 0x0000000103DB9008
103DB7020 01080015 000070F3 0F8B2470 00004000 [......p...$p..@.]
103DB8FE0 C11F2C00 0803C24A4605534D 49544805 [..,....JF.SMITH.]
103DB8FF0 434C4552 4B03C250 030777B4 0C110101 [CLERK..P..w.....]
103DB9000 0102C209 FF02C115 [........]
Dump of memory from0x0000000103DB9008 to 0x0000000103DB9009
103DB9000 06401F3A [.@.:]
REDO RECORD -Thread:1 RBA: 0x000085.00000030.0128 LEN: 0x01b0 VLD: 0x01
SCN: 0x0000.0f8b260c SUBSCN: 103/19/2006 20:24:14
CHANGE #1 TYP:0CLS:33 AFN:2 DBA:0x00800089 SCN:0x0000.0f8b25c9 SEQ: 1 OP:5.2
...
KDO Op code: URP rowdependencies Disabled
xtype: XA bdba: 0x0040a482 hdba: 0x0040a481
itli: 1 ispac: 0 maxfr: 4863
tabn: 0 slot: 7(0x7)flag: 0x2c lock: 1ckix: 0
ncol: 8 nnew: 1 size:0
col 5: [ 2] c1 0b
CHANGE #4 MEDIARECOVERY MARKER SCN:0x0000.00000000 SEQ: 0 OP:5.20
session number = 12
serial number = 28
transaction name =
在Oracle数据库内部,存在一个隐含参数控制这个行为:
这个参数缺省值为TRUE,设置在热备份期间允许在redo中记录数据块信息,如果数据库块大小等于操作系统块大小,则可以设置该参数为False,减少热备期间数据库的负担(这种情况极为少见)。
分裂块产生的根本原因在于备份过程中引入了操作系统工具(如cp工具等),操作系统工具无法保证Oracle数据块的一致性。如果使用RMAN备份,由于Rman可以通过反复读取获得一致的Blok,从而可以避免SPLIT Block的生成,所以不会产生额外的REDO。所以建议在备份时(特别是繁忙的数据库),应该尽量采用RMAN备份。
3、定制自适应的热备份脚本
由于数据库经常处于变化之中,如表空间文件的增减、新表空间的创建等,所以热备份的脚本不能一成不变,如果想让备份充分的自动化,必须定制自适应的备份脚本。以下是在生产库中经过实践的脚本范例(来自UNIX平台)供大家参考(为了简洁,省略了脚本中部分环境变量设置等内容,详细脚本可以从本书代码包中获得)。
首先看看crontab中的定义:
$ crontab -l
30 2 * * 0-6 /opt/oracle/tools/hotbackup/startbak.sh
整个备份是通过一个叫做startbak.sh的脚本启动的,这个脚本的主要内容如下:
……
week=`date+"%w"`
today=`date+"%m%d"`
ps -ef|grepdbw0_$ORACLE_SID |grep -v grep >>/dev/null
if [ $? -eq 0 ];then
if [ $week = "1" ] ; then
$SH_HOME/genbaksql.sh
else
$SH_HOME/dobakarch.sh
fi
fi
脚本根据时间进行判断,如果是周一则执行全备份,否则执行归档日志备份,全备份由脚本genbaksql.sh调用并执行,这个脚本就是根据数据库信息自动生成自适应备份脚本的过程,shell脚本包含一系列的SQL*Plus执行SQL,执行过程通过spool的方式生成另外一个文件:
……
SQLPLUS -S "/ as sysdba" >/dev/null <<EOF
……
SPOOL $SH_HOME/dobackup.sql
genbaksql.sh这个脚本的主体部分包含如下语句:
SELECT 'PROMPT Begin Backup Tablespace '
|| tablespace_name|| '.'|| file_name|| CHR (10)
|| 'alter tablespace ' || tablespace_name|| ' begin backup;'|| CHR (10)
|| '! cp '|| file_name|| ' $BACKUP_FILE'|| CHR (10)
|| 'alter tablespace '|| tablespace_name|| ' end backup;'|| CHR (10)
|| '! compress '|| '$BACKUP_FILE'|| '/*.dbf'|| CHR (10)
|| 'PROMPT Successed End Backup This File . ;'|| CHR (10)
|| 'select CHR(10) from dual;'|| CHR (10)
FROM dba_data_files
WHERE status = 'AVAILABLE';
这个SQL执行之后,会根据数据库当前的情况生成备份脚本,生成的内容类似:
PROMPT Begin Backup Tablespace SYSTEM./opt/oracle/oradata/eygle/system01.dbf
alter tablespace SYSTEM begin backup;
! cp /opt/oracle/oradata/eygle/system01.dbf $BACKUP_FILE
alter tablespace SYSTEM end backup;
! compress $BACKUP_FILE/*.dbf
PROMPT Successed End Backup This File . ;
select CHR(10) from dual;
其中压缩步骤是为了节省空间,不同环境可以根据情况来修改。genbaksql.sh的最后部分通过调用另外一个编译好的shell脚本来调用以上动态生成的dobackup.sql脚本,执行备份:
chmod u+x $SH_HOME/dobackup.sh
$SH_HOME/dobackup.sh
这个dobackup.sh脚本主要用来执行备份,并最终对所有备份文件进行打包(还可以加入简单的过期备份删除功能),这个脚本的主要内容如下(最后一部分是用来备份归档日志的):
date
echo "--------------------------------------------------------------"
sqlplus "/ as sysdba" @$SH_HOME/dobackup.sql
date
$TAR_COMMAND cvf $BACKUP_FILE/hotback`date +"%Y%m%d"`.tar `find $BACKUP_FILE -name "*.Z"`
$RM_COMMAND $BACKUP_FILE/*.Z
find $BACKUP_FILE -name "*.tar" -mtime +10 -exec rm {} \;
cat $SH_HOME/dobackup.log |$MAIL_COMMAND -s "HOT BACKUP FROM $LOCAL" $DBA
echo "-------------------------------------------------"
echo "Begin backup today archive log files....."
$SH_HOME/dobakarch.sh
echo "Successed in backup archive files"
echo "--------------------------------------------------"
备份归档日志的脚本dobakarch.sh是一个简单的通用脚本,除了全备份时调用,日常也可以在startbak.sh脚本中调用,其主要内容如下:
$SH_HOME/bakarch.sh > $SH_HOME/dobakarch.log
最终的归档备份是通过bakarch.sh完成的:
date
echo "-------------------------------------------------"
echo "Begin backup today archive log files....."
$MV_COMMAND `find $RAEL_ARCH -name "*.arc"` $BACK_ARCH
$TAR_COMMAND cvf $BACK_ARCH/arch$DATE.tar `find $BACK_ARCH -name "*.arc"`
$COMPRESS_COMMAND $BACK_ARCH/arch$DATE.tar
if [ $? = "0" ]; then
$RM_COMMAND $BACK_ARCH/*.arc
fi
find $BACK_ARCH -name "*.tar.Z" -mtime +10 -exec rm {} \;
if [ $? = "0" ]; then
echo "Successed in backup archive files"
else
echo "File not find!"
fi
echo "-------------------------------------------------"
这样就完成了这个自定制备份脚本。
4.Oracle10g 的增强
在Oracle 10g中,Oracle新增命令用以简化用户管理的备份,现在可以通过alter databasebegin/end backup来进行数据库备份模式的切换,在Oracle 10g之前,需要对每个表空间逐一进行热备设置:
SQL> alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss';
Session altered.
SQL> alter database begin backup;
Database altered.
当执行了alter database begin backup之后,所有表空间一次被置于热备状态,可以通过并行方式对数据库进行备份:
SQL> select * from v$backup;
FILE# STATUS CHANGE# TIME
------ --------------- ---------- -------------------
1 ACTIVE 8.9255E+12 2010-12-22 16:26:18
2 ACTIVE 8.9255E+12 2010-12-22 16:26:18
3 ACTIVE 8.9255E+12 2010-12-22 16:26:18
4 ACTIVE 8.9255E+12 2010-12-22 16:26:18
5 ACTIVE 8.9255E+12 2010-12-22 16:26:18
6 ACTIVE 8.9255E+12 2010-12-22 16:26:18
在执行alter databaseend backup,所有表空间将一次性停止热备:
这是Oracle 10g对于用户管理热备的一个增强。
5.用户管理备份的完全恢复
当进行了完善的备份之后,接下来的工作就是等候故障出现时一展身手了(当然能够不出故障是更理想的)。以下通过实例来说明对于用户管理的备份进行完全恢复。
完全恢复的前提是有一个全备份,为了验证恢复情况,执行如下一系列的数据库操作:
SQL> connect eygle/eygle
SQL> create table eygle as select * from dba_users;
SQL> select count(*) from eygle;
COUNT(*)
----------
8
SQL> delete from eygle where rownum <5;
SQL> commit;
SQL> alter system switch logfile;
SQL> insert into eygle select * from dba_users;
SQL> commit;
SQL> select count(*) from eygle;
COUNT(*)
----------
12
SQL> alter system switch logfile;
System altered.
假定此后发生数据库故障,所有数据文件丢失,但是还有当前的控制文件和日志文件。
注意:如果当前的日志文件丢失,那么数据库只能进行不完全恢复,因为当前日志中包含的Redo信息对应的数据修改可能尚未写入到数据文件,丢失当前日志文件意味着数据库将会丢失提交成功的数据,恢复的最大限度只能是进行不完全恢复,恢复完成后将需要通过Resetlogs打开数据库;如果当前控制文件丢失,那么只能通过备份的控制文件或重建控制文件来进行恢复。
但是如果拥有控制文件和日志文件,那么接下来可以进行完全恢复,首先Restore数据文件到原有目录:
[oracle@jumper oradata]$ cp eyglebak/*.dbfeygle
此时如果尝试启动数据库,Oracle就会提示需要介质恢复,这是根据控制文件及数据文件头的信息进行判断的:
SQL> startup
ORACLE instance started.
Database mounted.
ORA-01113: file 1 needs media recovery
ORA-01110: data file 1:'/opt/oracle/oradata/eygle/system01.dbf'
正常情况下,可以先启动数据库到Mount状态,然后开始恢复:
SQL> startup mount;
ORACLE instance started.
Database mounted.
因为我们拥有最新的控制文件以及所有的归档及在线日志文件,所以可以简单地通过一条命令执行恢复,在恢复过程中,Oracle会提示归档文件,可以输入Auto让Oracle自动执行恢复:
SQL> recover database;
ORA-00279: change 18995632784 generated at03/09/2007 15:41:26 needed for thread 1
ORA-00289: suggestion : oracle/prod/9.2.0/dbs/arch1_6503.dbf
ORA-00280: change 18995632784 for thread 1 isin sequence #6503
Specify log: {<RET>=suggested | filename| AUTO | CANCEL} auto
ORA-00279: change 18995632919 generated at03/09/2007 15:47:01 needed for thread 1
ORA-00289: suggestion : oracle/prod/9.2.0/dbs/arch1_6504.dbf
ORA-00280: change 18995632919 for thread 1 isin sequence #6504
ORA-00278: log file '/oracle/prod/9.2.0/dbs/arch1_6503.dbf'no longer needed for this recovery
ORA-00279: change 18995652925 generated at03/09/2007 16:01:02 needed for thread 1
ORA-00289: suggestion : oracle/prod/9.2.0/dbs/arch1_6505.dbf
ORA-00280: change 18995652925 for thread 1 isin sequence #6505
ORA-00278: log file '/oracle/prod/9.2.0/dbs/arch1_6504.dbf'no longer needed for this recovery
Log applied.
Mediarecovery complete.
恢复完成之后,可以open打开数据库:
SQL> alter database open;
Database altered.
恢复之后可以验证之前的数据:
SQL> select count(*) from eygle.eygle;
COUNT(*)
----------
12
由于控制文件和在线日志文件都没有损失,数据库执行了完全恢复,数据被正确的恢复了出来。从alert文件中,我们可以看到完整的介质恢复过程,Oracle通过应用归档日志对数据文件进行不断的推进恢复:
Fri Mar 9 16:05:29 2007
ALTERDATABASE RECOVER database
Media Recovery Start
Starting datafile 1 recovery in thread 1sequence 6503
Datafile 1:'/opt/oracle/oradata/eygle/system01.dbf'
Starting datafile 2 recovery in thread 1sequence 6503
Datafile 2: '/opt/oracle/oradata/eygle/undotbs01.dbf'
Starting datafile 3 recovery in thread 1sequence 6503
Datafile 3:'/opt/oracle/oradata/eygle/eygle01.dbf'
Media Recovery Log
ORA-279 signalled during: ALTER DATABASERECOVER database ...
Fri Mar 9 16:05:37 2007
ALTERDATABASE RECOVER CONTINUE DEFAULT
Media Recovery Log opt/oracle/product/9.2.0/dbs/arch1_6503.dbf
ORA-279 signalled during: ALTER DATABASERECOVER CONTINUE DEFAULT ...
Fri Mar 9 16:05:38 2007
ALTER DATABASE RECOVER CONTINUE DEFAULT
Media Recovery Log /opt/oracle/product/9.2.0/dbs/arch1_6504.dbf
ORA-279 signalled during: ALTER DATABASERECOVER CONTINUE DEFAULT ...
Fri Mar 9 16:05:38 2007
ALTER DATABASE RECOVER CONTINUE DEFAULT
Media Recovery Log /opt/oracle/product/9.2.0/dbs/arch1_6505.dbf
然后根据日志序列,顺序应用在线日志文件,完成恢复:
Recovery of Online Redo Log: Thread 1 Group 3Seq 6506 Reading mem 0
Mem# 0errs 0: /opt/oracle/oradata/eygle/redo03.log
Recovery of Online Redo Log: Thread 1 Group 4Seq 6507 Reading mem 0
Mem# 0errs 0: /opt/oracle/oradata/eygle/redo04.dbf
Recovery of Online Redo Log: Thread 1 Group 5Seq 6508 Reading mem 0
Mem# 0errs 0: /opt/oracle/oradata/eygle/redo05.dbf
Media Recovery Complete
Completed: ALTER DATABASE RECOVER CONTINUE DEFAULT
在Open过程中,Oracle进行实例恢复,实例恢复对日志的扫描是通过两阶段扫描完成的:
Fri Mar 9 16:05:44 2007
alter database open
Fri Mar 9 16:05:44 2007
Beginning crash recovery of 1 threads
Fri Mar 9 16:05:44 2007
Startedfirst pass scan
Fri Mar 9 16:05:44 2007
Completed first pass scan
0 redoblocks read, 0 data blocks need recovery
Fri Mar 9 16:05:44 2007
Startedrecovery at
Thread1: logseq 6508, block 2, scn 4.1815783972
Recovery of Online Redo Log: Thread 1 Group 5Seq 6508 Reading mem 0
Mem# 0errs 0: /opt/oracle/oradata/eygle/redo05.dbf
Fri Mar 9 16:05:44 2007
Completed redo application
Fri Mar 9 16:05:44 2007
Ended recovery at
Thread1: logseq 6508, block 2, scn 4.1815803973
0 datablocks read, 0 data blocks written, 0 redo blocks read
Crashrecovery completed successfully
恢复完成后,数据库可以成功Open打开。
本次分享的内容到此结束,下期将会详细介绍RMAN备份。今天你备份了吗?
如何加入"云和恩墨大讲堂"微信群
搜索 盖国强(Eygle) :eyygle,或者扫描下面二维码,备注:云和恩墨大讲堂,即可入群。每周与千人共享免费技术分享,与讲师在线讨论。
Oracle微信推出全新检索功能,只要你记得题目,作者,或者关键字中的任何一个,都可以通过回复获取想要的文章!更多精彩,请关注Oracle微信获取。