介绍 (Introduction)

SQL Server keeps track of all database modifications and every database transaction. This is done in a file called the transaction log or TLOG. This transaction log is particular to a SQL Server database and there is, at least, one transaction log per SQL Server database.

SQL Server跟踪所有数据库修改和每个数据库事务。 这在称为事务日志或TLOG的文件中完成。 此事务日志特定于SQL Server数据库,并且每个SQL Server数据库至少有一个事务日志。

As explained on MSDN, the transaction log is a critical component of the database and, if there is a system failure, the transaction log might be required to bring your database back to a consistent state. The transaction log should never be deleted or moved unless you fully understand the ramifications of doing this.

如MSDN上 所述,事务日志是数据库的重要组成部分,如果发生系统故障,可能需要事务日志才能使数据库恢复一致状态。 除非您完全了解这样做的后果,否则切勿删除或移动事务日志

So, we can conclude that the information contained inside the transaction log is valuable and maybe we could even take advantage of this information to answer questions like “Who is responsible for this table drop?” or “When did the operation occurred?” in order to recover a data alteration or data loss. Let’s explore which tools can help and what we actually can do with it…

因此,我们可以得出结论,事务日志中包含的信息是有价值的,也许我们甚至可以利用此信息来回答诸如“谁负责此表删除?”之类的问题。 或“手术何时发生?” 为了恢复数据更改或数据丢失。 让我们探究哪些工具可以提供帮助,以及我们实际上可以做什么……

为什么要从事务日志取回信息? (Why getting back information from transaction log?)

Let’s say there is an issue: somebody dropped a table or deleted rows in a particular table and you want to get the version of those rows just before he dropped it. Well, you’ve well done your job and have taken backups but, in general, you can’t simply restore it (somewhere else than on the production database) and take back data because the time between the last backup and the moment when the person who did the mistake actually fired its DROP or DELETE query.

假设存在一个问题:某人删除了表或删除了特定表中的行,而您想在删除表之前获取这些行的版本。 好吧,您已经做好了工作并进行了备份,但是一般来说,您不能简单地还原它(除了生产数据库上的其他地方)并取回数据,因为从上次备份到备份之间的时间间隔很短。犯错的人实际上触发了其DROP或DELETE查询。

To be able to do it and being sure of the data we get back, we need to know precisely in which transaction the query was executed so that we can get a Log Sequence Number (LSN) and be able to use the STOPBEFOREMARK option of RESTORE LOG command like the code below.

为了做到这一点并确保我们能返回数据,我们需要精确地知道在哪个事务中执行了查询,以便我们可以获取日志序列号(LSN)并能够使用RESTORE的STOPBEFOREMARK选项。 LOG命令类似下面的代码。


RESTORE LOG  WITH STOPBEFOREMARK = 'lsn:';

如何从事务日志中获取信息? (How to get back information from the transaction log?)

The first way to have access to information inside the transaction log is to use DBCC LOG(<DbName>) command. But as you will see in the figure below, we won’t go that far with its output: you don’t know anything about the object that has been impacted and it’s not really helpful in recovery.

访问事务日志内部信息的第一种方法是使用DBCC LOG(<DbName>)命令。 但是,如您在下图中所看到的,我们在输出方面不会走得太远:您对已受影响的对象一无所知,并且对恢复毫无帮助。

Fortunately, there is another way. Unfortunately, this operation will require the use of undocumented SQL functions and, since they are not documented by Microsoft, they are not supported and you must use it at your own risk.

幸运的是,还有另一种方法。 不幸的是,此操作将需要使用未记录SQL函数,并且由于Microsoft没有记录这些函数,因此不支持它们,您必须自担风险。

The first one we will discuss is called sys.fn_dblog. This function reads the active part of the transaction log in a log sequence number (LSN) interval. What I call “the active part of a transaction” is the portion of the transaction log where transaction details are still there and have not been truncated. The truncation can happen automatically if the database is in simple recovery model, when a TRUNCATE LOG operation has been fired or after a backup occurred.

我们将讨论的第一个称为sys.fn_dblog。 此函数以日志序列号( LSN )间隔读取事务日志的活动部分。 我所谓的“交易的活动部分”是交易日志中仍存在交易明细且未被截断的部分。 如果数据库处于简单恢复模型中 ,已触发TRUNCATE LOG操作或发生备份后,截断会自动发生。

You will find below a simple way to call it:

您将在下面找到一种简单的调用方式:

SELECT *
FROM fn_dblog(NULL, NULL) -- (StartLSN,StopLSN) – NULL means Everything-- The LSN must be decimal "X:Y:Z"

Here is a sample result. As you can see, the first columns are [CurrentLSN] and [Operation]. The former allows to know the log sequence number corresponding to the entry in transaction log while the latter tells the kind of operation that have been made. There is also a [TransactionId] column. So, we can tell what have been done for a single TransactionId and maybe. Two columns that are not displayed here but seem interesting is [AllocUnitId] and [AllocUnitName].

