恢复SQL Server被误删除的数据

恢复SQL Server被误删除的数据(再扩展)

地址:http://www.cnblogs.com/lyhabc/p/4620764.html

曾经想实现Log Explorer for SQL Server的功能,利用ldf里面的日志来还原误删除的数据

这里有一篇文章做到了,不过似乎不是所有的数据类型都支持

以下为译文:http://raresql.com/2011/10/22/how-to-recover-deleted-data-from-sql-sever/

在我使用SQLSERVER的这些年里面,大部分人都会问我一个问题:“能不能恢复被删除的数据??”

现在,从SQLSERVER2005 或以上版本能很容易能够恢复被删除的数据

(注意:这个脚本能恢复下面的数据类型的数据 而且兼容CS 排序规则)

  • image
  • text
  • uniqueidentifier
  • tinyint
  • smallint
  • int
  • smalldatetime
  • real
  • money
  • datetime
  • float
  • sql_variant
  • ntext
  • bit
  • decimal
  • numeric
  • smallmoney
  • bigint
  • varbinary
  • varchar
  • binary
  • char
  • timestamp
  • nvarchar
  • nchar
  • xml
  • sysname

让我来用demo来解释一下我是怎么做到的

  1. USE master
  2. GO
  3. --创建数据库
  4. CREATE DATABASE test
  5. GO
  6.  
  7. USE [test]
  8. GO
  9.  
  10. --创建表
  11. CREATE TABLE [dbo].[aa](
  12. [id] [int] IDENTITY(1,1) NOT NULL,
  13. [NAME] [nvarchar](200) NULL
  14. ) ON [PRIMARY]
  15. GO
  16.  
  17. --插入测试数据
  18. INSERT [dbo].[aa]
  19. ( [NAME] )
  20. SELECT '你好'
  21. GO
  22.  
  23. --删除数据
  24. Delete from aa
  25. Go
  26.  
  27. --验证数据是否已经删除
  28. Select * from aa
  29. Go

