偷梁换柱:国产数据库如何防范误写入?

前几天和用户交流,提到了数据库中的一种情形,那就是,数据库如何防范数据库外部的改写或篡改。也就是说,如果操作系统管理员(SA),不经过数据库的SQL/DML操作,改写了数据,数据库有办法检测到吗?

8624f5e9c9a0b793546ba9832b5d7d3a.png

我们先说结论:

1. 外部数据篡改、侵入的情况确实可能存在(后面我举例说明),通过OS级别的数据块替换,可以改写数据(更不要说类似BBED的工具);

2. 大部分自研的国产数据库,还做不到精确校验和防范这类情况,我测试过几种,大家可以针对性的测试一下。由开源演进而来的国产数据库,具备一定的写丢失安全检测能力。

3. Oracle在 12.2 版本开始实现了严谨的写丢失检测机制。

今天的数据库体系已经非常庞大,所以斯通布雷克说的:站在前人的肩膀上,而不是站在他们的脚趾上。这句话很有一些道理。在开源基础上演进,就能够集成其持续积累,而从头写作一个数据库,所有的工作都需要重来一次,任重而道尤远。

【外部写损坏案例】

关于数据库的文件损坏,我曾经遇到这样一个案例。在帮助用户恢复数据库时,发现了罕见的归档日志损坏。如下操作过程,在进行归档recover时,数据库报错,提示归档日志损坏: 

***
Corrupt block seq: 37288 blocknum=1.
Bad header found during deleting archived log
Data in bad block - seq:810559520. bno:170473264. time:707406346
beg:21280 cks:21061
calculated check value: 9226
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
Reread of seq=37288, blocknum=1, file=/ARCH/arch_1_37288_632509987.dbf, found same corrupt data
***

信息比较详细,说37288号归档日志Header损坏,无法读取数据。提一个小问题:如果你遇到了这样的错误?会怎样思考和处置?

现在需要分析:为什么日志会损坏?是如何损坏的?

我首先要做的就是,看看日志文件的内容,通过最简单的命令将日志文件中的内容输出出来:

strings arch_1_37288_632509987.dbf > log.txt

然后检查生成的这个日志文件,我就发现了问题。在这个归档日志文件中,被写入了大量的跟踪文件内容,其中开头部分就是一个跟踪文件的全部信息。

这是一种我从来没有遇到过的现象,也就是说,当数据库调用操作系统I/O子命令在写出跟踪文件时,错误的覆盖掉了已经存在的归档文件,最后导致归档日志损坏,非常奇妙,从所未见。

这个故障应当是操作系统在写出时出现了问题,存在文件的空间仍然被认为是可写的,这样就导致了写冲突,出现这类问题,应当立即检查硬件,看看是否是硬件问题导致了如此严重的异常。这就是静默错误的一种情况,对现有文件写入了错误的数据,并且没有发出任何错误预警:

Dump file /ADMIN/bdump/erp_p007_19216.trc
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Production
With the Partitioning, OLAP and Data Mining options
ORACLE_HOME = /DBMS/erp/erpdb/10g
Linux
2.6.9-34.ELhugemem
i686
Instance name: erp
Redo thread mounted by this instance: 1
Oracle process number: 22
Unix process pid: 19216, image: oracle@eygle.com (P007)
KCRP: blocks claimed = 61, eliminated = 0
----- Recovery Hash Table Statistics ---------
Hash table buckets = 32768
Longest hash chain = 1
Average hash chain = 61/61 = 1.0
Max compares per lookup = 0
Avg compares per lookup = 0/61 = 0.0
----------------------------------------------
----- Recovery Hash Table Statistics ---------
Hash table buckets = 32768
Longest hash chain = 1
Average hash chain = 61/61 = 1.0
Max compares per lookup = 1
Avg compares per lookup = 1426/1426 = 1.0
----------------------------------------------
\GPAYMENTdxn
AP_CHECKS
PaymentsN
a'VND
Userxn
AP_INVOICE_PAYMENTS
105273
5406105305-20101020-003
3001CASH CLEARING
CREATED