这是一个示例结果。 如您所见,第一列是[CurrentLSN]和[Operation]。 前者允许知道与事务日志中的条目相对应的日志序列号,而后者则告诉已执行的操作类型。 还有[TransactionId]列。 因此,我们可以判断对单个TransactionId所做的操作,也许可以。 [AllocUnitId]和[AllocUnitName]在此处未显示但看起来很有趣的两列。

One more word about the way to call the sys.fn_dblog function is that, as written in the code sample above, you can’t use as a parameter the values returned by the function in the [CurrentLSN] column. The reason is that the [CurrentLSN] value is expressed in hexadecimal and the function takes decimal expressions of the LSN. So, you need to convert a [CurrentLSN] value before using it. There are multiple ways to do it: either using a website resource like BinaryHexConverter. To get it right, you will have to firstly split the [CurrentLSN] value into three pieces using the ‘:’ character as separator then convert each individual value using the website and finally concatenate everything with the ‘:’ character in the same order. The operation is quite the same if you want to use this LSN in conjunction with the STOPBEFOREMARK option of RESTORE LOG command: you still split the [CurrentLSN] the same way but concatenate differently. In fact, the difference resides in the fact that the second number is padded by leading 0 so that its length is 10 digits and the third number is padded by leading 0 so that its length is 5 digits.

关于调用sys.fn_dblog函数的方法的另一句话是,如上面的代码示例中所写,您不能将[CurrentLSN]列中该函数返回的值用作参数。 原因是[CurrentLSN]值以十六进制表示,并且该函数采用LSN的十进制表达式。 因此,您需要在使用[CurrentLSN]值之前对其进行转换。 有多种方法可以执行此操作:使用BinaryHexConverter之类的网站资源。 为正确起见,您必须首先使用[:]字符将[CurrentLSN]值分成三部分,然后使用网站转换每个单独的值,最后将所有带有':'字符的内容按相同顺序连接起来。 如果要将此LSN与RESTORE LOG命令的STOPBEFOREMARK选项一起使用,则操作是完全相同的:您仍然以相同的方式拆分[CurrentLSN],但连接方式不同。 实际上,差异在于以下事实:第二个数字用前导0填充,以使其长度为10位数字,而第三个数字用前导0填充,以使其长度为5位数字。

As an alternative, you can use the bit of T-SQL code shown below:

或者,您可以使用下面显示的T-SQL代码:

SET NOCOUNT ON
DECLARE @LSN        NVARCHAR(64);SET @LSN = 'CHANGEME' ;
-- To test :
--      SELECT TOP 1 [Current LSN] FROM fn_dblog(NULL, NULL)DECLARE @LSN_Decimal    NVARCHAR(64); -- LSN expression to use with fn_dblog, db_dump_dblog
DECLARE @LSN_Decimal2   NVARCHAR(64); -- LSN expression to use with STOPBEFOREMARK
DECLARE @tsql           NVARCHAR(MAX);DECLARE @tbl        TABLE (id  INT identity(1,1), val VARCHAR(16)
);-- Extract first part
SET @tsql = 'SELECT CONVERT(VARCHAR(16),CAST(0x' + SUBSTRING(@LSN, 1, 8) + ' AS INT))';
INSERT INTO @tbl EXEC(@tsql);SELECT @LSN_Decimal = val , @LSN_Decimal2 = val FROM @tbl;
-- table variable =&gt; SQL Server always thinks it only returns 1 row.
-- deleting content
delete from @tbl;SET @tsql = 'SELECT CONVERT(VARCHAR(16),CAST(0x' + SUBSTRING(@LSN, 10, 8) + ' AS INT))';
INSERT INTO @tbl EXEC(@tsql);SELECT  @LSN_Decimal  = @LSN_Decimal + ':' + val, @LSN_Decimal2 = @LSN_Decimal2 + RIGHT('0000000000'+isnull(val,''),10) /*10 digits*/
FROM @tbl;
delete from @tbl;SET @tsql = 'SELECT CONVERT(VARCHAR(16),CAST(0x' + SUBSTRING(@LSN, 19, 4) + ' AS INT))';
INSERT INTO @tbl EXEC(@tsql);SELECT @LSN_Decimal  = @LSN_Decimal + ':' + val, @LSN_Decimal2 = @LSN_Decimal2 + RIGHT('00000'+isnull(val,''),5) /*5 digits*/
FROM @tbl;
delete from @tbl;PRINT 'LSN Decimal translation for fn_db_log      :' + @LSN_Decimal;
PRINT 'LSN Decimal expression for STOPBEFOREMARK  :' + @LSN_Decimal2;/*
Choose :
SELECT *
FROM ::fn_dblog(NULL, @LSN_Decimal); SELECT *
FROM ::fn_dblog(@LSN_Decimal, NULL);
*/

So, what kind of operations can be found with it? You will find below a partial list of the operations that can be found:

那么,可以找到什么样的操作呢? 您将在下面找到可以找到的部分操作列表:

OPERATION DESCRIPTION
LOP_ABORT_XACT   Indicates that a transaction was aborted and rolled back.
LOP_BEGIN_CKPT  A checkpoint has begun.
LOP_BEGIN_XACT  Indicates the start of a transaction.
LOP_BUF_WRITE Writing to Buffer.
LOP_COMMIT_XACT Indicates that a transaction has committed.
LOP_COUNT_DELTA  ?
LOP_CREATE_ALLOCCHAIN New Allocation chain
LOP_CREATE_INDEX Creating an index.
LOP_DELETE_ROWS Rows were deleted from a table.
LOP_DELETE_SPLIT  A page split has occurred. Rows have moved physically.
LOP_DELTA_SYSIND   SYSINDEXES table has been modified.
LOP_DROP_INDEX Dropping an index.
LOP_END_CKPT Checkpoint has finished.
LOP_EXPUNGE_ROWS Row physically expunged from a page, now free for new rows.
LOP_FILE_HDR_MODIF   SQL Server has grown a database file.
LOP_FORGET_XACT Shows that a 2-phase commit transaction was rolled back.
LOP_FORMAT_PAGE   Write a header of a newly allocated database page.
LOP_HOBT_DDL  ?
LOP_HOBT_DELTA  ?
LOP_IDENT_NEWVAL Identity’s New reseed values
LOP_INSERT_ROWS   Insert a row into a user or system table.
LOP_LOCK_XACT
LOP_MARK_DDL Data Definition Language change – table schema was modified.
LOP_MARK_SAVEPOINT Designate that an application has issued a ‘SAVE TRANSACTION’ command.
LOP_MIGRATE_LOCKS
LOP_MODIFY_COLUMNS    Designates that a row was modified as the result of an Update command.
LOP_MODIFY_HEADER   A new data page created and has initialized the header of that page.
LOP_MODIFY_ROW   Row modification as a result of an Update command.
LOP_PREP_XACT Transaction is in a 2-phase commit protocol.
LOP_SET_BITS
LOP_SET_BITS Designates that the DBMS modified space allocation bits as the result of allocating a new extent.
LOP_SET_FREE_SPACE   Designates that a previously allocated extent has been returned to the free pool.
LOP_SORT_BEGIN  A sort begins with index creation. – SORT_END end of the sorting while creating an index.
LOP_SORT_EXTENT Sorting extents as part of building an index.
LOP_UNDO_DELETE_SPLIT The page split process has been dumped.
LOP_XACT_CKPT During the Checkpoint, open transactions were detected.
操作方式 描述
LOP_ABORT_XACT 指示事务已中止并回滚。
LOP_BEGIN_CKPT 检查点已经开始。
LOP_BEGIN_XACT 指示交易开始。
LOP_BUF_WRITE 写入缓冲区。
LOP_COMMIT_XACT 指示事务已提交。
LOP_COUNT_DELTA
LOP_CREATE_ALLOCCHAIN 新分配链
LOP_CREATE_INDEX 创建索引。
LOP_DELETE_ROWS 行已从表中删除。
LOP_DELETE_SPLIT 发生页面拆分。 行已物理移动。
LOP_DELTA_SYSIND SYSINDEXES表已被修改。
LOP_DROP_INDEX 删除索引。
LOP_END_CKPT 检查点已完成。
LOP_EXPUNGE_ROWS 该行实际上已从页面上删除,现在可以免费用于新行。
LOP_FILE_HDR_MODIF SQL Server已增长数据库文件。
LOP_FORGET_XACT 显示两阶段提交事务已回滚。
LOP_FORMAT_PAGE 编写新分配的数据库页面的标头。
LOP_HOBT_DDL
LOP_HOBT_DELTA
LOP_IDENT_NEWVAL 身份的新种子价值
LOP_INSERT_ROWS 在用户或系统表中插入一行。
LOP_LOCK_XACT
LOP_MARK_DDL 数据定义语言更改–表架构已修改。
LOP_MARK_SAVEPOINT 指定应用程序已发出“保存交易”命令。
LOP_MIGRATE_LOCKS
LOP_MODIFY_COLUMNS 指定由于更新命令的结果而修改了一行。
LOP_MODIFY_HEADER 创建了一个新的数据页面,并已初始化该页面的标题。
LOP_MODIFY_ROW 更新命令导致的行修改。
LOP_PREP_XACT 事务采用两阶段提交协议。
LOP_SET_BITS
LOP_SET_BITS 指定DBMS修改空间分配位,作为分配新扩展区的结果。
LOP_SET_FREE_SPACE 指定先前分配的扩展区已返回到空闲池。
LOP_SORT_BEGIN 排序从创建索引开始。 –创建索引时,排序结束SORT_END。
LOP_SORT_EXTENT 对范围进行排序是构建索引的一部分。
LOP_UNDO_DELETE_SPLIT 分页过程已转储。
LOP_XACT_CKPT 在检查点期间,检测到未清事务。

