SQLServer数据库恢复工具FGSDU(FGEDU SQLServer DUL)

SQLServer数据库恢复工具FGPDU(FGEDU SQLServer DUL)

目录

  1. 程序介绍
  2. 作者信息
  3. 功能特性
  4. 支持环境
  5. 程序安装与编译
  6. 程序使用
  7. 命令详解
  8. 案例场景与操作过程
  9. 数据恢复原理说明
  10. 常用问题与排查
  11. 附录

1. 程序介绍

1.1 程序概述

FGSDU(全称 fgedu SQLServer dul,即 fgedu SQLServer Data Unload Loading 工具)是一款专业、强大、高效的 SQLServer 数据库数据恢复工具。该工具由风哥研发,专为解决 SQLServer 数据库在各种极端场景下的数据丢失问题而设计。

当您的 SQLServer 数据库遇到以下情况时,FGSDU 可以为您提供强大的数据恢复能力:

  • SQLServer 服务无法启动,包括 master 系统数据库损坏、实例崩溃、服务配置错误等导致数据库完全不可用的场景
  • 数据库文件(MDF/NDF)存在磁盘坏块、部分损坏或文件系统错误
  • 操作人员误执行 DELETE 语句删除了重要业务数据
  • 开发或运维人员误执行 DROP TABLE 删除了整张表
  • 误执行 TRUNCATE TABLE 清空了表中的全部数据
  • 数据库经过 DBCC SHRINKFILE 收缩后仍有部分残留数据可恢复
  • 需要在不启动 SQLServer 服务的情况下直接从数据文件中提取数据
  • 需要对数据库文件进行取证分析或深度扫描

FGSDU 采用底层直接解析 MDF/NDF 数据文件的方式,完全不依赖 SQLServer 服务运行。工具通过深度解析 SQLServer 的 8KB 页结构、GAM/SGAM 分配位图、IAM 索引分配映射、系统表(sysobjects/syscolumns/systypes)元数据,以及数据页中的记录布局,能够在数据库完全不可用的情况下,最大限度地从原始数据文件中提取和恢复数据。

1.2 程序设计理念

FGSDU 的设计核心理念是"安全、高效、易用、可靠":

安全性第一:FGSDU 以只读方式打开所有 MDF/NDF 数据文件,绝对不会对原始数据库文件进行任何修改操作。这意味着您可以放心地在损坏的数据库文件上运行 FGSDU,而不必担心二次损坏。在进行任何恢复操作之前,我们仍强烈建议您先对原始文件进行完整备份。

最大化恢复率:工具采用多阶段、多层次的恢复算法,从常规恢复到深度扫描,层层递进,尽可能找出所有可恢复的数据。即使在系统表完全损坏无法读取的情况下,深度扫描模式仍能通过穷举所有页面的方式提取残留数据。

坏块自动容错:当数据文件存在磁盘坏块时,许多工具会直接报错退出,导致整个恢复过程失败。FGSDU 内置了完善的坏块处理机制,能够自动检测坏块、最多重试 3 次读取、跳过无法读取的页面并生成坏块报告,确保在磁盘损坏的情况下仍能最大程度地抢救可用数据。

数据完整性保障:导出的每一行数据都会经过 5 重完整性校验,包括列数一致性、NOT NULL 约束、数据类型匹配、长度限制和数值范围检查。有问题的行会在导出文件中用 WARNING 注释明确标记,让用户对恢复的数据质量一目了然。

内嵌恢复指导:所有导出的 SQL 文件和 DMP 文件配套说明中都内嵌了完整的 7 步数据恢复流程指南,即使是对 SQLServer 不熟悉的用户也能按照步骤一步步将恢复的数据导入新的数据库。

零依赖单文件部署:FGSDU 编译后为单文件可执行程序,无需安装任何运行库、无需配置环境变量、无需依赖任何第三方组件,复制到目标机器即可直接运行,极大地简化了在紧急恢复场景下的部署操作。

1.3 核心技术优势

FGSDU 相比其他同类工具,具有以下独特的技术优势:

  1. 深度页结构解析:完整支持 SQLServer 2005 至 2026 所有版本的页格式,包括数据页、索引页、LOB 页、IAM 页、GAM 页、SGAM 页、PFS 页等全部页类型的解析。

  2. 多阶段恢复算法:每种恢复类型(DELETE/DROP/TRUNCATE)都采用分阶段的恢复策略,结合多种技术手段综合恢复,比单一算法的恢复成功率更高。

  3. 无 Schema 推断能力:在系统表损坏无法获取表结构定义的情况下,工具能够通过分析记录的二进制布局自动推断列结构,生成 best-effort 的 INSERT 语句,最大程度挽救数据。

  4. 详细的进度输出:unload 全库抽取操作会实时显示每一张表的导出进度、导出行数、完成状态等详细信息,让用户清晰掌握恢复进展。

  5. 跨平台兼容性:通过 GLIBC 版本符号控制和 musl-libc 静态编译技术,Linux 版本可兼容从 RHEL 5 到 RHEL 10 的所有主流 Linux 发行版,同时提供 Windows 原生版本支持。


2. 作者信息

作者: 风哥
WX:itpux-com
官方网站 : http://www.fgedu.net.cn , http://www.itpux.com
数据库教程 : https://edu.51cto.com/lecturer/8020378.html


3. 功能特性

3.1 数据提取与导出功能

3.1.1 直接文件读取SQLServer

FGSDU 无需 SQLServer 服务运行,直接打开并解析 MDF(主数据文件)和 NDF(辅助数据文件)。工具绕过 SQLServer 引擎层,直接在文件系统层面读取和解析数据页,这使得它在数据库完全无法启动的灾难场景下依然能够正常工作。

支持同时加载多个数据文件,对于使用多个 NDF 辅助文件的大型数据库,可通过 -ndf 参数重复指定所有辅助文件,工具会统一管理多个文件的页空间,跨文件遍历页链。

3.1.2 双格式导出SQLServer

SQL 脚本格式(默认)

  • 生成标准的 T-SQL 脚本文件,包含 CREATE TABLE 建表语句和 INSERT 数据插入语句
  • 文件格式为纯文本,人类可读,可直接在 SQLServer Management Studio(SSMS)中打开和执行
  • 也可通过 sqlcmd 命令行工具批量执行,便于自动化脚本集成
  • 每条 INSERT 语句对应一行数据,方便逐行检查和筛选
  • 文件头部内嵌完整的恢复步骤说明和数据完整性校验报告
  • 有数据完整性问题的行会被特殊的 WARNING 注释标记

DMP 二进制格式

  • 采用紧凑的二进制存储格式,文件体积比 SQL 格式显著缩小
  • 存储效率高,适合大批量数据导出场景
  • 配套生成 .recovery_steps.txt 恢复步骤说明文件,包含建表语句、导入指南和校验报告
  • 支持 BULK INSERT、BCP 等多种高速导入方式
  • 适合数据迁移、批量备份和自动化恢复流程

3.1.3 SQLServer全库一键抽取(unload)

当数据库无法启动时,unload 命令是最快捷的数据抢救方式。它具有以下特点:

  • 自动遍历全库:无需手动指定表名,工具自动从系统元数据中识别所有可恢复的表
  • 目录树组织:按"用户名/Schema_表名"的结构生成独立子目录,每张表一个独立文件,便于按表管理和恢复
  • 按用户/Schema 过滤:支持通过 -user 参数只导出特定用户拥有的表,或通过 -schema 参数只导出特定 Schema 下的表
  • 汇总报告生成:自动生成 unload_report.txt 汇总报告文件,包含每张表的导出状态(成功/失败)、列数、导出行数、输出路径、完整的恢复步骤说明和坏块报告
  • 实时进度显示:导出过程中实时显示当前处理的表名、已导出行数、完成百分比等进度信息
  • 坏块自动跳过:遇到坏块自动跳过并继续处理后续表,不会因为个别页面损坏而中断整个全库抽取过程

3.2 数据恢复功能

3.2.1 SQLServer DELETE 误删除数据恢复

恢复通过 DELETE 语句删除的行记录。

技术实现

  • Ghost 记录扫描:SQLServer 删除行时不会立即清除数据,而是将记录标记为 Ghost(幽灵记录),数据内容仍保留在页面中。FGSDU 扫描所有数据页,识别并提取这些标记为已删除的 Ghost 记录
  • LSN 日志序列号分析:通过分析数据页头部的 LSN(Log Sequence Number,日志序列号),定位最近被修改过的页面,这些页面更可能包含刚被删除的数据
  • 页链完整遍历:通过页头中的 prev/next 页指针遍历完整的页链,确保不遗漏链上任何一个页面的数据
  • 智能去重处理:对从不同页面或不同扫描阶段提取到的重复记录进行自动去重,避免同一行数据被多次恢复

恢复成功率参考

  • 刚执行 DELETE(几分钟内):90%-95%
  • 执行 DELETE 后 1 小时内:70%-90%
  • 执行 DELETE 后数小时内:30%-70%
  • 执行 DELETE 后数天:10%-30%
  • 执行 DBCC SHRINKFILE 收缩后:低于 10%

3.2.2 SQLServer DROP TABLE 误删表恢复

恢复被 DROP TABLE 语句删除的整张表及其数据。

技术实现

  • sysobjects 残留扫描:在系统表中搜索被 DROP 表的元数据残留信息,即使表已被删除,系统表中可能仍保留有表名、ObjectID、列定义等元数据片段
  • 孤立 IAM 页检测:识别不再被任何现有表引用的孤立 IAM(Index Allocation Map,索引分配映射)页面,这些 IAM 页是被 DROP 表留下的"脚印"
  • ObjectID 关联映射:通过页头中的 ObjectID 字段将孤立的数据页归类到对应的被删除表
  • 页链遍历重建:从检测到的起始数据页开始,通过 prev/next 指针遍历完整页链,恢复表的全部数据页
  • Schema 自动推断:当元数据完全丢失无法获取表结构时,工具会分析记录二进制布局,自动推断列的数量、类型和长度,生成通用列名(col_1、col_2 等),导出 best-effort 的 INSERT 语句

3.2.3 SQLServer TRUNCATE TABLE 清空恢复

恢复被 TRUNCATE TABLE 语句清空的表数据。

TRUNCATE 操作的恢复难度最高,因为它会立即释放表占用的所有数据页,并更新 GAM/SGAM 位图标记这些区为可用。但在释放的页被新数据覆盖之前,原有的数据内容仍然保留在磁盘上。

技术实现

  • GAM/SGAM 位图解析:解析 GAM(Global Allocation Map,全局分配映射)页和 SGAM(Shared Global Allocation Map,共享全局分配映射)页,识别哪些区被标记为已释放
  • 释放区穷举扫描:扫描所有被标记为释放的区,检查其中的页面是否仍包含有效的残留数据
  • 高水位标记定位:通过分析分配映射的历史状态,定位 TRUNCATE 操作前的高水位标记,确定原表数据页的分布范围
  • 残留页数据提取:从已释放但数据内容尚未被覆盖的页面中提取完整的记录数据

3.2.4 SQLServer全量恢复(recover all)

一键按顺序执行所有三种恢复类型:DELETE 恢复 → DROP 恢复 → TRUNCATE 恢复。适用于不确定数据是如何丢失的、或数据库遭受过多种操作的综合场景。

3.2.5 深度扫描(recover deep)

当常规恢复方法无法找到足够数据时使用的终极恢复手段。

