一万字,浅谈Oracle数据库truncate原理,无备份,如何恢复!

图片.png

一:truncate table原理说明
二:truncate table恢复思路
三:Oracle ASSM管理,L3、L2、L1块介绍
四:truncate table恢复测试
五:truncate table恢复的其他方法

一:truncate table原理说明

参考文章:《Oracle TRUNCATE TABLE原理解析》
链接:

https://www.modb.pro/db/156582

Oracle数据库,truncate table时只产生非常少的redo和undo,就实现了清空表数据并降低表HWM的功能。可以通过10046以及redo dump去分析truncate的整个操作过程,详细过程可以参考上面的链接,本文直接引用上面文章的结论:

truncate table操作时,实际上自动完成以下操作:
对基表的修改主要是:

(1)修改obj$,tab$的dataobj#;
(2)修改seg$的对应信息如(extents,blocks,hwmincr等等);
(3)删除tab_stats$对应对象的统计信息;

对于L3位图块(segment header):

(1)修改块的dataobj#;
(2)修改LHWM和HHWM;
(3)修改extent map、aux map以及extents个数;

对于L2位图块:

(1)删除L1 ranges;
(2)修改L2块的dataobj#;

对于第一个L1位图块:

(1)修改第一个L1块的dataobj#;
(2)set hwm为ext#为0的第3+1个块(即段头块+1);

对于HWM block所属的L1位图块:

(1)clear HWM flag;

可以看到,Oracle truncate table的实质是在不修改数据块的情况下,通过修改segment header的data_object_id,hwm,extent map,aux map等信息来实现清空表的目的,其中还涉及数据字典基表以及L1、L2位图块的修改,truncate操作存储数据的数据块没有产生任何redo和undo,但是segment header,位图块,数据字典基表还是会产生redo和undo。

二:truncate table恢复思路

通过上面的truncate table原理可以知道,实际上并没有删除数据块里的数据,如果truncate table后,对应数据块没有数据写入(可以将对应的表空间改成只读),理论上可以找回数据,具体步骤入职:
对基表的修改,改回原值:

(1)修改obj$,tab$的dataobj#;
(2)修改seg$的对应信息如(extents,blocks,hwmincr ...);

对于L3位图块(segment header),改回原值:

(1)修改块的dataobj#;
(2)修改LHWM和HHWM;
(3)修改extent map、aux map以及extents个数;

对于L2位图块,改回原值:

(1)修改L2块的dataobj#;

对于第一个L1位图块,改回原值:

(1)修改第一个L1块的dataobj#;
(2)set hwm为ext#为0的第3+1个块(即段头块+1);

而改回原值的信息,可以通过dump对应数据块、redo log file(或archive log file)获得。

三:Oracle ASSM管理,L3、L2、L1块介绍

什么是L1,L2,L3位图块?什么又是hwm,extent map,aux map …?
参考书籍:《Oracle内核技术解密》-吕海波
参考文章:《Oracle官方文档》

链接:

https://docs.oracle.com/cd/E11882_01/server.112/e40540/logical.htm#CNCPT301

Oracle数据库为数据库中的所有数据分配逻辑空间。数据库空间分配的逻辑单元是data blocks, extents, segments和tablespace。在物理层面,数据存储在磁盘上的数据文件中。数据文件中的数据存储在操作系统块中。
图片.png
先看看表空间管理的两种方式:

Logical Space Management

Oracle数据库使用Logical Space Management来跟踪和分配表空间中的extents。当数据库对象需要一个extents时,数据库必须有一种查找和分配它的方法。同样,当对象不再需要一个extents时,数据库也必须有一个使空闲extents可用的方法。

Oracle创建表空间时,可以选择两种类型,默认是Locally managed tablespaces:

(1)Locally managed tablespaces (default)
数据库使用表空间本身中的位图来管理数据块。因此,本地管理的表空间有一部分表空间专门用于位图。
在表空间中,数据库可以通过自动段空间管理(ASSM)或手动段空间管理来管理段(MSSM)。

(2)Dictionary-managed tablespaces
数据库使用数据字典来管理extents。
图片.png
Dictionary-Managed Tablespaces

字典管理的表空间使用数据字典来管理其范围。每当分配或释放数据块以供重用时,Oracle数据库都会更新数据字典中的表。例如,当一个表需要一个区段时,数据库会查询数据字典表,并搜索空闲区段。如果数据库找到空间,那么它会修改一个数据字典表,并将一行插入到另一个表中。通过这种方式,数据库通过修改和移动数据来管理空间。

