如何解读SQL Server日志(3/3)
如何查看被截断的日志
如果数据库做了日志备份操作,则日志会被截断,然后原来活动的VLF会被重用。使用sys.fn_dblog将会看不到任何被截断的日志。那如何查看日志备份中的日志呢?使用fn_dump_dblog读取日志备份的内容。它的输出和sys.fn_dblog是一样的,所以进行查询过滤时也可以跟其一样。
use master
go
backup log logtest to disk='d:\logtest.trn';
go
select [Current LSN], [Operation], [AllocUnitName], [Transaction Name]
from fn_dump_dblog (
NULL, NULL, 'DISK', 1, 'D:\logtest.trn',
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT)
where [Transaction ID]='0000:00000367'
go
当[Lock Infomation]的内容无用时
前面我们使用[Lock Infomation]的内容帮助我们定位和分析日志操作,但是如果数据操作没有单独锁定某个行时(e.g.锁升级和使用表提示锁定表),则它的内容对我们的帮助就很少了。
delete top(1) from demotable with (tablockx);
select [Current LSN], [Operation], [Transaction ID],
[AllocUnitName], [Page ID], [Slot ID], [Lock Information]
from fn_dblog(null, null)
where [transaction id]='0000:0000036a'
上面的删除使用tablockx表提示,使得删除是直接对表使用X锁,而不是行锁。这样在[Lock Information]就没有包含键锁的HASH值(Lockres)。如图,我们现在能获取到Page ID、Slot ID和Object ID,但是不能定位到行。
但是日志的有效工作负载还是包含了所有的内容,因此可以通过搜索[Log Record]或者 [RowLog Contents 0~5] 来找到具体的行。
最小化日志操作也会让查找日志变得更困难。最小化日志操作的日志内容只扫描了某个页包含了最小华日志操作,SQL Server必需保证它在事务提交前被刷新到磁盘。在相关日志中只看到LOP_FORMAT_PAGE操作及其操作(最小化日志操作)的Page ID。最小化日志只有INSERT、blob .WRITE和WRITETEXT/UPDATETEXT操作,其它的操作都会完整记录日志,这样分析日志要分析的内容就少了很多。有一种情况会增加操作的日志量:更新事务复制中的发布表的数据。
实践时的注意事项
本文的例子的日志量非常少,搜索起来也很快。在真实环境中可能要透过数百万行日志才能找到所需要的日志记录,当然会很慢。最好将sys.fn_dblog返回的日志行先存到表中,然后建立需要的索引,这样查询时会快很多。使用sys.fn_dblog时如果能传入开始和结束的LSN,也会让其它返回速度快一些。注意,如果传入不正确的LSN给sys.fn_dblog,则会导致服务器内核转储( server core dump ),在生产环境这会导致SQL Server僵死一会儿。
DDL 修改
分析DDL修改的思路跟分析数据修改的一样。数据库对象是存储于一个或多个系统表中的数据,所有DDL修改最终会通过修改系统表的数据而完成。这样会让所有的DDL修改在分析日志时看起来差不多,例如LOP_INSER_ROWS操作插入数据到sys.syschhobjs,它可能是CREATE TABLE,也可能是CREATE VIEW或者CREATE PROC,甚至CREATE TABLE foo和CREATE TABLE bar的日志看起来是一样的。
DDL操作和DML操作两者修改数据时的主要区别在于使用不同锁。DDL使用元数据锁(Metedata Lock),一般情况,DDL会以包含Object ID的SCH_M锁开始。
另外,分析日志中DDL,要与特定的版本和更新相匹配。不同的版本、SP甚至CU,元数据表和元数据表结构可能会不同。
利用OBJECT ID
找出某个对象的DDL具体操作的最好方法是通过找出在对象上请求SCH_M锁的事务。先通过OBJECT_ID()得对象的ID,然后在[Lock Information]列中匹配搜索模式N'%SCH%%'得到事务。通过事务再找到所有相关的操作日志。
--executed 2 DDLs: CREATE and ALTER
create table xx(id int ,val nchar(10));
insert into xx values(1,'A'),(2,'B')
alter table xx
alter column val nchar(20);
go
select OBJECT_ID('xx')
-- returned 293576084
go
select [Current LSN], Operation, [Lock Information], [Transaction ID], [Description]
from sys.fn_dblog(null,null)
where [Lock Information] like N'%SCH%293576084%'
go
Current LSN Operation Lock Information Transaction ID Description
----------- ------------ ---------------------------------------------- -------------- -------------
00000023:000000a9:0002 LOP_LOCK_XACT HoBt 0:ACQUIRE_LOCK_SCH_M OBJECT: 7:293576084:0 0000:0000036b
00000023:000000b7:0002 LOP_LOCK_XACT HoBt 0:ACQUIRE_LOCK_SCH_M OBJECT: 7:293576084:0 0000:00000370
返回两个事务,我们逐一查看。
select [Current LSN], Operation, [AllocUnitName], [Lock Information], [Transaction ID],
[Description], [Begin Time], [Transaction Name], [Transaction SID]
from fn_dblog(null, null)
where [Transaction ID] = N'0000:0000036b';
从第一行LOP_BEGIN_XACT事务开始,在Description和Transaction Name看到CREATE TABLE,还有Begin Time,然后还有Transaction SID。由这些信息我们可以知道:谁什么时候创建了这个表。
余下的行结合System Base Tables可以解读出其含义:
LOP_INSERT_ROWS 插入 sys.sysschobjs
这个操作是向sys.objects插入数据行。有4个LOP_INSERT_ROWS操作是因为sys.sysschobjs表有1个聚集索引和3个非聚集索引。LOP_INSERT_ROWS 插入 sys.syscolpars
这个操作向sys.columns插入2行数据。每一个插入操作有2条记录,分别为sys.syscolpars上的聚集索引和非聚集索引。LOP_INSERT_ROWS 插入 sys.sysidxstats
插入数据到sys.indexes。LOP_INSERT_ROWS 插入 sys.sysiscols
插入数据到sys.index_columns。
通过上面的分析,虽然没有得到具体的DDL语句,但是我们知道一些非常有用的信息:在2016/08/22 17:13:47:667 创建的表,表中有2列。如果我们进一步解析rowlog contents和log content还能知道列名。例子中的CREATE语句非常简单,如果是复杂的CREATE语句,会生成很多的日志记录。比如对表进行分区,则会增加很多LOP_INSERT_ROWS记录,因为每个分区必须往sys.sysrowstes添加行,分区上的每一个列和索引都必须被记录在sys.sysrscols中。而这些操作都需要在低级别的HoBT元数被插入前完成。
再来看看另一个事务:
select [Current LSN], Operation, [AllocUnitName],[Lock Information], [Transaction ID],
[Description], [Begin Time], [Transaction Name], [Transaction SID]
from fn_dblog(null, null)
where [Transaction ID] = N'0000:00000370'
从上面的日志可以看到这是个ALTER TABLE操作,并且操作是一个堆表,也能知道操作时间和SID。通过LOP_MODIFY_COLUMNS操作,知道这是修改列属性的操作。
查找被删除对象的OBJECT ID
对象被删除后,就没有办法根据OBEJCT ID去找相关日志了。但是我们知道删除对象本质上从sys.sysschobjs删除数据,所以搜索sys.sysschobjs的LOP_DELETE_ROWS操作。我们还知道操作的分配单元是sys.sysschobjs.clst。同时,被删除对象的名字肯定存在于Log Contents中的。
drop table xx;
go
select [Current LSN], Operation, [AllocUnitName], [Lock Information], [Transaction ID],
[Description], [Begin Time], [Transaction Name], [Transaction SID]
from fn_dblog(null, null)
where [Operation] = N'LOP_DELETE_ROWS'
and [AllocUnitName] = N'sys.sysschobjs.clst'
and CHARINDEX(cast(N'xx' as varbinary(4000)), [Log Record]) > 0;
根据找到的事务ID,去找到相关的日志记录:
select [Current LSN], Operation, [AllocUnitName], [Lock Information], [Transaction ID],
[Description], [Begin Time], [Transaction Name], [Transaction SID]
from fn_dblog(null, null)
where [Transaction ID] = N'0000:00000371';
从上在的日志很容易就知道谁什么时候删除的。注意在LOP_BEGIN_XACT后的LOP_LOCK_XACT,在它的锁信息里有被删除对象的OBJECT ID。有了OBJECT ID,我们就可以利它进一步的分析日志。
注意,OBJECT ID是能够被重用的。 所以在使用OBJECT ID分析日志时,要分辨出它是否被重用了。在日志中,LSN严格按照时间排序的,所以可以利用之来区分OBEJCT ID的重用。
还要注意CHARINDEX(cast(N'xx' as varbinary(4000)), [Log Record]) > 0,它可能会返回名字中包含‘xx’的其它对象。
分析日志不是一种常规的和常用的技术。同时,它的难度高,也很容易被海量的日志误导,所以分析时最好从多个角度,使用多种方法去定位相关的日志。
文中的例子,我用的SQL Server 2012 - 11.0.5343.0,同时dbo.xx表,我用是一个堆表。这两个地方导致与原文有较大差异。 BY Joe . TJ
如何解读SQL Server日志(3/3)的更多相关文章
- 如何解读SQL Server日志(2/3)
接下来说说返回的RowLogo Content列,例子中返回了三个列.这些列包含了数据操作的"有效工作负载(Playload)"记录.根据不同操作类型有效负载的内容也是不同的,但是 ...
- 如何解读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 ...
随机推荐
- Java多线程1:进程与线程概述
进程和线程 谈到多线程,就得先讲进程和线程的概念. 进程 进程可以理解为受操作系统管理的基本运行单元.360浏览器是一个进程.WPS也是一个进程,正在操作系统中运行的".exe"都 ...
- Spring AOP在函数接口调用性能分析及其日志处理方面的应用
面向切面编程可以实现在不修改原来代码的情况下,增加我们所需的业务处理逻辑,比如:添加日志.本文AOP实例是基于Aspect Around注解实现的,我们需要在调用API函数的时候,统计函数调用的具体信 ...
- JavaScript很牛
几年前,我从来没有想过现在的JavaScript竟然会变得几乎无处不在.下面是几个要关注JavaScript的原因. 首先,我认为JavaScript能够得到普及的主要原因之一是,JavaScript ...
- 团队项目——站立会议DAY7
第七次站立会议记录: 参会人员:张靖颜,钟灵毓秀,何玥,赵莹,王梓萱 项目进展: 1.张靖颜:对功能模块代码进行近一步的审查和辅助,并对出错处进行修改和完善. 2.钟灵毓秀:对代码近一步的修改,将各个 ...
- php json与xml序列化/反序列化
在web开发中对象的序列化与反序列化经常使用,比较主流的有json格式与xml格式的序列化与反序列化,今天想写个jsop的小demo,结果发现不会使用php序列化,查了一下资料,做个笔记 简单数组js ...
- Git学习笔记(4)——添加远程仓库,克隆远程库,以及库的推送
本文记录了远程库的连接和库的克隆和推送. 远程仓库简介 Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上.有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且 ...
- c#生成MD5字符串
public static string EncryptWithMD5(string source) { byte[] sor = Encoding.UTF8.GetBytes(source); MD ...
- osgi 2
基础的API BundleActivator BundleContext ServiceReference HelloServiceFactory ServiceTracker osgi 疑惑: I ...
- jmap
环境: 现有一个独立运行的系统S(有独立的jre,但是没jdk),现想通过jmap导出其内存堆栈信息.于是另外安装一个jdk.可是jdk的版本跟S系统的jre不能对应上.出了很多错误. 总是报错: C ...
- vs2013中的“任务列表”菜单
以前在java项目中经常用到todo. 现在vs2013也完美支持了. 首先,对于目前不确定而尚需完善的代码,在前面加 //TODO:by who --注释文字,比如: //TODO:lhl--类目I ...