So, basically, the transaction log contains information not only on DML and DDL operations but also on SQL Server internal operations like a checkpoint, a buffer write or a page split. But it has a limit as explained above. Let’s prove this with this simple example.

因此,基本上,事务日志不仅包含有关DML和DDL操作的信息,而且还包含有关SQL Server内部操作(如检查点,缓冲区写入或页面拆分)的信息。 但是它有一个如上所述的限制。 让我们用这个简单的例子来证明这一点。

--Create DB.
USE [master];
GO
IF(DB_ID('DBLogReading') IS NULL)Exec  sp_executesql N'CREATE DATABASE DBLogReading';
GO
-- Create tables.
USE DBLogReading;
GO
CREATE TABLE [City] ([CityId]  INT IDENTITY,[Country] CHAR (32) DEFAULT 'Belgium',[Name]    CHAR (32) DEFAULT 'Liege',[LastVisitDate] DATETIME DEFAULT GETDATE ()
);
GO
-- Insert some data
SET NOCOUNT ON
USE DBLogReading
go
INSERT INTO City DEFAULT VALUES ;
GO 100-- How many rows returned by fn_dblog ?
SELECT COUNT(*)
FROM fn_dblog(null,null)
GO-- take a backup
BACKUP DATABASE DBLogReading TO DISK = 'D:\Backup\DBLogReading_Full.bak'-- How many rows returned by fn_dblog ?
SELECT COUNT(*)
FROM fn_dblog(null,null)
GO

You should see a tremendous reduction in the number of returned values. For me, it passed from 545 to 10. This means that the inactive part of the transaction log which tracked the transactions has been dumped to a backup file and the original entries from the log file have been flushed which means the log truncation can occur to reduce space consumption inside the transaction log.

您应该看到返回值的数量大大减少了。 对我来说,它从545传递到10。这意味着跟踪事务的事务日志的非活动部分已被转储到备份文件中,并且该日志文件中的原始条目已被刷新,这意味着可能会发生日志截断减少事务日志中的空间消耗。

You will find demos later. Those demos are a step by step procedure to recover data after a DELETE or DROP operation has occurred using sys.fn_dblog function.

稍后您将找到演示。 这些演示是使用sys.fn_dblog函数在执行DELETE或DROP操作后恢复数据的分步过程。

Before jumping to the other function, you will find another query below which just lists committed DML operations. You can customize it to get some other information:

在转到其他功能之前,您将在下面找到另一个查询,该查询仅列出已提交的DML操作。 您可以对其进行自定义以获取其他信息:

SELECT[Current LSN],[Transaction ID],[Operation],[Transaction Name],[CONTEXT],[AllocUnitName],[Page ID],[Slot ID],[Begin Time],[End Time],[Number of Locks],[Lock Information]
FROM sys.fn_dblog(NULL,NULL)
WHERE Operation IN ('LOP_INSERT_ROWS','LOP_MODIFY_ROW','LOP_DELETE_ROWS','LOP_BEGIN_XACT','LOP_COMMIT_XACT')  

如何从事务日志备份文件中获取信息? (How to get back information from a transaction log backup file?)

Here again, you must use it at your own risks and according to Paul Randal on his blog post, it seems that every time this function is called, a new SQLOS scheduler and three threads are created. Those threads won’t disappear until a server restart. Still according to Paul Randal, this has been addressed as a bug to Microsoft. It’s fixed in SQL Server 2012 SP2+ and SQL Server 2014 but won’t be backported to SQL Server 2008 (R2).

再次重申,您必须自担风险,根据Paul Randal在其博客文章中的说法,似乎每次调用此函数时,都会创建一个新SQLOS调度程序和三个线程。 在服务器重新启动之前,这些线程不会消失。 仍然根据Paul Randal所说,这已解决为Microsoft的一个错误。 它已在SQL Server 2012 SP2 +和SQL Server 2014中修复,但不会反向移植到SQL Server 2008(R2)。

This function has a lot of parameters. The two first parameters are the same as sys.fn_dblog i.e. an interval in LSN. Then it takes a device type which can be NULL (which means DISK and is the default value), ‘DISK’, ‘TAPE’, or ‘VIRTUAL_DEVICE’. The next parameter is called @seqnum with a default value of 1. I don’t currently know its meaning. Finally, there are 64 parameters for specifying paths to a log backup device.

此功能有很多参数。 前两个参数与sys.fn_dblog相同,即LSN中的间隔。 然后,它采用的设备类型可以为NULL(表示DISK,它是默认值),“ DISK”,“ TAPE”或“ VIRTUAL_DEVICE”。 下一个参数称为@seqnum,默认值为1。我目前不知道其含义。 最后,有64个参数用于指定日志备份设备的路径。

Here is its interface:

这是它的界面:

create function sys.fn_dump_dblog  (  @start    nvarchar (25) = NULL,  @end      nvarchar (25) = NULL,  @devtype  nvarchar (260) = NULL, --@devtype values: NULL(DISK) | DISK | TAPE | VIRTUAL_DEVICE  @seqnum   Int         = 1,  @fname1   nvarchar (260) = NULL,  @fname2   nvarchar (260) = NULL,  @fname3   nvarchar (260) = NULL, ...@fname64   nvarchar (260) = NULL  )
returns table  …

Let’s now see an example call, assuming we have a backup file on disk which is on the following path:

现在让我们看一个示例调用,假设我们在磁盘上有一个备份文件,该文件位于以下路径中:

                         D:\Backup\DBLogReading_log1.bak

When the following query will be fired against a database, we’ll get the same kind of dataset as returned by sys.fn_dblog function:

当对数据库触发以下查询时,我们将获得与sys.fn_dblog函数返回的数据集相同的数据集:

SELECT *
FROMfn_dump_dblog (NULL, NULL, N'DISK', 1, N'D:\Backup\DBLogReading_log1.bak',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);
GO 

演示版 (Demos)

In the following, we will try two things: recover data after a DELETE statement occurred and recover data after a DROP statement occurred. As the principles are the same for sys.fn_dblog and for sys.fn_dump_dblog functions, we will just use the first one.

在下文中,我们将尝试两件事:在发生DELETE语句之后恢复数据,以及在发生DROP语句之后恢复数据。 由于sys.fn_dblog和sys.fn_dump_dblog函数的原理相同,因此我们仅使用第一个。

Demo setup 演示设置

Let’s first create a new, empty database and add a simple table inside this database.

让我们首先创建一个新的空数据库,然后在该数据库中添加一个简单表。

--Create DB.
USE [master];
GO
CREATE DATABASE DBLogReading;
GO
-- Create tables.
USE DBLogReading;
GO
CREATE TABLE [City] ([CityId]  INT IDENTITY,[Country] CHAR (32) DEFAULT 'Belgium',[Name]    CHAR (32) DEFAULT 'Liege',[LastVisitDate] DATETIME DEFAULT GETDATE ()
);
GO
select OBJECT_ID('dbo.City');
-- Keep the returned Object_ID somewhere (Mine:357576312)

Let’s launch a FULL and a LOG backup to initialize things:

让我们启动FULL和LOG备份来初始化事情:

-- FULL Backup
BACKUP DATABASE [DBLogReading]
TO  DISK = N'D:\Backup\DBLogReading-InitialFULL.bak'
GO
-- LOG Backup
BACKUP LOG [DBLogReading]
TO  DISK = N'D:\Backup\DBLogReading-InitialLog.trn'
GO
SET NOCOUNT ON
USE DBLogReading
go
INSERT INTO City DEFAULT VALUES;
GO 100

Use case 1: Recover deleted data and get to know who did it 用例1:恢复已删除的数据并了解是谁做的

Let’s pretend we are doing the following operation by error. The operation consists in some data deletion:

假设我们正在错误地执行以下操作。 该操作包括一些数据删除:

USE DBLogReading
Go
DELETE City
WHERE [CityId] < 12
go
--select min(CityId) from City 

We have made some transactions so far. The transaction log must be filled with some useful information and rows have been “unexpectedly” deleted.

到目前为止,我们已经进行了一些交易。 事务日志中必须填充一些有用的信息,并且行已“意外”删除。

The following query calls sys.fn_dblog function and filters its output to only get back information on delete operations in City table:

以下查询调用sys.fn_dblog函数并过滤其输出以仅返回有关City表中删除操作的信息:

use DBLogReading
go
SELECT [Current LSN],    [Transaction ID],Operation,Context,AllocUnitName
FROM fn_dblog(NULL, NULL)
WHERE Operation = 'LOP_DELETE_ROWS'
and AllocUnitName = 'dbo.City' ;

The output of this query is as follows:

该查询的输出如下:

With this query, we are able to know the starting LSN for delete operation at which delete operation started for the transaction Id 0000:00000461. But the transaction was already running! We need to find the LSN corresponding to the beginning of the transaction. This can be obtained by filtering the output on LOP_BEGIN_XACT operations only for the given transaction.

通过此查询,我们能够知道删除操作的起始LSN,在该LSN处针对事务ID 0000:00000461的删除操作已开始。 但是事务已经在运行! 我们需要找到与交易开始相对应的LSN。 这可以通过仅过滤给定事务的LOP_BEGIN_XACT操作上的输出来获得。

The following query will provide this information:

以下查询将提供此信息:

USE DBLogReading
Go
DECLARE @TranId VARCHAR(32);
SET @TranId = '0000:00000461'; -- CHANGEME
SELECT[Current LSN],    Operation,[Transaction ID],[Begin Time],[Transaction Name],[Transaction SID]
FROMfn_dblog(NULL, NULL)
WHERE[Transaction ID] = @TranId
AND[Operation] = 'LOP_BEGIN_XACT';

We now know that the DELETE operation started at [Begin Time] value of the column with LSN 0000020:0000e7:0001