深度扫描策略

  1. 穷举扫描 MDF/NDF 文件中的每一个页面,不限制页类型
  2. 检查所有类型的页面:数据页、LOB 大对象页、索引页、甚至系统页
  3. 恢复所有状态的记录:正常记录、Ghost 已删除记录、Forwarded 转发记录
  4. 有完整 Schema 信息时:完整解码记录字段,输出标准 INSERT 语句
  5. 无 Schema 信息时:自动推断列结构,输出 best-effort INSERT 语句
  6. 同时输出 Raw Hex 原始十六进制数据作为取证备份
  7. 生成详细的扫描统计报告

深度扫描模式特别适用于以下场景:

  • 数据库严重损坏,系统表完全不可读
  • 常规 DELETE/DROP/TRUNCATE 恢复找不到数据
  • 需要最大化数据恢复率,不惜以更长扫描时间为代价
  • 司法取证场景,需要尽可能提取所有残留数据痕迹

3.3 坏块处理机制

FGSDU 内置了完善的坏块(Bad Block)处理机制,确保在磁盘存在物理损坏时仍能最大程度恢复可用数据。

处理流程

  1. 坏块自动检测:读取页面时自动检测以下异常情况:

    • 底层磁盘 I/O 读取失败
    • 页面数据全部为零(典型的磁盘坏块特征)
    • 页头中的页类型字段无效(不在合法范围 1-14 内)
    • 页头中的页 ID 与实际读取位置的预期页 ID 不匹配
  2. 3 次自动重试:遇到瞬时性的 I/O 错误时,自动进行最多 3 次重试读取,排除偶发的读取干扰

  3. 跳过并记录:确认页面确实损坏无法读取后,跳过该页面,同时将坏块的页号、文件索引、错误类型和跳过次数记录到坏块列表中

  4. 继续恢复流程:跳过坏块后不中断整体恢复流程,继续扫描和处理其他所有正常页面

  5. 坏块报告生成:在恢复完成后,将坏块详情写入导出文件末尾和控制台输出,包括每个坏块的页号、损坏原因和处理方式

3.4 数据完整性校验

FGSDU 在导出数据时自动执行 5 重完整性校验,确保恢复的数据质量可追溯。

5 重校验项目

校验项 校验内容 违规示例
列数一致性 行记录中的字段数量是否与表定义的列数完全匹配 表定义 8 列,但行中只有 7 个字段值
NOT NULL 约束 定义为 NOT NULL 的列是否出现了 NULL 值 主键列被恢复出 NULL 值
数据类型匹配 值的实际数据类型与列定义的类型是否兼容(允许同类型间的强制转换) 整数列中出现非数字字符数据
长度违规检查 字符串、二进制等变长数据的实际长度是否超过列定义的 max_length 限制 VARCHAR(50) 列中出现 100 字符的字符串
数值范围检查 数值型和日期型数据是否在合法范围内 TINYINT 出现 300(超出 0-255 范围)、DATETIME 出现 9999 年以后的日期

校验结果输出位置

  • 控制台实时输出校验摘要(通过行数、失败行数、通过率)
  • SQL 导出文件头部包含完整的校验报告和违规详情
  • 有问题的 INSERT 语句前会附加 -- WARNING: Row XXX has integrity issues (...) 注释
  • DMP 格式的校验报告写入配套的 .recovery_steps.txt 文件中

3.5 目标过滤功能

支持多种维度的精确过滤,避免恢复无关数据,提高恢复效率和结果的针对性。

过滤维度

  • 按表名过滤-table <表名>,只恢复指定表名的数据
  • 按 Schema 过滤-schema <Schema名>,只恢复指定 Schema 下的数据(如 dbo、sales、hr、finance)
  • 按用户过滤-user <用户名>,只恢复指定数据库用户拥有的表

组合使用:以上过滤条件可以任意组合使用,例如同时指定 -schema sales -table orders 可精确恢复 sales.orders 这一张表。

3.6 其他高级功能

3.6.1 日志级别控制

通过 -log 参数控制输出详细程度:

  • -log 0(静默模式):仅输出错误信息和最终结果,适合自动化脚本调用
  • -log 1(信息模式,默认):输出操作进度、扫描统计、阶段状态等常规信息
  • -log 2(调试模式):输出详细的诊断信息,包括每页的解析结果、中间状态转换等,用于故障排查和问题定位

3.6.2 数据库分析工具集

除了核心的恢复功能,FGSDU 还提供了丰富的数据库结构分析命令:

  • scan:快速列出所有用户表及估计行数
  • info:显示数据库文件的头部信息(大小、页大小、SQLServer 版本、兼容级别、恢复模式、排序规则等)
  • query:查询特定表的详细列结构(列名、数据类型、长度、是否可空)
  • stats:显示数据库整体统计信息(表数、各类型页数、估计总行数、数据大小等)
  • analyze:深度分析数据库结构,生成包含页类型分布、碎片情况、Ghost 记录统计、孤立页检测等内容的综合分析报告

4. 支持环境

4.1 支持的操作系统

4.1.1 Windows 平台

FGSDU Windows 版本支持以下操作系统:

  • Windows 7 SP1 及以上(x86_64)
  • Windows 8 / Windows 8.1(x86_64)
  • Windows 10 所有版本(x86_64)
  • Windows 11 所有版本(x86_64)
  • Windows Server 2008 R2 SP1 及以上
  • Windows Server 2012 / 2012 R2
  • Windows Server 2016
  • Windows Server 2019
  • Windows Server 2022
  • Windows Server 2025 及后续版本

Windows 版本说明

  • 提供原生 Win32 可执行程序(fgsdu.exe)
  • 静态编译版本无任何运行库依赖,直接双击或在命令行运行即可
  • 建议以管理员身份运行,以确保对 MDF/NDF 文件有足够的读取权限

4.1.2 Linux 平台

FGSDU Linux 版本支持以下主流发行版:

红帽系发行版

  • Red Hat Enterprise Linux (RHEL) 5.x / 6.x / 7.x / 8.x / 9.x / 10.x
  • Oracle Enterprise Linux (OEL) 5.x 及以上
  • CentOS 5.x / 6.x / 7.x / 8.x / 9.x
  • Rocky Linux 8.x / 9.x
  • AlmaLinux 8.x / 9.x
  • Fedora Linux 所有稳定版本

Debian 系发行版

  • Ubuntu 14.04 LTS / 16.04 LTS / 18.04 LTS / 20.04 LTS / 22.04 LTS / 24.04 LTS
  • Debian GNU/Linux 8 (Jessie) 及以上所有稳定版本
  • Linux Mint 17.x 及以上
  • Pop!_OS 所有版本

SUSE 系发行版

  • SUSE Linux Enterprise Server (SLES) 11 SPx / 12 SPx / 15 SPx
  • openSUSE Leap 42.x 及以上所有稳定版本
  • openSUSE Tumbleweed 滚动发行版

国产操作系统

  • 银河麒麟操作系统(Kylin OS)桌面版和服务器版
  • 统信操作系统(UOS / UnionTech OS)桌面版和服务器版
  • 中标麒麟(NeoKylin)操作系统
  • 深度操作系统(Deepin)
  • 其他基于 glibc 的国产 Linux 发行版

Linux 兼容性说明

  • 默认动态编译版本通过版本脚本强制符号依赖于 GLIBC_2.2.5,可在 RHEL 5 及以上的所有 glibc 系统上运行
  • 提供 musl-libc 完全静态编译版本,无任何外部依赖,可在任意 x86_64 Linux 系统上运行,包括最小化安装的 Alpine Linux
  • 仅支持 x86_64(amd64)架构,不支持 32 位 x86 或 ARM 架构

4.2 支持的 SQLServer 版本

FGSDU 支持以下所有 SQLServer 版本生成的 MDF/NDF 数据文件:

  • SQL Server 2005(包括 SP1/SP2/SP3/SP4)
  • SQL Server 2008(包括 SP1/SP2/SP3/SP4)
  • SQL Server 2008 R2(包括 SP1/SP2/SP3)
  • SQL Server 2012(包括 SP1/SP2/SP3/SP4)
  • SQL Server 2014(包括 SP1/SP2/SP3)
  • SQL Server 2016(包括 SP1/SP2/SP3)
  • SQL Server 2017(包括 CU1-CU3x 所有累积更新)
  • SQL Server 2019(包括 CU1-CU2x 所有累积更新)
  • SQL Server 2022(包括所有累积更新)
  • SQL Server 2025(即将发布版本的预支持)
  • SQL Server 2026(未来版本的预支持)

工具会自动从 MDF 文件头部检测 SQLServer 版本,并根据检测结果选择对应的页结构解析逻辑和系统表查询方式,无需用户手动指定版本。

4.3 系统资源要求

4.3.1 最低硬件配置

  • CPU:x86_64 架构,单核 1.0GHz 及以上
  • 内存:最低 512MB 可用内存(建议 1GB 以上)
  • 磁盘空间:程序本身占用约 5MB。用于存放导出文件的可用磁盘空间建议为源 MDF 文件总大小的 1.5-2 倍
  • 磁盘类型:建议使用 SSD 固态硬盘以获得更好的扫描性能;HDD 机械硬盘也可正常使用,但扫描速度较慢

4.3.2 推荐硬件配置

  • CPU:x86_64 架构,4 核及以上
  • 内存:4GB 及以上可用内存
  • 磁盘空间:源 MDF 文件总大小的 2-3 倍可用空间
  • 磁盘类型:NVMe SSD 或 SATA SSD,将源 MDF 文件和导出目标文件放在不同物理磁盘上可获得最佳性能

4.3.3 大型数据库配置建议

对于超过 100GB 的大型 MDF 文件,建议:

  • 内存不少于 8GB
  • 使用 SSD 存储存放 MDF 文件和导出文件
  • 分表导出(使用 -table 参数逐表导出),避免单次操作数据量过大
  • 使用 DMP 二进制格式导出以减少磁盘占用
  • 使用 -log 0 静默模式减少控制台 I/O 开销

5. 程序安装与编译

5.1 获取程序

用户可以通过项目提供的源代码自行编译 FGSDU。项目采用纯 C 语言编写,无任何第三方依赖库,编译过程非常简单。

5.2 Linux 平台编译

5.2.1 标准编译(推荐)

标准编译生成的动态链接版本具有最佳的 GLIBC 兼容性,可在 RHEL 5 及以上的绝大多数 Linux 发行版上直接运行。

# 进入项目根目录
cd FGSDU

# 执行标准编译
make

# 编译完成后,产物位于 build/fgsdu
ls -lh build/fgsdu

# 验证版本信息
./build/fgsdu version

# 验证 GLIBC 兼容性(预期主要符号为 GLIBC_2.2.5)
readelf -V build/fgsdu | grep GLIBC

5.2.2 GLIBC 静态编译

生成完全静态链接的可执行文件,不依赖系统 GLIBC,适合需要最大可移植性的场景。

# CentOS/RHEL 需先安装静态库
sudo yum install glibc-static libstdc++-static -y

# Ubuntu/Debian 需先安装静态库
sudo apt install libc6-dev -y

# 静态编译
make static

# 验证无动态依赖(预期输出:not a dynamic executable)
ldd build/fgsdu

5.2.3 musl-libc 静态编译(完全可移植)

使用 musl-gcc 编译生成完全静态、零依赖的二进制文件,可在任意 x86_64 Linux 系统上运行,包括 Alpine Linux 等非 glibc 系统。