现在你需要创建一个存储过程来恢复你的数据

  1. -- Script Name: Recover_Deleted_Data_Proc
  2. -- Script Type : Recovery Procedure
  3. -- Develop By: Muhammad Imran
  4. -- Date Created: 15 Oct 2011
  5. -- Modify Date: 22 Aug 2012
  6. -- Version : 3.1
  7. -- Notes : Included BLOB data types for recovery.& Compatibile with Default , CS collation , Arabic_CI_AS.
  8.  
  9. CREATE PROCEDURE Recover_Deleted_Data_Proc
  10. @Database_Name NVARCHAR(MAX) ,
  11. @SchemaName_n_TableName NVARCHAR(MAX) ,
  12. @Date_From DATETIME = '1900/01/01' ,
  13. @Date_To DATETIME = '9999/12/31'
  14. AS
  15. DECLARE @RowLogContents VARBINARY(8000)
  16. DECLARE @TransactionID NVARCHAR(MAX)
  17. DECLARE @AllocUnitID BIGINT
  18. DECLARE @AllocUnitName NVARCHAR(MAX)
  19. DECLARE @SQL NVARCHAR(MAX)
  20. DECLARE @Compatibility_Level INT
  21.  
  22. SELECT @Compatibility_Level = dtb.compatibility_level
  23. FROM master.sys.databases AS dtb
  24. WHERE dtb.name = @Database_Name
  25.  
  26. IF ISNULL(@Compatibility_Level, 0) <= 80
  27. BEGIN
  28. RAISERROR('The compatibility level should be equal to or greater SQL SERVER 2005 (90)',16,1)
  29. RETURN
  30. END
  31.  
  32. IF ( SELECT COUNT(*)
  33. FROM INFORMATION_SCHEMA.TABLES
  34. WHERE [TABLE_SCHEMA] + '.' + [TABLE_NAME] = @SchemaName_n_TableName
  35. ) = 0
  36. BEGIN
  37. RAISERROR('Could not found the table in the defined database',16,1)
  38. RETURN
  39. END
  40.  
  41. DECLARE @bitTable TABLE
  42. (
  43. [ID] INT ,
  44. [Bitvalue] INT
  45. )
  46. --Create table to set the bit position of one byte.
  47.  
  48. INSERT INTO @bitTable
  49. SELECT 0 ,
  50. 2
  51. UNION ALL
  52. SELECT 1 ,
  53. 2
  54. UNION ALL
  55. SELECT 2 ,
  56. 4
  57. UNION ALL
  58. SELECT 3 ,
  59. 8
  60. UNION ALL
  61. SELECT 4 ,
  62. 16
  63. UNION ALL
  64. SELECT 5 ,
  65. 32
  66. UNION ALL
  67. SELECT 6 ,
  68. 64
  69. UNION ALL
  70. SELECT 7 ,
  71. 128
  72.  
  73. --Create table to collect the row data.
  74. DECLARE @DeletedRecords TABLE
  75. (
  76. [Row ID] INT IDENTITY(1, 1) ,
  77. [RowLogContents] VARBINARY(8000) ,
  78. [AllocUnitID] BIGINT ,
  79. [Transaction ID] NVARCHAR(MAX) ,
  80. [FixedLengthData] SMALLINT ,
  81. [TotalNoOfCols] SMALLINT ,
  82. [NullBitMapLength] SMALLINT ,
  83. [NullBytes] VARBINARY(8000) ,
  84. [TotalNoofVarCols] SMALLINT ,
  85. [ColumnOffsetArray] VARBINARY(8000) ,
  86. [VarColumnStart] SMALLINT ,
  87. [Slot ID] INT ,
  88. [NullBitMap] VARCHAR(MAX)
  89. )
  90. --Create a common table expression to get all the row data plus how many bytes we have for each row.
  91. ;
  92. WITH RowData
  93. AS ( SELECT [RowLog Contents 0] AS [RowLogContents] ,
  94. [AllocUnitID] AS [AllocUnitID] ,
  95. [Transaction ID] AS [Transaction ID]
  96.  
  97. --[Fixed Length Data] = Substring (RowLog content 0, Status Bit A+ Status Bit B + 1,2 bytes)
  98. ,
  99. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  100. 2 + 1, 2)))) AS [FixedLengthData] --@FixedLengthData
  101.  
  102. -- [TotalnoOfCols] = Substring (RowLog content 0, [Fixed Length Data] + 1,2 bytes)
  103. ,
  104. CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  105. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  106. 2 + 1, 2)))) + 1,
  107. 2)))) AS [TotalNoOfCols]
  108.  
  109. --[NullBitMapLength]=ceiling([Total No of Columns] /8.0)
  110. ,
  111. CONVERT(INT, CEILING(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  112. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  113. 2 + 1, 2)))) + 1,
  114. 2)))) / 8.0)) AS [NullBitMapLength]
  115.  
  116. --[Null Bytes] = Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [NullBitMapLength] )
  117. ,
  118. SUBSTRING([RowLog Contents 0],
  119. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  120. 2 + 1, 2)))) + 3,
  121. CONVERT(INT, CEILING(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  122. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  123. 2 + 1, 2)))) + 1,
  124. 2)))) / 8.0))) AS [NullBytes]
  125.  
  126. --[TotalNoofVarCols] = Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [Null Bitmap length] + 2 )
  127. ,
  128. ( CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) IN (
  129. 0x10, 0x30, 0x70 )
  130. THEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  131. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  132. 2 + 1, 2)))) + 3
  133. + CONVERT(INT, CEILING(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  134. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  135. 2 + 1, 2)))) + 1,
  136. 2)))) / 8.0)), 2))))
  137. ELSE NULL
  138. END ) AS [TotalNoofVarCols]
  139.  
  140. --[ColumnOffsetArray]= Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [Null Bitmap length] + 2 , [TotalNoofVarCols]*2 )
  141. ,
  142. ( CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) IN (
  143. 0x10, 0x30, 0x70 )
  144. THEN SUBSTRING([RowLog Contents 0],
  145. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  146. 2 + 1, 2)))) + 3
  147. + CONVERT(INT, CEILING(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  148. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  149. 2 + 1, 2)))) + 1,
  150. 2)))) / 8.0))
  151. + 2,
  152. ( CASE WHEN SUBSTRING([RowLog Contents 0],
  153. 1, 1) IN ( 0x10,
  154. 0x30, 0x70 )
  155. THEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  156. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  157. 2 + 1, 2)))) + 3
  158. + CONVERT(INT, CEILING(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  159. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  160. 2 + 1, 2)))) + 1,
  161. 2)))) / 8.0)), 2))))
  162. ELSE NULL
  163. END ) * 2)
  164. ELSE NULL
  165. END ) AS [ColumnOffsetArray]
  166.  
  167. -- Variable column Start = Status Bit A+ Status Bit B + [Fixed Length Data] + [Null Bitmap length] + 2+([TotalNoofVarCols]*2)
  168. ,
  169. CASE WHEN SUBSTRING([RowLog Contents 0], 1, 1) IN (
  170. 0x10, 0x30, 0x70 )
  171. THEN ( CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  172. 2 + 1, 2)))) + 4
  173. + CONVERT(INT, CEILING(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  174. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  175. 2 + 1, 2)))) + 1,
  176. 2)))) / 8.0))
  177. + ( ( CASE WHEN SUBSTRING([RowLog Contents 0],
  178. 1, 1) IN ( 0x10,
  179. 0x30, 0x70 )
  180. THEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  181. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  182. 2 + 1, 2)))) + 3
  183. + CONVERT(INT, CEILING(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  184. CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(SUBSTRING([RowLog Contents 0],
  185. 2 + 1, 2)))) + 1,
  186. 2)))) / 8.0)), 2))))
  187. ELSE NULL
  188. END ) * 2 ) )
  189. ELSE NULL
  190. END AS [VarColumnStart] ,
  191. [Slot ID]
  192. FROM sys.fn_dblog(NULL, NULL)
  193. WHERE AllocUnitId IN (
  194. SELECT [Allocation_unit_id]
  195. FROM sys.allocation_units allocunits
  196. INNER JOIN sys.partitions partitions ON ( allocunits.type IN (
  197. 1, 3 )
  198. AND partitions.hobt_id = allocunits.container_id
  199. )
  200. OR ( allocunits.type = 2
  201. AND partitions.partition_id = allocunits.container_id
  202. )
  203. WHERE object_id = OBJECT_ID(''
  204. + @SchemaName_n_TableName
  205. + '') )
  206. AND Context IN ( 'LCX_MARK_AS_GHOST', 'LCX_HEAP' )
  207. AND Operation IN ( 'LOP_DELETE_ROWS' )
  208. AND SUBSTRING([RowLog Contents 0], 1, 1) IN ( 0x10,
  209. 0x30, 0x70 )
  210.  
  211. /*Use this subquery to filter the date*/
  212. AND [TRANSACTION ID] IN (
  213. SELECT DISTINCT
  214. [TRANSACTION ID]
  215. FROM sys.fn_dblog(NULL, NULL)
  216. WHERE Context IN ( 'LCX_NULL' )
  217. AND Operation IN ( 'LOP_BEGIN_XACT' )
  218. AND [Transaction Name] IN ( 'DELETE',
  219. 'user_transaction' )
  220. AND CONVERT(NVARCHAR(11), [Begin Time]) BETWEEN @Date_From
  221. AND
  222. @Date_To )
  223. ),
  224.  
  225. --Use this technique to repeate the row till the no of bytes of the row.
  226. N1 ( n )
  227. AS ( SELECT 1
  228. UNION ALL
  229. SELECT 1
  230. ),
  231. N2 ( n )
  232. AS ( SELECT 1
  233. FROM N1 AS X ,
  234. N1 AS Y
  235. ),
  236. N3 ( n )
  237. AS ( SELECT 1
  238. FROM N2 AS X ,
  239. N2 AS Y
  240. ),
  241. N4 ( n )
  242. AS ( SELECT ROW_NUMBER() OVER ( ORDER BY X.n )
  243. FROM N3 AS X ,
  244. N3 AS Y
  245. )
  246. INSERT INTO @DeletedRecords
  247. SELECT RowLogContents ,
  248. [AllocUnitID] ,
  249. [Transaction ID] ,
  250. [FixedLengthData] ,
  251. [TotalNoOfCols] ,
  252. [NullBitMapLength] ,
  253. [NullBytes] ,
  254. [TotalNoofVarCols] ,
  255. [ColumnOffsetArray] ,
  256. [VarColumnStart] ,
  257. [Slot ID]
  258. ---Get the Null value against each column (1 means null zero means not null)
  259. ,
  260. [NullBitMap] = ( REPLACE(STUFF(( SELECT
  261. ','
  262. + ( CASE
  263. WHEN [ID] = 0
  264. THEN CONVERT(NVARCHAR(1), ( SUBSTRING(NullBytes,
  265. n, 1) % 2 ))
  266. ELSE CONVERT(NVARCHAR(1), ( ( SUBSTRING(NullBytes,
  267. n, 1)
  268. / [Bitvalue] )
  269. % 2 ))
  270. END ) --as [nullBitMap]
  271. FROM N4 AS Nums
  272. JOIN RowData AS C ON n <= NullBitMapLength
  273. CROSS JOIN @bitTable
  274. WHERE
  275. C.[RowLogContents] = D.[RowLogContents]
  276. ORDER BY [RowLogContents] ,
  277. n ASC
  278. FOR
  279. XML PATH('')
  280. ), 1, 1, ''), ',', '') )
  281. FROM RowData D
  282.  
  283. IF ( SELECT COUNT(*)
  284. FROM @DeletedRecords
  285. ) = 0
  286. BEGIN
  287. RAISERROR('There is no data in the log as per the search criteria',16,1)
  288. RETURN
  289. END
  290.  
  291. DECLARE @ColumnNameAndData TABLE
  292. (
  293. [Row ID] INT ,
  294. [Rowlogcontents] VARBINARY(MAX) ,
  295. [NAME] SYSNAME ,
  296. [nullbit] SMALLINT ,
  297. [leaf_offset] SMALLINT ,
  298. [length] SMALLINT ,
  299. [system_type_id] TINYINT ,
  300. [bitpos] TINYINT ,
  301. [xprec] TINYINT ,
  302. [xscale] TINYINT ,
  303. [is_null] INT ,
  304. [Column value Size] INT ,
  305. [Column Length] INT ,
  306. [hex_Value] VARBINARY(MAX) ,
  307. [Slot ID] INT ,
  308. [Update] INT
  309. )
  310.  
  311. --Create common table expression and join it with the rowdata table
  312. -- to get each column details
  313. /*This part is for variable data columns*/
  314. --@RowLogContents,
  315. --(col.columnOffValue - col.columnLength) + 1,
  316. --col.columnLength
  317. --)
  318. INSERT INTO @ColumnNameAndData
  319. SELECT [Row ID] ,
  320. Rowlogcontents ,
  321. NAME ,
  322. cols.leaf_null_bit AS nullbit ,
  323. leaf_offset ,
  324. ISNULL(syscolumns.length, cols.max_length) AS [length] ,
  325. cols.system_type_id ,
  326. cols.leaf_bit_position AS bitpos ,
  327. ISNULL(syscolumns.xprec, cols.precision) AS xprec ,
  328. ISNULL(syscolumns.xscale, cols.scale) AS xscale ,
  329. SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) AS is_null ,
  330. ( CASE WHEN leaf_offset < 1
  331. AND SUBSTRING([nullBitMap], cols.leaf_null_bit,
  332. 1) = 0
  333. THEN ( CASE WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  334. ( 2
  335. * leaf_offset
  336. * -1 ) - 1, 2)))) > 30000
  337. THEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  338. ( 2
  339. * leaf_offset
  340. * -1 ) - 1, 2))))
  341. - POWER(2, 15)
  342. ELSE CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  343. ( 2
  344. * leaf_offset
  345. * -1 ) - 1, 2))))
  346. END )
  347. END ) AS [Column value Size] ,
  348. ( CASE WHEN leaf_offset < 1
  349. AND SUBSTRING([nullBitMap], cols.leaf_null_bit,
  350. 1) = 0
  351. THEN ( CASE WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  352. ( 2
  353. * leaf_offset
  354. * -1 ) - 1, 2)))) > 30000
  355. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  356. ( 2
  357. * ( ( leaf_offset
  358. * -1 ) - 1 ) )
  359. - 1, 2)))), 0),
  360. [varColumnStart]) < 30000
  361. THEN ( CASE WHEN [System_type_id] IN (
  362. 35, 34, 99 ) THEN 16
  363. ELSE 24
  364. END )
  365. WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  366. ( 2
  367. * leaf_offset
  368. * -1 ) - 1, 2)))) > 30000
  369. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  370. ( 2
  371. * ( ( leaf_offset
  372. * -1 ) - 1 ) )
  373. - 1, 2)))), 0),
  374. [varColumnStart]) > 30000
  375. THEN ( CASE WHEN [System_type_id] IN (
  376. 35, 34, 99 ) THEN 16
  377. ELSE 24
  378. END ) --24
  379. WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  380. ( 2
  381. * leaf_offset
  382. * -1 ) - 1, 2)))) < 30000
  383. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  384. ( 2
  385. * ( ( leaf_offset
  386. * -1 ) - 1 ) )
  387. - 1, 2)))), 0),
  388. [varColumnStart]) < 30000
  389. THEN ( CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  390. ( 2
  391. * leaf_offset
  392. * -1 ) - 1, 2))))
  393. - ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  394. ( 2
  395. * ( ( leaf_offset
  396. * -1 ) - 1 ) )
  397. - 1, 2)))), 0),
  398. [varColumnStart]) )
  399. WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  400. ( 2
  401. * leaf_offset
  402. * -1 ) - 1, 2)))) < 30000
  403. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  404. ( 2
  405. * ( ( leaf_offset
  406. * -1 ) - 1 ) )
  407. - 1, 2)))), 0),
  408. [varColumnStart]) > 30000
  409. THEN POWER(2, 15)
  410. + CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  411. ( 2
  412. * leaf_offset
  413. * -1 ) - 1, 2))))
  414. - ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  415. ( 2
  416. * ( ( leaf_offset
  417. * -1 ) - 1 ) )
  418. - 1, 2)))), 0),
  419. [varColumnStart])
  420. END )
  421. END ) AS [Column Length] ,
  422. ( CASE WHEN SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) = 1
  423. THEN NULL
  424. ELSE SUBSTRING(Rowlogcontents,
  425. ( ( CASE WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  426. ( 2
  427. * leaf_offset
  428. * -1 ) - 1, 2)))) > 30000
  429. THEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  430. ( 2
  431. * leaf_offset
  432. * -1 ) - 1, 2))))
  433. - POWER(2, 15)
  434. ELSE CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  435. ( 2
  436. * leaf_offset
  437. * -1 ) - 1, 2))))
  438. END )
  439. - ( CASE WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  440. ( 2
  441. * leaf_offset
  442. * -1 ) - 1, 2)))) > 30000
  443. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  444. ( 2
  445. * ( ( leaf_offset
  446. * -1 ) - 1 ) )
  447. - 1, 2)))), 0),
  448. [varColumnStart]) < 30000
  449. THEN ( CASE
  450. WHEN [System_type_id] IN (
  451. 35, 34, 99 )
  452. THEN 16
  453. ELSE 24
  454. END ) --24
  455. WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  456. ( 2
  457. * leaf_offset
  458. * -1 ) - 1, 2)))) > 30000
  459. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  460. ( 2
  461. * ( ( leaf_offset
  462. * -1 ) - 1 ) )
  463. - 1, 2)))), 0),
  464. [varColumnStart]) > 30000
  465. THEN ( CASE
  466. WHEN [System_type_id] IN (
  467. 35, 34, 99 )
  468. THEN 16
  469. ELSE 24
  470. END ) --24
  471. WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  472. ( 2
  473. * leaf_offset
  474. * -1 ) - 1, 2)))) < 30000
  475. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  476. ( 2
  477. * ( ( leaf_offset
  478. * -1 ) - 1 ) )
  479. - 1, 2)))), 0),
  480. [varColumnStart]) < 30000
  481. THEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  482. ( 2
  483. * leaf_offset
  484. * -1 ) - 1, 2))))
  485. - ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  486. ( 2
  487. * ( ( leaf_offset
  488. * -1 ) - 1 ) )
  489. - 1, 2)))), 0),
  490. [varColumnStart])
  491. WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  492. ( 2
  493. * leaf_offset
  494. * -1 ) - 1, 2)))) < 30000
  495. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  496. ( 2
  497. * ( ( leaf_offset
  498. * -1 ) - 1 ) )
  499. - 1, 2)))), 0),
  500. [varColumnStart]) > 30000
  501. THEN POWER(2, 15)
  502. + CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  503. ( 2
  504. * leaf_offset
  505. * -1 ) - 1, 2))))
  506. - ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  507. ( 2
  508. * ( ( leaf_offset
  509. * -1 ) - 1 ) )
  510. - 1, 2)))), 0),
  511. [varColumnStart])
  512. END ) ) + 1,
  513. ( CASE WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  514. ( 2
  515. * leaf_offset
  516. * -1 ) - 1, 2)))) > 30000
  517. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  518. ( 2
  519. * ( ( leaf_offset
  520. * -1 ) - 1 ) )
  521. - 1, 2)))), 0),
  522. [varColumnStart]) < 30000
  523. THEN ( CASE WHEN [System_type_id] IN (
  524. 35, 34, 99 )
  525. THEN 16
  526. ELSE 24
  527. END ) --24
  528. WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  529. ( 2
  530. * leaf_offset
  531. * -1 ) - 1, 2)))) > 30000
  532. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  533. ( 2
  534. * ( ( leaf_offset
  535. * -1 ) - 1 ) )
  536. - 1, 2)))), 0),
  537. [varColumnStart]) > 30000
  538. THEN ( CASE WHEN [System_type_id] IN (
  539. 35, 34, 99 )
  540. THEN 16
  541. ELSE 24
  542. END ) --24
  543. WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  544. ( 2
  545. * leaf_offset
  546. * -1 ) - 1, 2)))) < 30000
  547. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  548. ( 2
  549. * ( ( leaf_offset
  550. * -1 ) - 1 ) )
  551. - 1, 2)))), 0),
  552. [varColumnStart]) < 30000
  553. THEN ABS(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  554. ( 2
  555. * leaf_offset
  556. * -1 ) - 1, 2))))
  557. - ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  558. ( 2
  559. * ( ( leaf_offset
  560. * -1 ) - 1 ) )
  561. - 1, 2)))), 0),
  562. [varColumnStart]))
  563. WHEN CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  564. ( 2
  565. * leaf_offset
  566. * -1 ) - 1, 2)))) < 30000
  567. AND ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  568. ( 2
  569. * ( ( leaf_offset
  570. * -1 ) - 1 ) )
  571. - 1, 2)))), 0),
  572. [varColumnStart]) > 30000
  573. THEN POWER(2, 15)
  574. + CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  575. ( 2
  576. * leaf_offset
  577. * -1 ) - 1, 2))))
  578. - ISNULL(NULLIF(CONVERT(INT, CONVERT(BINARY(2), REVERSE(SUBSTRING([ColumnOffsetArray],
  579. ( 2
  580. * ( ( leaf_offset
  581. * -1 ) - 1 ) )
  582. - 1, 2)))), 0),
  583. [varColumnStart])
  584. END ))
  585. END ) AS hex_Value ,
  586. [Slot ID] ,
  587. 0
  588. FROM @DeletedRecords A
  589. INNER JOIN sys.allocation_units allocunits ON A.[AllocUnitId] = allocunits.[Allocation_Unit_Id]
  590. INNER JOIN sys.partitions partitions ON ( allocunits.type IN (
  591. 1, 3 )
  592. AND partitions.hobt_id = allocunits.container_id
  593. )
  594. OR ( allocunits.type = 2
  595. AND partitions.partition_id = allocunits.container_id
  596. )
  597. INNER JOIN sys.system_internals_partition_columns cols ON cols.partition_id = partitions.partition_id
  598. LEFT OUTER JOIN syscolumns ON syscolumns.id = partitions.object_id
  599. AND syscolumns.colid = cols.partition_column_id
  600. WHERE leaf_offset < 0
  601. UNION
  602. /*This part is for fixed data columns*/
  603. SELECT [Row ID] ,
  604. Rowlogcontents ,
  605. NAME ,
  606. cols.leaf_null_bit AS nullbit ,
  607. leaf_offset ,
  608. ISNULL(syscolumns.length, cols.max_length) AS [length] ,
  609. cols.system_type_id ,
  610. cols.leaf_bit_position AS bitpos ,
  611. ISNULL(syscolumns.xprec, cols.precision) AS xprec ,
  612. ISNULL(syscolumns.xscale, cols.scale) AS xscale ,
  613. SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) AS is_null ,
  614. ( SELECT TOP 1
  615. ISNULL(SUM(CASE WHEN C.leaf_offset > 1
  616. THEN max_length
  617. ELSE 0
  618. END), 0)
  619. FROM sys.system_internals_partition_columns C
  620. WHERE cols.partition_id = C.partition_id
  621. AND C.leaf_null_bit < cols.leaf_null_bit
  622. ) + 5 AS [Column value Size] ,
  623. syscolumns.length AS [Column Length] ,
  624. CASE WHEN SUBSTRING([nullBitMap], cols.leaf_null_bit, 1) = 1
  625. THEN NULL
  626. ELSE SUBSTRING(Rowlogcontents,
  627. ( SELECT TOP 1
  628. ISNULL(SUM(CASE
  629. WHEN C.leaf_offset > 1
  630. AND C.leaf_bit_position = 0
  631. THEN max_length
  632. ELSE 0
  633. END), 0)
  634. FROM sys.system_internals_partition_columns C
  635. WHERE cols.partition_id = C.partition_id
  636. AND C.leaf_null_bit < cols.leaf_null_bit
  637. ) + 5, syscolumns.length)
  638. END AS hex_Value ,
  639. [Slot ID] ,
  640. 0
  641. FROM @DeletedRecords A
  642. INNER JOIN sys.allocation_units allocunits ON A.[AllocUnitId] = allocunits.[Allocation_Unit_Id]
  643. INNER JOIN sys.partitions partitions ON ( allocunits.type IN (
  644. 1, 3 )
  645. AND partitions.hobt_id = allocunits.container_id
  646. )
  647. OR ( allocunits.type = 2
  648. AND partitions.partition_id = allocunits.container_id
  649. )
  650. INNER JOIN sys.system_internals_partition_columns cols ON cols.partition_id = partitions.partition_id
  651. LEFT OUTER JOIN syscolumns ON syscolumns.id = partitions.object_id
  652. AND syscolumns.colid = cols.partition_column_id
  653. WHERE leaf_offset > 0
  654. ORDER BY nullbit
  655.  
  656. DECLARE @BitColumnByte AS INT
  657. SELECT @BitColumnByte = CONVERT(INT, CEILING(COUNT(*) / 8.0))
  658. FROM @ColumnNameAndData
  659. WHERE [System_Type_id] = 104;
  660. WITH N1 ( n )
  661. AS ( SELECT 1
  662. UNION ALL
  663. SELECT 1
  664. ),
  665. N2 ( n )
  666. AS ( SELECT 1
  667. FROM N1 AS X ,
  668. N1 AS Y
  669. ),
  670. N3 ( n )
  671. AS ( SELECT 1
  672. FROM N2 AS X ,
  673. N2 AS Y
  674. ),
  675. N4 ( n )
  676. AS ( SELECT ROW_NUMBER() OVER ( ORDER BY X.n )
  677. FROM N3 AS X ,
  678. N3 AS Y
  679. ),
  680. CTE
  681. AS ( SELECT RowLogContents ,
  682. [nullbit] ,
  683. [BitMap] = CONVERT(VARBINARY(1), CONVERT(INT, SUBSTRING(( REPLACE(STUFF(( SELECT
  684. ','
  685. + ( CASE
  686. WHEN [ID] = 0
  687. THEN CONVERT(NVARCHAR(1), ( SUBSTRING(hex_Value,
  688. n, 1) % 2 ))
  689. ELSE CONVERT(NVARCHAR(1), ( ( SUBSTRING(hex_Value,
  690. n, 1)
  691. / [Bitvalue] )
  692. % 2 ))
  693. END ) --as [nullBitMap]
  694. FROM
  695. N4 AS Nums
  696. JOIN @ColumnNameAndData
  697. AS C ON n <= @BitColumnByte
  698. AND [System_Type_id] = 104
  699. AND bitpos = 0
  700. CROSS JOIN @bitTable
  701. WHERE
  702. C.[RowLogContents] = D.[RowLogContents]
  703. ORDER BY [RowLogContents] ,
  704. n ASC
  705. FOR
  706. XML
  707. PATH('')
  708. ), 1, 1, ''),
  709. ',', '') ),
  710. bitpos + 1, 1)))
  711. FROM @ColumnNameAndData D
  712. WHERE [System_Type_id] = 104
  713. )
  714. UPDATE A
  715. SET [hex_Value] = [BitMap]
  716. FROM @ColumnNameAndData A
  717. INNER JOIN CTE B ON A.[RowLogContents] = B.[RowLogContents]
  718. AND A.[nullbit] = B.[nullbit]
  719.  
  720. /**************Check for BLOB DATA TYPES******************************/
  721. DECLARE @Fileid INT
  722. DECLARE @Pageid INT
  723. DECLARE @Slotid INT
  724. DECLARE @CurrentLSN INT
  725. DECLARE @LinkID INT
  726. DECLARE @Context VARCHAR(50)
  727. DECLARE @ConsolidatedPageID VARCHAR(MAX)
  728. DECLARE @LCX_TEXT_MIX VARBINARY(MAX)
  729.  
  730. DECLARE @temppagedata TABLE
  731. (
  732. [ParentObject] SYSNAME ,
  733. [Object] SYSNAME ,
  734. [Field] SYSNAME ,
  735. [Value] SYSNAME
  736. )
  737.  
  738. DECLARE @pagedata TABLE
  739. (
  740. [Page ID] SYSNAME ,
  741. [File IDS] INT ,
  742. [Page IDS] INT ,
  743. [AllocUnitId] BIGINT ,
  744. [ParentObject] SYSNAME ,
  745. [Object] SYSNAME ,
  746. [Field] SYSNAME ,
  747. [Value] SYSNAME
  748. )
  749.  
  750. DECLARE @ModifiedRawData TABLE
  751. (
  752. [ID] INT IDENTITY(1, 1) ,
  753. [PAGE ID] VARCHAR(MAX) ,
  754. [FILE IDS] INT ,
  755. [PAGE IDS] INT ,
  756. [Slot ID] INT ,
  757. [AllocUnitId] BIGINT ,
  758. [RowLog Contents 0_var] VARCHAR(MAX) ,
  759. [RowLog Length] VARCHAR(50) ,
  760. [RowLog Len] INT ,
  761. [RowLog Contents 0] VARBINARY(MAX) ,
  762. [Link ID] INT DEFAULT ( 0 ) ,
  763. [Update] INT
  764. )
  765.  
  766. DECLARE Page_Data_Cursor CURSOR
  767. FOR
  768. /*We need to filter LOP_MODIFY_ROW,LOP_MODIFY_COLUMNS from log for deleted records of BLOB data type& Get its Slot No, Page ID & AllocUnit ID*/
  769. SELECT LTRIM(RTRIM(REPLACE([Description], 'Deallocated', ''))) AS [PAGE ID] ,
  770. [Slot ID] ,
  771. [AllocUnitId] ,
  772. NULL AS [RowLog Contents 0] ,
  773. NULL AS [RowLog Contents 0] ,
  774. Context
  775. FROM sys.fn_dblog(NULL, NULL)
  776. WHERE AllocUnitId IN (
  777. SELECT [Allocation_unit_id]
  778. FROM sys.allocation_units allocunits
  779. INNER JOIN sys.partitions partitions ON ( allocunits.type IN (
  780. 1, 3 )
  781. AND partitions.hobt_id = allocunits.container_id
  782. )
  783. OR ( allocunits.type = 2
  784. AND partitions.partition_id = allocunits.container_id
  785. )
  786. WHERE object_id = OBJECT_ID('' + @SchemaName_n_TableName
  787. + '') )
  788. AND Operation IN ( 'LOP_MODIFY_ROW' )
  789. AND [Context] IN ( 'LCX_PFS' )
  790. AND Description LIKE '%Deallocated%'
  791. /*Use this subquery to filter the date*/
  792. AND [TRANSACTION ID] IN (
  793. SELECT DISTINCT
  794. [TRANSACTION ID]
  795. FROM sys.fn_dblog(NULL, NULL)
  796. WHERE Context IN ( 'LCX_NULL' )
  797. AND Operation IN ( 'LOP_BEGIN_XACT' )
  798. AND [Transaction Name] = 'DELETE'
  799. AND CONVERT(NVARCHAR(11), [Begin Time]) BETWEEN @Date_From
  800. AND
  801. @Date_To )
  802. GROUP BY [Description] ,
  803. [Slot ID] ,
  804. [AllocUnitId] ,
  805. Context
  806. UNION
  807. SELECT [PAGE ID] ,
  808. [Slot ID] ,
  809. [AllocUnitId] ,
  810. SUBSTRING([RowLog Contents 0], 15,
  811. LEN([RowLog Contents 0])) AS [RowLog Contents 0] ,
  812. CONVERT(INT, SUBSTRING([RowLog Contents 0], 7, 2)) ,
  813. Context --,CAST(RIGHT([Current LSN],4) AS INT) AS [Current LSN]
  814. FROM sys.fn_dblog(NULL, NULL)
  815. WHERE AllocUnitId IN (
  816. SELECT [Allocation_unit_id]
  817. FROM sys.allocation_units allocunits
  818. INNER JOIN sys.partitions partitions ON ( allocunits.type IN (
  819. 1, 3 )
  820. AND partitions.hobt_id = allocunits.container_id
  821. )
  822. OR ( allocunits.type = 2
  823. AND partitions.partition_id = allocunits.container_id
  824. )
  825. WHERE object_id = OBJECT_ID('' + @SchemaName_n_TableName
  826. + '') )
  827. AND Context IN ( 'LCX_TEXT_MIX' )
  828. AND Operation IN ( 'LOP_DELETE_ROWS' )
  829. /*Use this subquery to filter the date*/
  830. AND [TRANSACTION ID] IN (
  831. SELECT DISTINCT
  832. [TRANSACTION ID]
  833. FROM sys.fn_dblog(NULL, NULL)
  834. WHERE Context IN ( 'LCX_NULL' )
  835. AND Operation IN ( 'LOP_BEGIN_XACT' )
  836. AND [Transaction Name] = 'DELETE'
  837. AND CONVERT(NVARCHAR(11), [Begin Time]) BETWEEN @Date_From
  838. AND
  839. @Date_To )
  840.  
  841. /****************************************/
  842.  
  843. OPEN Page_Data_Cursor
  844.  
  845. FETCH NEXT FROM Page_Data_Cursor INTO @ConsolidatedPageID, @Slotid,
  846. @AllocUnitID, @LCX_TEXT_MIX, @LinkID, @Context
  847.  
  848. WHILE @@FETCH_STATUS = 0
  849. BEGIN
  850. DECLARE @hex_pageid AS VARCHAR(MAX)
  851. /*Page ID contains File Number and page number It looks like 0001:00000130.
  852. In this example 0001 is file Number & 00000130 is Page Number & These numbers are in Hex format*/
  853. SET @Fileid = SUBSTRING(@ConsolidatedPageID, 0,
  854. CHARINDEX(':', @ConsolidatedPageID)) -- Seperate File ID from Page ID
  855.  
  856. SET @hex_pageid = '0x' + SUBSTRING(@ConsolidatedPageID,
  857. CHARINDEX(':',
  858. @ConsolidatedPageID)
  859. + 1, LEN(@ConsolidatedPageID)) ---Seperate the page ID
  860. SELECT @Pageid = CONVERT(INT, CAST('' AS XML).value('xs:hexBinary(substring(sql:variable("@hex_pageid"),sql:column("t.pos")) )',
  861. 'varbinary(max)')) -- Convert Page ID from hex to integer
  862. FROM ( SELECT CASE SUBSTRING(@hex_pageid, 1, 2)
  863. WHEN '0x' THEN 3
  864. ELSE 0
  865. END
  866. ) AS t ( pos )
  867.  
  868. IF @Context = 'LCX_PFS'
  869. BEGIN
  870. DELETE @temppagedata
  871. INSERT INTO @temppagedata
  872. EXEC
  873. ( 'DBCC PAGE(' + @DataBase_Name + ', '
  874. + @fileid + ', ' + @pageid
  875. + ', 1) with tableresults,no_infomsgs;'
  876. );
  877. INSERT INTO @pagedata
  878. SELECT @ConsolidatedPageID ,
  879. @fileid ,
  880. @pageid ,
  881. @AllocUnitID ,
  882. [ParentObject] ,
  883. [Object] ,
  884. [Field] ,
  885. [Value]
  886. FROM @temppagedata
  887. END
  888. ELSE
  889. IF @Context = 'LCX_TEXT_MIX'
  890. BEGIN
  891. INSERT INTO @ModifiedRawData
  892. SELECT @ConsolidatedPageID ,
  893. @fileid ,
  894. @pageid ,
  895. @Slotid ,
  896. @AllocUnitID ,
  897. NULL ,
  898. 0 ,
  899. CONVERT(INT, CONVERT(VARBINARY, REVERSE(SUBSTRING(@LCX_TEXT_MIX,
  900. 11, 2)))) ,
  901. @LCX_TEXT_MIX ,
  902. @LinkID ,
  903. 0
  904. END
  905. FETCH NEXT FROM Page_Data_Cursor INTO @ConsolidatedPageID, @Slotid,
  906. @AllocUnitID, @LCX_TEXT_MIX, @LinkID, @Context
  907. END
  908.  
  909. CLOSE Page_Data_Cursor
  910. DEALLOCATE Page_Data_Cursor
  911.  
  912. DECLARE @Newhexstring VARCHAR(MAX);
  913.  
  914. --The data is in multiple rows in the page, so we need to convert it into one row as a single hex value.
  915. --This hex value is in string format
  916. INSERT INTO @ModifiedRawData
  917. ( [PAGE ID] ,
  918. [FILE IDS] ,
  919. [PAGE IDS] ,
  920. [Slot ID] ,
  921. [AllocUnitId] ,
  922. [RowLog Contents 0_var] ,
  923. [RowLog Length]
  924. )
  925. SELECT [Page ID] ,
  926. [FILE IDS] ,
  927. [PAGE IDS] ,
  928. SUBSTRING([ParentObject],
  929. CHARINDEX('Slot', [ParentObject]) + 4,
  930. ( CHARINDEX('Offset', [ParentObject])
  931. - ( CHARINDEX('Slot', [ParentObject]) + 4 ) )
  932. - 2) AS [Slot ID] ,
  933. [AllocUnitId] ,
  934. SUBSTRING(( SELECT REPLACE(STUFF(( SELECT
  935. REPLACE(SUBSTRING([Value],
  936. CHARINDEX(':',
  937. [Value]) + 1,
  938. CHARINDEX('†',
  939. [Value])
  940. - CHARINDEX(':',
  941. [Value])), '†',
  942. '')
  943. FROM @pagedata C
  944. WHERE B.[Page ID] = C.[Page ID]
  945. AND SUBSTRING(B.[ParentObject],
  946. CHARINDEX('Slot',
  947. B.[ParentObject])
  948. + 4,
  949. ( CHARINDEX('Offset',
  950. B.[ParentObject])
  951. - ( CHARINDEX('Slot',
  952. B.[ParentObject])
  953. + 4 ) )) = SUBSTRING(C.[ParentObject],
  954. CHARINDEX('Slot',
  955. C.[ParentObject])
  956. + 4,
  957. ( CHARINDEX('Offset',
  958. C.[ParentObject])
  959. - ( CHARINDEX('Slot',
  960. C.[ParentObject])
  961. + 4 ) ))
  962. AND [Object] LIKE '%Memory Dump%'
  963. ORDER BY '0x'
  964. + LEFT([Value],
  965. CHARINDEX(':',
  966. [Value]) - 1)
  967. FOR
  968. XML PATH('')
  969. ), 1, 1, ''), ' ', '')
  970. ), 1, 20000) AS [Value] ,
  971. SUBSTRING(( SELECT '0x'
  972. + REPLACE(STUFF(( SELECT
  973. REPLACE(SUBSTRING([Value],
  974. CHARINDEX(':',
  975. [Value]) + 1,
  976. CHARINDEX('†',
  977. [Value])
  978. - CHARINDEX(':',
  979. [Value])), '†',
  980. '')
  981. FROM
  982. @pagedata C
  983. WHERE
  984. B.[Page ID] = C.[Page ID]
  985. AND SUBSTRING(B.[ParentObject],
  986. CHARINDEX('Slot',
  987. B.[ParentObject])
  988. + 4,
  989. ( CHARINDEX('Offset',
  990. B.[ParentObject])
  991. - ( CHARINDEX('Slot',
  992. B.[ParentObject])
  993. + 4 ) )) = SUBSTRING(C.[ParentObject],
  994. CHARINDEX('Slot',
  995. C.[ParentObject])
  996. + 4,
  997. ( CHARINDEX('Offset',
  998. C.[ParentObject])
  999. - ( CHARINDEX('Slot',
  1000. C.[ParentObject])
  1001. + 4 ) ))
  1002. AND [Object] LIKE '%Memory Dump%'
  1003. ORDER BY '0x'
  1004. + LEFT([Value],
  1005. CHARINDEX(':',
  1006. [Value]) - 1)
  1007. FOR
  1008. XML PATH('')
  1009. ), 1, 1, ''), ' ', '')
  1010. ), 7, 4) AS [Length]
  1011. FROM @pagedata B
  1012. WHERE [Object] LIKE '%Memory Dump%'
  1013. GROUP BY [Page ID] ,
  1014. [FILE IDS] ,
  1015. [PAGE IDS] ,
  1016. [ParentObject] ,
  1017. [AllocUnitId]--,[Current LSN]
  1018. ORDER BY [Slot ID]
  1019.  
  1020. UPDATE @ModifiedRawData
  1021. SET [RowLog Len] = CONVERT(VARBINARY(8000), REVERSE(CAST('' AS XML).value('xs:hexBinary(substring(sql:column("[RowLog Length]"),0))',
  1022. 'varbinary(Max)')))
  1023. FROM @ModifiedRawData
  1024. WHERE [LINK ID] = 0
  1025.  
  1026. UPDATE @ModifiedRawData
  1027. SET [RowLog Contents 0] = CAST('' AS XML).value('xs:hexBinary(substring(sql:column("[RowLog Contents 0_var]"),0))',
  1028. 'varbinary(Max)')
  1029. FROM @ModifiedRawData
  1030. WHERE [LINK ID] = 0
  1031.  
  1032. UPDATE B
  1033. SET B.[RowLog Contents 0] = ( CASE WHEN A.[RowLog Contents 0] IS NOT NULL
  1034. AND C.[RowLog Contents 0] IS NOT NULL
  1035. THEN A.[RowLog Contents 0]
  1036. + C.[RowLog Contents 0]
  1037. WHEN A.[RowLog Contents 0] IS NULL
  1038. AND C.[RowLog Contents 0] IS NOT NULL
  1039. THEN C.[RowLog Contents 0]
  1040. WHEN A.[RowLog Contents 0] IS NOT NULL
  1041. AND C.[RowLog Contents 0] IS NULL
  1042. THEN A.[RowLog Contents 0]
  1043. END ) ,
  1044. B.[Update] = ISNULL(B.[Update], 0) + 1
  1045. FROM @ModifiedRawData B
  1046. LEFT JOIN @ModifiedRawData A ON A.[Page IDS] = CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING(B.[RowLog Contents 0],
  1047. 15 + 14, 2))))
  1048. AND A.[File IDS] = CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING(B.[RowLog Contents 0],
  1049. 19 + 14, 2))))
  1050. AND A.[Link ID] = B.[Link ID]
  1051. LEFT JOIN @ModifiedRawData C ON C.[Page IDS] = CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING(B.[RowLog Contents 0],
  1052. 27 + 14, 2))))
  1053. AND C.[File IDS] = CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING(B.[RowLog Contents 0],
  1054. 31 + 14, 2))))
  1055. AND C.[Link ID] = B.[Link ID]
  1056. WHERE ( A.[RowLog Contents 0] IS NOT NULL
  1057. OR C.[RowLog Contents 0] IS NOT NULL
  1058. )
  1059.  
  1060. UPDATE B
  1061. SET B.[RowLog Contents 0] = ( CASE WHEN A.[RowLog Contents 0] IS NOT NULL
  1062. AND C.[RowLog Contents 0] IS NOT NULL
  1063. THEN A.[RowLog Contents 0]
  1064. + C.[RowLog Contents 0]
  1065. WHEN A.[RowLog Contents 0] IS NULL
  1066. AND C.[RowLog Contents 0] IS NOT NULL
  1067. THEN C.[RowLog Contents 0]
  1068. WHEN A.[RowLog Contents 0] IS NOT NULL
  1069. AND C.[RowLog Contents 0] IS NULL
  1070. THEN A.[RowLog Contents 0]
  1071. END )
  1072. --,B.[Update]=ISNULL(B.[Update],0)+1
  1073. FROM @ModifiedRawData B
  1074. LEFT JOIN @ModifiedRawData A ON A.[Page IDS] = CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING(B.[RowLog Contents 0],
  1075. 15 + 14, 2))))
  1076. AND A.[File IDS] = CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING(B.[RowLog Contents 0],
  1077. 19 + 14, 2))))
  1078. AND A.[Link ID] <> B.[Link ID]
  1079. AND B.[Update] = 0
  1080. LEFT JOIN @ModifiedRawData C ON C.[Page IDS] = CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING(B.[RowLog Contents 0],
  1081. 27 + 14, 2))))
  1082. AND C.[File IDS] = CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING(B.[RowLog Contents 0],
  1083. 31 + 14, 2))))
  1084. AND C.[Link ID] <> B.[Link ID]
  1085. AND B.[Update] = 0
  1086. WHERE ( A.[RowLog Contents 0] IS NOT NULL
  1087. OR C.[RowLog Contents 0] IS NOT NULL
  1088. )
  1089.  
  1090. UPDATE @ModifiedRawData
  1091. SET [RowLog Contents 0] = ( CASE WHEN [RowLog Len] >= 8000
  1092. THEN SUBSTRING([RowLog Contents 0],
  1093. 15, [RowLog Len])
  1094. WHEN [RowLog Len] < 8000
  1095. THEN SUBSTRING([RowLog Contents 0],
  1096. 15 + 6,
  1097. CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING([RowLog Contents 0],
  1098. 15, 6)))))
  1099. END )
  1100. FROM @ModifiedRawData
  1101. WHERE [LINK ID] = 0
  1102.  
  1103. UPDATE @ColumnNameAndData
  1104. SET [hex_Value] = [RowLog Contents 0]
  1105. --,A.[Update]=A.[Update]+1
  1106. FROM @ColumnNameAndData A
  1107. INNER JOIN @ModifiedRawData B ON CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING([hex_value],
  1108. 17, 4)))) = [PAGE IDS]
  1109. AND CONVERT(INT, SUBSTRING([hex_value],
  1110. 9, 2)) = B.[Link ID]
  1111. WHERE [System_Type_Id] IN ( 99, 167, 175, 231, 239, 241, 165, 98 )
  1112. AND [Link ID] <> 0
  1113.  
  1114. UPDATE @ColumnNameAndData
  1115. SET [hex_Value] = ( CASE WHEN B.[RowLog Contents 0] IS NOT NULL
  1116. AND C.[RowLog Contents 0] IS NOT NULL
  1117. THEN B.[RowLog Contents 0]
  1118. + C.[RowLog Contents 0]
  1119. WHEN B.[RowLog Contents 0] IS NULL
  1120. AND C.[RowLog Contents 0] IS NOT NULL
  1121. THEN C.[RowLog Contents 0]
  1122. WHEN B.[RowLog Contents 0] IS NOT NULL
  1123. AND C.[RowLog Contents 0] IS NULL
  1124. THEN B.[RowLog Contents 0]
  1125. END )
  1126. --,A.[Update]=A.[Update]+1
  1127. FROM @ColumnNameAndData A
  1128. LEFT JOIN @ModifiedRawData B ON CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING([hex_value],
  1129. 5, 4)))) = B.[PAGE IDS]
  1130. AND B.[Link ID] = 0
  1131. LEFT JOIN @ModifiedRawData C ON CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING([hex_value],
  1132. 17, 4)))) = C.[PAGE IDS]
  1133. AND C.[Link ID] = 0
  1134. WHERE [System_Type_Id] IN ( 99, 167, 175, 231, 239, 241, 165, 98 )
  1135. AND ( B.[RowLog Contents 0] IS NOT NULL
  1136. OR C.[RowLog Contents 0] IS NOT NULL
  1137. )
  1138.  
  1139. UPDATE @ColumnNameAndData
  1140. SET [hex_Value] = [RowLog Contents 0]
  1141. --,A.[Update]=A.[Update]+1
  1142. FROM @ColumnNameAndData A
  1143. INNER JOIN @ModifiedRawData B ON CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING([hex_value],
  1144. 9, 4)))) = [PAGE IDS]
  1145. AND CONVERT(INT, SUBSTRING([hex_value],
  1146. 3, 2)) = [Link ID]
  1147. WHERE [System_Type_Id] IN ( 35, 34, 99 )
  1148. AND [Link ID] <> 0
  1149.  
  1150. UPDATE @ColumnNameAndData
  1151. SET [hex_Value] = [RowLog Contents 0]
  1152. --,A.[Update]=A.[Update]+10
  1153. FROM @ColumnNameAndData A
  1154. INNER JOIN @ModifiedRawData B ON CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING([hex_value],
  1155. 9, 4)))) = [PAGE IDS]
  1156. WHERE [System_Type_Id] IN ( 35, 34, 99 )
  1157. AND [Link ID] = 0
  1158.  
  1159. UPDATE @ColumnNameAndData
  1160. SET [hex_Value] = [RowLog Contents 0]
  1161. --,A.[Update]=A.[Update]+1
  1162. FROM @ColumnNameAndData A
  1163. INNER JOIN @ModifiedRawData B ON CONVERT(INT, CONVERT(VARBINARY(MAX), REVERSE(SUBSTRING([hex_value],
  1164. 15, 4)))) = [PAGE IDS]
  1165. WHERE [System_Type_Id] IN ( 35, 34, 99 )
  1166. AND [Link ID] = 0
  1167.  
  1168. UPDATE @ColumnNameAndData
  1169. SET [hex_value] = 0xFFFE + SUBSTRING([hex_value], 9, LEN([hex_value]))
  1170. --,[Update]=[Update]+1
  1171. WHERE [system_type_id] = 241
  1172.  
  1173. CREATE TABLE [#temp_Data]
  1174. (
  1175. [FieldName] VARCHAR(MAX) ,
  1176. [FieldValue] NVARCHAR(MAX) ,
  1177. [Rowlogcontents] VARBINARY(8000) ,
  1178. [Row ID] INT
  1179. )
  1180.  
  1181. INSERT INTO #temp_Data
  1182. SELECT NAME ,
  1183. CASE WHEN system_type_id IN ( 231, 239 )
  1184. THEN LTRIM(RTRIM(CONVERT(NVARCHAR(MAX), hex_Value))) --NVARCHAR ,NCHAR
  1185. WHEN system_type_id IN ( 167, 175 )
  1186. THEN LTRIM(RTRIM(CONVERT(VARCHAR(MAX), hex_Value))) --VARCHAR,CHAR
  1187. WHEN system_type_id IN ( 35 )
  1188. THEN LTRIM(RTRIM(CONVERT(VARCHAR(MAX), hex_Value))) --Text
  1189. WHEN system_type_id IN ( 99 )
  1190. THEN LTRIM(RTRIM(CONVERT(NVARCHAR(MAX), hex_Value))) --nText
  1191. WHEN system_type_id = 48
  1192. THEN CONVERT(VARCHAR(MAX), CONVERT(TINYINT, CONVERT(BINARY(1), REVERSE(hex_Value)))) --TINY INTEGER
  1193. WHEN system_type_id = 52
  1194. THEN CONVERT(VARCHAR(MAX), CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE(hex_Value)))) --SMALL INTEGER
  1195. WHEN system_type_id = 56
  1196. THEN CONVERT(VARCHAR(MAX), CONVERT(INT, CONVERT(BINARY(4), REVERSE(hex_Value)))) -- INTEGER
  1197. WHEN system_type_id = 127
  1198. THEN CONVERT(VARCHAR(MAX), CONVERT(BIGINT, CONVERT(BINARY(8), REVERSE(hex_Value))))-- BIG INTEGER
  1199. WHEN system_type_id = 61
  1200. THEN CONVERT(VARCHAR(MAX), CONVERT(DATETIME, CONVERT(VARBINARY(8000), REVERSE(hex_Value))), 100) --DATETIME
  1201. WHEN system_type_id = 58
  1202. THEN CONVERT(VARCHAR(MAX), CONVERT(SMALLDATETIME, CONVERT(VARBINARY(8000), REVERSE(hex_Value))), 100) --SMALL DATETIME
  1203. WHEN system_type_id = 108
  1204. THEN CONVERT(VARCHAR(MAX), CONVERT(NUMERIC(38, 20), CONVERT(VARBINARY, CONVERT(VARBINARY(1), xprec)
  1205. + CONVERT(VARBINARY(1), xscale))
  1206. + CONVERT(VARBINARY(1), 0) + hex_Value)) --- NUMERIC
  1207. WHEN system_type_id = 106
  1208. THEN CONVERT(VARCHAR(MAX), CONVERT(DECIMAL(38, 20), CONVERT(VARBINARY, CONVERT(VARBINARY(1), xprec)
  1209. + CONVERT(VARBINARY(1), xscale))
  1210. + CONVERT(VARBINARY(1), 0) + hex_Value)) --- DECIMAL
  1211. WHEN system_type_id IN ( 60, 122 )
  1212. THEN CONVERT(VARCHAR(MAX), CONVERT(MONEY, CONVERT(VARBINARY(8000), REVERSE(hex_Value))), 2) --MONEY,SMALLMONEY
  1213. WHEN system_type_id = 104
  1214. THEN CONVERT(VARCHAR(MAX), CONVERT (BIT, CONVERT(BINARY(1), hex_Value)
  1215. % 2)) -- BIT
  1216. WHEN system_type_id = 62
  1217. THEN RTRIM(LTRIM(STR(CONVERT(FLOAT, SIGN(CAST(CONVERT(VARBINARY(8000), REVERSE(hex_Value)) AS BIGINT))
  1218. * ( 1.0
  1219. + ( CAST(CONVERT(VARBINARY(8000), REVERSE(hex_Value)) AS BIGINT)
  1220. & 0x000FFFFFFFFFFFFF )
  1221. * POWER(CAST(2 AS FLOAT),
  1222. -52) )
  1223. * POWER(CAST(2 AS FLOAT),
  1224. ( ( CAST(CONVERT(VARBINARY(8000), REVERSE(hex_Value)) AS BIGINT)
  1225. & 0x7ff0000000000000 )
  1226. / EXP(52 * LOG(2))
  1227. - 1023 ))), 53,
  1228. LEN(hex_Value)))) --- FLOAT
  1229. WHEN system_type_id = 59
  1230. THEN LEFT(LTRIM(STR(CAST(SIGN(CAST(CONVERT(VARBINARY(8000), REVERSE(hex_Value)) AS BIGINT))
  1231. * ( 1.0
  1232. + ( CAST(CONVERT(VARBINARY(8000), REVERSE(hex_Value)) AS BIGINT)
  1233. & 0x007FFFFF )
  1234. * POWER(CAST(2 AS REAL), -23) )
  1235. * POWER(CAST(2 AS REAL),
  1236. ( ( ( CAST(CONVERT(VARBINARY(8000), REVERSE(hex_Value)) AS INT) )
  1237. & 0x7f800000 )
  1238. / EXP(23 * LOG(2))
  1239. - 127 )) AS REAL), 23,
  1240. 23)), 8) --Real
  1241. WHEN system_type_id IN ( 165, 173 )
  1242. THEN ( CASE WHEN CHARINDEX(0x,
  1243. CAST('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))',
  1244. 'VARBINARY(8000)')) = 0
  1245. THEN '0x'
  1246. ELSE ''
  1247. END ) + CAST('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))',
  1248. 'varchar(max)') -- BINARY,VARBINARY
  1249. WHEN system_type_id = 34
  1250. THEN ( CASE WHEN CHARINDEX(0x,
  1251. CAST('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))',
  1252. 'VARBINARY(8000)')) = 0
  1253. THEN '0x'
  1254. ELSE ''
  1255. END ) + CAST('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))',
  1256. 'varchar(max)') --IMAGE
  1257. WHEN system_type_id = 36
  1258. THEN CONVERT(VARCHAR(MAX), CONVERT(UNIQUEIDENTIFIER, hex_Value)) --UNIQUEIDENTIFIER
  1259. WHEN system_type_id = 231
  1260. THEN CONVERT(VARCHAR(MAX), CONVERT(SYSNAME, hex_Value)) --SYSNAME
  1261. WHEN system_type_id = 241
  1262. THEN CONVERT(VARCHAR(MAX), CONVERT(XML, hex_Value)) --XML
  1263. WHEN system_type_id = 189
  1264. THEN ( CASE WHEN CHARINDEX(0x,
  1265. CAST('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))',
  1266. 'VARBINARY(8000)')) = 0
  1267. THEN '0x'
  1268. ELSE ''
  1269. END ) + CAST('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))',
  1270. 'varchar(max)') --TIMESTAMP
  1271. WHEN system_type_id = 98
  1272. THEN ( CASE WHEN CONVERT(INT, SUBSTRING(hex_Value, 1,
  1273. 1)) = 56
  1274. THEN CONVERT(VARCHAR(MAX), CONVERT(INT, CONVERT(BINARY(4), REVERSE(SUBSTRING(hex_Value,
  1275. 3,
  1276. LEN(hex_Value)))))) -- INTEGER
  1277. WHEN CONVERT(INT, SUBSTRING(hex_Value, 1,
  1278. 1)) = 108
  1279. THEN CONVERT(VARCHAR(MAX), CONVERT(NUMERIC(38,
  1280. 20), CONVERT(VARBINARY(1), SUBSTRING(hex_Value,
  1281. 3, 1))
  1282. + CONVERT(VARBINARY(1), SUBSTRING(hex_Value,
  1283. 4, 1))
  1284. + CONVERT(VARBINARY(1), 0)
  1285. + SUBSTRING(hex_Value, 5,
  1286. LEN(hex_Value)))) --- NUMERIC
  1287. WHEN CONVERT(INT, SUBSTRING(hex_Value, 1,
  1288. 1)) = 167
  1289. THEN LTRIM(RTRIM(CONVERT(VARCHAR(MAX), SUBSTRING(hex_Value,
  1290. 9,
  1291. LEN(hex_Value))))) --VARCHAR,CHAR
  1292. WHEN CONVERT(INT, SUBSTRING(hex_Value, 1,
  1293. 1)) = 36
  1294. THEN CONVERT(VARCHAR(MAX), CONVERT(UNIQUEIDENTIFIER, SUBSTRING(( hex_Value ),
  1295. 3, 20))) --UNIQUEIDENTIFIER
  1296. WHEN CONVERT(INT, SUBSTRING(hex_Value, 1,
  1297. 1)) = 61
  1298. THEN CONVERT(VARCHAR(MAX), CONVERT(DATETIME, CONVERT(VARBINARY(8000), REVERSE(SUBSTRING(hex_Value,
  1299. 3,
  1300. LEN(hex_Value))))), 100) --DATETIME
  1301. WHEN CONVERT(INT, SUBSTRING(hex_Value, 1,
  1302. 1)) = 165
  1303. THEN '0x'
  1304. + SUBSTRING(( CASE WHEN CHARINDEX(0x,
  1305. CAST('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))',
  1306. 'VARBINARY(8000)')) = 0
  1307. THEN '0x'
  1308. ELSE ''
  1309. END )
  1310. + CAST('' AS XML).value('xs:hexBinary(sql:column("hex_Value"))',
  1311. 'varchar(max)'),
  1312. 11, LEN(hex_Value)) -- BINARY,VARBINARY
  1313. END )
  1314. END AS FieldValue ,
  1315. [Rowlogcontents] ,
  1316. [Row ID]
  1317. FROM @ColumnNameAndData
  1318. ORDER BY nullbit
  1319.  
  1320. --Create the column name in the same order to do pivot table.
  1321.  
  1322. DECLARE @FieldName VARCHAR(MAX)
  1323. SET @FieldName = STUFF(( SELECT ','
  1324. + CAST(QUOTENAME([Name]) AS VARCHAR(MAX))
  1325. FROM syscolumns
  1326. WHERE id = OBJECT_ID(''
  1327. + @SchemaName_n_TableName
  1328. + '')
  1329. FOR
  1330. XML PATH('')
  1331. ), 1, 1, '')
  1332.  
  1333. --Finally did pivot table and get the data back in the same format.
  1334.  
  1335. SET @sql = 'SELECT ' + @FieldName
  1336. + ' FROM #temp_Data PIVOT (Min([FieldValue]) FOR FieldName IN ('
  1337. + @FieldName + ')) AS pvt'
  1338. EXEC sp_executesql @sql
  1339.  
  1340. GO

