11月25日,大雾。

今天要去一个客户那里做数据库安全方面的培训和演示,为了尽早赶到,早上6点就“挣扎”着起了床。在去车站的路上,的哥聊到高速公路上又出了车祸,我心想,完了,今天这么大的雾,高速公路要关闭了。不幸的是,到了车站发现高速公路真的关闭了。不过只等了约一小时就又开放了。

赶到客户所在的城市,接上在另一个客户那里做完事的一位同事——老赵,我们一起到了客户那里,开了个简短的小会,吃过午饭,搭上环境,就开始了培训以及演示。这里不得不说的是,Oracle的Database Vault只能说是差强人意,演示中出现的几次状况,总是让我很尴尬。不过客户的技术人员没有因为出现的小问题而失去兴趣,反而是保持兴致,一直到21点才结束培训。

正当我和老赵准备回宾馆休息的时候,老赵的电话响了。他接过电话,没说上几句,脸色就凝重起来,我知道肯定是出事了。老赵放下电话,对我说,今天晚上要干通宵了。原来,之前他服务的那个客户,一个生产库上的用户被删除了,该用户下所有的对象都被删除了。

我们立即赶到现场,大致了解了一下事情的经过:一个开发人员,试图通过OEM(Oracle Enterprise Manager Console)连接到开发库上,删除一个用户。在删除时确认OEM上的连接字符串是正确的,然而很快发现,生产库的数据被删除了。万幸的是,删除数据的时候已经是下班时间,这个库上运行的业务并不是要求7*24的高可用性的;在发现数据被删除后,立即关闭了业务系统,甚至把数据库都关闭了;数据库是运行在归档模式下的,同时也使用第三方的软件将数据备份到磁带库上;DBA使用屏幕录像软件记录了删除数据的完整操作过程,这为事后检查事故原因提供了很大的帮助。

在了解大致的情况后,我们决定先恢复数据,然后再查找出现这个问题的原因。由于没有用于创建辅助数据库的备用主机,只能在数据库主机上进行恢复,为安全起见,没有使用rman,而全部使用手工操作。

下面简要列出整个恢复步骤:


1. 检查确认数据库和监听已经被关闭。

2. 客户的数据库DBA确认只须恢复被删除用户的数据,而不用包含其他用户的数据。

3. 启动数据库,取得数据库所有数据文件、日志文件、参数文件、控制文件的列表。并取得恢复所需要的表空间和数据文件。

4. 通过使用DBMS_LOGMNR,对归档日志进行检索,由DBA确认数据恢复的时间点。

5. 创建一个文件系统,用于存放辅助数据库的数据文件、控制文件等。这个新的文件系统为/backup/ bakora。

6. 使用spfile创建pfile文件,并修改生成的参数文件中的USER_DUMP_DEST、BACKGROUND_ DUMP_DEST、CONTROL_FILES等参数,并备份一个当前的控制文件到/backup/bakora/ctl.dbf。

7. 关闭生产数据库,为安全起见,将生产数据库的所有数据文件、控制文件、在线日志文件的属主全部改为root用户,以避免被意外覆盖。这些文件全部都是使用的裸设备。

8. 用新创建的pfile启动辅助数据库实例到NOMOUNT状态。用备份的控制文件恢复到CONTROL_FILES指向的目录/backup/bakora,然后启动辅助实例到MOUNT状态。

9. OFFLINE所有数据文件,改名需要用于恢复的数据文件,并ONLINE。

10. 使用第三方备份软件还原所有需要的数据文件到/backup/bakora目录。

11. 使用第三方备份软件还原所有需要的归档日志。

12. 在SQLPLUS中使用recover database命令执行恢复操作,具体命令为:recover databaseuntil time ‘xxxx-xx-xx xx:xx:xx’ using backupcontrolfile。这里“untl time”后为恢复的时间点。

13.恢复完成后,使用dd命令将原来的联机日志

复制到/backup/bakora目录。

14. 修改控制文件中联机日志文件名为/backup/ bakora目录下新的文件。

15. 使用resetlogs打开数据库(当然也可以使用read only方式打开数据库)。

16. 由数据库DBA确认所有需要的数据已经得到恢复。

17. 使用exp导出被删除用户的数据。

18. 关闭辅助数据库,还原生产库所有数据文件、日志文件、控制文件的属主和权限,启动生产数据库。

19. 将数据用imp导入生产数据库。

20. 启动数据库监听,开启应用,确认业务系统正常。这样终于在早上8点上班之前恢复了所有的数据。