类似的故障肯定并不少见,很多时候可能我们并未追根究底,这更说明进行多角度的数据损坏防范就变得非常重要。针对数据文件,在 Oracle 12c中,通过 ASM 实现的 ASMFD特性,Oracle 可以将外部写错误完全隔绝。这是一种更全面的保护。

【写丢失保护特性】

在Oracle 11g中,引入了DB_LOST_WRITE_PROTECT特性,通过这个参数可以启用或禁用丢失的写入检测。当I/O子系统确认块写入完成,而实际上写入没有或错误的发生在持久存储中,就意味着发生了数据块写入丢失,这个特性用于检测这种情况。

参数 DB_LOST_WRITE_PROTECT 有三个选项设置,分别是NONE、TYPICAL、FULL。

l当主数据库上的参数设置为TYPICAL时,实例会在重做日志中记录读写(Read Write)表空间的Buffer Cache读取信息,这对于检测丢失的写入是必需的。

l当主数据库上的参数设置为FULL时,实例日志将为只读(Read-Only)表空间以及读写(Read-Write)表空间产生额外记录。

lTYPICAL模式的性能开销约为5到10%,对于FULL模式可能更高。

与Data Guard一起使用时,丢失写入检测最有效。在这种情况下在主数据库和备用数据库中都设置了DB_LOST_WRITE_PROTECT。当备用数据库在托管恢复(Managed recovery)期间应用重做时,它会读取相应的块并将SCN与重做日志中的SCN进行比较。如果主数据库上的块SCN低于备用数据库上的块SCN,则它会检测主数据库上的写入丢失并引发外部错误(ORA-752)。如果SCN较高,则会检测到备用数据库上的写入丢失并引发内部错误(ORA-600 [3020])。在任何一种情况下,备用数据库都会在警报日志和跟踪文件中写入失败的原因。

要修复主数据库上的丢失写入,可以通过备库的Failover,或者通过备库获取完整的数据文件进行恢复;要修复备用数据库上的丢失写入,必须重新创建整个备用数据库或还原受影响文件的备份。

即便不使用Data Guard时,启用丢失写入检测也很有用。在这种情况下,可能在两种情形遇到丢失写入:在正常数据库操作期间在介质恢复期间。在第一种情况下,没有直接的方法来检测错误,但是可以通过将备份还原到相应的疑点位置进行判断,将数据库或表空间恢复到相应的SCN,就会触发丢失写入错误(ORA-752)。如果在介质恢复期间遇到丢失的写入错误,则需要使用RESETLOGS选项打开数据库,数据库是已知的,但是会导致Resetlogs SCN之后的数据被抛弃;如果数据库的备份能追溯到完好的备份,则可以执行顺序的恢复,但是如果备份(数据文件和归档日志)都发生在写丢失之后,则意味着写丢失无法挽回。

通过以下测试,来验证一下数据库在这一特性之下的表征。我们在主库和备库同时设置

SQL> select banner from v$version;
BANNER
---------------------------------------------------------------
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production
SQL> alter system set db_lost_write_protect='TYPICAL';
System altered.

备库设置:

SQL> select open_mode from v$database;
OPEN_MODE
--------------------
READ ONLY WITH APPLY
SQL> alter system set db_lost_write_protect='TYPICAL';
System altered.

来看一个测试:

SQL> create tablespace lostwrite datafile size 10M;
Tablespace created.
SQL> create user enmo identified by enmo default tablespace lostwrite;
User created.
SQL> grant connect,resource,dba to enmo;
Grant succeeded.
SQL> connect enmo/enmo
SQL> create table enmotech (id number);
Table created.
SQL> insert into enmotech values(200);
1 row created.
SQL> commit;
Commit complete.
SQL> alter system flush buffer_cache;
System altered.
SQL> alter system switch logfile;
System altered.

此时备份数据文件,此时的数据文件中,包含了 200 的数据:

[oracle@DEVDB datafile]$ cp o1_mf_lostwrit_g47tb4vg_.dbf o1_mf_lostwrit_g47tb4vg_.dbf.old

然后更新信息:

SQL> update enmotech set id=888;
1 row updated.
SQL> commit;
Commit complete.
SQL> select * from enmotech;
ID
----------
888
SQL> alter system flush buffer_cache;
System altered.