恢复你的数据

--恢复数据,不加时间段条件  参数:数据库名,表名
--EXAMPLE #1 : FOR ALL DELETED RECORDS
EXEC Recover_Deleted_Data_Proc 'test','dbo.aa'
GO --恢复数据,加时间段条件
--EXAMPLE #2 : FOR ANY SPECIFIC DATE RANGE
EXEC Recover_Deleted_Data_Proc 'test','dbo.aa','2014-04-23','2014-04-23'

执行了下面的存储过程之后你会发现会显示出刚才删除的数据

EXEC Recover_Deleted_Data_Proc 'test','dbo.aa'
GO


解释

究竟他是如何工作的?让我们来一步一步来,这个过程涉及到7个步骤:

步骤1:

我们需要获得SQLSERVER删除的数据记录.使用标准SQLSERVER函数fn_dblog,我们能够容易的获得事务日志记录(包括

已删的数据。不过,我们只需要事务日志中选中的被删数据,所以我们的过滤条件需要包含3个字段 Context, Operation & AllocUnitName)

We need to get the deleted records from sql server. By using the standard SQL Server function fn_blog, we can easily get all transaction log (Including deleted data. But, we need only the selected deleted records from the transaction log. So we included three filters (Context, Operation , AllocUnitName).

  • Context (‘LCX_MARK_AS_GHOST’and ‘LCX_HEAP’)
  • Operation (‘LOP_DELETE_ROWS’)
  • AllocUnitName(‘dbo.aa’) –- Schema + table Name

Context可以说明是堆表还是聚集表

Operation:删除操作

AllocUnitName:分配单元名称,表名

下面是一个代码片段

SELECT  [RowLog Contents 0]
FROM sys.fn_dblog(NULL, NULL)
WHERE AllocUnitName = 'dbo.aa'
AND Context IN ( 'LCX_MARK_AS_GHOST', 'LCX_HEAP' )
AND Operation IN ( 'LOP_DELETE_ROWS' )

这个查询会返回不同列的信息,但是我们只需要选择[RowLog Contents 0]列,去获得被删除的数据的内容

RowLog content 0列的内容类似于这样

“0x300018000100000000000000006B0000564920205900000

00500E001002800426F62206A65727279″

步骤2:

现在,我们已经删除了数据,这些数据以hex码的形式放在事务日志里,这些hex码是有规律的,我们根据这些规律可以很容易恢复这些数据。

不过在恢复这些数据之前,我们需要理解这些格式。这些格式在KalenDelaney’s SQL Internal’s book.的书里面有讲解

  • 1 Byte : Status Bit A
  • 1 Byte : Status Bit B
  • 2 Bytes : Fixed length size
  • n Bytes : Fixed length data
  • 2 Bytes : Total Number of Columns
  • n Bytes : NULL Bitmap (1 bit for each column as 1 indicates that the column is null and 0 indicate that the column is not null)
  • 2 Bytes : Number of variable-length columns
  • n Bytes : Column offset array (2x variable length column)
  • n Bytes : Data for variable length columns

所以, hex码的“RowLog content 0″列的内容就等价于

“Status Bit A +Status Bit B +Fixed length size +Fixed length data +Total Number of Columns +NULL Bitmap +Number of variable-length columns +NULL Bitmap+Number of variable-length columns +Column offset array +Data for variable length columns.”

更详细的可以参考:SQL Server2008存储结构之堆表、行溢出

关于数据行的结构我们还可以采用稍微宏观一些的视角来查看。

  

步骤3:

现在,我们需要解剖RowLog Content o列的内容(我们删除的数据的Hex码),利用上面的数据行的结构

  • [Fixed Length Data] = Substring (RowLog content 0, Status Bit A+Status Bit B + 1,2 bytes)
  • [Total No of Columns]= Substring (RowLog content 0, [Fixed Length Data] + 1,2 bytes)
  • [Null Bitmap length] = Ceiling ([Total No of Columns]/8.0)
  • [Null Bytes]= Substring (RowLog content 0, Status Bit A+ Status Bit B +[Fixed Length Data] +1, [Null Bitmap length] )
  • Total no of variable columns = Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [Null Bitmap length] + 2 )
  • Column Offset Array= Substring (RowLog content 0, Status Bit A+ Status Bit B + [Fixed Length Data] +1, [Null Bitmap length] + 2 , Total no of variable columns*2 )
  • Variable Column Start = Status Bit A+ Status Bit B + [Fixed Length Data] + [Null Bitmap length] + 2+( Total no of variable columns*2)