以上就是进行数据恢复的大致步骤。虽然经过一天的工作,再熬夜进行恢复,实在是相当辛苦,还好现场是我与老赵两个人,一个人写好操作脚本,另一个人进行审核,在操作时也互相在旁边看着,这无疑大大减少了出错和误操作的可能。

不得不提到这次恢复中出现的状况。客户使用的第三方备份软件,是我以前从来没有见过的软件,或许是我孤陋寡闻,但接下来这个软件的表现实在让人大跌眼镜,这个软件居然不能还原指定的数据文件或表空间,客户请备份软件厂商的工程师远程解决未果,最后不得已只有全库还原。然而,数据库主机的磁盘阵列可用空间又太小,在进行还原时,对不需要的数据文件还要手动删除。这样一来,使得还原数据文件的时间,至少多了三分之二。我们只能大呼倒霉。

在数据恢复完成后,我们观看了数据库用户被删除时的屏幕录像,从录像中可以看到,操作时的确是连接到开发库的,为什么会删除了生产库上的用户呢?

原来进行删除操作的那台客户端机器运行的是Windows系统,在系统环境变量(我的电脑=>属性=>高级=>环境变量=>系统变量)中设置了TNS_ ADMIN,指向了另外的目录。现在,TNS_ADMIN指向的目录(下面简称TNS_ADMIN目录)和%ORACLE_HOME%\NETWORK\ADMIN(下面简称Oracle目录)下都有tnsnames.ora这个文件。在TNS_ADMIN中,tnsnames.ora有一tnsname指向生产库。在Oracle目录中,tnsnames.ora中有一同样名称的tnsname指向开发库。

OEM在处理TNS_ADMIN上是有问题的。OEM在启动后,左边的数据库目录树是从Oracle目录的tnsnames.ora中解析出来的,完全忽略了TNS_其不意ADMIN环境变量,即使是执行“将数据库添加到树”操作,也是完全忽略了TNS_ADMIN变量,而是将Oracle目录中的tnsnames.ora的项添加到树中。下面是两幅截图,如图3-1、图3-2所示:

TNS_ADMIN和OEM引起的血案_python

图3-1 选中名为XTY的数据库目录树截图

TNS_ADMIN和OEM引起的血案_数据库_02

图3-2 数据库XTY的一般信息截图

图3-1中显示的被选中的数据库为“XTY”,图3-2显示的是这个库的连接字符串。

然而,在用这个tnsname连接数据库时,却是按照TNS_ADMIN目录中的tnsnames.ora文件的配置进行连接的,如果这两个tnsnames.ora都有TNS Name,那么错误就发生了,本来我们期望是连接到OEM中显示的那个数据库上,结果却连接到了另一个库上。这可以是说OEM的重大Bug。

这里谈到的OEM是9i的版本,NetCA也有这个问题,但Net Manager没有这个问题。

事情虽然过去了,但是有很多值得我们深思的地方。软件不可避免地存在Bug,所以在出现问题后,一味抱怨软件的Bug无济于事。我们只有吸取教训,从管理上、技术手段上去防止此类问题的发生,才是有意义的。

很多朋友提出了各种不同的措施来避免此类问题的发生,归纳起来有:


1. 开发库和生产库、测试库的密码不能相同,特别是要严格保护生产库的密码,生产库的密码应该定期修改。

2. 开发环境不能有到生产主机的TnsName。

3. 通过IP地址来限制可以连接生产库的机器。

4. 对于一些重要的特别是危及数据安全的操作,应分步进行。比如删除用户(Drop User)时,应先Lock User;删除表空间时,先将表空间OFFLINE;删除表等其他对象时,先进行改名(rename)操作;完成这些操作后,观察一段时间确认没有问题后,再真正执行删除操作。

5. 通过使用触发器,来限制可以进行的ddl操作。

以上是从安全管理的角度来谈避免问题再次发生的。那么,笔者还须强调的是:

1. 有效的备份是在发生数据丢失和损坏后能够进行恢复的基础。每一个数据库DBA都应该切实关注备份问题。曾经有客户,虽然使用VERITAS进行数据库备份,然而在某次阵列出现问题不可用要进行恢复时才发现居然有部分数据文件没有备份,结果可想而知。

2. 进行恢复测试。恢复测试是数据备份恢复策略的重要组成部分。通过恢复测试,一方面检查备份是否有效,同时能够确认恢复流程是否正确、安全,恢复所需要的环境是否能够满足要求,恢复所需要的时间有多长,出现数据丢失或损坏后,能不能在预定的时间内完成恢复。