You are on page 1of 13

监控 Oracle 数据库的常用 shell 脚本

本文首先回顾了一些 DBA 常用的 Unix 命令,以及解释了如何通过 Unix Cron 来定时执行 DBA 脚本。同时
文章还介绍了 8 个重要的脚本来监控 Oracle 数据库:
检查实例的可用性
检查监听器的可用性
检查 alert 日志文件中的错误信息
在存放 log 文件的地方满以前清空旧的 log 文件
分析 table 和 index 以获得更好的性能
检查表空间的使用情况
找出无效的对象
监控用户和事务
DBA 需要的 Unix 基本知识

基本的 UNIX 命令
以下是一些常用的 Unix 命令:

ps--显示进程 grep--搜索文件中的某种文本模式 mailx--读取或者发送 mail cat--连接文件或者显示它们
cut--选择显示的列 awk--模式匹配语言 df--显示剩余的磁盘空间

以下是 DBA 如何使用这些命令的一些例子:


显示服务器上的可用实例:
$ ps -ef | grep smon
oracle 21832 1 0 Feb 24 ? 19:05 ora_smon_oradb1
oracle 898 1 0 Feb 15 ? 0:00 ora_smon_oradb2
dliu 25199 19038 0 10:48:57 pts/6 0:00 grep smon
oracle 27798 1 0 05:43:54 ? 0:00 ora_smon_oradb3
oracle 28781 1 0 Mar 03 ? 0:01 ora_smon_oradb4、

显示服务器上的可用监听器:

$ ps -ef | grep listener | grep -v grep


(译者注:grep 命令应该加上-i 参数,即 grep -i listener,
该参数的作用是忽略大小写,因为有些时候 listener 是大写的,这时就会看不到结果)
oracle 23879 1 0 Feb 24 ? 33:36 /8.1.7/bin/tnslsnr listener_db1 -inherit
oracle 27939 1 0 05:44:02 ? 0:00 /8.1.7/bin/tnslsnr listener_db2 -inherit
oracle 23536 1 0 Feb 12 ? 4:19 /8.1.7/bin/tnslsnr listener_db3 -inherit
oracle 28891 1 0 Mar 03 ? 0:01 /8.1.7/bin/tnslsnr listener_db4 -inherit

查看 Oracle 存档目录的文件系统使用情况

$ df -k | grep oraarch
/dev/vx/dsk/proddg/oraarch 71123968 4754872 65850768 7% /u09/oraarch
alter.log 文件中的行数:
$ cat alert.log | wc -l
2984

列出 alert.log 文件中的全部 Oracle 错误信息:

$ grep ORA- alert.log


ORA-00600: internal error code, arguments: [kcrrrfswda.1], [], [], [], [], []
ORA-00600: internal error code, arguments: [1881], [25860496], [25857716], []

CRONTAB 基本

一个 crontab 文件中包含有六个字段:
分 0-59
小时 0-23
月中的第几天 1-31
月份 1 - 12
星期几 0 - 6, with 0 = Sunday

Unix 命令或者 Shell 脚本

要编辑一个 crontab 文件,输入:Crontab -e


要查看一个 crontab 文件,输入:
Crontab -l
0 4 * * 5 /dba/admin/analyze_table.ksh
30 3 * * 3,6 /dba/admin/hotbackup.ksh /dev/null 2>&1

在上面的例子中,第一行显示了一个分析表的脚本在每个星期 5 的 4:00am 运行。第二行显示了一个执行


热备份的脚本在每个周三和周六的 3:00a.m.运行。

监控数据库的常用 Shell 脚本
以下提供的 8 个 shell 脚本覆盖了 DBA 每日监控工作的 90%,你可能还需要修改 UNIX 的环境变量。
检查 Oracle 实例的可用性

oratab 文件中列出了服务器上的所有数据库
$ cat /var/opt/oracle/oratab
############################################################

## /var/opt/oracle/oratab##

############################################################

oradb1:/u01/app/oracle/product/8.1.7:Y
oradb2:/u01/app/oracle/product/8.1.7:Y
oradb3:/u01/app/oracle/product/8.1.7:N
oradb4:/u01/app/oracle/product/8.1.7:Y

以下的脚本检查 oratab 文件中列出的所有数据库,并且找出该数据库的状态(启动还是关闭)

##############################################################