步骤4:

现在我们已经将hex码切开了(0x300008000100000002000001001300604F7D59),所以,我们能找到删除的行的某列的数据是否为null值

根据NULL位图。为了完成将NULL Bytes的hex码转换为二进制格式(正如之前讨论的,1表示行中对应的那一列为null,而0则表示对应的列有实际的数据)

如果还不是明白的童鞋可以看一下我写的这篇文章:《SQLSERVER中NULL位图的作用

步骤5:

现在,我们已经做了初步的数据分割 (Step-3) 和null值判断(Step-4) 。然后我们需要使用代码片段去获得列数据,例如:列名,列大小,精度,范围

和最重要的叶子的null位(确保列数据是固定长度的(<=-1表示可变长度)或者固定长度的(>=1))

使用下面的SQL语句

SELECT  *
FROM sys.allocation_units allocunits
INNER JOIN sys.partitions partitions ON ( allocunits.type IN ( 1, 3 )
AND partitions.hobt_id = allocunits.container_id
)
OR ( allocunits.type = 2
AND partitions.partition_id = allocunits.container_id
)
INNER JOIN sys.system_internals_partition_columns cols ON cols.partition_id = partitions.partition_id
LEFT OUTER JOIN syscolumns ON syscolumns.id = partitions.object_id
AND syscolumns.colid = cols.partition_column_id

