接下来讲说返回的RowLogo Content列,例子中返回了三个列。这些列包含了数据操做的“有效工做负载(Playload)”记录。根据不一样操做类型有效负载的内容也是不一样的,可是它必须包含足够的信息,能让相应的数据操做在恢复时能被REDO和UNDO。对于每个INSERT而言,它包含了插入行的全部数据。咱们来验证一下,先看看RowLogo Contents 0的内容:数组
0x10002400010000007374616E64616C6F6E6520786163742020202020797D6F0064A60000030000
而后再看DBCC PAGE中Slot 0的内容:日志
0000000000000000: 10002400 01000000 7374616e 64616c6f 6e652078 ..$.....standalone x 0000000000000014: 61637420 20202020 797d6f00 64a60000 030000 act y}o.d......
DBCC PAGE输出行的内容被分红了左、中、右三个部分:code
- 左:表示对应内容在行内的字符位置(或者起始偏移量)
- 中:表示存储在页上实际数据,由5段8位的16进制数组成
- 右:实际数据的ASCII表示形式,只有字符数据可以被辨认出来。
BY Joe .TJ
对比RowLogo Contents 0和DBCC PAGE输出Slot 0的中段数据,发现二者数据是同样的。同时证实了LOP_INSERT_ROWS操做的RowLogo Contents 0日志内容包含了完整的被插入数据的内容。然而,经过分析RowLogo Contents的内容去找出某一特定的行,这个是很是困难的事情。若是你熟悉行结构,RowLogo Contents 0 列的内容能够分解为:orm
00 (NULL位图=Ceiling(NULL列数量/8))blog
关于行结构的更多内容,参考《SQL Server 2012 Internals》第六章'Table Storage'中的'The structure of data row'部分。行结构图:
事务
我用几个例子来讲明如何使用这些知识帮助咱们解决问题。先修改表中的数据:ci
insert into demotable (data) values ('junk'), ('important'), ('do not change!'); update demotable set data='changed' where data = 'do not change!'; update demotable set created_at = getutcdate() where data = 'changed'; delete from demotable where data='important'
通过上面的修改,新插入的三行中只有('junk')没有改变。('do not changed')被改为了('change'),建立时间也被改为如今的UTC时间。important行被删除了。咱们可否从日志找到这些修改操做对应的日志呢?先从较简单的问题开始字符串
由于'junk'还存在于表中,咱们经过找到它的lockres,而后根据lockres去搜索日志中的[Lock Information],再从匹配的行中[Transaction ID]字段获得INSERT的事务ID,再经过事务ID去找出与这个事务相关的全部日志记录。get
--get lockres value of 'junk' declare @lockres nchar(14); select top(1) @lockres=%%lockres%% from dbo.demotable where data='junk'; --Using lockres to find Transaction ID declare @xactid nvarchar(14); select top(1) @xactid=[Transaction ID] from sys.fn_dblog(null,null) where CHARINDEX(@lockres,[Lock Information] )>0 --get all log record of the found Transaction ID select [Current LSN], [Operation], [Transaction ID], [Transaction SID], [Begin Time], [End Time] from sys.fn_dblog(null,null) where [Transaction ID]=@xactid; Current LSN Operation Transaction ID Transaction SID Begin Time End Time ----------------------- ---------------- -------------------- ------------------ ----------------------- --------- 00000023:0000007b:0001 LOP_BEGIN_XACT 0000:00000363 0x01 2016/08/19 14:50:27:917 NULL 00000023:0000007b:0002 LOP_INSERT_ROWS 0000:00000363 NULL NULL NULL 00000023:0000007b:0003 LOP_INSERT_ROWS 0000:00000363 NULL NULL NULL 00000023:0000007b:0004 LOP_INSERT_ROWS 0000:00000363 NULL NULL NULL 00000023:0000007b:0005 LOP_COMMIT_XACT 0000:00000363 NULL NULL 2016/08/19 14:50:27:920
使用上面的方法时,我假设了几个前提:it
在现实中状况不会这么简单:HASH碰撞的问题,可能有不少事务曾锁定过这一行等等。
跟以前的思路差很少,先从表中存的'changed'找到lockres,而后根据lockres找到全部事务ID,而后再找出全部的日志记录。
--get lockres value of the updated 'changed' declare @lockres nchar(14); select @lockres=%%lockres%% from dbo.demotable where data='changed'; --Using lockres to find ALL relevant Transaction IDs declare @xactid table (xid nvarchar(14)); insert into @xactid select [Transaction ID] from sys.fn_dblog(null,null) where CHARINDEX(@lockres,[Lock Information] )>0 --get all log records of the found Transaction ID select [Current LSN], [Operation], [Transaction ID], [Transaction SID], [Begin Time], [End Time], [Num Elements], [RowLog Contents 0], [RowLog Contents 1], [RowLog Contents 2], [RowLog Contents 3], [RowLog Contents 4], [RowLog Contents 5] from sys.fn_dblog(null,null)as a join @xactid b on a.[Transaction ID]=b.xid
从结果能够看到,有2个LOP_MODIFY_ROW操做的日志,怎么知道哪一条日志是我想要找的?在查询时,我增了[Num Elements]列,由于[Num Elements]=6,表示这个操做有6个有效工做负载,因此我增长了和[RowLog Contents 0]到[RowLog Contents 5]这6个字段。这个6个字段中,包含全部的效工做负载。咱们以前说过,日志必须包含足够多的信息,才能支持恢复时的REDO和UNDO。也谅是说这个6个有效工做负载包含了修改先后的数据。从LSN=00000023:0000007d:0002的LOP_MODIFY_ROW的[RowLog Contents 0]和[RowLog Contents 1]的有效负载内容,能够看出是ASCII。因而:
select cast(0x646F206E6F74206368616E676521 as char(20)), cast (0x6368616E67656420202020202020 as char(20)) -------------------- -------------------- do not change! changed
如今咱们能够肯定事务0000:00000364谅是咱们要找的UPDATE的日志。而后经过[Begin Time]和[Transaction SID]能够谁何时修改了这条数据。
'important'被删除了,因此没有办法使用lockres来寻找。若是咱们知道它的ID值,咱们能够插入一条相同ID的记录,它会生成同样lockres,惋惜咱们也不知道ID值。咱们现知道的只有data列的值为'important',若是'important'在data列中的筛选度足够高的话,能够尝试经过[Log Record]去找到一些匹配的日志记录。
select [Current LSN],Operation,Context,[Transaction ID] from fn_dblog(null, null) where charindex(cast('important' as varbinary(20)), [Log Record]) > 0; Current LSN Operation Context Transaction ID ----------------------- ---------------- ----------------- -------------- 00000023:0000007b:0003 LOP_INSERT_ROWS LCX_CLUSTERED 0000:00000363 00000023:0000007f:0002 LOP_DELETE_ROWS LCX_MARK_AS_GHOST 0000:00000366 select [Current LSN], [Operation], [AllocUnitName], [Transaction Name] from fn_dblog(null, null) where [Transaction ID] = '0000:00000366';
从找到的结果,能够看出LOP_INSERT_ROWS是的插入时的操做,LOP_DELETE_ROWS是一个插入操做。咱们能够试着用LOP_DELETE_ROWS 的事务0000:00000366去找到相关的日志记录。例子中这种复杂粗暴找到日志的方式在现实中可能会很是困难。例子用的一个简单的ASCII的字符串,若是是其它类型的,你须要知道值在SQL Server中的内部表示形式(如numeric,decimal),还要用写出正确的Intel平台的LSB值 (如int,datetime)。这个是很是很是困难的。