# Ubuntu/Debian 安装 musl 工具链
sudo apt install musl-tools -y

# CentOS/RHEL 安装 musl 工具链
sudo yum install musl-libc-devel -y

# 使用 musl-gcc 编译
make musl

# 验证产物
file build/fgsdu
# 预期输出:ELF 64-bit LSB executable, x86-64, statically linked, ...

5.2.4 Docker 构建(推荐用于跨平台部署)

通过项目提供的 Dockerfile,基于 Alpine Linux + musl-libc 构建完全可移植的静态二进制文件。这是生成跨发行版可执行文件的最推荐方式,无需在宿主机安装任何编译工具链。

# 确保系统已安装 Docker 并启动服务

# 构建 Docker 镜像并生成静态二进制
make docker

# 编译完成后,产物位于 build/fgsdu
# 该文件为完全静态链接,可复制到任何 x86_64 Linux 系统直接运行

或者手动执行 Docker 命令:

docker build -t fgsdu-builder .
docker run --rm -v $(pwd)/build:/output fgsdu-builder

5.2.5 调试版本编译

用于开发调试目的,包含调试符号且不进行编译优化。

make debug

5.2.6 编译产物安装

将编译好的程序安装到系统标准路径:

# 默认安装到 /usr/local/bin
sudo make install

# 或指定自定义安装路径
make install PREFIX=/opt/fgsdu

5.3 Windows 平台编译

5.3.1 交叉编译(在 Linux 上编译 Windows 版本)

在 Linux 主机上使用 MinGW 交叉编译工具链生成 Windows 原生可执行文件。

# Ubuntu/Debian 安装 MinGW 交叉编译器
sudo apt install mingw-w64 -y

# CentOS/RHEL 安装 MinGW 交叉编译器
sudo yum install mingw32-gcc -y

# 交叉编译 Windows 版本
make win

# 编译产物位于 build/win/fgsdu.exe
# 将该文件复制到 Windows 系统即可直接运行
ls -lh build/win/fgsdu.exe

5.3.2 Windows 原生编译

在 Windows 系统上,可使用以下方式编译:

  1. Visual Studio 命令提示符:使用项目提供的 scripts/build_win.bat 批处理脚本
  2. MinGW/MSYS2 环境:在 MSYS2 shell 中执行标准 make 命令

5.4 编译目标速查

编译命令 生成版本 适用场景
make GLIBC 动态版(兼容 GLIBC 2.2.5+) 绝大多数 Linux 系统默认使用
make static GLIBC 静态版 需要避开系统 GLIBC 版本差异
make musl musl-libc 完全静态版 极致可移植性,Alpine 等非 glibc 系统
make docker musl-libc 完全静态版(通过 Docker) 宿主机无编译环境或需确保可移植性
make win Windows 原生 exe(静态) Windows 平台使用
make debug 调试版本(含符号,无优化) 开发调试和问题定位

6. 程序使用

6.1 基本语法

FGSDU 的命令行基本语法格式如下:

fgsdu <命令> [MDF文件路径] [选项参数]

其中:

  • <命令>:必填,指定要执行的操作,如 scan、export、unload、recover 等
  • [MDF文件路径]:必填(help 和 version 命令除外),指定主数据文件(.mdf)的路径
  • [选项参数]:选填,通过 -参数名 值 的形式指定额外选项

6.2 快速入门示例

以下是一组最常用的快速入门命令,建议首次使用时按顺序尝试:

# 1. 查看帮助信息,熟悉所有可用命令
./build/fgsdu help

# 2. 查看当前程序版本
./build/fgsdu version

# 3. 查看 MDF 文件的基本信息(验证文件可读且格式有效)
./build/fgsdu info /path/to/database.mdf

# 4. 列出 MDF 文件中包含的所有用户表
./build/fgsdu scan /path/to/database.mdf

# 5. 查看指定表的列结构定义
./build/fgsdu query /path/to/database.mdf orders

# 6. 导出指定表为 SQL 脚本(含恢复步骤和校验报告)
./build/fgsdu export /path/to/database.mdf orders -o orders.sql

# 7. 全库一键抽取(数据库无法启动时,推荐方式)
./build/fgsdu unload /path/to/database.mdf -o /backup/unload_all

# 8. 恢复误 DELETE 删除的数据
./build/fgsdu recover delete /path/to/database.mdf -o deleted_recovery.sql

# 9. 恢复误 DROP TABLE 删除的表
./build/fgsdu recover drop /path/to/database.mdf -o dropped_recovery.sql

# 10. 恢复误 TRUNCATE TABLE 清空的数据
./build/fgsdu recover truncate /path/to/database.mdf -o truncate_recovery.sql

6.3 全局选项参数说明

以下选项参数几乎可用于所有命令:

参数 简写 说明 示例
-ndf <文件路径> 添加 NDF 辅助数据文件,可重复使用多次指定多个 NDF 文件 -ndf data1.ndf -ndf data2.ndf
-table <表名> 指定目标表名,用于精确过滤恢复/导出 -table orders
-schema <Schema名> 指定目标 Schema 名,如 dbo、sales、hr 等 -schema sales
-user <用户名> 指定目标数据库用户名,用于按用户过滤 -user fgedu
-o <输出路径> 指定输出文件或输出目录路径 -o output.sql
-format <格式> 输出格式:sql(默认)或 dmp -format dmp
-log <级别> 日志级别:0=静默,1=信息(默认),2=调试 -log 2
-h--help -h 显示当前命令的帮助信息 -h
-v--version -v 显示程序版本号(与 version 命令等效) -v

7. 命令详解

7.1 信息查询类命令

7.1.1 help - 显示帮助信息

显示完整的命令列表、参数说明和使用示例。

fgsdu help
fgsdu ?          # 别名
fgsdu recover help  # 显示 recover 子命令的帮助

7.1.2 version - 显示版本信息

fgsdu version
fgsdu ver        # 别名
fgsdu -v         # 简写

7.1.3 info - 显示数据库文件信息

读取 MDF 文件头,显示数据库的基本属性信息,包括文件大小、页大小、总页数、SQLServer 版本、兼容级别、恢复模式、排序规则等。

fgsdu info mydb.mdf
fgsdu fileinfo mydb.mdf   # 别名
fgsdu info primary.mdf -ndf secondary.ndf   # 带 NDF 文件

输出示例说明

  • File:文件名
  • Size:文件总大小(KB / MB / GB)
  • Page Size:页大小,固定为 8192 字节(8KB)
  • Total Pages:文件总页数(= 文件大小 / 8KB)
  • File ID:文件 ID,MDF 通常为 1,NDF 从 2 开始递增
  • Database ID:数据库 ID
  • SQLServer Ver:检测到的 SQLServer 版本号
  • Compatibility:数据库兼容级别
  • Recovery Mode:恢复模式(FULL / BULK_LOGGED / SIMPLE)
  • Collation:数据库默认排序规则

7.1.4 scan - 列出所有表

遍历系统表,列出 MDF 文件中所有可识别的用户表及其基本信息。

fgsdu scan mydb.mdf
fgsdu ls mydb.mdf       # 别名
fgsdu list mydb.mdf     # 别名

输出包含:序号、Schema.表名、Object ID、估计行数。

7.1.5 query - 查询表结构

查询指定表的详细列定义。

fgsdu query mydb.mdf orders          # 查询 orders 表结构
fgsdu qry mydb.mdf orders            # 别名
fgsdu query mydb.mdf                 # 不指定表名时列出所有表的结构

输出包含:列序号、列名、数据类型、最大长度、是否可空。

7.1.6 stats - 数据库统计信息

显示数据库整体的统计汇总信息。

fgsdu stats mydb.mdf
fgsdu st mydb.mdf         # 别名

输出包含:表总数、总页数、各类页(数据页/索引页/IAM页/系统页)的数量、Ghost 记录数、估计总行数、数据总大小。

7.1.7 analyze - 深度结构分析

执行完整的数据库结构深度分析,生成详细报告。相比 stats 命令,analyze 会扫描所有页面,提供更全面的分析结果,包括页类型分布、碎片情况、孤立页检测、潜在问题识别等。

fgsdu analyze mydb.mdf
fgsdu ana mydb.mdf             # 别名
fgsdu analyze mydb.mdf -log 2  # 调试模式输出更详细信息

7.2 数据导出类命令

7.2.1 export - 导出表数据

导出指定表的数据为 SQL 或 DMP 格式。

# 基本用法:导出为 SQL(自动生成文件名 <table>.sql)
fgsdu export mydb.mdf orders

# 指定输出文件路径
fgsdu export mydb.mdf orders -o backup/orders.sql
fgsdu exp mydb.mdf orders -o backup/orders.sql   # 别名 exp
fgsdu dump mydb.mdf orders -o backup/orders.sql  # 别名 dump

# 导出为 DMP 二进制格式
fgsdu export mydb.mdf orders -format dmp -o backup/orders.dmp

# 导出所有表到单个文件
fgsdu export mydb.mdf all -o all_tables.sql

# 导出多个表(逗号分隔)
fgsdu export mydb.mdf "orders,customers,products" -o multi_tables.sql

# 带 NDF 文件导出
fgsdu export primary.mdf -ndf data.ndf orders -o orders.sql

7.2.2 unload - 全库一键抽取

数据库无法启动时的首选命令,自动遍历所有表,按用户名/Schema 生成目录树结构,每张表导出为独立文件。

# 全库抽取所有表(推荐用于数据库无法启动场景)
fgsdu unload production.mdf -o /backup/unload_all
fgsdu extract production.mdf -o /backup/unload_all    # 别名 extract

# 按用户名过滤,只导出指定用户的表
fgsdu unload production.mdf -user fgedu -o /backup/unload_fgedu

# 按 Schema 过滤,只导出指定 Schema 的表
fgsdu unload production.mdf -schema sales -o /backup/unload_sales

# 导出为 DMP 二进制格式
fgsdu unload production.mdf -user fgedu -format dmp -o /backup/unload_dmp

# 带多个 NDF 文件的全库抽取
fgsdu unload primary.mdf -ndf data1.ndf -ndf data2.ndf -o /backup/unload_multi

unload 生成的目录结构示例

unload_fgedu/
├── dbo_orders/
│   └── orders.sql                          # 每张表的 SQL 导出文件
├── dbo_customers/
│   └── customers.sql
├── sales_orders/
│   └── orders.sql
├── hr_employees/
│   └── employees.sql
└── unload_report.txt                       # 汇总报告(必看!)

unload_report.txt 包含的内容

  1. 导出基本信息:导出时间、源文件、过滤条件、导出格式
  2. 逐表导出状态列表:表名、状态(OK/FAIL)、列数、导出行数、输出文件路径
  3. 汇总统计:成功表数、失败表数、总导出数据行数、跳过的坏块数
  4. 完整的数据恢复步骤(7 步导入指南)
  5. 坏块详情报告(如有坏块)

7.3 数据恢复类命令

recover 命令是 FGSDU 的核心功能,包含 5 个子命令:

子命令 说明
recover delete 恢复 DELETE 删除的行
recover drop 恢复 DROP TABLE 删除的表
recover truncate 恢复 TRUNCATE TABLE 清空的数据
recover all 按顺序执行以上三种恢复
recover deep 深度穷举扫描,最大化恢复率

7.3.1 recover delete - DELETE 数据恢复