现在我们知道,DELETE操作从具有LSN 0000020:0000e7:0001的列的[Begin Time]值开始

We need to convert this value to decimal using one of the methods described previously and only consider the result for STOPBEFOREMARK option usage. In his case, the value to use is 32000000023100001

我们需要使用上述方法之一将此值转换为十进制,并且仅考虑使用STOPBEFOREMARK选项的结果。 在他的情况下,要使用的值为32000000023100001

By the way, at the same time, we get a [TransactionSID] column which tells us which database user ran the DELETE statement.

顺便说一下,同时,我们获得了[TransactionSID]列,该列告诉我们哪个数据库用户运行了DELETE语句。

We can get to know it by running the following query:

我们可以通过运行以下查询来了解它:

SELECT SUSER_SNAME(0x0105000000000005150000005FE4EAD69F711842619025A78B530000)

This can be a plus to ensure that it was the good DELETE statement we considered so far.

这可能是一个加号,以确保它是我们到目前为止考虑的良好的DELETE语句。

So, basically, we have the value to use during the last log restore. We have to first restore a full backup media to another location and database with NORECOVERY option enabled then successfully restore logs until the LSN is the expected boundary i.e. 32000000023100001

因此,基本上,我们具有在上次日志还原期间要使用的值。 我们必须首先将启用了NORECOVERY选项的完整备份媒体还原到另一个位置和数据库,然后成功还原日志,直到LSN是预期的边界,即32000000023100001

But before that, we need to run a transaction log backup. That’s because we are using sys.fn_dblog which queries the active part of transaction log. It wouldn’t be the case if we were using sys.fn_dump_dblog since it queries a transaction log backup

但是在此之前,我们需要运行事务日志备份。 那是因为我们正在使用sys.fn_dblog来查询事务日志的活动部分。 如果我们使用sys.fn_dump_dblog,则情况并非如此,因为它查询事务日志备份

-- LOG Backup
BACKUP LOG [DBLogReading]
TO  DISK = N'D:\Backup\DBLogReading_logbackup_ThisMorning.trn'
GO

If we had not found the corresponding operation with sys.fn_dblog, we would have used sys.fn_dump_dblog with the last transaction log backup media the same way we did.

如果找不到与sys.fn_dblog相对应的操作,则我们将sys.fn_dump_dblog与上次事务日志备份介质一起使用,方法与我们相同。

Once we have all the transaction log backups needed to recover, we can start restoring. An example code sample is provided below:

拥有恢复所需的所有事务日志备份后,就可以开始还原了。 下面提供了示例代码示例:

--Restoring Full backup with norecovery.
RESTORE DATABASE DBLogReading_COPYFROM DISK = 'D:\Backup\DBLogReading-InitialFULL.bak'
WITHMOVE 'DBLogReading' TO 'P:\MSSQL\DBLogReading_copy.mdf',MOVE 'DBLogReading_log' TO 'L:\MSSQL\DBLogReading_copy_log.ldf',REPLACE, NORECOVERY;
GO-- Restore successive Logs with norecovery until we come to
-- the last relievant transaction log backup.
RESTORE LOG DBLogReading_COPY
FROMDISK = N'D:\Backup\DBLogReading-InitialLog.trn'
WITH NORECOVERY;
GO
--Restore Log backup with STOPBEFOREMARK option to recover exact LSN.RESTORE LOG DBLogReading_COPY
FROMDISK = N'D:\Backup\DBLogReading_logbackup_ThisMorning.trn'
WITHSTOPBEFOREMARK = 'lsn:32000000023100001'; -- CHANGEME
GO

If we take a look at the content of City table, we will see that the rows we deleted are present.

如果我们查看“城市”表的内容,将看到存在我们删除的行。

The query to check:

要查询的查询:

select 'Current' as DbType , min(CityId) as MinCityID ,COUNT(*)    as CityCount
from DbLogReading.dbo.City
union  all
select 'Copy' , min(CityId) ,COUNT(*)
from DbLogReading_COPY.dbo.City;

Its output:

其输出:

In summary, we’ve found the transaction that did the DELETE operation (this is the hard part of the work in production environment where DELETE statements can occur regularly against multiple tables). Then we got back the LSN corresponding to the beginning of the transaction that contains the DELETE statement. We translated this LSN to a decimal padded value and restored to that LSN in another database. The next step would be to export the deleted rows from the restored database to the actual production database.

总而言之,我们发现执行DELETE操作的事务(这是生产环境中工作的难点,在生产环境中,可以定期对多个表执行DELETE语句)。 然后,我们返回与包含DELETE语句的事务开始相对应的LSN。 我们将此LSN转换为十进制填充值,并在另一个数据库中恢复为该LSN。 下一步将是将已删除的行从还原的数据库导出到实际的生产数据库。

Use case 2: Discover LSN corresponding to the start of a table drop operation 用例2:发现与表删除操作开始相对应的LSN