## ckinstance.ksh ## ###################################################################

ORATAB=/var/opt/oracle/oratab
echo `date`
echo Oracle Database(s) Status `hostname` :
db=`egrep -i :Y|:N $ORATAB | cut -d: -f1 | grep -v # | grep -v *`
pslist=`ps -ef | grep pmon`
for i in $db ; do
echo $pslist | grep ora_pmon_$i > /dev/null 2>$1
if (( $? )); then
echo Oracle Instance - $i: Down
else
echo Oracle Instance - $i: Up
fi
done

使用以下的命令来确认该脚本是可以执行的:

$ chmod 744 ckinstance.ksh


$ ls -l ckinstance.ksh
-rwxr--r-- 1 oracle dba 657 Mar 5 22:59 ckinstance.ksh*

以下是实例可用性的报表:

$ ckinstance.ksh
Mon Mar 4 10:44:12 PST 2002
Oracle Database(s) Status for DBHOST server:
Oracle Instance - oradb1: Up
Oracle Instance - oradb2: Up
Oracle Instance - oradb3: Down
Oracle Instance - oradb4: Up

检查 Oracle 监听器的可用性

以下有一个类似的脚本检查 Oracle 监听器。如果监听器停了,该脚本将会重新启动监听器:


#######################################################################

## cklsnr.sh ##

#######################################################################

#!/bin/ksh
DBALIST=primary.dba@company.com,another.dba@company.com;export DBALIST
cd /var/opt/oracle
rm -f lsnr.exist
ps -ef | grep mylsnr | grep -v grep > lsnr.exist
if [ -s lsnr.exist ]
then
echo
else
echo Alert | mailx -s Listener ‘mylsnr‘ on `hostname` is down $DBALIST

TNS_ADMIN=/var/opt/oracle; export TNS_ADMIN


ORACLE_SID=db1; export ORACLE_SID
ORAENV_ASK=NO; export ORAENV_ASK
PATH=$PATH:/bin:/usr/local/bin; export PATH

. oraenv

LD_LIBRARY_PATH=${ORACLE_HOME}/lib;export LD_LIBRARY_PATH

lsnrctl start mylsnr

fi

检查 Alert 日志(ORA-XXXXX)

每个脚本所使用的一些环境变量可以放到一个 profile 中:

#######################################################################

## oracle.profile ##

#######################################################################

EDITOR=vi;export EDITOR ORACLE_BASE=/u01/app/oracle; export


ORACLE_BASE ORACLE_HOME=$ORACLE_BASE/product/8.1.7; export
ORACLE_HOME LD_LIBRARY_PATH=$ORACLE_HOME/lib; export
LD_LIBRARY_PATH TNS_ADMIN=/var/opt/oracle;export
TNS_ADMIN NLS_LANG=american; export
NLS_LANG NLS_DATE_FORMAT=‘Mon DD YYYY HH24:MI:SS‘; export
NLS_DATE_FORMAT ORATAB=/var/opt/oracle/oratab;export
ORATAB PATH=$PATH:$ORACLE_HOME:$ORACLE_HOME/bin:/usr/ccs/bin:/bin:/usr/bin:/usr/sbin:/

sbin:/usr/openwin/bin:/opt/bin:.; export

PATH DBALIST=primary.dba@company.com,another.dba@company.com;export
DBALIST

以下的脚本首先调用 oracle.profile 来设置全部的环境变量。如果发现任何的 Oracle 错误,该脚本还会给


DBA 发送一个警告的 email。

####################################################################

## ckalertlog.sh ##

####################################################################

#!/bin/ksh
.. /etc/oracle.profile
for SID in `cat $ORACLE_HOME/sidlist`
do
cd $ORACLE_BASE/admin/$SID/bdump
if [ -f alert_${SID}.log ]
then
mv alert_${SID}.log alert_work.log
touch alert_${SID}.log
cat alert_work.log >> alert_${SID}.hist
grep ORA- alert_work.log > alert.err
fi
if [ `cat alert.err|wc -l` -gt 0 ]
then
mailx -s ${SID} ORACLE ALERT ERRORS $DBALIST < alert.err
fi
rm -f alert.err
rm -f alert_work.log
done

清除旧的归档文件

以下的脚本将会在 log 文件达到 90%容量的时候清空旧的归档文件:

$ df -k | grep arch
Filesystem kbytes used avail capacity Mounted on
/dev/vx/dsk/proddg/archive 71123968 30210248 40594232 43% /u08/archive
#######################################################################

## clean_arch.ksh ##

#######################################################################
#!/bin/ksh
df -k | grep arch > dfk.result
archive_filesystem=`awk -F ‘{ print $6 }‘ dfk.result`
archive_capacity=`awk -F ‘{ print $5 }‘ dfk.result`
if [[ $archive_capacity > 90% ]]
then
echo Filesystem ${archive_filesystem} is ${archive_capacity} filled
# try one of the following option depend on your need
find $archive_filesystem -type f -mtime +2 -exec rm -r {} ;
tar
rman
fi

分析表和索引(以得到更好的性能)

以下我将展示如果传送参数到一个脚本中:
####################################################################

## analyze_table.sh ##

####################################################################
#!/bin/ksh
# input parameter: 1: password # 2: SID
if (($#<1)) then echo "Please enter oracle user password as the first parameter !" exit 0
fi
if (($#<2)) then echo "Please enter instance name as the second parameter!" exit 0
fi
要传入参数以执行该脚本,输入:
$ analyze_table.sh manager oradb1

脚本的第一部分产生了一个 analyze.sql 文件,里面包含了分析表用的语句。脚本的第二部分分析全部的表:


#####################################################################

## analyze_table.sh ##

#####################################################################
sqlplus -s <
oracle/$1@$2
set heading off
set feed off
set pagesize 200
set linesize 100
spool analyze_table.sql
select ANALYZE TABLE || owner || . || segment_name ||
ESTIMATE STATISTICS SAMPLE 10 PERCENT;
from dba_segments
where segment_type = TABLE
and owner not in (SYS, SYSTEM);
spool off
exit
!
sqlplus -s <
oracle/$1@$2
@./analyze_table.sql
exit
!
以下是 analyze.sql 的一个例子:
$ cat analyze.sql
ANALYZE TABLE HIRWIN.JANUSAGE_SUMMARY ESTIMATE STATISTICS SAMPLE 10 PERCENT;
ANALYZE TABLE HIRWIN.JANUSER_PROFILE ESTIMATE STATISTICS SAMPLE 10 PERCENT;
ANALYZE TABLE APPSSYS.HIST_SYSTEM_ACTIVITY ESTIMATE STATISTICS SAMPLE 10 PERCENT;
ANALYZE TABLE HTOMEH.QUEST_IM_VERSION ESTIMATE STATISTICS SAMPLE 10 PERCENT;
ANALYZE TABLE JSTENZEL.HIST_SYS_ACT_0615 ESTIMATE STATISTICS SAMPLE 10 PERCENT;
ANALYZE TABLE JSTENZEL.HISTORY_SYSTEM_0614 ESTIMATE STATISTICS SAMPLE 10 PERCENT;
ANALYZE TABLE JSTENZEL.CALC_SUMMARY3 ESTIMATE STATISTICS SAMPLE 10 PERCENT;
ANALYZE TABLE IMON.QUEST_IM_LOCK_TREE ESTIMATE STATISTICS SAMPLE 10 PERCENT;
ANALYZE TABLE APPSSYS.HIST_USAGE_SUMMARY ESTIMATE STATISTICS SAMPLE 10 PERCENT;
ANALYZE TABLE PATROL.P$LOCKCONFLICTTX ESTIMATE STATISTICS SAMPLE 10 PERCENT;

检查表空间的使用

以下的脚本检测表空间的使用。如果表空间只剩下 10%,它将会发送一个警告 email。


#####################################################################

## ck_tbsp.sh ##

#####################################################################
#!/bin/ksh
sqlplus -s <
oracle/$1@$2
set feed off
set linesize 100
set pagesize 200
spool tablespace.alert
SELECT F.TABLESPACE_NAME,
TO_CHAR ((T.TOTAL_SPACE - F.FREE_SPACE),999,999) "USED (MB)",
TO_CHAR (F.FREE_SPACE, 999,999) "FREE (MB)",
TO_CHAR (T.TOTAL_SPACE, 999,999) "TOTAL (MB)",
TO_CHAR ((ROUND ((F.FREE_SPACE/T.TOTAL_SPACE)*100)),999)|| % PER_FREE

FROM (
SELECT TABLESPACE_NAME,
ROUND (SUM (BLOCKS*(SELECT VALUE/1024
FROM V$PARAMETER
WHERE NAME = db_block_size)/1024)
) FREE_SPACE
FROM DBA_FREE_SPACE
GROUP BY TABLESPACE_NAME
) F,
(
SELECT TABLESPACE_NAME,
ROUND (SUM (BYTES/1048576)) TOTAL_SPACE
FROM DBA_DATA_FILES
GROUP BY TABLESPACE_NAME
)T
WHERE F.TABLESPACE_NAME = T.TABLESPACE_NAME
AND (ROUND ((F.FREE_SPACE/T.TOTAL_SPACE)*100)) < 10;
spool off
exit
!
if [ `cat tablespace.alert|wc -l` -gt 0 ]
then
cat tablespace.alert -l tablespace.alert > tablespace.tmp
mailx -s "TABLESPACE ALERT for ${2}" $DBALIST < tablespace.tmp
fi
警告 email 输出的例子如下:

TABLESPACE_NAME USED (MB) FREE (MB) TOTAL (MB) PER_FREE


------------------- --------- ----------- ------------------- ------------------
SYSTEM 2,047 203 2,250 9 %
STBS01 302 25 327 8 %
STBS02 241 11 252 4 %
STBS03 233 19 252 8 %
查找出无效的数据库对象
以下查找出无效的数据库对象:
#####################################################################

## invalid_object_alert.sh ##

#####################################################################
#!/bin/ksh . /etc/oracle.profile
sqlplus -s <
oracle/$1@$2
set feed off
set heading off column object_name format a30
spool invalid_object.alert
SELECT OWNER, OBJECT_NAME, OBJECT_TYPE,
STATUS FROM DBA_OBJECTS WHERE STATUS =
INVALID ORDER BY OWNER, OBJECT_TYPE, OBJECT_NAME;
spool off
exit ! if [ `cat invalid_object.alert|wc -l` -gt 0 ] then
mailx -s "INVALID OBJECTS for ${2}" $DBALIST < invalid_object.alert
fi$ cat invalid_object.alert
OWNER OBJECT_NAME OBJECT_TYPE STATUS
--------------------------------------------
HTOMEH DBMS_SHARED_POOL PACKAGE BODY INVALID
HTOMEH X_$KCBFWAIT VIEW INVALID
IMON IW_MON PACKAGE INVALID
IMON IW_MON PACKAGE BODY INVALID
IMON IW_ARCHIVED_LOG VIEW INVALID
IMON IW_FILESTAT VIEW INVALID
IMON IW_SQL_FULL_TEXT VIEW INVALID
IMON IW_SYSTEM_EVENT1 VIEW INVALID
IMON IW_SYSTEM_EVENT_CAT VIEW INVALIDLBAILEY CHECK_TABLESPACE_USAGE
PROCEDURE INVALID
PATROL P$AUTO_EXTEND_TBSP VIEW INVALID
SYS DBMS_CRYPTO_TOOLKIT PACKAGE INVALID
SYS DBMS_CRYPTO_TOOLKIT PACKAGE BODY INVALID
SYS UPGRADE_SYSTEM_TYPES_TO_816 PROCEDURE INVALID
SYS AQ$_DEQUEUE_HISTORY_T TYPE INVALID
SYS HS_CLASS_CAPS VIEW INVALID SYS HS_CLASS_DD VIEW INVALID
监视用户和事务(死锁等)
以下的脚本在死锁发生的时候发送一个警告 e-mail:
###################################################################

## deadlock_alert.sh ##

##################################################################
##!/bin/ksh
.. /etc/oracle.profile
sqlplus -s <
oracle/$1@$2
set feed off
set heading off
spool deadlock.alert
SELECT SID, DECODE(BLOCK, 0, NO, YES ) BLOCKER,
DECODE(REQUEST, 0, NO,YES ) WAITER
FROM V$LOCK
WHERE REQUEST > 0 OR BLOCK > 0
ORDER BY block DESC;
spool off
exit
!
if [ `cat deadlock.alert|wc -l` -gt 0 ]
then
mailx -s "DEADLOCK ALERT for ${2}" $DBALIST < deadlock.alert
fi
结论
0,20,40 7-17 * * 1-5 /dba/scripts/ckinstance.sh > /dev/null 2>&1
0,20,40 7-17 * * 1-5 /dba/scripts/cklsnr.sh > /dev/null 2>&1
0,20,40 7-17 * * 1-5 /dba/scripts/ckalertlog.sh > /dev/null 2>&1
30 * * * 0-6 /dba/scripts/clean_arch.sh > /dev/null 2>&1
* 5 * * 1,3 /dba/scripts/analyze_table.sh > /dev/null 2>&1
* 5 * * 0-6 /dba/scripts/ck_tbsp.sh > /dev/null 2>&1
* 5 * * 0-6 /dba/scripts/invalid_object_alert.sh > /dev/null 2>&1
0,20,40 7-17 * * 1-5 /dba/scripts/deadlock_alert.sh > /dev/null 2>&1
通过以上的脚本,可大大减轻你的工作。你可以使用这些是来做更重要的工作,例如性能调整。

DBA 面试题
一:SQL tuning 类
1:列举几种表连接方式答:merge join,hash join,nested loop

2:不借助第三方工具,怎样查看 sql 的执行计划?答:sqlplus


set autotrace ...
utlxplan.sql 创建 plan_table 表

3 : 如 何 使 用 CBO , CBO 与 RULE 的 区 别 ? 答 : 在 初 始 化 参 数 里 面 设 置


optimizer_mode=choose/all_rows/first_row 等可以使用 cbo。
rbo 会选择不合适的索引,cbo 需要统计信息。

4:如何定位重要(消耗资源多)的 SQL?答:根据 v$sqlarea 中的逻辑读/disk_read。以及寻找 CPU 使用过


量的 session,查出当前 session 的当前 SQL 语句,或者:监控 WIN 平台 Oracle 的运行
5 : 如 何 跟 踪 某 个 session 的 SQL ? 答 : 先 找 出 对 应 的 'sid,serial' , 然 后 调 用
system_system.set_sql_trace_in_session(sid,serial,true);参考:跟踪某个会话

6:SQL 调整最关注的是什么?答:逻辑读。IO 量

7:说说你对索引的认识(索引的结构、对 dml 影响、对查询影响、为什么提高查询性能)答:默认的索引是


b-tree。
对 insert 的影响:分裂,要保证 tree 的平衡。
对 delete 的影响:删除行的时候要标记改节点为删除。
对 update 的影响:如果更新表中的索引字段,则要相应的更新索引中的键值。查询中包含索引字段的键值
和行的物理地址。

8:使用索引查询一定能提高查询的性能吗?为什么?答:不能。如果返回的行数目较大,使用全表扫描的
性能较好。

9:绑定变量是什么?绑定变量有什么优缺点?答:通俗的说,绑定变量就是变量的一个占位符,使用绑
定变量可以减少只有变量值不同的语句的解析。

10:如何稳定(固定)执行计划?答:使用 stored outline。

11 : 和 排 序 相 关 的 内 存 在 8i 和 9i 分 别 怎 样 调 整 , 临 时 表 空 间 的 作 用 是 什 么 ? 答 : 8i : 使 用
sort_area_size,hash_area_size,每个 session 分配相同的值,不管有无使用。
9i:使用 pga_aggregate 来统一管理。临时表空间的作用:
在 sort_area_size 中不能完成的部分在临时表空间完成,临时表空间在重建索引,创建临时表等都要用到。
还有 hash join 不能完成的也在临时表空间中做。

12:存在表 T(a,b,c,d),要根据字段 c 排序后取第 21—30 条记录显示,请给出 sqlselect a,b,c,d from (select


a,b,c,d from T order by c) where rownum<=30
minus
select a,b,c,d from (select a,b,c,d from T order by c) where rownum <=20;
或者:
select * from (select rownum rn,a.* from (select a,b,c,d from T order by c) a )where rn between 21 and 30;

二:数据库基本概念类
1:pctused and pctfree 表示什么含义有什么作用?答:表示数据块什么时候移入和移出 freelist。
pctused:如果数据块的使用率小于 pctused 的值,则该数据块重新加入到 fresslist 中。
pctfree:如果数据块的使用率高于 pctfree 的值,则该数据块从 freelist 中移出。

2:简单描述 table / segment / extent / block 之间的关系答:一个 table 至少是一个 segment,如果分区表,则


每个分区是一个 segment,table 可以看成是一个逻辑上的概念,segment 可以看成是这个逻辑概念的物理
实现;
segment 由一个或多个 extents 组成,segment 不可以跨表空间但可以跨数据文件;
extent 由多个连续的 blocks 组成,不可以跨数据文件;
block 由 1-多个 os 块组成,是 oracle i/o 的最小存储单位。
3:描述 tablespace 和 datafile 之间的关系答:tablespace 是逻辑上的概念,datafile 是物理上的概念。
一个 tablespace 可以由多个 datafile 组成,一个 datafile 不能跨越多个 tablespace。

4:本地管理表空间和字典管理表空间的特点,ASSM 有什么特点?答:一个使用 freelist 管理,一个使用


位图管理。

5:回滚段的作用是什么?答:保存数据的前像,保证数据读取的时间点一致性。Oracle 里数据的多版本特
性就是通过回滚段来实现的,正因为此,Oracle 数据库实现了读写不竞争的性能优势!

6:日志的作用是什么?答:记录对数据库的操作,便于恢复。

7: SGA 主要有那些部分,主要作用是什么?答:db_cache(缓存数据块),shared_pool(缓存 sql,执行计划,


数据字典信息等), large_pool(MTS 模式、parallel 、rman 等要用到),java pool(java 程序如 SQLJ 存储过程运
行时要用到)。

8: Oracle 系统进程主要有哪些,作用是什么?答:smon(合并空间,实例恢复),pmon(清理失败的进
程),归档进程(负责在日志切换的时候归档日志文件), lgmr(日志书写器进程,负责写日志),
ckpt(检查点进程,触发检查点),dbwr(数据库写入器,负责把数据写入导 datafile)。

三:备份恢复类
1:备份如何分类?答:逻辑备份(exp)与物理备份。或者冷备份与热备份。

2:归档是什么含义?答:把日志文件放到另一个地方。

3:如果一个表在 2004-08-04 10:30:00 被 drop,在有完善的归档和备份的情况下,如何恢复答:拷贝备份,


recover database until time 2004-08-04 10:30:00
alter database open resetlogs;

4:rman 是什么,有何特点?答:rman 叫恢复管理器。


特点很多。可以在线备份,到少目标数据库是 mount 状态。1)热备份。
2)可以存储脚本。
3)可以增量备份。
4)自动管理备份集。