# 恢复所有被 DELETE 的行
fgsdu recover delete production.mdf -o deleted.sql
fgsdu rec delete production.mdf -o deleted.sql      # 别名 rec

# 只恢复指定表的已删除行
fgsdu recover delete production.mdf -table orders -o orders_deleted.sql

# 只恢复指定 Schema 的已删除行
fgsdu recover delete production.mdf -schema sales -o sales_deleted.sql

# 按用户+Schema+表名 组合精确过滤
fgsdu recover delete production.mdf -user admin -schema sales -table orders -o targeted.sql

# 导出为 DMP 格式
fgsdu recover delete production.mdf -format dmp -o deleted.dmp

7.3.2 recover drop - DROP TABLE 表恢复

# 恢复所有被 DROP 的表
fgsdu recover drop production.mdf -o dropped.sql
fgsdu rec drop production.mdf -o dropped.sql        # 别名

# 按 Schema 过滤恢复
fgsdu recover drop production.mdf -schema hr -o hr_dropped.sql

# 按用户过滤恢复
fgsdu recover drop production.mdf -user dbadmin -o admin_dropped.sql

# 组合过滤
fgsdu recover drop production.mdf -user hr_owner -schema hr -o hr_owner_dropped.sql

# 调试模式查看详细扫描过程
fgsdu recover drop production.mdf -o dropped.sql -log 2

7.3.3 recover truncate - TRUNCATE 数据恢复

# 恢复所有 TRUNCATE 清空的数据
fgsdu recover truncate production.mdf -o truncate.sql
fgsdu rec truncate production.mdf -o truncate.sql   # 别名

# 指定表名恢复
fgsdu recover truncate production.mdf -table orders -o orders_truncated.sql

# 指定 Schema 和表名
fgsdu recover truncate production.mdf -schema sales -table orders -o sales_orders.sql

# 带 NDF 文件
fgsdu recover truncate primary.mdf -ndf data.ndf -o truncate.sql

7.3.4 recover all - 全量恢复

按顺序执行 DELETE → DROP → TRUNCATE 三种恢复操作。

# 执行所有类型的恢复
fgsdu recover all production.mdf -o full_recovery.sql
fgsdu rec all production.mdf -o full_recovery.sql   # 别名

# 带 Schema 过滤的全量恢复
fgsdu recover all production.mdf -schema sales -o sales_recovery.sql

# 带用户+Schema 组合过滤
fgsdu recover all production.mdf -user hr_owner -schema hr -o hr_recovery.sql

# 带多个 NDF 文件
fgsdu recover all primary.mdf -ndf d1.ndf -ndf d2.ndf -o recovery.sql

# 导出为 DMP 格式
fgsdu recover all production.mdf -format dmp -o full_recovery.dmp

7.3.5 recover deep - 深度扫描恢复

穷举扫描 MDF/NDF 文件中的每一个页面,最大化数据恢复率。

# 完整深度扫描
fgsdu recover deep production.mdf -o deep_recovery.sql
fgsdu rec deep production.mdf -o deep_recovery.sql    # 别名

# 带过滤条件的深度扫描
fgsdu recover deep production.mdf -schema sales -o sales_deep.sql
fgsdu recover deep production.mdf -user admin -table orders -o targeted_deep.sql

# 调试模式输出详细信息
fgsdu recover deep production.mdf -o deep.sql -log 2

# 多文件深度扫描
fgsdu recover deep primary.mdf -ndf d1.ndf -ndf d2.ndf -o deep.sql

8. SQLServer数据恢复案例场景与操作过程

8.1 案例一:SQLServer生产数据库无法启动,全库紧急抢救

场景描述
某企业 SQLServer 生产数据库因意外断电导致 master 系统数据库损坏,SQLServer 服务无法启动,业务完全中断。运维人员紧急联系数据库管理员,要求在最短时间内恢复所有业务数据。DBA 尝试多种方法修复 master 库均失败,决定使用 FGSDU 直接从 MDF 文件中提取数据。

操作过程

步骤 1:立即停止操作并备份原始文件(至关重要!)

# 如果 SQLServer 服务还在异常运行,先强制停止
sudo systemctl stop mssql-server

# 将 MDF 和 NDF 文件复制到备份目录,所有操作都在副本上进行
# 假设数据文件位于 /var/opt/mssql/data/
cp /var/opt/mssql/data/production.mdf /backup/raw/production.mdf
cp /var/opt/mssql/data/production_log.ldf /backup/raw/production_log.ldf
cp /var/opt/mssql/data/production_data2.ndf /backup/raw/production_data2.ndf

# 对备份文件设置只读权限,防止误操作修改
chmod 444 /backup/raw/*

步骤 2:验证 MDF 文件可读取

# 使用 info 命令检查文件头是否有效
./fgsdu info /backup/raw/production.mdf -ndf /backup/raw/production_data2.ndf

预期输出显示正常的文件信息,包括 SQLServer 版本、页大小、总页数等。如果此步骤报错,说明文件头部严重损坏,需直接跳到深度扫描。

步骤 3:列出所有表,确认表结构可识别

./fgsdu scan /backup/raw/production.mdf -ndf /backup/raw/production_data2.ndf

记录输出的表列表,确认所有重要业务表都在列表中。

步骤 4:使用 unload 全库抽取(最快方式)

# 创建带时间戳的输出目录
OUTDIR="/backup/recovery/$(date +%Y%m%d_%H%M%S)_unload"
mkdir -p "$OUTDIR"

# 全库抽取,按 Schema 生成目录树
./fgsdu unload /backup/raw/production.mdf \
    -ndf /backup/raw/production_data2.ndf \
    -o "$OUTDIR" \
    2>&1 | tee "$OUTDIR/unload_console.log"

导出过程中会实时显示每张表的进度:

Unloading table: dbo.orders ... OK (15230 rows)
Unloading table: dbo.customers ... OK (5120 rows)
Unloading table: dbo.order_items ... OK (45670 rows)
...
Unload complete: 25 tables OK, 0 tables FAILED, 78932 total rows exported

步骤 5:查看汇总报告

cat "$OUTDIR/unload_report.txt"

重点查看报告中的:

  • Summary 部分确认所有表导出成功
  • Bad Block Report 检查是否有坏块
  • Recovery Steps 部分了解导入步骤

步骤 6:在新 SQLServer 实例上恢复数据

# 在新服务器上创建恢复数据库
sqlcmd -S newserver -E -Q "CREATE DATABASE recovered_prod;"

# 批量导入所有 SQL 文件
for sqlfile in "$OUTDIR"/*/*.sql; do
    echo "Importing: $sqlfile"
    sqlcmd -S newserver -E -d recovered_prod -i "$sqlfile"
done

# 验证表数量
sqlcmd -S newserver -E -d recovered_prod -Q "SELECT COUNT(*) AS TableCount FROM sys.tables;"

# 抽样验证几张关键表的行数
sqlcmd -S newserver -E -d recovered_prod -Q "
SELECT 'orders' AS TableName, COUNT(*) AS Rows FROM dbo.orders
UNION ALL
SELECT 'customers', COUNT(*) FROM dbo.customers
UNION ALL
SELECT 'order_items', COUNT(*) FROM dbo.order_items;
"

步骤 7:备份恢复后的数据库

sqlcmd -S newserver -E -Q "
BACKUP DATABASE recovered_prod
TO DISK = '/backup/recovered_prod_full.bak'
WITH FORMAT, COMPRESSION, STATS=10;
"

8.2 案例二:业务人员误 DELETE 删除SQLServer订单数据

场景描述
某业务人员在清理测试数据时,忘记加 WHERE 条件直接执行了 DELETE FROM sales.orders,导致 50000 多条有效订单被全部删除。发现后立即停止了应用程序的写入操作。

操作过程

步骤 1:立即停止所有写入操作

# 停止应用服务器(示例,根据实际部署调整)
sudo systemctl stop business-app

# 或在数据库层面设置数据库为只读
sqlcmd -S dbserver -E -Q "ALTER DATABASE salesdb SET READ_ONLY WITH ROLLBACK IMMEDIATE;"

步骤 2:立即备份 MDF 文件

# 先分离数据库(如果 SQLServer 还能访问)
sqlcmd -S dbserver -E -Q "
USE master;
GO
ALTER DATABASE salesdb SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
GO
EXEC sp_detach_db 'salesdb';
GO
"

# 复制 MDF 和 NDF 文件
cp /var/opt/mssql/data/salesdb.mdf /backup/raw/salesdb.mdf
cp /var/opt/mssql/data/salesdb_data.ndf /backup/raw/salesdb_data.ndf

# 操作完毕后可重新附加数据库恢复服务

步骤 3:精确恢复 sales.orders 表的已删除行

# 步骤 3A:先尝试精确恢复指定 Schema 和表
./fgsdu recover delete /backup/raw/salesdb.mdf \
    -ndf /backup/raw/salesdb_data.ndf \
    -schema sales \
    -table orders \
    -o /backup/recovery/sales_orders_deleted.sql \
    2>&1 | tee /backup/recovery/step3a.log

查看输出中的"Ghost rows extracted"和"Total rows recovered"数字,确认恢复的行数是否接近预期。

步骤 4:如果精确恢复数据量不足,执行全表 DELETE 恢复

./fgsdu recover delete /backup/raw/salesdb.mdf \
    -ndf /backup/raw/salesdb_data.ndf \
    -o /backup/recovery/all_deleted.sql \
    2>&1 | tee /backup/recovery/step4.log

步骤 5:如果仍有缺失,执行深度扫描

./fgsdu recover deep /backup/raw/salesdb.mdf \
    -ndf /backup/raw/salesdb_data.ndf \
    -schema sales \
    -table orders \
    -o /backup/recovery/sales_orders_deep.sql \
    2>&1 | tee /backup/recovery/step5.log

步骤 6:在测试数据库中验证恢复数据

# 创建测试数据库
sqlcmd -S testserver -E -Q "CREATE DATABASE recovery_test;"

# 导入恢复的 SQL 文件
sqlcmd -S testserver -E -d recovery_test -i /backup/recovery/sales_orders_deleted.sql

# 验证行数
sqlcmd -S testserver -E -d recovery_test -Q "SELECT COUNT(*) FROM sales.orders;"

# 抽样检查数据质量(查找有 WARNING 标记的问题行)
grep "WARNING" /backup/recovery/sales_orders_deleted.sql | head -20

步骤 7:将恢复的数据合并回生产库

确认数据无误后,由业务方确认,使用 INSERT INTO ... SELECT 将恢复的数据合并回生产表。

8.3 案例三:开发人员误 DROP TABLE 删除SQLServer员工表

场景描述
某开发人员在生产环境执行脚本时,误将 DROP TABLE hr.employees 语句带到了生产库执行,导致包含 20000 多条员工信息的表被删除。HR 部门紧急要求恢复数据。

操作过程

步骤 1:立即停止数据库操作

# 将数据库设为单用户模式,防止任何进一步的写入
sqlcmd -S dbserver -E -Q "
ALTER DATABASE hrdb SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
"

# 或直接停止 SQLServer 服务
sudo systemctl stop mssql-server

步骤 2:备份 MDF/NDF 文件

