解剖SQLSERVER 第十六篇 OrcaMDF RawDatabase --MDF文件的瑞士军刀(译)
时间:2022-03-14 01:07
解析页头
现在我们获取到页面,我们如何把页头dump出来
// Get the header of page 197 in file 1 var db = new RawDatabase(@"C:\AWLT2008R2.mdf"); db.GetPage(1, 197).Header.Dump();
解析行偏移阵列
就像页头那样,我们也可以把页尾的行偏移阵列条目dump出来
// Get the slot array entries of page 197 in file 1 var db = new RawDatabase(@"C:\AWLT2008R2.mdf"); db.GetPage(1, 197).SlotArray.Dump();
解析数据记录
当获取到行偏移条目的原始数据,你通常想看一下数据行记录的内容。幸运的是,这也很容易做到
// Get all records on page 197 in file 1 var db = new RawDatabase(@"C:\AWLT2008R2.mdf"); db.GetPage(1, 197).Records.Dump();
从记录中检索数据
一旦你得到记录,你现在可以利用FixedLengthData 或者 VariableLengthOffsetValues 属性
去获取原始的定长数据内容和变长数据内容。然而,你肯定只想获取到实际的已解析的数据值。
对于解析,OrcaMDF会帮你解析,你只需要为他提供schema.
// Read the record contents of the first record on page 197 of file 1 var db = new RawDatabase(@"C:\AWLT2008R2.mdf"); RawPrimaryRecord firstRecord = (RawPrimaryRecord)db.GetPage(1, 197).Records.First(); var values = RawColumnParser.Parse(firstRecord, new IRawType[] { RawType.Int("AddressID"), RawType.NVarchar("AddressLine1"), RawType.NVarchar("AddressLine2"), RawType.NVarchar("City"), RawType.NVarchar("StateProvince"), RawType.NVarchar("CountryRegion"), RawType.NVarchar("PostalCode"), RawType.UniqueIdentifier("rowguid"), RawType.DateTime("ModifiedDate") }); values.Dump();
RawColumnParser.Parse方法做的事情是 跟他一个schema,他帮你自动将raw bytes转换为Dictionary<string, object>,key就是从schema 那里获取到的列名,
而value就是数据列的实际值,例如int,short,guid,string等等。让你的用户给定schema, OrcaMDF 可以跳过大量的依赖的元数据进行解析,因此可以忽略可能的元数据错误带来的数据读取失败。
由于页头已经给出了 NextPageID 和 PreviousPageID属性 ,这能够让软件简单的遍历链表中的所有页面,并解析这些页面里面的数据 --他基本上是根据给定的allocation unit来进行扫描
过滤页面
除非检索一个特定的页面,RawDatabase 也有一个页面属性能够枚举数据库中的所有页面。
使用这个属性,举个例子,获取数据库中所有的IAM页面的列表
// Get a list of all IAM pages in the database var db = new RawDatabase(@"C:\AWLT2008R2.mdf"); db.Pages .Where(x => x.Header.Type == PageType.IAM) .Dump();
并且由于这是使用LINQ技术,这很容易去设计你想要的属性。
举个例子,你可以获取所有的 index pages 和他们的 slot counts 就像这样:
// Get all index pages and their slot counts var db = new RawDatabase(@"C:\AWLT2008R2.mdf"); db.Pages .Where(x => x.Header.Type == PageType.Index) .Select(x => new { x.PageID, x.Header.SlotCnt }).Dump();
或者假设你想获得如下条件的页面
1、页面里面至少有一条记录
2、free space空间至少有7000 bytes
下面是page id, free count, record count 和 平均记录大小的输出
var db = new RawDatabase(@"C:\AWLT2008R2.mdf"); db.Pages .Where(x => x.Header.FreeCnt > 7000) .Where(x => x.Header.SlotCnt >= 1) .Where(x => x.Header.Type == PageType.Data) .Select(x => new { x.PageID, x.Header.FreeCnt, RecordCount = x.Records.Count(), RecordSize = (8096 - x.Header.FreeCnt) / x.Records.Count() }).Dump();
最后一个例子,,假设你只有一个MDF文件并且你已经忘记了有哪些对象存储在MDF文件里面。
不要紧,我们只需要查询系统表sysschobjs !sysschobjs 系统表包含了所有对象的数据
并且幸运的是,他的object ID 是 34。利用这些信息,我们可以把所有属于object ID 34的数据页面
过滤出来,并且从这些页面里读取记录并只需要解析这个表的前两列(你可以定义一个分部schema, 只要你在最后忽略列)
最后我们只需要把名称dump出来(当然我们可以把表里的所有列都查询出来,如果我们想的话)
SELECT * FROM sys.sysschobjs
var db = new RawDatabase(@"C:\AWLT2008R2.mdf"); var records = db.Pages .Where(x => x.Header.ObjectID == 34 && x.Header.Type == PageType.Data) .SelectMany(x => x.Records); var rows = records.Select(x => RawColumnParser.Parse((RawPrimaryRecord)x, new IRawType[] { RawType.Int("id"), RawType.NVarchar("name") })); rows.Select(x => x["name"]).Dump();
兼容性
可以看到 RawDatabase并不依赖于元数据,这很容易兼容多个版本的SQLSERVER。
因此,我很高兴的宣布:RawDatabase 完全兼容SQL Server 2005, 2008, 2008R2 , 2012.
这也有可能兼容2014,不过我还未进行测试。说到测试,所有的单元测试都是自动运行的
在测试期间使用AdventureWorksLT for 2005, 2008, 2008R2 and 2012 。
现在有一些测试demo来让OrcaMDF RawDatabase去解析AdventureWorks LT 数据库里面每个表的每条记录
数据损坏
其中一个有趣的使用RawDatabase 的方法是用来附加损坏的数据库。你可以检索特定object id的所有页面然后硬解析每个页面
无论他们是否是可读的。如果元数据损坏,你可以忽略他,你手工提供schema (输入表的每个列的列名)并且只需要沿着页面链表
或者解析IAM页面去读取堆表里面的数据。接下来的几个星期我将会 写一些关于OrcaMDF RawDatabase 的使用场景的博客,其中包括数据损坏
源代码和反馈
我非常兴奋因为最新的RawDatabase 已经添加到OrcaMDF 里面并且我希望不单只只有我一个见证他的威力。
如果你也想试一试,或者有任何想法,建议或者其他反馈,我都很乐意接受。
如果你想试用,在上签出OrcaMDF项目。一旦这个工具做得比较完美了,我会把他放上去NuGet 。
就好像OrcaMDF一样,在GPL v3 licensed 下发布
第十六篇完