5:standby 的特点答:利用 传输 重做日志来达到同步的目的。可以 设 定多个保 护级 别, 9i 后支持逻辑


standbyDB。

6:对于一个要求恢复时间比较短的系统(数据库 50G,每天归档 5G),你如何设计备份策略答:每天一个


全备份。

四:系统管理类
1:对于一个存在系统性能的系统,说出你的诊断处理思路答:做一个 statspack,根据 top 5,system
load,top sql 等来做相应的调整。
2:列举几种诊断 IO、CPU、性能状况的方法答:hp-unix:iostat -x 1 5;
top/vmstat/glance
3:对 statspack 有何认识?答:一个性能诊断工具而已,其本质就是在两个时间点采样两个系统数据。(动
态性能视图),然后根据两个 snapshot,产生一个报告。
4:如果系统现在需要在一个很大的表上创建一个索引,你会考虑那些因素,如何做以尽量减小对应用的
影响答:
1)增大 sort_area_size(8i)/pga_aggregate_target(9i)值。
2)如果表有分区(一般大表都要用到分区的),按分区逐个建索引,如果是本地索引的话。
3)系统空闲的时候建。
5:对 raid10 和 raid5 有何认识?答:raid10 是先镜像后条带,适合对写入速度要求较高的数据库系统,特
别是 online redolog 文件,raid5 适合大部分的数据库系统和数据仓库系统,读性能优于写性能。

五:综合随意类
1:你最擅长的是 oracle 哪部分?答:性能/sql 调优、备份恢复。
2:喜欢 oracle 吗?喜欢上论坛吗?或者偏好 oracle 的哪一部分?答:http://www.itpub.net/,Oracle 的 SQL
优化。
3:随意说说你觉得 oracle 最有意思的部分或者最困难的部分答:SQL 调优,最困难的是 Oracle 的网 管
理,Oracle 的 Connect Manager 没用过。
4:为何要选择做 DBA 呢?答:爱好加职业历史积累

You might also like