cp /data/hrdb.mdf /backup/raw/hrdb.mdf
cp /data/hrdb_data.ndf /backup/raw/hrdb_data.ndf
chmod 444 /backup/raw/*

步骤 3:使用 recover drop 恢复被 DROP 的表

# 按 hr Schema 过滤恢复
./fgsdu recover drop /backup/raw/hrdb.mdf \
    -ndf /backup/raw/hrdb_data.ndf \
    -schema hr \
    -o /backup/recovery/hr_dropped.sql \
    2>&1 | tee /backup/recovery/drop_restore.log

查看输出中的关键信息:

DROP Recovery Scan Complete:
  Orphan pages found: 280
  Found 1 potential dropped table(s)
  Table 1 (ObjectID: 123456):
    Data pages: 250
    Estimated rows: 21500
    Confidence: 82%
Extracting data... extracted 21487 rows
Recovery export completed: hr_dropped.sql
  Total rows recovered: 21487

步骤 4:如果 Schema 过滤恢复不完整,执行全局 DROP 恢复

./fgsdu recover drop /backup/raw/hrdb.mdf \
    -ndf /backup/raw/hrdb_data.ndf \
    -o /backup/recovery/all_dropped.sql

步骤 5:终极手段 - 深度扫描

./fgsdu recover deep /backup/raw/hrdb.mdf \
    -ndf /backup/raw/hrdb_data.ndf \
    -o /backup/recovery/hr_deep.sql -log 2

步骤 6:验证并导入恢复的数据

# 在测试库中创建 hr Schema
sqlcmd -S testserver -E -d TestDB -Q "CREATE SCHEMA hr;"

# 导入恢复的 SQL
sqlcmd -S testserver -E -d TestDB -i /backup/recovery/hr_dropped.sql

# 检查表是否存在及行数
sqlcmd -S testserver -E -d TestDB -Q "
SELECT name, type_desc, create_date
FROM sys.tables
WHERE schema_id = SCHEMA_ID('hr');

SELECT COUNT(*) AS EmpRows FROM hr.employees;
"

# 抽样检查员工数据
sqlcmd -S testserver -E -d TestDB -Q "SELECT TOP 10 * FROM hr.employees ORDER BY emp_id;"

步骤 7:业务方确认后恢复到生产库

由 HR 部门核对员工数据无误后,DBA 将表导回生产数据库,并重建索引、约束和权限。

8.4 案例四:误 TRUNCATE 清空SQLServer订单明细表

场景描述
运维人员在清理历史数据时,误对生产表执行了 TRUNCATE TABLE sales.order_items,导致 450000 多条订单明细数据被清空。TRUNCATE 操作执行极快,发现时操作已完成。

操作过程

步骤 1:立即停止所有写入(TRUNCATE 已释放页面,必须争分夺秒!)

# 立即停止应用和 SQLServer 服务
sudo systemctl stop business-app
sudo systemctl stop mssql-server

注意:TRUNCATE 会立即将所有数据页标记为"已释放",如果此时有新数据写入数据库,这些已释放的页面随时可能被分配给新数据,导致原始数据被永久覆盖。时间越短,恢复成功率越高!

步骤 2:备份文件

# 复制所有数据文件
cp /data/salesdb.mdf /backup/raw/salesdb.mdf
cp /data/salesdb_idx.ndf /backup/raw/salesdb_idx.ndf
cp /data/salesdb_archive.ndf /backup/raw/salesdb_archive.ndf

步骤 3:TRUNCATE 恢复

# 指定 Schema 和表名精确恢复
./fgsdu recover truncate /backup/raw/salesdb.mdf \
    -ndf /backup/raw/salesdb_idx.ndf \
    -ndf /backup/raw/salesdb_archive.ndf \
    -schema sales \
    -table order_items \
    -o /backup/recovery/order_items_truncated.sql \
    2>&1 | tee /backup/recovery/truncate.log

步骤 4:执行全量恢复(包含 DELETE/DROP/TRUNCATE 全部算法)

如果单独 TRUNCATE 恢复的数据量不足,继续执行:

./fgsdu recover all /backup/raw/salesdb.mdf \
    -ndf /backup/raw/salesdb_idx.ndf \
    -ndf /backup/raw/salesdb_archive.ndf \
    -schema sales \
    -o /backup/recovery/sales_all.sql

步骤 5:深度扫描

./fgsdu recover deep /backup/raw/salesdb.mdf \
    -ndf /backup/raw/salesdb_idx.ndf \
    -ndf /backup/raw/salesdb_archive.ndf \
    -schema sales \
    -table order_items \
    -o /backup/recovery/order_items_deep.sql

步骤 6:验证数据,去重后导入生产库

TRUNCATE 恢复的数据可能存在重复行,导入后需执行去重。

-- 导入后去重
;WITH CTE AS (
    SELECT *, ROW_NUMBER() OVER (
        PARTITION BY order_id, product_id  -- 按实际主键或唯一列
        ORDER BY (SELECT 0)
    ) AS rn
    FROM sales.order_items
)
DELETE FROM CTE WHERE rn > 1;
GO

8.5 案例五:磁盘存在坏块的SQLServer数据库文件恢复

场景描述
某数据库服务器的 RAID 卡出现故障,导致部分磁盘坏道。SQLServer 服务频繁报错,尝试 DBCC CHECKDB 修复失败,大量页面报告一致性错误。需要在磁盘彻底损坏前尽可能抢救数据。

操作过程

步骤 1:创建磁盘镜像(建议优先使用 ddrescue)

# 使用 ddrescue 创建磁盘级镜像,跳过坏块
# ddrescue /dev/sdb1 /backup/raw/disk_image.img /backup/raw/rescue.log

# 如果没有 ddrescue,直接复制文件,遇到坏块操作系统会报错跳过
cp --sparse=always /data/production.mdf /backup/raw/production.mdf
cp --sparse=always /data/production.ndf /backup/raw/production.ndf

步骤 2:使用 FGSDU 全库抽取(自动跳过坏块)

./fgsdu unload /backup/raw/production.mdf \
    -ndf /backup/raw/production.ndf \
    -o /backup/recovery/unload_with_badblocks \
    2>&1 | tee /backup/recovery/unload.log

执行过程中会看到类似警告:

Warning: Skipping bad block page 12345 (all-zero data)
Warning: Skipping bad block page 23456 (invalid page type: 0)
Note: Page 34567 read succeeded on retry 2
Warning: Skipping bad block page 45678 (Read failed after 3 attempts: IO error)

步骤 3:查看坏块报告

cat /backup/recovery/unload_with_badblocks/unload_report.txt

查看末尾的 Bad Block Report 部分:

Bad Block Report
========================================
Total bad blocks encountered: 5
Total pages skipped (with retries): 5
Strategy: All corrupted blocks were SKIPPED to maximize data recovery

Details:
  Page 12345 (file 0): All-zero page data (disk corruption) (skipped 1 time)
  Page 23456 (file 0): Invalid page type in header (skipped 1 time)
  Page 45678 (file 0): Read failed after 3 attempts: IO error (skipped 1 time)
  ...

步骤 4:对导出失败的表单独尝试深度扫描

# 假设 unload_report 显示 dbo.big_table 导出失败或行数严重偏少
./fgsdu recover deep /backup/raw/production.mdf \
    -ndf /backup/raw/production.ndf \
    -schema dbo \
    -table big_table \
    -o /backup/recovery/big_table_deep.sql \
    -log 2

步骤 5:执行全量恢复和深度扫描,尽量找回更多数据

./fgsdu recover all /backup/raw/production.mdf \
    -ndf /backup/raw/production.ndf \
    -o /backup/recovery/full_recovery.sql

./fgsdu recover deep /backup/raw/production.mdf \
    -ndf /backup/raw/production.ndf \
    -o /backup/recovery/deep_scan.sql

8.6 案例六:SQLServer多 Schema 数据库按业务分批恢复

场景描述
某大型数据库包含多个业务 Schema(dbo、sales、hr、finance、inventory、crm),数据库损坏后需要按业务线的优先级分批恢复,优先恢复最关键的销售和财务数据。

操作过程

#!/bin/bash
# batch_recovery_by_priority.sh - 按优先级分批恢复脚本

MDF="/backup/raw/production.mdf"
NDF1="/backup/raw/prod_data1.ndf"
NDF2="/backup/raw/prod_data2.ndf"
BASEOUT="/backup/recovery/$(date +%Y%m%d_%H%M%S)"
FGSDU="/opt/fgsdu/fgsdu"

echo "=== FGSDU 按优先级分批恢复 ==="
echo "开始时间: $(date)"
echo "输出根目录: $BASEOUT"
echo ""

# === 第一优先级:销售 (sales) 和 财务 (finance) ===
echo "[优先级 1/3] 恢复销售和财务数据..."
for SCHEMA in sales finance; do
    OUTDIR="$BASEOUT/priority1_$SCHEMA"
    mkdir -p "$OUTDIR"
    echo "  恢复 Schema: $SCHEMA"
   
    echo "    - 全库抽取..."
    $FGSDU unload "$MDF" -ndf "$NDF1" -ndf "$NDF2" \
        -schema "$SCHEMA" -o "$OUTDIR/unload" > "$OUTDIR/unload.log" 2>&1
   
    echo "    - 全量恢复 (DELETE+DROP+TRUNCATE)..."
    $FGSDU recover all "$MDF" -ndf "$NDF1" -ndf "$NDF2" \
        -schema "$SCHEMA" -o "$OUTDIR/recovery_all.sql" > "$OUTDIR/recovery.log" 2>&1
   
    echo "    - 深度扫描..."
    $FGSDU recover deep "$MDF" -ndf "$NDF1" -ndf "$NDF2" \
        -schema "$SCHEMA" -o "$OUTDIR/deep_scan.sql" > "$OUTDIR/deep.log" 2>&1
done

# === 第二优先级:CRM 和 库存 (inventory) ===
echo ""
echo "[优先级 2/3] 恢复 CRM 和库存数据..."
for SCHEMA in crm inventory; do
    OUTDIR="$BASEOUT/priority2_$SCHEMA"
    mkdir -p "$OUTDIR"
    echo "  恢复 Schema: $SCHEMA"
    $FGSDU unload "$MDF" -ndf "$NDF1" -ndf "$NDF2" \
        -schema "$SCHEMA" -o "$OUTDIR/unload" > "$OUTDIR/unload.log" 2>&1
    $FGSDU recover all "$MDF" -ndf "$NDF1" -ndf "$NDF2" \
        -schema "$SCHEMA" -o "$OUTDIR/recovery_all.sql" > "$OUTDIR/recovery.log" 2>&1
done

# === 第三优先级:HR 和 dbo ===
echo ""
echo "[优先级 3/3] 恢复 HR 和公共 dbo 数据..."
for SCHEMA in hr dbo; do
    OUTDIR="$BASEOUT/priority3_$SCHEMA"
    mkdir -p "$OUTDIR"
    echo "  恢复 Schema: $SCHEMA"
    $FGSDU unload "$MDF" -ndf "$NDF1" -ndf "$NDF2" \
        -schema "$SCHEMA" -o "$OUTDIR/unload" > "$OUTDIR/unload.log" 2>&1
    $FGSDU recover all "$MDF" -ndf "$NDF1" -ndf "$NDF2" \
        -schema "$SCHEMA" -o "$OUTDIR/recovery_all.sql" > "$OUTDIR/recovery.log" 2>&1
done

echo ""
echo "=== 全部恢复完成 ==="
echo "结束时间: $(date)"
echo "所有文件在: $BASEOUT"
find "$BASEOUT" -type f -name "*.sql" -o -name "*.txt" -o -name "*.log" | sort

8.7 案例七:使用导出的 SQL 文件恢复到SQLServer新数据库(完整流程)

场景描述
已使用 FGSDU 导出了若干 SQL 文件,现在需要将这些文件中的数据导入到新搭建的 SQLServer 数据库中。

操作过程

步骤 1:阅读导出 SQL 文件头部的恢复步骤

每个由 FGSDU 导出的 SQL 文件头部都包含了完整的恢复步骤说明和校验报告。在执行前务必先阅读:

head -80 /backup/recovery/dbo_orders.sql

重点关注:

  • 表结构定义(CREATE TABLE 语句)
  • 数据完整性校验报告的通过率和违规详情
  • 内嵌的 7 步恢复指南

步骤 2:创建目标数据库

-- 在 SSMS 中执行,或通过 sqlcmd
CREATE DATABASE recovered_database
ON PRIMARY (
    NAME = recovered_database_data,
    FILENAME = '/var/opt/mssql/data/recovered_database.mdf',
    SIZE = 10GB,
    MAXSIZE = 100GB,
    FILEGROWTH = 1GB
)
LOG ON (
    NAME = recovered_database_log,
    FILENAME = '/var/opt/mssql/data/recovered_database_log.ldf',
    SIZE = 2GB,
    MAXSIZE = 50GB,
    FILEGROWTH = 512MB
);
GO

ALTER DATABASE recovered_database SET RECOVERY SIMPLE;
GO

USE recovered_database;
GO

步骤 3:创建需要的 Schema

如果恢复的表不属于 dbo Schema,需要先手动创建 Schema:

CREATE SCHEMA sales;
GO
CREATE SCHEMA hr;
GO
CREATE SCHEMA finance;
GO

步骤 4:执行 SQL 文件导入数据

方式 A - 使用 SSMS:

  1. 打开 SQL Server Management Studio
  2. 连接到目标数据库实例
  3. 点击"文件" → "打开" → "文件",选择导出的 SQL 文件
  4. 确认当前数据库为 recovered_database
  5. 点击"执行"按钮(F5)运行脚本

方式 B - 使用 sqlcmd 命令行(推荐用于自动化和大文件):

# 基本用法
sqlcmd -S dbserver -E -d recovered_database -i /backup/recovery/dbo_orders.sql

# 带用户名密码(SQL Server 认证模式)
sqlcmd -S dbserver -U sa -P 'YourPassword' -d recovered_database \
    -i /backup/recovery/dbo_orders.sql

# 设置大文件批次参数
sqlcmd -S dbserver -E -d recovered_database \
    -i /backup/recovery/dbo_big_table.sql \
    -b -o import_big_table.log

方式 C - 批量导入多个 SQL 文件(Shell 脚本):

#!/bin/bash
SQLDIR="/backup/recovery/unload_all"
SERVER="dbserver"
DB="recovered_database"

for sqldir in "$SQLDIR"/*/; do
    sqlfile=$(find "$sqldir" -maxdepth 1 -name "*.sql" | head -1)
    if [ -f "$sqlfile" ]; then
        echo "Importing: $(basename $sqldir) -> $sqlfile"
        sqlcmd -S "$SERVER" -E -d "$DB" -i "$sqlfile" \
            -o "${sqlfile}.import.log"
        if [ $? -eq 0 ]; then
            echo "  OK"
        else
            echo "  FAILED - see ${sqlfile}.import.log"
        fi
    fi
