如何解读SQL Server日志(2/3)
接下来说说返回的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输出行的内容被分成了左、中、右三个部分:
- 左:表示对应内容在行内的字符位置(或者起始偏移量)
- 中:表示存储在页上实际数据,由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 列的内容可以分解为:
- 1000 (列头,2Bytes状态位)
- 2400 (2Bytes 列数量值的Offset量,也表示列数量值前有多少字节被使用)
- 01000000 (ID列,INT)
- 7374616E64616C6F6E6520786163742020202020 (data列,char(20))
- 797D6F0064A60000 (created_at列,datetime)
- 0300 (列数量,2Bytes)
- 00 (NULL位图=Ceiling(NULL列数量/8))
关于行结构的更多内容,参考《SQL Server 2012 Internals》第六章'Table Storage'中的'The structure of data row'部分。行结构图:
我用几个例子来说明如何使用这些知识帮助我们解决问题。先修改表中的数据:
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'相关的日志
因为'junk'还存在于表中,我们通过找到它的lockres,然后根据lockres去搜索日志中的[Lock Information],再从匹配的行中[Transaction ID]字段得到INSERT的事务ID,再通过事务ID去找出与这个事务相关的所有日志记录。
--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
使用上面的方法时,我假设了几个前提:
- lockres是唯一的,没有HASH碰撞
- 锁定行的第一个事务就是我想找的事务
在现实中情况不会这么简单:HASH碰撞的问题,可能有很多事务曾锁定过这一行等等。
- 找出把'do not change!'修改成'changed!'的事务
跟之前的思路差不多,先从表中存的'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'的日志
'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)。这个是非常非常困难的。
如何解读SQL Server日志(2/3)的更多相关文章
- 如何解读SQL Server日志(3/3)
如何查看被截断的日志 如果数据库做了日志备份操作,则日志会被截断,然后原来活动的VLF会被重用.使用sys.fn_dblog将会看不到任何被截断的日志.那如何查看日志备份中的日志呢?使用fn_dump ...
- 如何解读SQL Server日志(1/3)
SQL Server 的事务日志包含所有数据修改的操作记录.分析日志一般作为解决某些问题的最后手段,如查看某些意外的修改.理解和分析日志内容是件非常困难的事情,fn_dblog通常会输出非常多的数据, ...
- 清理SQL Server日志释放文件空间的终极方法
清理SQL Server日志释放文件空间的终极方法 转自:http://www.cnblogs.com/dudu/archive/2013/04/10/3011416.html [问题场景]有一个数 ...
- SQL Server日志文件庞大收缩方法(实测好用)
原文:SQL Server日志文件庞大收缩方法(实测好用) 这两个命令连续执行,间隔时间越少越明显(可多次运行),直到达到效果 --截断 BACKUP LOG CloudMonitor TO DISK ...
- 收缩SQL Server日志不是那么简单
收缩SQL Server日志不是那么简单的(翻译) 原文地址:http://rusanu.com/2012/07/27/how-to-shrink-the-sql-server-log/ 说明:本 ...
- SQL Server 日志和代理的错误日志
本文介绍的日志不是事务日志,而是SQL Server 日志和代理的错误日志,按照主体把错误日志分为SQL Server.SQL Server Agent.Database Mail,以及 Window ...
- sql server 日志文件结构及误操作数据找回
一. 概述 在sql server 里有数据文件.mdf和日志文件.ldf,日志文件是sqlserver数据库的另一个重要组成部分,日志文件记录了所有事务以及每个事务对数据库所做的修改.为了提高数据库 ...
- SQL Server日志文件过大 大日志文件清理方法 不分离数据库
SQL Server日志文件过大 大日志文件清理方法 ,网上提供了很多分离数据库——〉删除日志文件-〉附加数据库 的方法,此方法风险太大,过程也比较久,有时候也会出现分离不成功的现象.下面的方式 ...
- 解决Sql Server 日志满了,设置收缩
解决Sql Server 日志满了,设置收缩: --查看文件占用空间 . '文件大小(MB)',* from sysfiles; ALTER DATABASE SpyData SET RECOVERY ...
随机推荐
- Restful.Data v2.0发布,谢谢你们的支持和鼓励
v1.0发布后,承蒙各位博友们的热心关注,也给我不少意见和建议,在此我真诚的感谢 @冰麟轻武 等朋友,你们的支持和鼓励,是这个开源项目最大的推动力. v2.0在除了细枝末节外,在功能上主要做了一下更新 ...
- 【Bugly安卓开发干货分享】Android APP 快速 Pad 化实现
项目背景 采用最新版本手机 APP(之后称为 MyApp)代码,实现其 Pad 化,为平板和大屏手机用户提供更好的体验.为实现 MyApp 的 Pad 化工作,需要我们首先来了解一下 MyApp 项目 ...
- WPF,Silverlight与XAML读书笔记第四十七 - Silverlight与浏览器
说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. 这部分内容主要介绍Silverlight与浏 ...
- Web端导出CSV
前端导出文件大部分还是通过服务器端的方式生成文件,然后传递到客户端.但很多情况下当我们导出CSV时并不需要后端参与,甚至没有后端. 做过WebGIS的同学经常会碰到这种场景,用户的兴趣点数据以csv文 ...
- STSdb,最强纯C#开源NoSQL和虚拟文件系统 4.0 RC2 支持C/S架构
STSdb是什么 再来说明一下STSdb是什么:STSdb是C#写的开源嵌入式数据库和虚拟文件系统,支持实时索引,性能是同类产品的几倍到几十倍,访问官方网站. 温故知新 之前发了文章<STSdb ...
- 字符串查找String.IndexOf
String.indexOf的模拟实现,没想象中有多么高深的查找算法,就是最普通的遍历查找 思路:先找到第一个相同的字符,然后依次比较后面的字符,若都相等则表示查找成功 /** * 查找字符串patt ...
- Java-继承,多态练习0922-06
编写一个Shape类,具有属性:周长和面积: 定义其子类三角形和矩形,分别具有求周长的方法. 定义主类E,在其main方法中创建三角形和矩形类的对象, 并赋给Shape类的对象a.b,使用对象a.b来 ...
- 第十六回 IoC组件Unity续~批量动态为Unity添加类型和行为
回到目录 之前的一篇Unity的文章主要是基本的实现,并没有什么特别的地方,使用Unity可以方便的实现应用程序的IoC控制反转,这给我们的应用程序在耦合度上变得高了,同时可测试性加强了,当然,这些的 ...
- Docker容器入门
为什么要看docker 从去年起就或多或少的接受了docker的熏陶,主要还是Infoq在去年有很多关于docker的实践视频讲座,记得有一篇是<Docker在雪球的技术实践>,当时听的也 ...
- Atitit blend mode COLOR_DODGE 混合模式 “颜色减淡”模式
Atitit blend mode COLOR_DODGE 混合模式 "颜色减淡"模式 1.1. 混合模式是图像处理技术中的一个技术名词1 1.2. 目录1 1.3. 颜色减淡C ...