此时备库已经能够读取更新后的数据:

SQL> select * from enmo.enmotech;
ID
----------
888

注意此时在主库,用备份文件覆盖原始文件(也可以通过dd只覆盖数据块部分):

[oracle@DEVDB datafile]$ cp o1_mf_lostwrit_g47tb4vg_.dbf.old o1_mf_lostwrit_g47tb4vg_.dbf

主库执行查询,返回值变成了200,这就模拟了一次写丢失:

SQL> select * from enmotech;
ID
----------
200

现在来检查一下主备库的告警日志,看看数据库发现了什么,以下是主库的信息,一切正常没有任何错误:

2019-01-20T11:21:40.836426+08:00
create tablespace lostwrite datafile size 10M
2019-01-20T11:21:41.725876+08:00
Control autobackup written to DISK device
handle '/fast_recovery_area/DB18C/autobackup/2019_01_20/o1_mf_s_998047301_g47tb5mb_.bkp'
Completed: create tablespace lostwrite datafile size 10M
2019-01-20T11:23:30.535149+08:00
ALTER SYSTEM: Flushing buffer cache inst=0 container=0 global
2019-01-20T11:23:44.006236+08:00
Thread 1 advanced to log sequence 1722 (LGWR switch)
Current log# 1 seq# 1722 mem# 0: /DB18C/onlinelog/o1_mf_1_f9ovsb94_.log
2019-01-20T11:23:44.087462+08:00
ARC1 (PID:23353): Archived Log entry 153 added for T-1.S-1721 ID 0x3926be67 LAD:1
2019-01-20T11:23:44.125533+08:00
TT03 (PID:7880): SRL selected for T-1.S-1722 for LAD:2
2019-01-20T11:25:39.365092+08:00
ALTER SYSTEM: Flushing buffer cache inst=0 container=0 global

接下来看看备库信息,备库已经抛出了异常,提示主库发生了写丢失,停止了数据库恢复,请求人工介入处理:

2019-01-20T11:26:03.408260+08:00
Errors in file /db18c_s/DB18C_S/trace/DB18C_S_mz00_28990.trc:
ORA-00312: online log 2 thread 1: '/DB18C/onlinelog/o1_mf_2_f9ovsbj1_.log'
ORA-27037: unable to obtain file status
Linux-x86_64 Error: 2: No such file or directory
Additional information: 7
Standby redo application has detected that the primary database lost a disk write.
No redo at or after SCN 0x0000000015734233 can be used for recovery.
BLOCK THAT LOST WRITE 133, FILE 2, TABLESPACE# 7
The block read during the normal successful database operation had SCN 359874679 (0x0000000015734077) seq 1 (0x01)
ERROR: ORA-00752 detected lost write on primary
Slave exiting with ORA-752 exception
2019-01-20T11:26:03.555719+08:00
Recovery Slave PR02 previously exited with exception 752
2019-01-20T11:26:04.501889+08:00
Background Media Recovery process shutdown (DB18C_S)

这就是Oracle数据库在防止数据损坏上不断的改进。当备库检测到错误之后,会停止恢复,直到问题解决:

2019-01-20T11:26:04.442236+08:00
PR00 (PID:28727): MRP0: Background Media Recovery terminated with error 448
2019-01-20T11:26:04.442855+08:00
Errors in file /db18c_s/DB18C_S/trace/DB18C_S_pr00_28727.trc:
ORA-00448: normal completion of background process
PR00 (PID:28727): Managed Standby Recovery not using Real Time Apply
Recovery interrupted!

主库的错误检测在两天后发出警告,提示这个数据文件属于旧版本,出现了介质失败:

2019-01-22T13:22:27.415907+08:00
Errors in file /db18c/DB18C/trace/DB18C_ckpt_20835.trc:
ORA-63999: data file suffered media failure
ORA-01122: database file 2 failed verification check
ORA-01110: data file 2: '/DB18C/datafile/o1_mf_lostwrit_g47tb4vg_.dbf'
ORA-01208: data file is an old version - not accessing current version