done

步骤 5:验证数据导入结果

-- 1. 验证所有表都已创建
SELECT
    SCHEMA_NAME(schema_id) AS SchemaName,
    name AS TableName,
    create_date
FROM sys.tables
ORDER BY SchemaName, TableName;

-- 2. 验证每张表的行数(与导出时对比)
SELECT
    SCHEMA_NAME(t.schema_id) + '.' + t.name AS TableName,
    p.rows AS RowCounts
FROM sys.tables t
INNER JOIN sys.partitions p ON t.object_id = p.object_id
WHERE p.index_id IN (0, 1)
ORDER BY TableName;

-- 3. 抽样检查数据质量
SELECT TOP 100 * FROM dbo.orders ORDER BY order_id DESC;
SELECT TOP 100 * FROM hr.employees ORDER BY emp_id;

-- 4. 检查是否有完整性问题的行(搜索 WARNING 标记)
-- 可在 SQL 文件中查找包含 WARNING 注释的 INSERT 行

步骤 6:去除重复行

恢复的数据(尤其是 DELETE 和深度扫描恢复的)可能包含重复行,需要去重:

-- 通用去重脚本(按实际主键修改 PARTITION BY 列)
DECLARE @TableName NVARCHAR(200) = 'dbo.orders';
DECLARE @KeyColumns NVARCHAR(500) = 'order_id';  -- 主键或唯一列

DECLARE @SQL NVARCHAR(MAX) = N'
;WITH CTE AS (
    SELECT *,
        ROW_NUMBER() OVER (
            PARTITION BY ' + @KeyColumns + '
            ORDER BY (SELECT 0)
        ) AS rn
    FROM ' + @TableName + '
)
DELETE FROM CTE WHERE rn > 1;
';

EXEC sp_executesql @SQL;
PRINT 'Deduplication completed for ' + @TableName;
GO

步骤 7:重建索引和约束

FGSDU 导出的 SQL 通常只包含基本的 CREATE TABLE 和 INSERT,不包含索引、主键、外键等约束(因为恢复场景下表结构可能不完整)。数据验证无误后,需要根据原始数据库设计重建:

-- 示例:重建主键和索引
ALTER TABLE dbo.orders
ADD CONSTRAINT PK_orders PRIMARY KEY CLUSTERED (order_id);
GO

CREATE NONCLUSTERED INDEX IX_orders_customer_id
ON dbo.orders(customer_id);
GO

CREATE NONCLUSTERED INDEX IX_orders_order_date
ON dbo.orders(order_date);
GO

-- 重建外键
ALTER TABLE dbo.order_items
ADD CONSTRAINT FK_order_items_orders
FOREIGN KEY (order_id) REFERENCES dbo.orders(order_id);
GO

步骤 8:备份恢复后的数据库

-- 完整备份
BACKUP DATABASE recovered_database
TO DISK = '/backup/recovered_database_FULL.bak'
WITH
    FORMAT,
    COMPRESSION,
    STATS = 5,
    DESCRIPTION = 'Post-recovery full backup - FGSDU';
GO

9. 数据恢复原理说明

9.1 SQLServer 存储基础

SQLServer 数据库的物理存储基于以下核心概念:

页(Page):SQLServer 中最基本的 I/O 单元,固定大小为 8KB(8192 字节)。每个页都有一个 96 字节的页头,记录页类型、ObjectID、LSN、前后页指针等关键信息。

区(Extent):由 8 个连续的页组成,共 64KB。区是空间分配的基本单位。

GAM 页(Global Allocation Map):全局分配映射页,每 4GB 数据空间一个 GAM 页,用位图记录哪些区已被分配。

SGAM 页(Shared Global Allocation Map):共享全局分配映射页,记录哪些区是共享区(包含来自多个对象的页)。

IAM 页(Index Allocation Map):索引分配映射页,每个表或索引至少有一个 IAM 页,记录表占用的所有区和页。

系统表

  • sysobjects:存储所有数据库对象(表、视图、存储过程等)的元数据
  • syscolumns:存储所有列的定义(列名、数据类型、长度、是否可空)
  • systypes:存储数据类型定义

9.2 DELETE 恢复原理详解

第一阶段:LSN 事务日志扫描
扫描所有数据页的页头 LSN(日志序列号)。LSN 越大表示页面最近被修改的时间越近。通过定位高 LSN 的页面,可快速找出近期发生删除操作的热点页。

第二阶段:Ghost 记录扫描
SQLServer 执行 DELETE 时,并不会立即清除行数据,而是将行头的状态位标记为 Ghost Record(幽灵记录)。FGSDU 逐页扫描,识别状态位被标记为已删除但数据内容仍然完整的 Ghost 记录,将其完整提取出来。

第三阶段:页链遍历
通过每个数据页头中的 prevPage 和 nextPage 指针,从对象的第一个页开始,顺着双向链表遍历完整的页链。这样可以确保不遗漏链上任何一个页面的 Ghost 记录。

第四阶段:自动去重
由于同一行数据可能在多个阶段被多次检测到(例如 LSN 扫描和 Ghost 扫描都找到了),FGSDU 会根据 ObjectID + 主键列值 + 行内容哈希进行智能去重,确保最终输出的每一行都是唯一的。

9.3 DROP TABLE 恢复原理详解

第一阶段:sysobjects 残留扫描
当执行 DROP TABLE 时,SQLServer 会从 sysobjects 和 syscolumns 中删除表的元数据记录。但在存储层面,这些被删除的系统记录本身可能以 Ghost 记录的形式仍然残留在系统表数据页中。FGSDU 专门扫描系统页的 Ghost 记录,尝试找回被 DROP 表的原始元数据(表名、列定义等)。

第二阶段:孤立页检测与归类
遍历所有数据页,检查页头中的 ObjectID。如果某个 ObjectID 在当前的 sysobjects 中已经找不到对应的表定义,那么该页就是一个"孤立页",很可能属于被 DROP 的表。将具有相同 ObjectID 的孤立数据页归集到一起,就形成了候选的被删除表。

第三阶段:页链遍历重建
从检测到的每个 ObjectID 的起始页开始,通过 prev/next 页指针沿着双向链表遍历,将属于同一表的所有数据页完整串连起来,恢复表的完整数据页集合。

第四阶段:Schema 自动推断
如果无法通过系统表残留获取到表结构(即使用户已清空回收站或系统页也被覆盖),FGSDU 会启动 Schema 推断引擎:

  1. 从样本记录中解析记录头:固定长度、列数、NULL 位图、可变长度偏移数组
  2. 根据固定长度字段的字节宽度,匹配最可能的数据类型(如 4 字节匹配 INT,8 字节匹配 BIGINT/DATETIME/MONEY)
  3. 对可变长度字段,根据内容特征(是否全 ASCII、是否含 Unicode BOM、是否数字/日期格式)推断 VARCHAR/NVARCHAR 等类型
  4. 生成通用列名 col_1、col_2、col_3 等,并输出 best-effort 的 INSERT 语句

9.4 TRUNCATE 恢复原理详解

TRUNCATE TABLE 的执行过程是:立即释放表的所有区(更新 GAM/SGAM 位图标记为可用)、清空 IAM 页链、更新系统表中的页计数元数据。TRUNCATE 不逐行删除,因此不产生 DELETE 日志,也不会留下 Ghost 记录。但它也有一个弱点:只是在分配位图上"标记区为可用",并没有实际清除区中页面上的数据内容。在这些已释放的页面被 SQLServer 重新分配给新对象并写入新数据之前,原表的数据仍然完好地保存在磁盘上。

第一阶段:GAM/SGAM 位图解析
解析所有 GAM 页和 SGAM 页的位图,找出当前被标记为"未分配/可用"的区。这些被释放的区就是 TRUNCATE 操作后原表数据所在的位置候选集。

第二阶段:释放区穷举扫描
对第一步找出的所有已释放区中的每一个页面,逐一读取并检查页头。如果某个页面的页头仍然包含有效的 ObjectID、有效的页类型(DATA_PAGE)、并且 slot 数组不为空,说明该页面仍然包含残留的数据。