数据库在后台执行以获取数据库对象空间的SQL是递归SQL。频繁使用递归SQL会对性能产生负面影响,因为数据字典的更新必须序列化。本地管理的表空间是默认的,可以避免这种性能问题。

Locally Managed Tablespaces
本地化管理的表空间
本地管理的表空间在数据文件头中维护一个位图,以跟踪数据文件体中的可用和已用空间。每个比特对应一组块。当分配或释放空间时,Oracle数据库会更改位图值以反映块的新状态。
下图是位图管理存储的概念表示。标头中的1表示已用空间,而0表示可用空间。
图片.png

段空间的管理:
段空间管理是从包含段的表空间继承的属性。在本地管理的表空间中,数据库可以自动或手动管理段。例如,表空间用户中的段可以自动管理(ASSM),而表空间工具中的段则可以手动管理(MSSM)。
1.自动分段空间管理(ASSM)
Automatic Segment Space Management
ASSM方法使用位图来管理空间。位图具有以下优势:
(1)简化管理:
ASSM避免了手动确定许多存储参数的正确设置的需要。只有一个关键的SQL参数控制空间分配:PCTFREE。此参数指定块中为将来更新保留的空间百分比。
(2)提高并发性:
多个事务可以搜索单独的空闲数据块列表,从而减少争用和等待。对于许多标准工作负载,使用ASSM的应用程序性能优于使用MSSM的经过良好调优的应用程序的性能。

2.手动分段空间管理
Manual Segment Space Management
传统的MSSM方法使用一个称为空闲列表的链表来管理段中的空闲空间。对于具有可用空间的数据库对象,可用列表跟踪高水位线(HWM)下的块,HWM是已使用和尚未使用的段空间之间的分界线。当使用块时,数据库会根据需要将块放在空闲列表上或从空闲列表中删除块。
除了PCTFREE,MSSM还要求您使用SQL参数(如PCTUSED、FREELIST和FREELIST GROUP)控制空间分配。PCTUSED设置当前使用的块中必须存在的可用空间百分比,以便数据库将其放入可用列表。例如,如果在CREATE TABLE语句中将PCTUSED设置为40,则在块空间使用率低于40%之前,无法将行插入到段中的块中。
作为示例,假设您在表中插入一行。数据库检查表中第一个可用块的空闲列表。如果该行无法放入块中,并且块中已使用的空间大于或等于PCTUSED,则数据库会从列表中删除该块并搜索另一个块。如果从块中删除行,则数据库会检查块中已使用的空间现在是否小于PCTUSED。如果是这样,则数据库将块放置在空闲列表的开头。
一个对象可能有多个自由列表。这样,对表执行DML的多个会话可以使用不同的列表,从而减少争用。每个数据库会话在其会话期间只使用一个空闲列表。
图片.png

位图块的作用:
记录表空间中区的分配情况。位图块中每一个二进制位对应一个区是否被分配给某个表、索引等对象。
如果第一个二进制位为0说明表空间中第一个区未分配,如果为1说明已分配;第二个二进制位对应第二个区,以此类推。
位图块又分为两部分,其中第一个位图块又被当作位图段头(Bitmapped File Space Header),1号块是文件头,2号块是位图头,在Oracle 10g中,3到8号块是位图数据块,共6个位图块,如果块大小是8KB,一个是48KB字节,每个字节8个二进制位,一共393216个二进制位。
每个二进制对应一个区,一共393216个区,在Oracle 11g更多。

Oracle ASSM管理,L3、L2、L1块介绍:
图片.png
ASSM的整体结构是3层位图块+数据库,即4层树状结构。

第一层位图块称为L3块,一个L3块中可以存放多个L2块地址,一个L2块可以存放多个L1块,一个L1块中可以存放多个数据块地址。

Oracle如何使用4层树状结构(3层位图块+数据块)来确定向哪个块中插入数据?

1.查找数据字典(dba_segments对应的基表),确定段头位置。
2.在段头中找到第一个L2块位置信息;
3.在L2块中根据执行插入操作进程的PID号,做HASH运算,得到一个随机数N,在L2中,找到第N个L1块的位置信息;
4.到第3步中确定的L1块中,再根据执行插入操作进程PID号,做HASH运算,得到一个随机数M,在L1中找到第M号的数据库;
5.向第M号数据块中插入。