与(Step-1,2,3,4) 获得的数据表做join连接,根据allocunits.[Allocation_Unit_Id]。

现在我们知道表和表中的数据信息,那么我们需要利用这些数据去将 [RowLog Contents 0] 列里的hex码的数据插入到表中的相应列

现在我们需要关心每一列的数据究竟是固定长度的还是可变长度的

步骤6:

我们收集了每列的hex格式的数据。现在我们需要利用[System_type_id]去转换这些数据回去正确的数据类型

每一种数据类型都有不同的数据类型转换机制。

--NVARCHAR ,NCHAR
WHEN system_type_id IN (231, 239) THEN LTRIM(RTRIM(CONVERT(NVARCHAR(max),hex_Value))) --VARCHAR,CHAR
WHEN system_type_id IN (167,175) THEN LTRIM(RTRIM(CONVERT(VARCHAR(max),REPLACE(hex_Value, 0x00, 0x20)))) --TINY INTEGER
WHEN system_type_id = 48 THEN CONVERT(VARCHAR(MAX), CONVERT(TINYINT, CONVERT(BINARY(1), REVERSE (hex_Value)))) --SMALL INTEGER
WHEN system_type_id = 52 THEN CONVERT(VARCHAR(MAX), CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (hex_Value)))) -- INTEGER
WHEN system_type_id = 56 THEN CONVERT(VARCHAR(MAX), CONVERT(INT, CONVERT(BINARY(4), REVERSE(hex_Value)))) -- BIG INTEGER
WHEN system_type_id = 127 THEN CONVERT(VARCHAR(MAX), CONVERT(BIGINT, CONVERT(BINARY(8), REVERSE(hex_Value)))) --DATETIME
WHEN system_type_id = 61 Then CONVERT(VARCHAR(Max),CONVERT(DATETIME,Convert(VARBINARY(max),REVERSE (hex_Value))),100) --SMALL DATETIME
WHEN system_type_id =58 Then CONVERT(VARCHAR(Max),CONVERT(SMALLDATETIME,CONVERT(VARBINARY(MAX),REVERSE(hex_Value))),100) --SMALL DATETIME --- NUMERIC
WHEN system_type_id = 108 THEN CONVERT(VARCHAR(MAX), CAST(CONVERT(NUMERIC(18,14), CONVERT(VARBINARY,CONVERT(VARBINARY,xprec)+CONVERT(VARBINARY,xscale))+CONVERT(VARBINARY(1),0) + hex_Value) as FLOAT)) --MONEY,SMALLMONEY
WHEN system_type_id In(60,122) THEN CONVERT(VARCHAR(MAX),Convert(MONEY,Convert(VARBINARY(MAX),Reverse(hex_Value))),2) --- DECIMAL
WHEN system_type_id = 106 THEN CONVERT(VARCHAR(MAX), CAST(CONVERT(Decimal(38,34), Convert(VARBINARY,Convert(VARBINARY,xprec)+CONVERT(VARBINARY,xscale))+CONVERT(VARBINARY(1),0) + hex_Value) as FLOAT)) -- BIT
WHEN system_type_id = 104 THEN CONVERT(VARCHAR(MAX),CONVERT (BIT,CONVERT(BINARY(1), hex_Value)%2)) --- FLOAT
WHEN system_type_id = 62 THEN RTRIM(LTRIM(Str(Convert(FLOAT,SIGN(CAST(Convert(VARBINARY(max),Reverse(hex_Value)) AS BIGINT)) * (1.0 + (CAST(CONVERT(VARBINARY(max),Reverse(hex_Value)) AS BIGINT) & 0x000FFFFFFFFFFFFF) * POWER(CAST(2 AS FLOAT), -52)) * POWER(CAST(2 AS FLOAT),((CAST(CONVERT(VARBINARY(max),Reverse(hex_Value)) AS BIGINT) & 0x7ff0000000000000) / EXP(52 * LOG(2))-1023))),53,LEN(hex_Value)))) --REAL
When system_type_id =59 THEN Left(LTRIM(STR(Cast(SIGN(CAST(Convert(VARBINARY(max),Reverse(hex_Value)) AS BIGINT))* (1.0 + (CAST(CONVERT(VARBINARY(max),Reverse(hex_Value)) AS BIGINT) & 0x007FFFFF) * POWER(CAST(2 AS Real), -23)) * POWER(CAST(2 AS Real),(((CAST(CONVERT(VARBINARY(max),Reverse(hex_Value)) AS INT) )& 0x7f800000)/ EXP(23 * LOG(2))-127))AS REAL),23,23)),8) --BINARY,VARBINARY
WHEN system_type_id In (165,173) THEN (CASE WHEN Charindex(0x,cast('' AS XML).value('xs:hexBinary(sql:column("hex_value"))', 'varbinary(max)')) = 0 THEN '0x' ELSE '' END) +cast('' AS XML).value('xs:hexBinary(sql:column("hex_value"))', 'varchar(max)') --UNIQUEIDENTIFIER
WHEN system_type_id =36 THEN CONVERT(VARCHAR(MAX),CONVERT(UNIQUEIDENTIFIER,hex_Value))