第三阶段:高水位定位与关联
通过分析已释放页面在文件中的物理分布规律,结合 IAM 页的残留痕迹,推断 TRUNCATE 前表的高水位标记(HWM,High Water Mark)位置,确定原表数据页的大致分布范围。

第四阶段:残留页数据提取
从所有包含有效数据的已释放页中,完整提取 slot 数组指向的每一条记录。由于此时通常已无元数据可用,提取的数据会配合 Schema 推断机制输出。


10. 常用问题与排查

10.1 程序运行类问题

Q1:运行 FGSDU 报错 "Error: Cannot open MDF file - Permission denied"

原因分析:当前操作系统用户对指定的 MDF/NDF 文件没有读取权限。这在从 SQLServer 默认数据目录(如 /var/opt/mssql/data/)读取时非常常见,因为这些目录通常只有 mssql 用户有读取权限。

解决方案

# 方案 1:先将文件复制到当前用户可访问的目录,再操作(推荐!)
sudo cp /var/opt/mssql/data/mydb.mdf ~/
sudo cp /var/opt/mssql/data/mydb.ndf ~/
sudo chown $(whoami):$(whoami) ~/mydb.mdf ~/mydb.ndf
./fgsdu info ~/mydb.mdf

# 方案 2:使用 sudo 运行 FGSDU
sudo ./fgsdu info /var/opt/mssql/data/mydb.mdf

# 方案 3:临时添加文件读取权限(操作完改回去)
sudo chmod 644 /var/opt/mssql/data/mydb.mdf
./fgsdu info /var/opt/mssql/data/mydb.mdf
sudo chmod 600 /var/opt/mssql/data/mydb.mdf  # 恢复原权限

Q2:报错 "Error: Invalid MDF file format" 或 "Bad magic number in file header"

原因分析:程序无法识别指定文件为有效的 SQLServer MDF 格式。可能的原因包括:

  1. 文件不是真正的 MDF 文件(例如误传了 LDF 日志文件、BAK 备份文件或其他文件)
  2. 文件头部被完全破坏(前 8KB 数据损坏)
  3. 文件在传输或复制过程中被截断(大小不完整)

排查步骤

# 1. 确认文件扩展名和实际类型
file /path/to/file.mdf
# MDF 文件通常显示为:"data" 或 "MS SQL Server Database"

# 2. 检查文件前几个字节的十六进制(MDF 以特定签名开头)
xxd /path/to/file.mdf | head -5

# 3. 检查文件大小是否为 8KB 的整数倍
ls -l /path/to/file.mdf
# 文件大小 (bytes) / 8192 应该是整数,且余数为 0

# 4. 确认不是 LDF 日志文件
# LDF 文件的格式完全不同,FGSDU 不处理 LDF

解决方案

  • 如果误传了 LDF 或 BAK 文件,请提供正确的 MDF 主数据文件
  • 如果文件被截断,请从原始位置重新复制完整文件
  • 如果文件头完全损坏,尝试使用 recover deep 深度扫描,即使头部损坏,文件后半部分的正常数据页仍可能被提取

Q3:Linux 运行时提示 "GLIBC_2.xx not found"

原因分析:当前系统的 GLIBC 版本低于编译 FGSDU 时使用的 GLIBC 版本。

排查步骤

# 查看当前系统 GLIBC 版本
ldd --version

# 查看 FGSDU 所需的 GLIBC 符号
readelf -V ./fgsdu | grep GLIBC

解决方案

# 方案 1(推荐):使用项目提供的 Docker 构建完全静态的 musl 版本
cd FGSDU
make docker
# build/fgsdu 为完全静态版本,无任何 GLIBC 依赖

# 方案 2:如果安装了 musl-gcc,直接编译
make musl

# 方案 3:在目标系统上本地重新编译
cd FGSDU
make clean && make

Q4:扫描或恢复速度非常慢,长时间没有输出

可能原因和对应解决方法

原因 现象 解决方法
MDF 文件很大(> 100GB)且使用机械硬盘 进度缓慢但持续前进 耐心等待;或将 MDF 迁移到 SSD;使用 -log 0 减少输出
磁盘存在大量坏块 频繁出现 "Retrying read" 提示 正常现象,坏块需要多次重试;先用 ddrescue 做磁盘镜像再操作
使用了深度扫描模式 进度条缓慢移动 正常现象,深度扫描会检查每一个页面的每一个字节;大文件可能需要数小时
操作系统缓冲区不足 磁盘 I/O 100% 但 CPU 使用率不高 增加系统内存;或调整 vm.dirty_ratio 等内核参数
输出文件写到了同一个慢速磁盘上 读写竞争导致瓶颈 将源 MDF 和输出文件放在不同物理磁盘上

性能优化建议

# 1. 将输出重定向到 SSD 上的不同物理磁盘
./fgsdu recover deep /mnt/hdd/bigdb.mdf -o /mnt/ssd/output.sql

# 2. 使用静默模式减少控制台 I/O
./fgsdu recover all /mnt/hdd/bigdb.mdf -o output.sql -log 0

# 3. 导出为 DMP 格式减少写入量
./fgsdu unload bigdb.mdf -format dmp -o /mnt/ssd/output_dmp

10.2 数据恢复效果类问题

Q5:DELETE 恢复只找回了极少部分数据,大部分丢失的数据没找到

可能原因与排查步骤

  1. 时间过久导致 Ghost 记录已被清理

    • SQLServer 后台有 Ghost Cleanup 线程会定期清理已删除的记录
    • 删除操作后经过数小时或数据库活动频繁,Ghost 记录大概率已被清理
    • 解决方法:尝试 recover deep 深度扫描
  2. 删除后数据库经过了收缩(DBCC SHRINKFILE)

    • 收缩操作会移动和重写页面,彻底覆盖已释放的页面
    • 这是导致 DELETE 恢复失败的最主要原因之一
    • 解决方法:深度扫描可能还能找到未被覆盖的零散页面,但恢复率会显著降低
  3. 已删除的页面被新数据重用了

    • 如果 DELETE 后有大量 INSERT/UPDATE 操作,原来的空闲页会被分配给新数据
    • 解决方法:确认删除后是否立即停止了写入;如果没有,尽快尝试深度扫描
  4. 过滤条件不正确

    • 指定的 -schema-table 名称与实际不符(注意大小写敏感设置)
    • 解决方法:先不加过滤条件运行 recover delete,看输出中是否能找到目标表

恢复顺序建议

精确 recover delete (带-schema/-table)
    ↓ 不够
全量 recover delete (不带过滤)
    ↓ 不够
recover all (DELETE+DROP+TRUNCATE)
    ↓ 不够
recover deep (终极方案)

Q6:DROP TABLE 恢复后表名显示为 ObjectID 或 inferred_table,列名显示为 col_1、col_2

原因分析:DROP 恢复找不到被删表的元数据(sysobjects/syscolumns 残留),只能通过孤立页检测。此时没有表名和列名信息,FGSDU 只能用:

  • ObjectID 作为临时表名(如 ObjectID_123456)
  • col_1、col_2、col_3 等作为推断列名

这是正常现象,不代表恢复失败。数据内容本身是正确的,只是缺少了人类可读的名称。

解决方案

/* 在导入到测试数据库后,根据业务知识手动重命名 */

-- 1. 先检查表中的推断列数据
SELECT TOP 50 * FROM dbo.ObjectID_123456;

-- 2. 根据数据内容人工判断列含义后重命名
EXEC sp_rename 'dbo.ObjectID_123456', 'hr.employees', 'OBJECT';
EXEC sp_rename 'hr.employees.col_1', 'emp_id', 'COLUMN';
EXEC sp_rename 'hr.employees.col_2', 'emp_name', 'COLUMN';
EXEC sp_rename 'hr.employees.col_3', 'department', 'COLUMN';
EXEC sp_rename 'hr.employees.col_4', 'hire_date', 'COLUMN';
EXEC sp_rename 'hr.employees.col_5', 'salary', 'COLUMN';
GO

-- 3. 修改列的数据类型(推断可能不准确,需要根据业务调整)
ALTER TABLE hr.employees ALTER COLUMN emp_id INT NOT NULL;
ALTER TABLE hr.employees ALTER COLUMN emp_name NVARCHAR(100);
ALTER TABLE hr.employees ALTER COLUMN hire_date DATETIME;
ALTER TABLE hr.employees ALTER COLUMN salary DECIMAL(18, 2);
GO

-- 4. 数据验证
SELECT TOP 10 * FROM hr.employees ORDER BY emp_id;

Q7:TRUNCATE 恢复几乎没有恢复出任何数据

原因分析:TRUNCATE 是三种恢复中难度最高、成功率最低的。TRUNCATE 执行后,SQLServer 立即将所有数据页标记为"空闲",这些空闲页随时可能被新的写操作覆盖。如果 TRUNCATE 后数据库继续运行了一段时间,即使只有少量写入,也可能导致大部分原始数据页被覆盖。

关键时间窗口

  • TRUNCATE 后立即停止所有写入(几分钟内):恢复成功率 50-70%
  • TRUNCATE 后 1 小时内仍有写入:成功率可能降到 10% 以下
  • TRUNCATE 后经过大量写入或收缩:成功率接近 0

建议的操作顺序

# 按顺序尝试,每一步都单独保存输出
Step 1: ./fgsdu recover truncate mdf -schema XX -table YY -o step1_truncate.sql
Step 2: ./fgsdu recover all mdf -schema XX -table YY -o step2_all.sql
Step 3: ./fgsdu recover deep mdf -schema XX -table YY -o step3_deep.sql

将三步的结果汇总导入测试库,合并后去重。

Q8:导出的 SQL 文件中有大量带 "-- WARNING" 标记的行

原因说明:这是正常且预期的行为,不是程序故障。这些 WARNING 行是 5 重完整性校验发现的有问题的行,说明这些行在某些校验项上未通过。FGSDU 选择将这些行仍然导出(而不是丢弃),并在前面加上 WARNING 注释明确标记,让用户可以人工判断这些行是否可用。

典型的 WARNING 示例:

-- WARNING: Row 47 has integrity issues (NULL violations: 1, Length violations: 1)
INSERT INTO dbo.orders (order_id, customer_id, amount, remark)
VALUES (47, NULL, 100.50, 'This is a very long remark text that exceeds the max length limit of 50 characters...');

如何处理这些 WARNING 行

-- 步骤 1:统计有问题的行数和类型
-- 直接在 SQL 文件中 grep 或导入后查询
SELECT
    SUM(CASE WHEN customer_id IS NULL THEN 1 ELSE 0 END) AS NullCustomerID,
    SUM(CASE WHEN LEN(remark) > 50 THEN 1 ELSE 0 END) AS LongRemark,
    COUNT(*) AS TotalRows
FROM dbo.orders;

-- 步骤 2:根据业务规则处理
-- 示例 1:customer_id 为 NULL 的行可能是部分恢复的,与业务方确认后可能需要丢弃
DELETE FROM dbo.orders WHERE customer_id IS NULL;

-- 示例 2:remark 超长,截断后保留
UPDATE dbo.orders SET remark = LEFT(remark, 50) WHERE LEN(remark) > 50;

-- 示例 3:如果业务能容忍部分问题,可保留这些行不做处理

10.3 数据导入类问题

Q9:在 SSMS 中执行导出的 SQL 文件报错 "Insufficient memory" 或脚本执行超时