As the instructions to restore to a given LSN we get back from sys.fn_dblog and sys.fn_dump_dblog functions, this section will only address the question to retrieving the starting LSN of a transaction which dropped a table.

作为还原到给定LSN的指令,我们从sys.fn_dblog和sys.fn_dump_dblog函数返回,本节仅解决以下问题:检索删除了表的事务的起始LSN。

So, let’s get started by dropping the City table:

因此,让我们开始删除City表:

USE DBLogReading
GO
DROP TABLE [City]
SELECT [Current LSN],Operation,[Transaction Id],[Transaction SID],[Transaction Name],[Begin Time],[SPID],[Description],AllocUnitName
FROM fn_dblog (NULL, NULL)
WHERE [Transaction Name] = 'DROPOBJ'

Well, all the demos I’ve seen on the internet which show this operation only have one row returned. This means only one object has been dropped since last log backup. The authors tell then “ok, we have the LSN, restore to that LSN and everything is gonna be alright.” But, it’s not representative of a true production system where there can be multiple object drops between two transaction log backups, or where the application uses tables for storing kind of “sequence numbers” and drops it daily (seriously, we have an application that does it here). Plus, in my experience, the DBA is rarely called directly after such an error.

好吧,我在互联网上看到的所有演示此操作的演示都只返回了一行。 这意味着自上次日志备份以来仅删除了一个对象。 然后作者说:“好吧,我们有了LSN,恢复到LSN,一切都会好起来的。” 但是,这并不代表一个真正的生产系统,在该生产系统中,两个事务日志备份之间可能存在多个对象删除,或者该应用程序使用表来存储某种“序列号”并将其每天删除(严重的是,我们有一个应用程序在这里)。 另外,根据我的经验,在发生此类错误后很少直接调用DBA。

Here, we have two rows returned.

在这里,我们返回了两行。

Fortunately, we can segregate thanks to the [BeginTime] column in our case (we take the last one). If you remember the previous use case, we also segregated on the [AllocUnitName] column, but this column is null for every row returned. This means that, in some cases, we cannot know with exactitude which [CurrentLSN] value should be taken, but I’ve not seen this warning anywhere on the other demos…

幸运的是,在本例中,我们可以通过[BeginTime]列来进行分隔(我们采用了最后一个)。 如果您还记得以前的用例,我们也会在[AllocUnitName]列上进行隔离,但是对于返回的每一行,此列都是null 。 这意味着在某些情况下,我们无法准确地知道应采用哪个[CurrentLSN]值,但在其他演示中的任何地方我都没有看到此警告…

I’ve tried different things but except if you know the Object_ID previously used by the object in the database, it’s more or less impossible to know with precision the right [CurrentLSN] value to be used.

我尝试了不同的方法,但是除非您知道数据库中对象先前使用的Object_ID,否则几乎不可能精确地知道要使用的正确[CurrentLSN]值。

So, to me, it’s more a “guess and check” process than a straightforward step by step procedure. But, when you have a very big database, it’s practically impossible due to the time needed to restore.

因此,对我而言,它比简单的逐步过程更像是“猜测和检查”过程。 但是,当您有一个非常大的数据库时,由于需要恢复时间,实际上几乎是不可能的。

By the way, here is a sample code, given as is which extracts information on the object_id of the dropped objects:

顺便说一下,这是一个示例代码,按原样给出,它提取有关所放置对象的object_id的信息:

if(OBJECT_ID('tempdb..#TransactionLog') IS NOT NULL)DROP TABLE #TransactionLog ;if(OBJECT_ID('tempdb..#ObjectInfo') IS NOT NULL)DROP TABLE #ObjectInfo ;-- Capture transaction log content
SELECT *
INTO #TransactionLog
FROM fn_dblog (NULL, NULL);CREATE TABLE #ObjectInfo (DropStartLSN    VARCHAR(32),TransactionId   VARCHAR(32),ObjectLockLSN   VARCHAR(32)
);WITH DropLSNs
as (SELECT [Current LSN],[Transaction ID]FROM #TransactionLogWHERE [Transaction Name] = 'DROPOBJ'
)
INSERT INTO #ObjectInfo (DropStartLSN,TransactionId,ObjectLockLSN
)
select DropLSNs.[Current LSN],DropLSNs.[Transaction ID],MIN(f.[Current LSN]) as LockObjectLSN
FROM DropLSNs
inner join #TransactionLog f
on DropLSNs.[Transaction ID] = f.[Transaction ID]
where [Operation] = 'LOP_LOCK_XACT'
And [Lock Information] like '%ACQUIRE_LOCK_SCH_M OBJECT:%'
group by DropLSNs.[Current LSN],DropLSNs.[Transaction ID];-- The lock information contains the object_id of the object
select oi.*, tl.[Lock Information]
FROM #ObjectInfo oi
INNER JOIN #TransactionLog tl
on tl.[Current LSN] = oi.ObjectLockLSNif(OBJECT_ID('tempdb..#TransactionLog') IS NOT NULL)DROP TABLE #TransactionLog ;if(OBJECT_ID('tempdb..#ObjectInfo') IS NOT NULL)DROP TABLE #ObjectInfo ;