四:truncate table恢复测试

参考文章:

《bbed修复truncate表(未被覆盖)》
https://blog.youkuaiyun.com/u014596132/article/details/135858545
《bbed-truncate表恢复》
https://blog.youkuaiyun.com/renyanjie123/article/details/122929522
《Oracle TRUNCATE TABLE原理解析》
https://www.modb.pro/db/156582
《揭秘Oracle数据库truncate原理》
https://blog.youkuaiyun.com/weixin_30907935/article/details/96765216

注意:仅供参考,请勿用于生产环境!
数据库版本:Oracle 11.2.0.4.0

创建测试数据

[oracle@cjc-db-01 ~]$ sqlplus cjc/******
create table t1123(id int,name varchar(10),adate varchar(20));
insert into t1123 values(1,'chen','20241123');
insert into t1123 values(2,'ju','20241124');
insert into t1123 values(3,'chao','20241125');
insert into t1123 values(4,'yyy','20241126');
insert into t1123 values(5,'zzz','20241127');
commit;

查看段头块

SQL> select HEADER_FILE,HEADER_BLOCK from dba_segments where owner='CJC' and segment_name='T1123';
HEADER_FILE HEADER_BLOCK
----------- ------------
   5      282

查看 extent_id

SQL> select extent_id,file_id,block_id,blocks from dba_extents where segment_name='T1123' and owner='CJC';
 EXTENT_ID    FILE_ID	BLOCK_ID     BLOCKS
---------- ---------- ---------- ----------
  0     5      280   8

查看数据所在数据块

SQL> select dbms_rowid.rowid_relative_fno(rowid) file#,dbms_rowid.rowid_block_number(rowid) block#,id,name,adate from cjc.t1123;
     FILE#     BLOCK#       ID NAME     ADATE
---------- ---------- ---------- ---------- --------------------
  5   287        1 chen     20241123
  5   287        2 ju     20241124
  5   287        3 chao     20241125
  5   287        4 yyy     20241126
  5   287        5 zzz     20241127

通过bbed dump功能,分别查看当前CJC.T1123表对应L3、L2、L1块信息:

已知 offset 信息如下:

offset 36:  total extents
offset 40:  total blocks
offset 48:  HWM所在的ext#
offset 52:  HWM所在的ext#的第几个block(从0开始)
offset 56:  HWM所在的ext#的extblocks
offset 60:  HWM所在的dba地址
offset 76:  HWM下有多少个block
offset 92:  LHWM所在的ext#
offset 96:  LHWM所在的ext#的第几个block(从0开始)
offset 100: LHWM所在的ext#的exttsize
offset 104: LHWM所在的dba地址
offset 2736:aux map信息,ext#为0的L1 dba
offset 2740:aux map信息,ext#为0的data dba
offset 2744:aux map信息,ext#为1的L1 dba
offset 2748:aux map信息,ext#为1的data dba
offset 5192:Second Level Bitmap block DBAs
[oracle@cjc-db-01 trace]$ bbed
BBED: Release 2.0.0.0.0 - Limited Production on Sat Nov 23 15:38:52 2024
Copyright (c) 1982, 2011, Oracle and/or its affiliates.  All rights reserved.
************* !!! For Oracle Internal Use only !!! ***************
BBED> set file 5 block 282
	FILE#          	5
	BLOCK#          282

段头块的第一个offset是23

BBED> dump /v offset 0 count 20
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282     Offsets:    0 to   19  Dba:0x0140011a
-------------------------------------------------------
 23a20000 1a014001 42390f00 00000304 l #.....@.B9......
 e6fe0000                            l ....
 <16 bytes per line>

段头块指向的L2位图块在offset 5192的位置

BBED> dump /v offset 5192 count 100
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282     Offsets: 5192 to 5291  Dba:0x0140011a
-------------------------------------------------------
 19014001 00000000 00000000 00000000 l ..@.............
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000                            l ....
 <16 bytes per line>

可以看到L2位图块是19014001,反序后是01400119,这里只有1个L2块,转换为对应的文件号和块号。

L2块:fileno=5,blockno=281。

select 
dbms_utility.data_block_address_file(to_number('01400119','xxxxxxxx')) as fileno,
dbms_utility.data_block_address_block(to_number('01400119','xxxxxxxx')) as blockno
from dual;
    FILENO    BLOCKNO
---------- ----------
  5   281

L2的第一个offset是21

BBED> set file 5 block 281
	FILE#          	5
	BLOCK#          281

BBED> dump /v offset 0 count 20
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 281     Offsets:    0 to   19  Dba:0x01400119
-------------------------------------------------------
 21a20000 19014001 3c390f00 00000204 l !.....@.<9......
 12d20000                            l ....
 <16 bytes per line>

L2指向L1数据块的位置从offset 116开始

BBED> dump /v offset 116 count 100
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 281     Offsets:  116 to  215  Dba:0x01400119
-------------------------------------------------------
 18014001 05000100 00000000 00000000 l ..@.............
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000                            l ....
 <16 bytes per line>

其中:18014001 反序 01400118,查询对应的数据块号;

L1块:fileno=5,blockno=280。

select 
dbms_utility.data_block_address_file(to_number('01400118','xxxxxxxx')) as fileno,
dbms_utility.data_block_address_block(to_number('01400118','xxxxxxxx')) as blockno 
from dual;
    FILENO    BLOCKNO
---------- ----------
  5   280

查看L1位图块指向的数据块:

BBED> dump /v offset 0 count 10
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 280     Offsets:    0 to    9  Dba:0x01400118
-------------------------------------------------------
 20a20000 18014001 4239              l  .....@.B9
 <16 bytes per line>

L1的第一个offset是20,L1指向数据块的位置从offset 204开始

BBED> dump /v offset 204 count 100
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 280     Offsets:  204 to  303  Dba:0x01400118
-------------------------------------------------------
 18014001 08000000 00000000 00000000 l ..@.............
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000 00000000 00000000 00000000 l ................
 00000000                            l ....
 <16 bytes per line>

18014001 表示 file 5 block 280,08000000 表示 这个块后面的连续7个块,也就是数据块号是287。

总结:
L3块(段头块):282 L2块:281 L1块:280 数据块:287

可以 dump L3,L2,L1块,有更多的信息:
L3块(段头块):282

alter system dump datafile 5 block 282;
select value from v$diag_info where name ='Default Trace File';
VALUE
--------------------------------------------------------------------------------
/oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_13608.trc
SQL> ho cp /oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_13608.trc /home/oracle/trace

L2块:281

alter system dump datafile 5 block 281;
---/oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_15178.trc

在trace里可以看到 Dump of Second Level Bitmap Block 信息。

L1块:280

alter system dump datafile 5 block 280;
---/oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_15268.trc

在trace里可以看到 Dump of First Level Bitmap Block 信息。

数据块:287

alter system dump datafile 5 block 287;
---/oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_15289.trc

ho cp /oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_15178.trc /home/oracle/trace
ho cp /oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_15268.trc /home/oracle/trace
ho cp /oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_15289.trc /home/oracle/trace

继续参看 HWM

BBED> dump /v offset 48 count 16
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282     Offsets:   48 to   63  Dba:0x0140011a
-------------------------------------------------------
 00000000 08000000 08000000 20014001 l ............ .@.
 <16 bytes per line>

查看 LHWM

BBED> dump /v offset 92 count 16
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282     Offsets:   92 to  107  Dba:0x0140011a
-------------------------------------------------------
 00000000 08000000 08000000 20014001 l ............ .@.
 <16 bytes per line>

HWM 和 LHWM 都是 20014001,反序后为 01400120

select 
dbms_utility.data_block_address_file(to_number('01400120','xxxxxxxx')) as fileno,
dbms_utility.data_block_address_block(to_number('01400120','xxxxxxxx')) as blockno 
from dual;
    FILENO    BLOCKNO
---------- ----------
  5   288

也可以在dump trace里查看:

[oracle@cjc-db-01 trace]$ cat cjc_ora_13608.trc|grep -i HWM
  Level 1 BMB for High HWM block: 0x01400118
  Level 1 BMB for Low HWM block: 0x01400118

[oracle@cjc-db-01 trace]$ cat cjc_ora_13608.trc|grep -i HighWater
      Highwater::  0x01400120  ext#: 0      blk#: 8      ext size: 8     
  Low HighWater Mark : 
      Highwater::  0x01400120  ext#: 0      blk#: 8      ext size: 8 

模拟误删除truncate

SQL> select * from cjc.t1123;
	ID NAME       ADATE
---------- ---------- --------------------
  1 chen       20241123
  2 ju       20241124
  3 chao       20241125
  4 yyy       20241126
  5 zzz       20241127

SQL> truncate table cjc.t1123;
Table truncated.

SQL> select * from cjc.t1123;
no rows selected

问题分析:

先dump 文件头 和 current redo log file。
1.dump file header

alter system dump datafile 5 block 282;
select value from v$diag_info where name ='Default Trace File';
VALUE
--------------------------------------------------------------------------------
/oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_23315.trc
SQL> ho cp /oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_23315.trc /home/oracle/trace

2.dump current redo log file

set line 100
col member for a50
select a.group#,a.status,b.member from v$log a,v$logfile b where a.group#=b.group#;
    GROUP# STATUS     MEMBER
---------- ---------------- --------------------------------------------------
  3 INACTIVE     /oracle/app/oracle/oradata/cjc/redo03.log
  2 INACTIVE     /oracle/app/oracle/oradata/cjc/redo02.log
  1 CURRENT     /oracle/app/oracle/oradata/cjc/redo01.log

alter system dump logfile '/oracle/app/oracle/oradata/cjc/redo01.log';

select value from v$diag_info where name ='Default Trace File';
VALUE
--------------------------------------------------------------------------------
/oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_23563.trc
SQL> ho cp /oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_23563.trc /home/oracle/trace

cjc.t1123表被truncate后,DATA_OBJECT_ID由87395变成了87397

[oracle@cjc-db-01 trace]$ cat /home/oracle/trace/cjc_ora_23563.trc|grep -i "CDOBJ"
   CDOBJ: new object number:87395
   CDOBJ: new object number:87397

SQL> select object_id, data_object_id from dba_objects where owner='CJC' and object_name='T1123';
 OBJECT_ID DATA_OBJECT_ID
---------- --------------
     87395     87397

SQL> select object_name from dba_objects where object_id=87397;
no rows selected

数据恢复:

一:修改数据字典表

SQL> select obj#,dataobj# from sys.obj$ where obj#=87395;
      OBJ#   DATAOBJ#
---------- ----------
     87395	87397

SQL> select obj#,dataobj# from sys.tab$ where obj#=87395;
      OBJ#   DATAOBJ#
---------- ----------
     87395	87397

SQL> update sys.obj$ set dataobj#=87395 where obj#=87395;
1 row updated.
SQL> update sys.tab$ set dataobj#=87395 where obj#=87395;
1 row updated.
SQL> commit;
Commit complete.

SQL> select * from cjc.t1123;
no rows selected

二、修改段头,L1、L2块头的dataobj#

通过redo log file dump信息查看:

L2 block:

[oracle@cjc-db-01 trace]$ cat /home/oracle/trace/cjc_ora_23315.trc |grep "Last Level II BMB"
  Last Level II BMB:  0x01400119

L1 block:

[oracle@cjc-db-01 trace]$ cat /home/oracle/trace/cjc_ora_23315.trc |grep "Last Level 1 BMB"
  Last Level 1 BMB:  0x01400118

L2块 --offset 104

select 
dbms_utility.data_block_address_file(to_number('01400119','xxxxxxxx')) as fileno,
dbms_utility.data_block_address_block(to_number('01400119','xxxxxxxx')) as blockno 
from dual;
    FILENO    BLOCKNO
---------- ----------
  5   281

L1块 --offset 192

select 
dbms_utility.data_block_address_file(to_number('01400118','xxxxxxxx')) as fileno,
dbms_utility.data_block_address_block(to_number('01400118','xxxxxxxx')) as blockno 
from dual;
    FILENO    BLOCKNO
---------- ----------
  5   280

段头块:

SQL> select to_char('87395','xxxxxxxx') old_dataobj,to_char('87397','xxxxxxxx') new_dataobj from dual;
OLD_DATAO NEW_DATAO
--------- ---------
    15563     15565

[oracle@cjc-db-01 trace]$ cat /home/oracle/trace/cjc_ora_23315.trc |grep 15565
7FFFF4E4DB10 00015565 10000000 01400118 00000008  [eU........@.....]

[oracle@cjc-db-01 trace]$ cat /home/oracle/trace/cjc_ora_23315.trc |grep 15563
[oracle@cjc-db-01 trace]$

修改段头块L3:

file 5 block 282 --offset 272

BBED> set file 5 block 282
	FILE#          	5
	BLOCK#          282

BBED> dump /v offset 272 count 16
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282     Offsets:  272 to  287  Dba:0x0140011a
-------------------------------------------------------
 65550100 00000010 18014001 08000000 l eU........@.....

需要将 00015565 改成 00015563

其中:
00015565 反序为 65550100
00015563 反序为 63550100
也就是将65改成63。

BBED> modify /x 63 offset 272
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282              Offsets:  272 to  287           Dba:0x0140011a
------------------------------------------------------------------------
 63550100 00000010 18014001 08000000 

继续修改L2:

BBED> set file 5 block 281
	FILE#          	5
	BLOCK#          281

BBED> dump /v offset 104 count 16
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 281     Offsets:  104 to  119  Dba:0x01400119
-------------------------------------------------------
 65550100 01000000 00000000 18014001 l eU............@.

BBED> modify /x 63 offset 104
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 281              Offsets:  104 to  119           Dba:0x01400119
------------------------------------------------------------------------
 63550100 01000000 00000000 18014001 

继续修改L1:

BBED> set file 5 block 280
	FILE#          	5
	BLOCK#          280

BBED> dump /v offset 192 count 16
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 280     Offsets:  192 to  207  Dba:0x01400118
-------------------------------------------------------
 65550100 ed570f00 00000000 18014001 l eU...W........@.
 <16 bytes per line>

BBED> modify /x 63 offset 192
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 280              Offsets:  192 to  207           Dba:0x01400118
------------------------------------------------------------------------
 63550100 ed570f00 00000000 18014001 

提交

BBED> sum apply
Check value for File 5, Block 280:
current = 0x9640, required = 0x9640

查看数据,还需要继续修改HWM

SQL> alter system flush buffer_cache;
System altered.

SQL> select * from cjc.t1123;
select * from cjc.t1123
*
ERROR at line 1:
ORA-08103: object no longer exists

三:修改高水位线

[oracle@cjc-db-01 ~]$ cat /home/oracle/trace/cjc_ora_23563.trc|grep -i "Highwater"
      Highwater::  0x01400120  ext#: 0      blk#: 8      ext size: 8     
      Highwater::  0x01400120  ext#: 0      blk#: 8      ext size: 8     
      Highwater::  0x0140011b  ext#: 0      blk#: 3      ext size: 8     
      Highwater::  0x0140011b  ext#: 0      blk#: 3      ext size: 8     
Opcode: 32      Highwater::  0x0140011b  ext#: 0      blk#: 3      ext size: 8  

高水位信息从

Highwater::  0x01400120  ext#: 0      blk#: 8      ext size: 8 

变成

Highwater::  0x0140011b  ext#: 0      blk#: 3      ext size: 8

其中,需要将

blk#:3 改成 blk#:8
0x0140011b 改成 0x01400120

其中 0x01400120 反序为 20014001

需要改回原值:

select to_char(8,'xxxxx') ext from dual;
EXT
------
     8

开始修改:

BBED> set file 5 block 282
	FILE#          	5
	BLOCK#          282

BBED> dump /v offset 52 count 16
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282     Offsets:   52 to   67  Dba:0x0140011a
-------------------------------------------------------
 03000000 08000000 1b014001 00000000 l ..........@.....

BBED> m /x 08 offset 52
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282              Offsets:   52 to   67           Dba:0x0140011a
------------------------------------------------------------------------
 08000000 08000000 1b014001 00000000 

BBED> sum apply
Check value for File 5, Block 282:
current = 0xfeed, required = 0xfeed

BBED> verify

BBED> dump /v offset 60 count 16
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282     Offsets:   60 to   75  Dba:0x0140011a
-------------------------------------------------------
 1b014001 00000000 00000000 00000000 l ..@.............
 <16 bytes per line>

BBED> m /x 20014001 offset 60
 File: /oracle/app/oracle/oradata/cjc/cjc01.dbf (5)
 Block: 282              Offsets:   60 to   75           Dba:0x0140011a
------------------------------------------------------------------------
 20014001 00000000 00000000 00000000 
 <32 bytes per line>

BBED> sum apply;
Check value for File 5, Block 282:
current = 0xfed6, required = 0xfed6

同理,修改 LHWM

BBED> m /x 08 offset 96
BBED> m /x 20014001 offset 104
BBED> sum apply;

BBED> verify
DBVERIFY - Verification starting
FILE = /oracle/app/oracle/oradata/cjc/cjc01.dbf
BLOCK = 282
DBVERIFY - Verification complete
Total Blocks Examined         : 1
Total Blocks Processed (Data) : 0
Total Blocks Failing   (Data) : 0
Total Blocks Processed (Index): 0
Total Blocks Failing   (Index): 0
Total Blocks Empty            : 0
Total Blocks Marked Corrupt   : 0
Total Blocks Influx           : 0
Message 531 not found;  product=RDBMS; facility=BBED

数据恢复成功:

SQL> alter system flush buffer_cache;
System altered.

SQL> alter system flush shared_pool;
System altered.

SQL>  select * from cjc.t1123;
	ID NAME       ADATE
---------- ---------- --------------------
  1 chen       20241123
  2 ju       20241124
  3 chao       20241125
  4 yyy       20241126
  5 zzz       20241127

立即将数据复制到另一张表里

SQL> create table cjc.t1123_A as select * from cjc.t1123;
Table created.

因为此时原表不支持 insert

SQL> insert into cjc.t1123 values(6,'xxx','20241128');
insert into cjc.t1123 values(6,'xxx','20241128')
                *
ERROR at line 1:
ORA-01578: ORACLE data block corrupted (file # 5, block # 281)
ORA-01110: data file 5: '/oracle/app/oracle/oradata/cjc/cjc01.dbf'

告警日志如下:

Sat Nov 23 20:28:38 2024
ALTER SYSTEM: Flushing buffer cache
Sat Nov 23 20:31:01 2024
Hex dump of (file 5, block 281) in trace file /oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_23563.trc
Corrupt block relative dba: 0x01400119 (file 5, block 281)
Bad check value found during buffer read
Data in bad block:
 type: 33 format: 2 rdba: 0x01400119
 last change scn: 0x0000.000f57ef seq: 0x2 flg: 0x04
 spare1: 0x0 spare2: 0x0 spare3: 0x0
 consistency value in tail: 0x57ef2102
 check value in block header: 0xd0ce
 computed block checksum: 0x6
Reading datafile '/oracle/app/oracle/oradata/cjc/cjc01.dbf' for corruption at rdba: 0x01400119 (file 5, block 281)
Reread (file 5, block 281) found same corrupt data (no logical check)
Sat Nov 23 20:31:01 2024
Corrupt Block Found
         TSN = 6, TSNAME = CJC
         RFN = 5, BLK = 281, RDBA = 20971801
         OBJN = 87395, OBJD = 87395, OBJECT = , SUBOBJECT = 
         SEGMENT OWNER = , SEGMENT TYPE = 
Errors in file /oracle/app/oracle/diag/rdbms/cjc/cjc/trace/cjc_ora_23563.trc  (incident=175410):
ORA-01578: ORACLE data block corrupted (file # 5, block # 281)
ORA-01110: data file 5: '/oracle/app/oracle/oradata/cjc/cjc01.dbf'
Incident details in: /oracle/app/oracle/diag/rdbms/cjc/cjc/incident/incdir_175410/cjc_ora_23563_i175410.trc
Sat Nov 23 20:31:03 2024
Dumping diagnostic data in directory=[cdmp_20241123203103], requested by (instance=1, osid=23563), summary=[incident=175410].
Sat Nov 23 20:31:03 2024
Sweep [inc][175410]: completed
Sweep [inc2][175410]: completed

删除原表

SQL> drop table cjc.t1123 purge;

重命名新表

SQL> conn cjc/******
SQL> rename t1123_A to t1123;
SQL> insert into cjc.t1123 values(6,'xxx','20241128');
SQL> commit;
SQL> select * from cjc.t1123;
	ID NAME       ADATE
---------- ---------- --------------------
  1 chen       20241123
  2 ju       20241124
  3 chao       20241125
  4 yyy       20241126
  5 zzz       20241127
  6 xxx       20241128
6 rows selected.

如果上述操作结束后,仍然查不到数据,还需要继续修改段头的extent map、Auxillary Map、extent个数等。

五:truncate table恢复的其他方法

1.OUD恢复

详细内容参考我的另一篇文字:
《震惊,一单几十个W的Oracle非常规恢复,原来这么简单?》

https://mp.weixin.qq.com/s?__biz=MzI5OTY2NzQ5MA==&mid=2247489614&idx=1&sn=ddf27726c53854d7e4bbc32d2489a29b&chksm=ec924103dbe5c815817ccb71315d4d15e70b34e99e359cbc137de705fe52a93e15ce724edd72&token=451153029&lang=zh_CN#rd
Oracle Delete表恢复(ODU)
Oracle Truncate表恢复(ODU)
Oracle Drop表(purge)恢复(ODU)

2.FY_Recover_Data.pck

参考文章:《Oracle使用fy_recover_data恢复truncate删除的数据》

链接:

https://www.cnblogs.com/lijiaman/p/12747658.html

测试过程如下:

SQL> conn cjc/******
SQL> select id,name,to_char(time,'YYYY-MM-DD HH24:MI:SS') time from cjc.t1121;
ID NAME       TIME
---------- ---------- -------------------
1 xxx        2024-11-21 12:32:58
2 cjc        2024-11-21 12:33:39
3 chen       2024-11-21 12:33:44
4 yyy        2024-11-21 12:33:49
5 zzz        2024-11-21 12:33:55

删除数据

SQL> truncate table cjc.t1121;
Table truncated.

安装工具

SQL> conn / as sysdba
SQL> @/home/oracle/tmp/20241121/FY_Recover_Data.pck

Enter value for files:
old 30:   --   1. Temp Restore and Recover tablespace & files                     ---
new 30:   --   1. Temp Restore and Recover tablespace                      ---
Package created.
Package body created.

执行恢复

SQL> set time on
12:44:16 SQL> set serveroutput on
12:44:20 SQL> exec fy_recover_data.recover_truncated_table('CJC','T1121');

12:44:26: New Directory Name: FY_DATA_DIR
12:44:26: Recover Tablespace: FY_REC_DATA; Data File: FY_REC_DATA.DAT
12:44:26: Restore Tablespace: FY_RST_DATA; Data File: FY_RST_DATA.DAT
12:44:26: Recover Table: CJC.T1121$
12:44:27: Restore Table: CJC.T1121$$
12:44:32: Copy file of Recover Tablespace: FY_REC_DATA_COPY.DAT
12:44:32: begin to recover table CJC.T1121
12:44:32: New Directory Name: TMP_HF_DIR
12:44:32: Recovering data in datafile /db/oradata/cjc/cjc01.dbf
12:44:32: Use existing Directory Name: TMP_HF_DIR
12:44:33: 5 truncated data blocks found.
12:44:33: 5 records recovered in backup table CJC.T1121$$
12:44:33: Total: 5 truncated data blocks found.
12:44:33: Total: 5 records recovered in backup table CJC.T1121$$
12:44:33: Recovery completed.
12:44:33: Data has been recovered to CJC.T1121$$
PL/SQL procedure successfully completed.

查看数据,已经自动将数据恢复到 CJC.T1121$$ 表

12:46:58 SQL> select id,name,to_char(time,'YYYY-MM-DD HH24:MI:SS') time from cjc.t1121;
no rows selected
12:46:59 SQL> select id,name,to_char(time,'YYYY-MM-DD HH24:MI:SS') time from cjc.t1121$$;
ID NAME       TIME
---------- ---------- -------------------
1 xxx        2024-11-21 12:32:58
2 cjc        2024-11-21 12:33:39
3 chen       2024-11-21 12:33:44
4 yyy        2024-11-21 12:33:49
5 zzz        2024-11-21 12:33:55

插入回原表:

insert into cjc.t1121 select * from cjc.t1121$$;
commit;
select id,name,to_char(time,'YYYY-MM-DD HH24:MI:SS') time from cjc.t1121;
ID NAME       TIME
---------- ---------- -------------------
1 xxx        2024-11-21 12:32:58
2 cjc        2024-11-21 12:33:39
3 chen       2024-11-21 12:33:44
4 yyy        2024-11-21 12:33:49
5 zzz        2024-11-21 12:33:55

3.脚本恢复

参考文章:《Oracle TRUNCATE TABLE恢复-脚本》
链接:

https://www.modb.pro/db/156580

六:总结

可以看到,无备份情况下恢复truncate误删除的表基本都需要一个前提条件:数据块没有新数据写入!而在实际场景中很难实现,除非发现误删除后在第一时间把对应表空间或数据文件设置为只读,这意味着可能影响更多的业务,所以类似的危险操作需要谨慎执行,最后,一定要有备份!
###chenjuchao 20241124###
欢迎关注我的公众号《IT小Chen》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值