解剖SQLSERVER 第一篇 数据库恢复软件商的黑幕 – 桦仔 | 查问题
汇聚最新编程技术,编程问题一网打尽
& 解剖SQLSERVER 第一篇 数据库恢复软件商的黑幕 – 桦仔
解剖SQLSERVER 第一篇 数据库恢复软件商的黑幕 – 桦仔
[ 分类: ]
解剖SQLSERVER&第一篇 &数据库恢复软件商的黑幕
这一系列,我们一起来解剖SQLSERVER
在系列的第一篇文章里本人可能会得罪某些人,但是作为一位SQLSERVER MVP,在我的MVP任期内希望可以对大家作出一些贡献
在第一篇里面涉及到某些内容可能不会以详细的方式给出截图并且和大家讲解,毕竟第一篇的篇幅比较长,希望大家见谅。。
在第一篇文章开始之前,先说三个题外话
第一个题外话 &更新丢失
首先要做的事情是,跟大家道歉
在之前《SQLSERVER走起》的微信公众帐号里推送了一篇文章,题目是《》
由于当时没有仔细看,就给大家推送了,文章里面的建议确实是误导了大家
文章里面这样说道
举个例子描述这个场景:
T1事务发起一个修改,读取原库存是10,需求修改库存减1,原库存应该变成9,因为是READ_COMMITTED_SNAPSHOT隔离级别,所以数据库会在tempdb里生成一个快照,但是事务未提交,在这时发起了第二事务T2,也来修改库存,因为看到事务T1未提交,所以他不能获取未提交事务修改的值9(如果获取9就是脏读了),而是他获取的是最后提交版本的库存为10,而正巧T2未提交前,T1先提交了,实际库存应该变9而不是10,但T2事务获取库存值是10,假设T2的需求是减库2,那么最后T2提交后,会覆盖T1事务所做的修改,库存变成了8(我们实际期望的是10-1-2=7),这样就造成了逻辑混乱。
实际上,这种情况不是逻辑混乱,这种情况是属于 &更新丢失&,大家随便拿起一本SQLSERVER教科书,里面都会有说到更新丢失这种现象
文章里面说的解决方案基本上是错误的
错误一:提高事务隔离级别将会造成更加多的死锁
错误二:没有对&更新丢失&进行错误处理
我的好朋友高继伟(博客园里的大牛)跟这个阿里云SQLSERVER经理说过这个问题,但是最后他还是没有改过来
解决方案有两种,都是在默认隔离级别&READ COMMITTED下,不需要修改默认隔离级别
第一种:使用try catch捕获更新丢失
—示例CREATE TABLE kucun(id INT PRIMARY KEY,qty INT,product NVARCHAR(20))
–插入一些测试数据
SELECT * FROM dbo.kucun
—————————————————session 1BEGIN TRAN DECLARE @qty INTSELECT @qty=qty FROM kucun WITH(UPDLOCK)
WHERE [product]=‘牙膏‘SELECT @qtyUPDATE kucun SET qty=@qty–1 WHERE [product]=‘牙膏‘COMMIT TRAN——————————————-—session 2BEGIN TRAN DECLARE @qty INTSELECT @qty=qty FROM kucun WITH(UPDLOCK)
WHERE [product]=‘牙膏‘—阻塞SELECT @qty—session 1提交之后才可以读,但是后面的update语句不会执行,这个时候更新丢失,使用try catch机制来捕获更新丢失UPDATE kucun SET qty=@qty–1 WHERE [product]=‘牙膏‘COMMIT TRAN
第二种: &如果使用的是SQLSERVER2008 可以使用merge语句来执行这个原子操作
—session 1
BEGIN TRANMERGE [dbo].[kucun] AS TGT
USING [dbo].[kucun] AS SRC
TGT.product = SRC.product AND TGT.id = SRC.id AND
TGT.product = ‘牙膏‘
WHEN MATCHED THEN
UPDATE SET
TGT.qty = TGT.qty – 1;
COMMIT TRAN
SELECT * FROM [dbo].[kucun]
WHERE product = ‘牙膏‘
—session 2
—当session 1没有提交的时候就会阻塞,当session 1提交的时候 session 2 也能成功update记录 ,不会造成更新丢失
BEGIN TRANMERGE [dbo].[kucun] AS TGT
USING [dbo].[kucun] AS SRC
TGT.product = SRC.product AND TGT.id = SRC.id AND
TGT.product = ‘牙膏‘
WHEN MATCHED THEN
UPDATE SET
TGT.qty = TGT.qty – 1;
COMMIT TRAN
希望大家升级一下SQLSERVER,使用SQLSERVER2008提供的最新的merge语句,因为merge语句确实能够减少很多不必要的麻烦,而且性能也会有提升
看到这里,可能大家对这种最基础最基础的知识不以为然,但是大家试想一下,恰好这种最基础的东西就有可能带来致命的后果
比如你的银行账户里有100万,你取出来了20万,还剩下80万
但是刚好遇到更新丢失,你的账户里面可能已经取出了钱但是系统里面没有扣取你的钱又或者扣除多了 、扣除少了
后果可大可小
还有库存系统,这里就不说了
大家可能觉得&桦仔想借用这个例子,趁机贬低他人来抬高自己& &。实际上,我对于这个经理也是很理解,
当你管理成千成万台服务器的时候,你的脑子里就会想到数据库架构、集群搭建、容灾、业务连续性。。。&
这是数据库架构师要做的事,很难会顾及到这些基础的东西,我自己也是管理着公司很多的数据库
但是作为数据库专家,你给客户的建议应该要足够专业吧???
第二个题外话 &估计行数
某一天,群里面某位童鞋给我看了一个执行计划,他说:&扫描运算符里面的估计行数为什麽误差这麽大?&
USE [sss]SELECT @@VERSION—Microsoft SQL Server 2005 .4035.00 (X64) —Nov 24 :31 —Copyright (c)
Microsoft Corporation—Developer Edition (64-bit) on Windows NT 6.1 (Build 7601: Service Pack 1)
CREATE TABLE teststat(id INT,name NVARCHAR(20))
—首先插入5条记录INSERT INTO teststatSELECT 1 ,‘nihao‘ UNION ALLSELECT 2 ,‘dajiahao‘ UNION ALLSELECT 3 ,‘nihao‘ UNION ALLSELECT 4 ,‘dajiahao‘ UNION ALLSELECT 5 ,‘nihao‘
—显示实际执行计划SELECT * FROM teststat—预估行数5条
—查看缓存的执行计划SELECT
[cacheobjtype] ,
[objtype] ,
[usecounts] ,
sys.[syscacheobjects]WHERE
[sql] NOT LIKE ‘%cache%‘
AND [sql] LIKE ‘%INSERT INTO%‘
—再插入100条记录INSERT INTO teststat(id,name)SELECT 6,‘dajiahao‘GO 100
—查询缓存的执行计划SELECT
[cacheobjtype] ,
[objtype] ,
[usecounts] ,
sys.[syscacheobjects]WHERE
[sql] NOT LIKE ‘%cache%‘
AND [sql] LIKE ‘%INSERT INTO%‘
—显示实际执行计划SELECT * FROM teststat—预估行数还是5条
—清空编译计划DBCC FREEPROCCACHEGO
—显示实际执行计划SELECT * FROM teststat—预估行数变成105条
第一次查看缓存的执行计划
第二次查看缓存的执行计划
清空plan cache之后
实际上,清空一下执行计划缓存就可以了,那位童鞋在第一次插入1条记录,第二次插入4条记录,
那么估计行数是1,实际行数是5,他就认为SQLSERVER估计得太不准确了,而且他认为统计信息有问题,
实际上,这个时候根本没有统计信息,又怎麽会跟统计信息扯上关系呢?
注意:DBCC FREEPROCCACHE是清空实例级别的计划缓存,请不要随意在生产环境下执行
第三个题外话 &性能太差的SQLSERVER
某一天,开发又抱怨了:&SQLSERVER很慢,查询要差不多9秒,这个问题怎麽彻底解决!&
这个例子不是证明SQLSERVER多牛逼,只是为了说明数据量大的时候,SQLSERVER也可以应付
开发查询的是一张xxclassifyxx表,表数据1.8亿+
查询语句如下
SELECT TOP 500
DBO.xxCLASSIFYxx with (nolock) WHERE ID& ORDER BY ID
聚集索引建立在ID列上
上面的查询大概需要8秒
大家看到这个SQL语句可能一开始并没有什么头绪,但大家会发现,聚集索引既然建立在ID这一列上,那么ORDER BY ID是不是有点多余呢???
语句修改之前
执行时间 8秒(500 行受影响)表 ‘xxClassifyxx‘。扫描计数 113,逻辑读取 619665 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
语句修改之后,只是去掉了ORDER BY ID
SELECT TOP 500
DBO.xxCLASSIFYxx with (nolock) WHERE ID&
(500 行受影响)表 ‘xxClassifyxx‘。扫描计数 1,逻辑读取 32 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
修改之后所用时间
在这里,加ORDER BY ID和不加ORDER BY ID对于SQLSERVER来看是不同的
注意:不要乱用(forceseek表提示),本来应该使用索引扫描的让SQLSERVER使用索引查找,使用(forceseek表提示)之前一定要先测试一下
在日常生产环境中,遇到数据库损坏是很正常的事情,数据库损坏的原因多种多样,很多时候,由于不能使用数据库内建的命令或工具
来修复数据库,由此催生了数据恢复这个行业
这些数据恢复软件商一般会把自己的数据恢复软件产品和数据库恢复技术吹得要多牛逼就有多牛逼
我们一起来看一下他们的广告吧~
北京某数据恢复商
广州某数据恢复商
深圳某数据恢复商
看到上面DBCC BUG这句话,其实我心里想说一句话&有本事你不用DBCC 命令来修复数据库!&
DBCC CHECKDB确实为了我们做了很多事情,而且这些事情也比较复杂,有兴趣的同学可以看一下《》第11章
DBCC 是由Paul Randal大神编写的,他是我敬重的人
我承认搞数据库恢复真的很好赚,前段时间博客园里某位园友找我,用的是用友U8的系统,磁盘阵列损坏了,数据库大小900MB
数据很重要,他找到论坛里一位搞用友系统的人,那个人不是数据库恢复公司的,平时帮搞一下用友的系统,他使用替换物理数据文件的方法
结果搞不定,于是那位园友来找我,我跟他说了一些方法,但是不保证一定能修复好,因为我也不是神仙,后来听说那个人将数据库修复好了
而且还收了500大洋,包开***,具体怎么修复好我就不清楚了。。
其实,数据恢复并不难,只要遵循格式,例如文件系统有文件系统的格式,jpg图片有jpg图片的格式,mdf文件有mdf文件的格式,只要按照格式,读出数据并不难
数据恢复我自己定义大概分两种:1、软件恢复 &2、人工恢复
人工恢复这里就不说了,我这里说一下软件恢复,一般为了效率,数据库软件恢复商都会开发数据恢复软件
而据我所知,国内的大部分数据恢复软件商都是小作坊式的,一个软件由1到2个人开发
由于这样,导致了开发不出像《》这样的靠谱的产品
我说一下这些数据库软件恢复商不靠谱的地方
不靠谱的地方1
如果你对SQLSERVER有深入研究就知道, &SQLSERVER从创建数据库到第一次进行数据库完整备份这段期间里是一直处于简单模式的
也就是说,如果你从来没有进行过完整备份,那么日志就会截断,即使你做过数据库完整备份,在数据库创建到第一次完整备份这段期间
的日志是找不到的,你不能通过ldf文件来恢复这段期间的数据,正确的做法还是要保留第一次的完整备份,还有想再说一次,备份真的很重要
DBCC CHECKDB属于强硬修理数据库(简称:硬修),而将数据库拿到数据库恢复商那里去修理就更加暴力了,除非真的万不得已。。。
相关文章《&》
不靠谱的地方2
微软并没有公开页面checksum校验的算法,那意味着,你恢复出来的数据很有可能是不正确的
不靠谱的地方3
对&极速SQL数据库修复软件预览&这款软件进行测试
以查看&极速SQL数据库修复软件预览& 对SQLSERVER数据存储新格式的支持情况
软件下载地址:
USE testnewformatGO
CREATE TABLE DBCCResult(
PageFID NVARCHAR(200) ,
PagePID NVARCHAR(200) ,
IAMFID NVARCHAR(200) ,
IAMPID NVARCHAR(200) ,
ObjectID NVARCHAR(200) ,
IndexID NVARCHAR(200) ,
PartitionNumber NVARCHAR(200) ,
PartitionID NVARCHAR(200) ,
iam_chain_type NVARCHAR(200) ,
PageType NVARCHAR(200) ,
IndexLevel NVARCHAR(200) ,
NextPageFID NVARCHAR(200) ,
NextPagePID NVARCHAR(200) ,
PrevPageFID NVARCHAR(200) ,
PrevPagePID NVARCHAR(200))
—测试稀疏列的表——————————————————————–CREATE TABLE test_sparse(id INT ,name CHAR(10) SPARSE,city CHAR(10) SPARSE)GO
DECLARE @i INT SET @i =1WHILE @i &6BEGIN INSERT test_sparseSELECT @i,‘小四@‘ ,‘广州@‘SET @i=@i+1END
DECLARE @i INT SET @i =1WHILE @i &6BEGIN INSERT test_sparseSELECT @i,NULL
,‘广州@‘SET @i=@i+1END
DECLARE @i INT SET @i =1WHILE @i &100BEGIN INSERT test_sparseSELECT @i,‘小四@‘
,NULLSET @i=@i+1END
DECLARE @i INT SET @i =1WHILE @i &6BEGIN INSERT test_sparseSELECT @i,NULL ,NULLSET @i=@i+1END
SELECT * FROM test_sparse
—测试vardecimal的表———————————————————————CREATE TABLE test_vardecimal(id INT ,qty DECIMAL(10,6))GO
—打开vardecimal storage format选项EXEC sys.[sp_tableoption] @TableNamePattern = N‘test_vardecimal‘, — nvarchar(776)@OptionName = ‘vardecimal storage format‘, — varchar(35)@OptionValue = ‘1‘ — varchar(12)
DECLARE @i INT SET @i =1WHILE @i &10BEGIN INSERT test_vardecimalSELECT @i,36.2693222+@iSET @i=@i+1END
—使用DBCC PAGE测试—decimal数据类型变成变长列
30|00|0 | 00|d2b32220SELECT * FROM test_vardecimal
—测试 页面压缩的表————————————————————————————CREATE TABLE test_pagecompress(id INT PRIMARY KEY IDENTITY,name CHAR(2000),city
CHAR(2000),qty DECIMAL(10,6))GOALTER INDEX [PK__test_pag__B15C9A5] ON [test_pagecompress] REBUILD WITH (DATA_COMPRESSION= PAGE)
DECLARE @i INT SET @i =1WHILE @i &10000BEGIN INSERT test_pagecompress([name],[city],[qty])SELECT REPLICATE(‘你好!藏123‘,100),REPLICATE(‘大家好好!藏123‘,100),36.269322+@iSET @i=@i+1END
SELECT * FROM [test_pagecompress]
INSERT INTO DBCCResult EXEC (‘DBCC IND(testnewformat,test_pagecompress,-1) ‘)SELECT * FROM DBCCResultDBCC TRACEON(3604,–1)GO
DBCC PAGE([testnewformat],1,739,3)GO
————————————————————–—新建数据库测试FilestreamEXEC sys.[sp_configure] @configname = ‘filestream access level‘, — varchar(35)
@configvalue = 1 — intRECONFIGURE WITH OVERRIDE
CREATE DATABASE MyFilestreamDB ON PRIMARY (NAME=ROWDATA1,FILENAME=‘D:MSSQLDataROWDATA1.MDF‘),
FILEGROUP FILESTREAMGROUP1 CONTAINS FILESTREAM DEFAULT(NAME=FSDATA1,FILENAME=‘D:MSSQLDataFILESTREAM1‘), FILEGROUP
FILESTREAMGROUP2 CONTAINS FILESTREAM (NAME=FSDATA2,FILENAME=‘D:MSSQLDataFILESTREAM2‘) LOG ON(NAME=FSDBLOG,FILENAME=‘D:MSSQLDataFSDB_LOG.LDF‘)
—新建Filestream表RECORDSUSE MyFilestreamDBCREATE TABLE MyFILESTREAMDB.DBO.RECORDS
ID UNIQUEIDENTIFIER ROWGUIDCOL
SERIALNUMBER INTEGER UNIQUE ,
CHART_PRIMARY VARBINARY(MAX) FILESTREAM
CHART_SEONDARY VARBINARY(MAX) FILESTREAM
) FILESTREAM_ON FILESTREAMGROUP1
USE MyFilestreamDBINSERT
INTO [dbo].[RECORDS]
CAST(REPLICATE(CONVERT(VARCHAR(MAX), ‘base data‘), 10000) AS VARBINARY(MAX)) ,
SELECT *,CAST([CHART_PRIMARY] AS VARCHAR(MAX)) AS ‘CHART_PRIMARY‘ FROM
[dbo].[RECORDS]
vardecimal
Filestream
地理空间类型
USE [AdventureWorks2008R2]GOSELECT TOP 100 * FROM [Person].[Address]
可以看到,除了稀疏列格式之外,软件对其他格式都歇菜了。。。
不靠谱的地方4
数据库恢复软件的性能一般都比较差,对于VLDB(very large database)一般都会崩溃
我用一个200G+的数据库进行测试
软件运行了5个小时之后就中途崩溃了
这些软件使用的扫描数据的方式一般是扫objectid然后根据objectid扫所属objectid的页面,如果系统表损坏效率就更低了
而数据装载的方式一般都是使用使用bulk行集提供程序
不靠谱的地方5
我把SQLSERVER数据库分三类表 &系统表、基本表(用户表)、隐藏表(内部表)
–查看用户库中的内部表USE [CT_DB]
SELECT * FROM sys.[internal_tables]
数据库恢复软件商不会帮你修复隐藏表,他们只会帮你修复系统表和基本表
隐藏表数据丢失有一个前提:就是数据库恢复软件商帮你修复数据库之后,数据库依然启动不起来(但是可以把数据导出来)
数据库启动不起来意味着你不能运行DBCC CHECKDB
不能运行DBCC CHECKDB就意味着你的隐藏表数据有可能丢失
为什麽我要强调隐藏表,大家可能觉得这些隐藏表里的数据不太重要,例如 service broker队列技术 我们都没有用到
那你错了,你没有用到不代表没有人用,据我说知,新蛋网就是利用service broker队列将一部分数据从美国传送到中国
还有扣减库存也使用到service broker
所以,大家一定要清楚认识一点&无论什么表,表里面的数据需不需要恢复,取决于表里面存储的公司的业务数据是否重要!!&
数据恢复的两大难处
那我来说一下数据恢复的难处,个人觉得数据恢复有两大难处
&第一难处 丢失数据统计难&
&第二难处 验证丢失数据的正确性难
第一难处:
无论什么表都会遇到这两大难处,第一难处,在你发现数据库出现损坏的时候,最好先备份一下数据库,然后再运行相关命令
如果不是VLDB,这一点很容易做到,在备份数据库的时候,你可以选择只备份主文件组又或者完整备份整个数据库
备份主文件组:大家知道SQLSERVER会把各个表的行记录数记录在系统表sysindexes里面
当大家使用《》这篇文章提供的脚本
来查看表记录数的时候,实际上读取的是sysindexes表里的rowcnt,所以速度会很快
当我们修复完数据库之后,只需要还原主文件组,然后比较一下修复前和修复后的数据量的差异就可以了,但是这种方法不准确
完整备份数据库:先玩着备份数据库,然后运行命令修复数据库
修复完数据库之后,还原完整备份,保证每张表都有一个not null 字段,然后在修复后的数据库上的每张表上
和还原出来的数据库上的每张表使用&SELECT COUNT(NOT NULL 字段)& 来检查丢失多少数据
实际上,这种方法也不太靠谱,不但效率低,而且如果有页面损坏,SELECT COUNT也不一定能读取页面
无论哪一种方式,要精确统计丢失的数据量都很难,你只有不停的备份数据,在数据库损坏前有一个完好
的数据库备份,这个数据库备份里面是没有数据损坏的,在第一时间发现数据库损坏之后,马上停库,然后正确还原备份比较出丢失的数据量
这里再穿插一个题外话
在SQLSERVER内部存在大量的计数器,这些计数器有各种各样的用途
例如 数据页面头上的slotcnt,保存页面上的行记录数
这个slotcnt计数器用来在checkdb的时候将扫描到的页面上的记录数和这个slotcnt计数器保存的记录数进行比较
要验证页面上保存的记录数是否是正确的
还有刚才提到sysindexes表里的rowcnt,rowcnt这个计数器用来保存表的总记录数,这样就不需要实际去读取表的页面
来统计表的总记录数,实际上很多时候,SQLSERVER利用计数器都是为了性能方面的考虑,就比如既然有OLTP数据库
为什麽还要存在数据仓库。
第二难处:
验证丢失数据的正确性难,大家知道,SQLSERVER引入了TORN_PAGE和CHECKSUM来验证页面上的数据的正确性
如果页面数据不正确就有可能引发著名的823和824错误
大家知道保存在磁盘里面的都是1010的二进制数据只要由原来的1变成0或者由原来的0变成1,你的数据就不正确了
SQLSERVER就是利用在页面头保存一个校验数值来对页面中的数据进行校验
而这些数据恢复软件对这些数据的正确性的校验是&不校验&
他们无需知道SQLSERVER的页面checksum算法
我只要把数据恢复出来就可以了,不管你的数据是正确的还是错误的!!
当数据库修复好了,你启动数据库,并查询数据库里面的数据是没有问题的
但是,当你使用DBCC CHECKDB检查数据库的时候,SQLSERVER很有可能会报823错误或824错误
这也是数据恢复软件商的黑暗之处,但是客户的要求也是比较低,你只需要能恢复出数据就可以了
管他的数据正确不正确
使用Visual Studio查看数据库文件数据
对于需要经常和数据打交道的那些数据恢复人可能会喜欢使用winhex这款hex编辑器软件来查看文件数据
我最喜欢的功能是数据着色,根据SQLSERVER数据页面的大小 8192字节,你可以输入8192字节为
页面大小,然后winhex会以8192的块来交替着色
指定8192字节为一个页面
winhex就会8192字节的块大小来交替着色,比如单数页面以白色为底色,双数页面以灰色为底色
输入偏移值来定位某行数据
我相信对于winhex这个软件比我还要精通的人有很多,小弟就不献丑了,对这个软件不作介绍
实际上也可以使用Visual Studio来查看mdf文件的数据,把Visual Studio看成是winhex的精简版
其实编辑文件用其他的hex编辑器也是可以的,而不局限于winhex和Visual Studio ,例如Editplus 、NotePad++
在开始之前,大家可以看一下这篇文章,张充大牛计算页面偏移位置的方式《》
张充大牛给出的页面偏移计算公式是:逻辑页号89 *8192 就是页面89 的偏移位置
按照公式 逻辑页面号 &* 8192 来算出偏移位置&
这个计算公式没有错,只是,如果你真的要靠这个公式来找数据页面,很多时候会找不到
例如没有考虑到逻辑碎片的问题,而且我们平时搜索数据的时候一般都不会使用这个公式
还有大家在研究的时候不要将DBCC PAGE的输出中左边的偏移值和winhex中左边的偏移值对应起来
create database testpagego
use testpagego
create table testpagevs(id int identity primary key,name nchar(10))go
INSERT INTO testpagevsSELECT N‘中国‘ UNION ALLSELECT ‘nihao‘
SELECT * FROM testpagevs
CREATE TABLE DBCCResult
PageFID NVARCHAR(200) ,
PagePID NVARCHAR(200) ,
IAMFID NVARCHAR(200) ,
IAMPID NVARCHAR(200) ,
ObjectID NVARCHAR(200) ,
IndexID NVARCHAR(200) ,
PartitionNumber NVARCHAR(200) ,
PartitionID NVARCHAR(200) ,
iam_chain_type NVARCHAR(200) ,
PageType NVARCHAR(200) ,
IndexLevel NVARCHAR(200) ,
NextPageFID NVARCHAR(200) ,
NextPagePID NVARCHAR(200) ,
PrevPageFID NVARCHAR(200) ,
PrevPagePID NVARCHAR(200)
TRUNCATE TABLE DBCCResultINSERT INTO DBCCResult EXEC (‘DBCC IND(testpage,testpagevs,-1) ‘)
SELECT * FROM DBCCResult
SELECT * FROM testpagevs
DBCC TRACEON(3604,–1)GO
DBCC PAGE([testpage],1,89,2)GO
我们看下怎么用VS查看数据文件内容
我们使数据库脱机,然后把mdf文件拖入VS
然后使数据库联机,这时候可以继续操作数据库,但是VS跟winhex都不会自动刷新你对数据库所做的修改
在这里VS当然没有winhex强大,winhex可以搜索文本字符无论是unicode还是ASCII
另一个也是相当强大的hex编辑器
HxD hex编辑器下载:
只是使用VisualStudio可能会方便大家一边看代码一边研究文件结构
还有说一下,不要随意用VS和winhex修改数据,否则你的数据库有90%机率起不来喔
用winhex修改数据,选中要修改的数据的字节区域,然后右键&》edit-》你可以选择剪切、添加字节、填充零等等
用Visual Studio修改数据更简单,选中要修改的字节区域,然后直接输入16进制数就可以了
在修改数据之前,把页面校验设置为NONE
ALTER DATABASE [sss] SET PAGE_VERIFY NONE
如果你修改数据页头的话,在修改完毕之后数据库还可以联机,但是你修改数据行的话,问题就严重了
下面是本人玩坏了的数据库的报错信息汇总
消息 824,级别 24,状态 2,第 1 行SQL Server 检测到基于一致性的逻辑 I/O 错误 校验和不正确(应为: 0xb70c8233,但实际为: 0xb7438233)。在文件 ‘E:DataBasesss.mdf‘ 中、偏移量为 0xa000 的位置对数据库 ID 8 中的页 (1:77) 执行 读取 期间,发生了该错误。SQL Server 错误日志或系统事件日志中的其他消息可能提供了更详细信息。这是一个威胁数据库完整性的严重错误条件,必须立即纠正。请执行完整的数据库一致性检查(DBCC CHECKDB)。此错误可以由许多因素导致;有关详细信息,请参阅 SQL Server 联机丛书。
消息 5028,级别 16,状态 4,第 1 行 系统无法激活足够的数据库来重建日志。 sss的 DBCC 结果。 CHECKDB 在数据库 ‘sss‘ 中发现 0 个分配错误和 0 个一致性错误。 消息 7909,级别 20,状态 1,第 1 行 紧急模式修复失败。您必须从备份中还原。
消息 601,级别 12,状态 3,第 1 行 由于数据移动,无法继续以 NOLOCK 方式扫描。
消息 926,级别 14,状态 1,第 1 行 无法打开数据库 ‘sss‘。恢复操作已将该数据库标记为 SUSPECT。有关详细信息,请参阅 SQL Server 错误日志。 消息 5069,级别 16,状态 1,第 1 行 ALTER DATABASE 语句失败。 消息 5125,级别 24,状态 2,第 1 行 文件 ‘E:DataBasesss.mdf‘ 似乎已被操作系统截断。其大小应为 3072 KB,但实际大小为 3064 KB。 消息 3414,级别 21,状态 1,第 1 行 恢复期间出错,导致数据库 ‘sss‘ (数据库 ID 8)无法重新启动。请诊断并纠正这些恢复错误,或者从已知的正确备份中还原。如果无法更正错误,或者为意外错误,请与技术支持人员联系。
分享某位牛人的代码
国外某位牛人开发了一个软件,这个软件能够读取SQLSERVER的mdf文件,而且这个软件支持大部分的SQLSERVER数据存储新格式
最重要的是,这个软件是开放源代码的
这个软件是一个winform程序
他里面带了四个测试数据库 ,支持SQL2005,SQL2008,SQL2008 R2,SQL2012
我们运行一下这个软件,OrcaMDF.OMS才是winform程序,其他项目只是程序集
我们打开AWLT2012.mdf
无论系统表、DMV、用户表、系统存储过程、系统视图都可以读取出来
附加到SQLSERVER之后,查看数据库属性,作者使用的是微软的标准示例数据库adventureworkoltp来做的测试
这几个库的版本号是611,655,661,706
我们可以自己新建一个数据库,然后测试一下
牛人的博客地址:
项目代码已经放上去GITHUB:
分享SQLSERVER技术内幕系列图书笔记
本人把一些SQLSERVER技术内幕读书笔记分享出来,其实也不算是分享,因为这些笔记一直躺在我的博客里
大家可以对我做的笔记进行搜索,技术内幕系列图书最大的一个特征是 :Microsoft Press &权威性不可忽视
大家平时遇到的很多问题,这些书本里面基本都有解决方案,除非是特别新的技术
对于一些教条之类的方法、技巧、规定,书本里面都给出了为什麽这麽做,里面个中的原理
先说一下《SQLSERVER6.5 技术内幕》
SQL6.5技术内幕里面介绍了:SQLSERVER开发团队刚开始开发SQLSERVER的时候采用单进程多线程的方式
而没有使用ORACLE的多进程的方式,书本里面解释了原因
执行计划修剪,buffer pool污染(mysql也有污染现象好像从5.5版本解决了),原行更新,原页更新
数据页面在那个年代还是2KB而不是现在的8KB
某些存储过程还保留着SQL6.5的代码
EXEC sys.[sp_helpconstraint] @objname = N‘[dbo].[nums]‘, — nvarchar(776)
@nomsg = ” — varchar(5)
select object_id, type, name,[parent_object_id] from sys.objects where parent_object_id = OBJECT_ID(‘customer‘)and type in (‘C ‘,‘PK‘,‘UQ‘,‘F ‘, ‘D ‘) — ONLY 6.5 sysconstraints objects
如果大家有兴趣的话可以下载SQL6.5下来玩一下,如果大家能下载下来并能***和运行的话 ,***包只有100MB不到
《SQLSERVER2005存储引擎》
SQLSERVER2005存储引擎里面把cachestore翻译为 存储仓库
SQLSERVER的工作线程是映射到Windows的线程池,SQLSERVER每条工作线程内存的分配都是由Windows来分配
SQL2005引入SQLOS,开始由SQLSERVER自己来调度线程,而先前是由Windows来调度
《SQLSERVER2005: T-SQ程序设计》
人们总是说游标性能不好,这本书里面解释了游标实际上也有他的优势的地方
《SQLSERVER2008 TSQL查询》
脏读、幻读、重复读解释得特别清楚,对脏读、幻读、重复读搞不清楚的童鞋可以看一下
某些人会把重复读理解成幻读《》
分区表查询的内部原理
内部碎片和外部碎片
《深入解析SQLSERVER2008 》
DBCC的工作原理解释得很清楚,大部分内容跟《SQLSERVER2005存储引擎》有重叠
在最后一章DBCC 揭秘,译者把鬼影记录翻译为备份记录,搞到一头雾水
一边看书,一边思考
例如这篇文章《》 作者为什么要写&DELETE TOP (5000) 呢? 有可能是5000行锁升级到表锁的原因
还有这一篇文章《》
大家看完书本之后,看一下存储过程的代码,自己是否理解里面代码的意思
SQLSERVER的水真的很深,我自己也没有办法精通,就例如 :一个开窗函数就可以写一本219页的书
《T-SQL性能调优秘笈&&基于SQL Server 2012窗口函数》这本书的作者是《SQL2005技术内幕 TSQL查询》的作者之一:Itzik Ben-Gan &&
我把这些笔记放上博客园目的只有一个&为大家提供尽可能多的笔记,让大家尽快学会SQLSERVER&
作为一位SQLSERVER MVP,我觉得自己已经尽了SQLSERVER MVP的义务,帮助大家迅速成长,成为数据库大牛
我高兴的是,还有很多使用SQLSERVER的大牛活跃在博客园里
这些SQLSERVER高手们还在不断的写文章,研究SQLSERVER,而且dudu园主也是不遗余力的将SQLSERVER方面的文章推上编辑推荐
解剖SQLSERVER系列目录
由于本人精力有限而且E文水平不太好,翻译过程可能有错漏,望大家见谅
建议先看一下技术内幕的书,否则一头栽进代码你会理解不了
通过阅读这些译文大家可能会觉得SQLSERVER的新存储格式比较复杂,要赶上SQLSERVER的步伐不太容易,
改天微软推出一个SQLSERVER补丁包,并在补丁包里面添加新的存储格式你的软件可能又要歇菜了~
而我写这系列文章并不是要与这些数据库恢复软件商作对,而是让大家知道他们能够恢复哪些数据,有哪些数据超出了他们的数据恢复能力
Mark S. Rasmussen大牛的PPT:
本文链接:,转载请注明。