Demo cleanup 演示清理

Dropping the databases:

删除数据库:

use master;
GO
IF(DB_ID('DBLogReading') IS NOT NULL)DROP DATABASE [DBLogReading];
IF(DB_ID('DBLogReading_COPY') IS NOT NULL)DROP DATABASE [DBLogReading_COPY];

翻译自: https://www.sqlshack.com/how-to-take-advantage-of-sql-servers-transaction-log/

如何利用SQL Server的事务日志?相关推荐

  1. SQL Server 为什么事务日志自动增长会降低你的性能

    原文地址:点击打开链接 在这篇文章里,我想详细谈下为什么你要避免事务日志(Transaction Log)上的自动增长操作(Auto Growth operations).很多运行的数据库服务器,对于 ...

  2. SQL Server中事务日志自动增长对性能的影响

    SQL Server中事务日志自动增长对性能的影响 SQL Server中事务日志自动增长对性能的影响(上) SQL Server中事务日志自动增长对性能的影响(下) posted on 2011-0 ...

  3. XenDesktop 5 SQL Server Mirror事务日志比较大的原因分析

    在实施XenDesktop5项目过程中,发现XenDesktop5版本的数据库镜像事务日志很大,在XenDesktop4和XenApp版本中不存在该问题:于是我根据该现象探究XenDesktop5及以 ...

  4. 如何读懂SQL Server的事务日志

    本文将介绍SQL Server的事务日志中记录了哪一些信息,如何来读懂这些事务日志中信息.首先介绍一个微软没有公开的函数fn_dblog,在文章的接下来的部分主要用到这个函数来读取事务日志. fn_d ...

  5. [ZT]SQL Server 的事务日志意外增大或充满的处理方法

    http://support.microsoft.com/kb/317375 事务日志文件Transaction Log File是用来记录数据库更新情况的文件,扩展名为ldf. 在 SQL Serv ...

  6. SQL Server中事务日志已满的原因以及解决办法

    错误描述:数据库的事务日志已满.若要查明无法重用日志中的空间的原因 ,请参阅sys.databases 中的 log_reuse_wait_desc 列 . 首先引入一下事务日志的概念(来自百度百科) ...

  7. sql server数据库事务日志已满请参阅log_reuse_wait_desc怎么解决?

    数据库使用时,莫名其妙出现关于事务日志已满的报错.具体报错如下: 数据库中的事务日志已满.若要查明无法重用日志中的空间的原因,请参阅sys.databases中的log_reuse_wait_desc ...

  8. SQL Server提高事务复制效率优化(二)快照初始化优化

    SQL Server提高事务复制效率优化(二)快照初始化优化 测试数据表量1500w+,使用初始化默认的快照代理参数,复制的三个过程包括快照初始化,订阅初始化和数据修改复制,主要对快照代理.分发代理. ...

  9. SQL Server 数据库清除日志的方法

    方法一: 1.打开查询分析器,输入命令  BACKUP LOG database_name WITH NO_LOG  2.再打开企业管理器--右键要压缩的数据库--所有任务--收缩数据库--收缩文件- ...

最新文章

  1. 我虐小车千百遍,小车待我如初恋
  2. WEB攻击手段及防御第1篇-XSS
  3. Mybatis的动态创建删除表
  4. boost::hana::take_front_c用法的测试程序
  5. ITK:从二进制图像中提取最大的连接组件
  6. 微型计算机中 i o接口位于6,北语15秋计算机基础作业1
  7. swing point 怎么让x 不变_Swing舞出我人生 Vol.02 / 我的人生分为跳舞之前和跳舞之后...
  8. Extjs中TextField中显示图标
  9. 用 VR 检查代码,码农们的必备神器!
  10. Android架构初探
  11. 最新android工程目录下armeabi-v7a,armeabi的具体含义,有什么区别
  12. uni-app开发和常规Vue开发
  13. Git使用小技巧【git reset和git revert, 你真的知道怎么用吗, 详细图解】
  14. BLUP育种值如何计算准确性
  15. 【OpenCV C++】照片换底
  16. 百度AI攻略:手写文字识别
  17. Excel 对象模型
  18. design pattern Builder 建造者设计模式
  19. uevent netlink(KOBJECT_UEVENT)
  20. 视频处理——去交错原理

热门文章

  1. [Linux]常用命令与目录全拼
  2. mybatis开启字段自动映射为java驼峰命名规则
  3. IBATIS的优缺点
  4. DP-桥接模式(Bridge Pattern)
  5. 句句真研—每日长难句打卡Day3
  6. win10计算机错误代码,Win10错误代码:0xc00000f 解决方案
  7. 买房贷款收入证明怎么开?
  8. 刚来公司时我却做了一件最傻的事
  9. 新能源车为什么不加变速箱解决高速高耗电的问题?
  10. 经营公司最核心的是经营员工的人生