步骤7:

最终我们做一个数据透视表,你会看到最后的结果:被删的数据回来了!

注意:这些数据只是展示出来并没有自动插入回表中,你需要将这些数据重新插入回去表中!


我的测试

经过测试,作者写的这个存储过程还是有些问题

如果你创建的测试表的数据类型有xml或者是一些text数据类型的字段会有报错

Msg 537, Level 16, State 3, Procedure Recover_Deleted_Data_Proc, Line 525
Invalid length parameter passed to the LEFT or SUBSTRING function. Msg 9420, Level 16, State 1, Procedure Recover_Deleted_Data_Proc, Line 651
XML parsing: line 1, character 2, illegal xml character

但是一般的数据类型则不会,例如nvarchar这些

还有不要在存储过程的最后加

--Recover the deleted data without date range
EXEC Recover_Deleted_Data_Proc 'test','dbo.Test_Table'
GO
--Recover the deleted data it with date range
EXEC Recover_Deleted_Data_Proc 'test','dbo.Test_Table','2012-06-01','2012-06-30'

否则会报错

消息 50000,级别 16,状态 1,过程 Recover_Deleted_Data_Proc,第 290 行
There is no data in the log as per the search criteria

总结

实际上这个存储过程还是挺有研究意义的,对于想做一个跟Log Explorer for SQL Server软件功能差不多的软件出来

