数据库损坏难题
在数据库的日常管理中,我们不可避免的会遇到服务器突然断电(没有进行电源冗余),服务器故障或者 SQL Server 服务突然停掉,
头大的是ldf事务日志文件也损毁了,SQL Server服务器起来之后,发现数据库处于"Recovery Pending" 状态。
更麻烦的是该数据库没有任何备份或者备份已经比较久远;
当然这些都不是最难的,最难的是连资深DBA使出ATTACH_REBUILD_LOG和 DBCC CEHECKDB 的 REPAIR_ALLOW_DATA_LOSS 选项等招数时候,
即使已经做好了最坏打算,做了丢失部分数据的准备,数据库还是无法上线。
本文将分享终极处理方法,帮助您成功恢复数据库。
测试环境:SQL Server 2022,Windows Server 2016
注意:奇技淫巧有风险,做任何操作之前注意先做备份!
模拟环境
首先,在数据库 testdb 中创建 testObject 表,并不停插入所有对象数据。
在窗口一我们运行插入数据脚本,使用多次 CROSS JOIN,以获得足够多的数据,插入数据脚本实际是一个模拟的大事务。
--窗口1
CREATE DATABASE testdb
GO
USE testdb
GO
SELECT * INTO testObject FROM sys.all_objects
--前面脚本执行完成再执行下面的插入语句
INSERT INTO dbo.testObject
SELECT o.* FROM sys.all_objects o
CROSS JOIN sys.all_objects o1
CROSS JOIN sys.all_objects o2
CROSS JOIN sys.all_objects o3
CROSS JOIN sys.all_objects o4
返回信息如下
-- Msg 109, Level 20, State 0, Line 0
--A transport-level error has occurred when receiving results from the server. (provider: Shared Memory Provider, error: 0 - 管道已结束。)
在窗口二我们在关闭测试实例时,窗口一的插入事务仍然在运行。
这将使得数据库处于不一致状态,在数据库启动时,执行数据库恢复。
--窗口2
--执行完下面语句之后,移走ldf文件,模拟ldf文件损坏
SHUTDOWN WITH NOWAIT
数据库停服后,将testdb数据库 的ldf事务日志文件改名或者移到其他路径,重新启动SQL Server 服务,可以看到,testdb 数据库处于“恢复挂起”状态。
因为在停服时候,还有未提交的插入事务保存在ldf事务日志文件,需要在数据库启动时候把事务日志捞出来做crash recovery。
数据库启动之前,已经把ldf事务日志文件移动到别的地方
此时,我们已经有一个孤立的,不一致的数据库文件。
现在我们必须先离线数据库,把mdf文件复制到别的地方作为备份,然后删除数据库,为后续的附加ldf事务日志文件做准备
--窗口3
USE master
GO
ALTER DATABASE [testdb] SET OFFLINE;
把mdf文件复制到别的地方作为备份,因为数据库离线了,并不会删除物理数据文件
--窗口4
USE master
GO
DROP DATABASE [testdb] ;
传统方法
使用 ATTACH_REBUILD_LOG 来重建ldf事务日志文件
--窗口5
USE master
GO
CREATE DATABASE [testdb] ON
(FILENAME='E:\DataBase\testdb.mdf')
FOR ATTACH_REBUILD_LOG
GO
报错信息如下
--文件激活失败。物理文件名称'E:\DataBase\testdb_log.ldf'可能不正确。
--无法重新生成日志,原因是数据库关闭时存在打开的事务/用户,该数据库没有检查点或者该数据库是只读的。如果事务日志文件被手动删除或者由于硬件或环境问题而丢失,则可能出现此错误。
--Msg 1813, Level 16, State 2, Line 8
--无法打开新数据库 'testdb'。CREATE DATABASE 中止。
到此为止,我们很可能只有去找备份文件还原了(如果有的话),否则可能就是一场灾难了。
就算资深DBA老司机也会在这里翻车
新方法
接下来将介绍终极恢复数据库的方法,以帮助您度过劫难。
使用 CREATE DATABASE 语句中非官方文档记载(undocument)的命令,这个命令就是ATTACH_FORCE_REBUILD_LOG
这个命令会强制重建ldf事务日志文件,即使数据库检测到ldf事务日志文件和mdf数据文件之间有不一致的情况。
--窗口6
USE master
GO
CREATE DATABASE [testdb] ON
(FILENAME='E:\DataBase\testdb.mdf')
FOR ATTACH_FORCE_REBUILD_LOG
GO
返回信息如下
--文件激活失败。物理文件名称'E:\DataBase\testdb_log.ldf'可能不正确。
--新的日志文件 'E:\DataBase\testdb_log.ldf' 已创建。
数据库虽然恢复正常,但数据表依然无法访问
--窗口7
USE [testdb]
GO
SELECT TOP 10 * FROM [dbo].[testObject]
SELECT COUNT(*) FROM [dbo].[testObject]
报错信息如下
--Msg 824, Level 24, State 2, Line 18
--SQL Server 检测到基于逻辑一致性的 I/O 错误: pageid 不正确(应为 1:69856,但实际为 0:0)。在文件“E:\DataBase\testdb.mdf”中的偏移 0x000000221c0000 处,在数据库 ID 9 中的页面 (1:69856) 的 读取 期间发生。SQL Server 错误日志或操作系统错误日志中的其他消息可能会提供更多详细信息。这是一个威胁数据库完整性的严重错误条件,必须立即更正。请执行完整的数据库一致性检查(DBCC CHECKDB)。此错误可以由许多因素导致;有关详细信息,请参阅 https://go.microsoft.com/fwlink/?linkid=2252374。
使用最小数据丢失的方式,修复数据库
头两个命令将数据库分别置于紧急模式和单用户模式,这是我们执行 DBCC CHECKDB 的 REPAIR_ALLOW_DATA_LOSS 选项的前提。
最后一句命令是将数据库恢复多用户模式。
--窗口8
--使用最小数据丢失的方式,修复数据库
USE [master]
GO
ALTER DATABASE [testdb] SET EMERGENCY
GO
ALTER DATABASE [testdb] SET SINGLE_USER WITH NO_WAIT
GO
DBCC CHECKDB([testdb],REPAIR_ALLOW_DATA_LOSS) WITH ALL_ERRORMSGS
--dbcc checkdb执行完毕之后执行下面语句,让数据库可以重新访问
ALTER DATABASE [testdb] SET MULTI_USER WITH NO_WAIT
DBCC CHECKDB返回信息如下,很多信息这里做了省略
可以看到有5924 个一致性错误,修复了 5924 个一致性错误,也就是全部修复了
--sys.filetable_updates_2105058535的 DBCC 结果。
--对象“sys.filetable_updates_2105058535”在 0 页中找到 0 行。
--CHECKDB 在数据库 'testdb' 中发现 0 个分配错误和 5924 个一致性错误。
--CHECKDB 在数据库 'testdb' 中修复了 0 个分配错误和 5924 个一致性错误。
--DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。
数据库处于单用户模式
设置回多用户模式之后,尝试查询数据
--窗口9
--从数据行数来看,具体你是不知道丢失多少数据的,只能说能挽救多少是多少吧
USE [testdb]
GO
SELECT TOP 10 * FROM [dbo].[testObject]
SELECT COUNT(*) AS'rowcount' FROM [dbo].[testObject]
数据是查询出来了,但是具体丢失多少数据,我们无法掌握
至少数据库最后一次checkpoint点之后的所有数据将会丢失。
总结
在传统的方法里面,还有一个方法就是 新建一个同名的空数据库作为傀儡数据库,然后替换傀儡数据库的数据文件
再对傀儡数据库执行DBCC CEHECKDB 的 REPAIR_ALLOW_DATA_LOSS 选项,但是实际上也不能保证100%有效
这个方法网上已经有相关文章,这里就不展开叙述了。
加入我们的微信群,与我们一起探讨数据库技术,以及SQL Server、 MySQL、PostgreSQL、MongoDB 、Oracle、Redis的相关话题。
微信群仅供学习交流使用,没有任何广告或商业活动。