在主库执行恢复时,会抛出异常,提示写丢失错误被检测到:

SQL> recover datafile 2;
ORA-00279: change 359874547 generated at01/20/2019 11:21:40 needed for thread 1
ORA-00289: suggestion : /DB18C/archivelog/2019_01_20/o1_mf_1_1721_g47tg02b_.arc
ORA-00280: change 359874547 for thread 1 is in sequence #1721
ORA-00283: recovery session canceled due to errors
ORA-00752: recovery detected a lost write of a data block
ORA-10567: Redo is inconsistent with data block (file# 2, block# 133, file offset is 1089536 bytes)
ORA-10564: tablespace LOSTWRITE
ORA-01110: data file 2: '/DB18C/datafile/o1_mf_lostwrit_g47tb4vg_.dbf'
ORA-10561: block type 'TRANSACTION MANAGED DATA BLOCK', data object# 186211

此时可以在主库复制备库的数据文件,关闭写丢失检测(否则无法执行不一致数据文件的恢复),然后执行恢复:

SQL> alter system set db_lost_write_protect=none;
System altered.
RMAN> recover datafile 2;
Starting recover at2019-01-22 13:47:50
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=151 device type=DISK
starting media recovery
archived log for thread 1 with sequence 1722 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1722_g4fb3ghj_.arc
archived log for thread 1 with sequence 1723 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1723_g4fb442h_.arc
archived log for thread 1 with sequence 1724 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1724_g4fbdlcq_.arc
archived log for thread 1 with sequence 1725 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1725_g4fbg000_.arc
archived log for thread 1 with sequence 1726 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1726_g4fbh4wv_.arc
archived log for thread 1 with sequence 1727 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1727_g4fbj7hc_.arc
archived log for thread 1 with sequence 1728 is already on disk as file /archivelog/2019_01_22/o1_mf_1_1728_g4fcmbvx_.arc
archived log file name=/archivelog/2019_01_22/o1_mf_1_1722_g4fb3ghj_.arc thread=1 sequence=1722
archived log file name=/archivelog/2019_01_22/o1_mf_1_1723_g4fb442h_.arc thread=1 sequence=1723
archived log file name=/archivelog/2019_01_22/o1_mf_1_1724_g4fbdlcq_.arc thread=1 sequence=1724
archived log file name=/archivelog/2019_01_22/o1_mf_1_1725_g4fbg000_.arc thread=1 sequence=1725
media recovery complete, elapsed time: 00:00:05
Finished recover at2019-01-22 13:47:57
RMAN> alter database datafile 2 online;
Statement processed

这就是这个丢失检测验证的完整过程,也就是Oracle实现写丢失检测的方法。

【Oracle 12.2 版本的写丢失检测增强】

在 Oracle 12c Release 2中引入了一项新功能Shadow Lost Write Protection,它可以在主数据库中进行丢失写检测,而无需Oracle Data Guard备用数据库。即使配置了Dataguard备用数据库,此功能也非常有价值,任何SELECT / DML都会在读取写丢失物理块时触发错误并记录。这更有利于指导DBA通过介质恢复来恢复块(或它的数据文件/表空间)。

在上一个测试的基础上,再继续深入。首先创建一个用于记录块信息的表空间,这个表空间的类型必须是BIGFILE表空间:

SQL> create bigfile tablespace shadow_tbs datafile '/DB18C/datafile/shadow_tbs.dbf' size 200M lost write protection;
Tablespace created.
SQL> select CONTENTS from DBA_TABLESPACES where TABLESPACE_NAME='SHADOW_TBS';
CONTENTS
---------------------
LOST WRITE PROTECTION

然后需要在数据库级别启用丢失写检测,并且在需要保护的数据表空间启用监控:

SQL> alter database enable lost write protection;
Database altered.
SQL> alter tablespace lostwrite enable lost write protection;
Tablespace altered.
SQL> select * from database_properties where property_name ='NEW_LOST_WRITE';
PROPERTY_NAME      PROPERTY_V  DESCRIPTION
-------------------- ---------- ------------------------------
NEW_LOST_WRITE      TRUE       new lost write protection
SQL> select LOST_WRITE_PROTECT from DBA_TABLESPACES where TABLESPACE_NAME='LOSTWRITE';
LOST_WR
-------
ENABLED

首先确认当前数据存储的数据块,表中有四条数据:

SQL> create index idxid on enmotech(id);
Index created.
SQL> select DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid) BLOCK#, DBMS_ROWID.ROWID_RELATIVE_FNO(rowid) RFILE#,a.* from enmo.enmotech a;
BLOCK#     RFILE#       ID
---------- ---------- ----------
132     2      999
133     2      888
133     2      888
134     2      666