还是有可能的,跟着作者的思路,一步一步实现

苦于最近太忙,先分享出来,以后再研究这个存储过程了~

如有不对的地方,欢迎大家拍砖o(∩_∩)o 

支持date类型 感谢园友 dwchaoyue

EXEC Recover_Deleted_Data_Proc 'sss','dbo.testdaterecover','2014-04-23','2014-10-23'
--DROP TABLE testdaterecover
CREATE TABLE testdaterecover(dd DATE,cc DATETIME)
GO
INSERT INTO [dbo].[testdaterecover]
( [dd], [cc] )
VALUES ( GETDATE(), -- dd - date
GETDATE() -- cc - datetime
) SELECT CONVERT(VARCHAR(MAX),CONVERT(DATETIME, CONVERT(VARBINARY(8000),[cc])),120) FROM [dbo].[testdaterecover] DELETE [dbo].[testdaterecover] THEN CONVERT(VARCHAR(MAX), CONVERT(DATETIME, CONVERT(VARBINARY(8000), REVERSE(hex_Value))), 121) --DATETIME

2015-12-17 补充

注意:脚本中大量使用了reverse函数来进行转换,因为reverse函数的定义是返回字符串值的逆序,不是字节的逆序

那么就会导致有些数据类型是按字节逆序的,就会出错