原因分析:导出的 SQL 文件过大(几百 MB 或几个 GB),SSMS 的编辑器无法一次性加载或执行超时。

解决方案

# 方案 1(推荐):使用 sqlcmd 命令行执行,支持大文件
sqlcmd -S servername -E -d recovered_db -i huge_export.sql -b -o import.log

# 方案 2:使用 sqlcmd 设置更长的超时时间
sqlcmd -S servername -E -d recovered_db -i huge_export.sql \
    -t 0 -b    # -t 0 = 无查询超时限制

# 方案 3:导出时使用 DMP 格式,然后用 BCP 高速导入
# 步骤 A:导出为 DMP
./fgsdu export mydb.mdf orders -format dmp -o orders.dmp
# 步骤 B:在新库中建表(从 .recovery_steps.txt 中复制 CREATE TABLE)
# 步骤 C:用 BCP 原生导入
bcp recovered_db.dbo.orders in orders.dmp -S servername -T -n -b 10000

# 方案 4:将大 SQL 文件按行拆分后分批导入
# 使用 split 命令拆分(每个文件 10 万行)
split -l 100000 -d huge_export.sql part_
# 然后逐个执行拆分后的文件

Q10:导入后发现大量重复行,怎么办?

原因分析:重复行在以下恢复场景中是可能出现的:

  • DELETE 恢复:同一行可能在多个扫描阶段都被找到(LSN、Ghost、页链)
  • DROP/TRUNCATE 恢复:数据可能同时存在于正常页和孤立已释放页中
  • 深度扫描:会尽可能多提取,可能造成重复

解决方案:执行去重 SQL(根据实际表的主键或唯一列调整):

-- 方法 A:有主键或唯一列时(推荐)
-- 假设主键是 order_id
;WITH CTE_Dedup AS (
    SELECT
        *,
        ROW_NUMBER() OVER (
            PARTITION BY order_id   -- 按主键去重
            ORDER BY (SELECT 0)     -- 保留第一条
        ) AS rn
    FROM dbo.orders
)
DELETE FROM CTE_Dedup WHERE rn > 1;
GO
PRINT 'Deduplicated by primary key';

-- 方法 B:没有主键时,按所有列值去重
;WITH CTE_Dedup AS (
    SELECT
        *,
        ROW_NUMBER() OVER (
            PARTITION BY col_1, col_2, col_3, col_4  -- 按所有列
            ORDER BY (SELECT 0)
        ) AS rn
    FROM dbo.inferred_table
)
DELETE FROM CTE_Dedup WHERE rn > 1;
GO

-- 方法 C:使用 SELECT DISTINCT INTO 重建表
-- 适用于没有主键且列数不多的情况
SELECT DISTINCT *
INTO dbo.orders_deduped
FROM dbo.orders;
GO
-- 验证行数差异
SELECT
    (SELECT COUNT(*) FROM dbo.orders) AS OriginalRows,
    (SELECT COUNT(*) FROM dbo.orders_deduped) AS DedupedRows;
GO

10.4 跨平台使用类问题

Q11:在 CentOS 5 上编译运行失败

解决方案

# 推荐方法:在新系统上用 Docker 编译出静态二进制,复制到 CentOS 5 上
# 在新系统(如 Ubuntu 22.04)上执行:
cd FGSDU
make docker
# 将 build/fgsdu 复制到 CentOS 5,直接运行即可,无需任何依赖

# 验证二进制兼容性(在 CentOS 5 上执行)
file ./fgsdu
./fgsdu version

Q12:Windows 版本运行报错 "缺少 MSVCR*.dll" 或 "无法启动此程序,因为计算机中丢失 ..."

原因分析:使用了动态编译的 Windows 版本,但目标系统缺少对应版本的 Visual C++ 运行库。

解决方案

  • 使用 make win 编译的版本是静态链接的,不依赖任何运行库,应直接使用该版本
  • 如果是其他来源的动态编译版本,安装对应版本的 Visual C++ Redistributable Package,或改用静态版本

11. 附录

附录 A:命令速查表

# =====================================================
# 一、信息查询命令
# =====================================================
fgsdu help                                显示完整帮助
fgsdu ?                                   帮助别名
fgsdu version / ver / -v                  显示版本号
fgsdu scan / ls / list <mdf>              列出所有表
fgsdu info / fileinfo <mdf>               显示文件头信息
fgsdu query / qry <mdf> <table>           查询表结构
fgsdu stats / st <mdf>                    显示数据库统计
fgsdu analyze / ana <mdf>                 深度结构分析
fgsdu analyze <mdf> -log 2                调试级深度分析

# =====================================================
# 二、数据导出命令
# =====================================================
fgsdu export / exp / dump <mdf> <table>   导出表为SQL
fgsdu export <mdf> <table> -o <file>      指定输出路径
fgsdu export <mdf> <table> -format dmp    导出为DMP格式
fgsdu export <mdf> all                    导出所有表
fgsdu unload / extract <mdf> -o <dir>     全库一键抽取
fgsdu unload <mdf> -user <name>           按用户过滤抽取
fgsdu unload <mdf> -schema <name>         按Schema过滤抽取
fgsdu unload <mdf> -format dmp            抽取为DMP格式

# =====================================================
# 三、数据恢复命令
# =====================================================
# DELETE 恢复
fgsdu recover / rec / restore delete <mdf>          恢复所有DELETE数据
fgsdu rec delete <mdf> -table <name>                按表名恢复
fgsdu rec delete <mdf> -schema <name>               按Schema恢复
fgsdu rec delete <mdf> -user <name>                 按用户恢复

# DROP 恢复
fgsdu rec drop <mdf>                                恢复所有DROP表
fgsdu rec drop <mdf> -schema <name>                 按Schema恢复
fgsdu rec drop <mdf> -user <name>                   按用户恢复

# TRUNCATE 恢复
fgsdu rec truncate <mdf>                            恢复TRUNCATE数据
fgsdu rec truncate <mdf> -table <name>              按表名恢复
fgsdu rec truncate <mdf> -schema <name>             按Schema恢复

# 全量恢复(DELETE+DROP+TRUNCATE)
fgsdu rec all <mdf>
fgsdu rec all <mdf> -schema <name> -user <name>     组合过滤

# 深度扫描(最大恢复)
fgsdu rec deep <mdf>
fgsdu rec deep <mdf> -schema XX -table YY -o out.sql

# =====================================================
# 四、多文件数据库(所有命令通用)
# =====================================================
fgsdu <任意命令> primary.mdf -ndf file1.ndf -ndf file2.ndf ...

# =====================================================
# 五、全局选项
# =====================================================
-o <path>              指定输出文件或目录
-format sql|dmp        输出格式(默认sql)
-log 0|1|2             日志级别(0静默/1信息/2调试)
-h / --help            显示帮助

附录 B:页类型与数据类型速查

SQLServer 页类型

类型值 类型名称 说明
1 DATA_PAGE 堆或聚集索引的叶子节点数据页
2 INDEX_PAGE 聚集索引的非叶子节点或非聚集索引页
3 TEXT_MIX_PAGE 小型 LOB 数据混合页(text/ntext/image/varchar(max))
4 SORT_PAGE 排序操作临时页
7 GAM_PAGE 全局分配映射页(每 4GB 一个)
8 SGAM_PAGE 共享全局分配映射页(每 4GB 一个)
9 IAM_PAGE 索引分配映射页(跟踪对象占用的区)
10 PFS_PAGE 页可用空间页(每 8088 页一个,记录每页空间使用率)
11 BULK_CHANGED_MAP 大容量操作变更映射页
13 DIFF_CHANGED_MAP 差异备份变更映射页
14 SYSTEM_PAGE 系统页

常用 SQLServer 数据类型编码

类型编码 数据类型 固定长度 说明
48 TINYINT 1 字节 0 到 255 的无符号整数
52 SMALLINT 2 字节 -32768 到 32767
56 INT 4 字节 -2^31 到 2^31-1
127 BIGINT 8 字节 -2^63 到 2^63-1
59 REAL 4 字节 单精度浮点数
62 FLOAT 8 字节 双精度浮点数
108 / 122 NUMERIC / DECIMAL 可变 固定精度和小数位数的数值
60 MONEY 8 字节 -922,337,203,685,477.5808 到 +922,337,203,685,477.5807
122 SMALLMONEY 4 字节 -214,748.3648 到 +214,748.3647
40 DATE 3 字节 0001-01-01 到 9999-12-31
41 TIME 可变 00:00:00.0000000 到 23:59:59.9999999
61 DATETIME 8 字节 1753-01-01 到 9999-12-31,精度 3.33ms
42 DATETIME2 可变 0001-01-01 到 9999-12-31,精度 100ns
43 DATETIMEOFFSET 可变 带时区偏移的日期时间
175 CHAR 固定 定长非 Unicode 字符串,最多 8000 字符
167 VARCHAR 可变 变长非 Unicode 字符串,最多 8000 字符(MAX 2GB)
239 NCHAR 固定 定长 Unicode 字符串,最多 4000 字符
231 NVARCHAR 可变 变长 Unicode 字符串,最多 4000 字符(MAX 2GB)
35 TEXT 可变 旧版变长非 Unicode 大文本,最多 2GB
99 NTEXT 可变 旧版变长 Unicode 大文本,最多 2GB
34 IMAGE 可变 旧版变长二进制大数据,最多 2GB
173 BINARY 固定 定长二进制数据,最多 8000 字节
165 VARBINARY 可变 变长二进制数据,最多 8000 字节(MAX 2GB)
36 UNIQUEIDENTIFIER 16 字节 GUID(全局唯一标识符)
104 BIT 1 字节(实际按位存储) 布尔值 0 或 1

附录 C:数据恢复成功率参考总表

操作类型 几分钟内立即恢复 1 小时内 1 天内 数天后 DBCC SHRINKFILE 后
DELETE 误删除 90%-95% 70%-90% 30%-70% 10%-30% 低于 10%
DROP TABLE 误删表 60%-80% 40%-60% 20%-40% 低于 10% 低于 5%
TRUNCATE TABLE 清空 50%-70% 30%-50% 10%-30% 低于 5% 低于 5%
(追加)深度扫描提升 +10%-20% +10%-20% +5%-10% +5% 基本无效

重要说明:以上数据仅为基于大量实践案例的经验参考值,实际恢复成功率受到无数具体因素的综合影响,包括但不限于:数据库大小、删除后数据写入量、SQLServer 版本、数据库恢复模式、Ghost 清理线程调度频率、是否有索引重建操作、磁盘类型等。切勿将上表数据作为恢复效果的保证。

作者: 风哥
WX:itpux-com
官方网站 : http://www.fgedu.net.cn , http://www.itpux.com
数据库教程 : https://edu.51cto.com/lecturer/8020378.html

提高恢复成功率的黄金法则

  1. 发现数据丢失后立即停止所有数据库写入操作,这是最重要的一条
  2. 不要执行 DBCC SHRINKFILE、数据库收缩、索引重建、大量批处理写入等操作
  3. 立即对 MDF/NDF 文件做完整备份副本,所有恢复操作都在副本上进行
  4. 尽快执行恢复操作,时间每过去一分钟,恢复成功率就可能下降一些
  5. 优先使用带精确过滤条件的恢复(-schema + -table),再逐级尝试更大范围的恢复

posted @ 2026-08-27 09:51  风哥数据库教程  阅读(8)  评论(0)    收藏  举报