分别对这几个数据块进行备份,以下是备份132号数据块(注意,由于操作系统保留一个数据块,所以dd时跳过132个数据块):

[oracle@DEVDB datafile]$ dd if=o1_mf_lostwrit_g47tb4vg_.dbf bs=8192 count=1 skip=132 of=bakblk132.dmp
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 0.000160311 s, 51.1 MB/s

然后进行数据修改,提交:

SQL> insert into enmotech values(777);
1 row created.
SQL> commit;
Commit complete.
SQL> select DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid) BLOCK#, DBMS_ROWID.ROWID_RELATIVE_FNO(rowid) RFILE#,a.* from enmo.enmotech a;
BLOCK#     RFILE#       ID
---------- ---------- ----------
132     2      999
132     2      777
133     2      888
133     2      888
134     2      666

现在可以关闭数据库,恢复备份数据块:

[oracle@DEVDB datafile]$ dd of=o1_mf_lostwrit_g47tb4vg_.dbf bs=8192 count=1 seek=132 if=bakblk132.dmp conv=notrunc
1+0 records in
1+0 records out
8192 bytes (8.2 kB) copied, 0.000212014 s, 38.6 MB/s

注意,这一步是关键,相当于偷梁换柱,将原有的一个Block替换掉,这是通过操作系统完成的,未经过数据库内部的DML修改操作。

4ca41adc8869100b1628797f40ee3911.png

现在启动数据库,进行查询,数据库立刻检测到了写丢失:

SQL> connect enmo/enmo
Connected.
SQL> select * from enmotech;
select * from enmotech
*
ERROR at line 1:
ORA-65478: shadow lost write protection - found lost write

告警日志中也会记录:

2019-02-10T22:25:14.776576+08:00
ERROR - I/O type:buffered I/O found lost write in block with file#:2 rdba:0x800084, Expected SCN:0x0000000015bd951a SCN in block:0x0000000015bd84e6, approx current SCN:0x0000000015bdab52, RAC instance:1 pdb:0

如果此时禁用写丢失保护,查询可以执行,输出了4条记录,值为『777』的数据丢失了:

SQL> alter database disable lost write protection;
Database altered.
SQL> select * from enmotech;
ID
----------
999
888
888
666

但是由于之前创建了索引,使用索引的查询,仍然能够输出这个丢失的信息,事实上索引和数据已经不一致了:

SQL> select * from enmotech where id=777;
ID
----------
777
Execution Plan
----------------------------------------------------------
Plan hash value: 3880444692
--------------------------------------------------------------------------
| Id | Operation  | Name  | Rows  | Bytes | Cost (%CPU)| Time  |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT |  |     1 |     4 |     1   (0)| 00:00:01 |
|* 1 |  INDEX RANGE SCAN| IDXID |     1 |     4 |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------

Oracle的这个特性进一步的增强了Oracle数据库的写丢失预防功能,当及时检测到问题后,可以通过块恢复(block recovery)进行数据块级别的修复,快速消除写丢失的影响

【小结】

今天,国产数据库时代已经到来,大量国产数据库产品正在不断增强其易用性和安全性,而在这些方面,经过广泛应用考验的传统数据库就成为了很好的学习对象。

Oracle数据库从控制文件校验、数据块校验,再到专用的写丢失特性校验,已经做出了大量探索,这些探索值得我们去学习、验证、借鉴。


云和恩墨大讲堂 | 一个分享交流的地方 

长按,识别二维码,加入万人交流社群

请备注:云和恩墨大讲堂

  点个“在看” 

你的喜欢会被看到❤

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值