WHEN system_type_id = 52 THEN CONVERT(VARCHAR(MAX), CONVERT(SMALLINT, CONVERT(BINARY(2), REVERSE (hex_Value))))

在SQL Server中,sys.fn_PhysLocFormatter这个函数也是使用reverse函数进行RID逆序,当遇到81-FE之间的字节时 被认为是双字节字符组合在一起参与逆序运算

所以得出来的某些结果也是错的,详细请参考《SQL Server性能调优实战》 P115

函数的逻辑是,每个字节加上相应的位数

比如 2这个数字是在第二个字节位置的,那么公式就是2*POWER(2,8) =512

相应进制

二进制后面加8个0

十六进制后面加2个0

只要学过计算机基础都不难理解的

本文版权归作者所有,未经作者同意不得转载。

恢复SQL Server被误删除的数据的更多相关文章

  1. 恢复SQL Server被误删除的数据(再扩展)

    恢复SQL Server被误删除的数据(再扩展) 大家对本人之前的文章<恢复SQL Server被误删除的数据> 反应非常热烈,但是文章里的存储过程不能实现对备份出来的日志备份里所删数据的 ...

  2. SQL Server中误删除数据的恢复

    SQL Server中误删除数据的恢复本来不是件难事,从事务日志恢复即可.但是,这个恢复需要有两个前提条件: 1. 至少有一个误删除之前的数据库完全备份. 2. 数据库的恢复模式(Recovery m ...

  3. ApexSQL Log 从意外UPDATE和DELETE操作中恢复SQL Server数据

    下载地址:https://www.apexsql.com/download.aspx 如何从意外UPDATE和DELETE操作中恢复SQL Server数据 ApexSQL Log 从意外UPDATE ...

  4. 通过日志恢复SQL Server的历史数据

    通过日志恢复SQL Server的历史数据 Posted on 2008-11-14 21:47 代码乱了 阅读(4658) 评论(10)  编辑 收藏 园子里前段时间发过一篇通过日志恢复MSSQL数 ...

  5. 如何恢复SQL Server 中的Master库

    如何恢复SQL Server 2005中的Master库 2011-05-10 16:34 Vegas Lee 博客园 我要评论(0) 字号:T | T   master库对于SQLServer来说, ...

  6. MS SQL Server数据库修复/MDF数据文件数据恢复/MDF质疑/mdf无法附加

    微软的SQL Server 数据库最常用的有两种类型的文件: 1.主要数据文件,文件后缀一般是.MDF: 2.事务日志文件,文件后缀一般是.LDF. 用户数据表.视图.存储过程等等数据,都是存放在MD ...

  7. SQL SERVER 和ACCESS的数据导入导出

            //批量导入Access         string filepath = Server.MapPath("student.mdb");         stri ...

  8. .SQL Server中 image类型数据的比较

    原文:.SQL Server中 image类型数据的比较 在SQL Server中如果你对text.ntext或者image数据类型的数据进行比较.将会提示:不能比较或排序 text.ntext 和 ...

  9. SQL Server 2008 批量插入数据时报错

    前几天在SQL Server 2008同步产品数据时,总是提示二进制文本被截断的错误,但是经过检查发现数据都符合格式要求. 百思不得其解,单独插入一条条数据则可以插入,但是批量导入则报错. 批量导入代 ...

随机推荐

  1. .NET Core中的认证管理解析

    .NET Core中的认证管理解析 0x00 问题来源 在新建.NET Core的Web项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...

  2. Git 在团队中的最佳实践--如何正确使用Git Flow

    我们已经从SVN 切换到Git很多年了,现在几乎所有的项目都在使用Github管理, 本篇文章讲一下为什么使用Git, 以及如何在团队中正确使用. Git的优点 Git的优点很多,但是这里只列出我认为 ...

  3. 【翻译】MongoDB指南/CRUD操作(二)

    [原文地址]https://docs.mongodb.com/manual/ MongoDB CRUD操作(二) 主要内容: 更新文档,删除文档,批量写操作,SQL与MongoDB映射图,读隔离(读关 ...

  4. mysql 7下载安装及问题解决

    mysql 7安装及问题解决 一.mysql下载 下载地址:https://www.mysql.com/downloads/ Community (GPL) Downloads MySQL Commu ...

  5. 展望未来:使用 PostCSS 和 cssnext 书写 CSS

    原文链接:A look into writing future CSS with PostCSS and cssnext 译者:nzbin 像twitter,google,bbc使用的一样,我打算看一 ...

  6. 算法与数据结构(十四) 堆排序 (Swift 3.0版)

    上篇博客主要讲了冒泡排序.插入排序.希尔排序以及选择排序.本篇博客就来讲一下堆排序(Heap Sort).看到堆排序这个名字我们就应该知道这种排序方式的特点,就是利用堆来讲我们的序列进行排序.&quo ...

  7. Javascript实现HashTable类

    散列算法可以尽快在数据结构中找出指定的一个值,因为可以通过Hash算法求出值的所在位置,存储和插入的时候都按照Hash算法放到指定位置. <script> function HashTab ...

  8. [原][Docker]特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

  9. 【云知道】究极秒杀Loadrunner乱码

    Loadrunner乱码一击必杀 之前有介绍一些简单的针对Loadrunner脚本或者调试输出内容中乱码的一些设置,但是并没能完全解决一些小伙伴的问题,因为那些设置实在能力有限,还是有很多做不到的事情 ...

  10. 使用po模式读取豆瓣读书最受关注的书籍,取出标题、评分、评论、题材 按评分从小到大排序并输出到txt文件中

    #coding=utf-8from time import sleepimport unittestfrom selenium import webdriverfrom selenium.webdri ...