SQL Server Transaction Log Management
SQL Server Transaction Log Management
The right of Tony Davis and Gail Shaw to be identified as the authors of this work has been asserted by them
in accordance with the Copyright, Designs and Patents Act 1988.
All rights reserved. No part of this publication may be reproduced, stored or introduced into a retrieval
system, or transmitted, in any form, or by any means (electronic, mechanical, photocopying, recording or
otherwise) without the prior written consent of the publisher. Any person who does any unauthorized act in
relation to this publication may be liable to criminal prosecution and civil claims for damages.
This book is sold subject to the condition that it shall not, by way of trade or otherwise, be lent, re-sold,
hired out, or otherwise circulated without the publisher's prior consent in any form other than which it
is published and without a similar condition including this condition being imposed on the subsequent
publisher.
Technical Review and Edit: Kalen Delaney
Additional Material: Jonathan Kehayias and Shawn McGehee
Cover Image by Andy Martin
Typeset by Peter Woodhouse and Gower Associates
Table of Contents
Introduction____________________________________________ 11
Chapter 1: Meet the Transaction Log_______________________ 15
How SQL Server Uses the Transaction Log_________________________________ 15
Write Ahead Logging and Transactional Consistency________________________16
Transaction Log Backup and Restore______________________________________18
Controlling the Size of the Log__________________________________________ 20
A Brief Example of Backing up the Transaction Log_________________________22
Summary____________________________________________________________ 26
ix
Acknowledgements
Tony Davis would like to thank:
Gail Shaw. It's been a pleasure to work with Gail on this book. She was tireless in
keeping me honest and accurate, and commendably patient in helping me understand
some of the finer points of transaction log mechanics.
Kalen Delaney. It was both reassuring and slightly intimidating to have Kalen edit
and review my efforts, given how much she knows about this topic, and SQL Server in
general. Her efforts have undoubtedly made this a far better book than it would have
been otherwise.
All of the people who give their time to contribute to the SQL Server community
knowledge base, in the form of books, articles, blogs and forum posts, as well as
technical presentations at events such as SQL Saturday and the PASS Summit. I've
worked with many of these people over the years, and learned from all of them.
Sarah, my wife. For her tolerance and patience during the trials and tribulations of
writing this book, and for far too many other things to mention here.
Introduction
Associated with every SQL Server database is a primary data file (.mdf), possibly some
secondary data files (.ndf), and a transaction log file (.ldf). The purpose of the data files is
self-explanatory, but the purpose of the log file remains, for many, shrouded in mystery,
and yet it is critical to the everyday operation of SQL Server, and to its ability to recover a
database in the event of failure.
A transaction log is a file in which SQL Server stores a record of all the transactions
performed on the database with which the log file is associated. In the event of a disaster
that causes SQL Server to shut down unexpectedly, such as an instance-level failure or
a hardware failure, SQL Server uses the transaction log in order to recover the database
to a consistent state, with data integrity intact. Upon restart, a database enters a crash
recovery process in which the transaction log is read to ensure that all valid, committed
data is written to the data files (rolled forward) and the effects of any partial, uncommitted transactions are undone (rolled back). SQL Server also uses the transaction log
during normal operation, to identify what it needs to do when a transaction rolls back,
due to either an error or a user-specified ROLLBACK statement. In short, the transaction
log is the fundamental means by which SQL Server ensures database integrity and the
ACID properties of transactions, notably durability.
In addition, backups of the transaction log enable DBAs to restore a database to the state
in which it existed at a previous, arbitrary point in time. During the restore phase, SQL
Server rolls forward the data changes described in a series of log backup files. We can
then recover the database and, via the recovery process described above, SQL Server will
ensure that the database is in a transactionally consistent state, at the recovery point.
When a SQL Server database is operating smoothly and performing well, there is no need
to be particularly conscious of exactly what the transaction log does, or how it works. As
a DBA, you just need to be confident that every database has the correct backup regime
in place. When things go wrong, however, a deeper understanding of the transaction log
11
Book Structure
The book starts with a discussion, in Chapters 1 and 2, of how SQL Server uses the
transaction log during normal operation as well as during database restore operations
and crash recovery. These chapters aim to provide just enough information so that you
understand the basic role of the log, how it works, and a little about its internal structure.
With this foundation laid, the book sets about describing all of the major necessary
management techniques for the transaction log, covering how to:
Choose the right recovery model SQL Server offers three database recovery models:
FULL (the default), SIMPLE, and BULK LOGGED. The DBA must choose the appropriate
model according to the business requirements for the database, and then establish
maintenance procedures appropriate to that mode. We cover this in Chapter 3, Transaction Logs, Backup and Recovery.
Perform transaction log backup and restore Unless working in SIMPLE model, it
is vital that the DBA perform regular backups of the transaction log. Once captured
in a backup file, the log records can subsequently be applied to a full database backup
in order to perform a database restore, and so re-create the database as it existed at a
previous point in time, for example, right before a failure. Chapters 4 to 6 cover this in
detail, for each of the recovery models.
Monitor and manage log growth In a busy database, the transaction log can grow
rapidly in size. If not regularly backed up, or if inappropriately sized, or assigned
12
incorrect growth characteristics, the transaction log file can fill up, leading to the
infamous "9002" (transaction log full) error, which puts SQL Server into a "read-only"
mode. We deal with this topic in Chapter 7, Dealing with Excessive Log Growth.
Optimize log throughput and availability In addition to basic maintenance such as
taking backups, the DBA must take steps to ensure adequate performance of the transaction log. This includes hardware considerations, as well as avoiding situations such
as log fragmentation, which can affect the performance of operations that read the log.
We cover this topic in Chapter 8, Optimizing Log Throughput.
Monitor the transaction log Having configured hardware for the log array, and
pre-sized the log according to workload and maintenance requirements, it's very
important that we monitor the I/O performance of the log array, as well as log
activity, tracking log growth, log fragmentation, and so on. Chapter 9, Monitoring
the Transaction Log, covers log monitoring tools and techniques.
Code Examples
You can download every script (denoted Listing X.X) in this book from the following URL:
http://www.simple-talk.com/RedGateBooks/DavisShaw/SQLServerTransactionLog_Code.zip
Most of the examples use custom-built databases, but a few rely on the readily available
AdventureWorks database. For SQL Server 2008 and later, you can download it from
Microsoft's codeplex site:
http://msftdbprodsamples.codeplex.com/releases/
For SQL Server 2005, use:
http://msftdbprodsamples.codeplex.com/releases/view/4004.
13
For SQL Server 2005 and 2008, run the file AdventureWorksDB.msi (SQL Server 2005) or
simply copy the data and log files to your hard drive (SQL Server 2008) and then, in SQL
Server Management Studio, attach the files to create the AdventureWorks database on
your preferred SQL Server instance.
For SQL Server 2008 R2 and later, simply follow the instructions here:
http://social.technet.microsoft.com/wiki/contents/articles/3735.sql-serversamples-readme-en-us.aspx#Readme_for_Adventure_Works_Sample_Databases.
Feedback
We've tried our very best to ensure that this book is useful, technically accurate, and
written in plain, simple language. If we've erred on any of these elements, we'd like to
hear about it. Please post your feedback and errata to the book page, here:
http://www.simple-talk.com/books/sql-books/sql-server-transaction-logmanagement/.
14
At each checkpoint, SQL Server scans the data cache and flushes to disk all dirty pages
in memory. A dirty page is any page in the cache that has changed since SQL Server read
it from disk, so that the page in cache is different from what's on disk. Again, this is not
a selective flushing; SQL Server flushes out all dirty pages, regardless of whether they
contain changes associated with open (uncommitted) transactions. However, the log
buffer manager always guarantees to write the change descriptions (log records) to the
transaction log, on disk, before it writes the changed data pages to the physical data files.
This mechanism, termed write ahead logging, allows SQL Server to ensure some of
the ACID properties (http://msdn.microsoft.com/en-gb/library/aa719484(VS.71).
aspx) of database transactions, notably durability. By writing changes to the log file first,
SQL Server can guarantee that a committed change will persist, even under exceptional
circumstances.
For example, let's say a transaction (T1) is committed, and hardened to the log, but SQL
Server crashes before a checkpoint occurs. Upon restart, the crash recovery process is
initiated, which attempts to reconcile the contents of the transactions log file and the
data files. It will read the transaction log file and find a series of log records relating to T1
ending with one that confirms the COMMIT. Therefore, it will ensure that all of the operations that comprise transaction T1 are "rolled forward" (redone) so that they are reflected
in the data files.
The database checkpoint process
By regularly flushing dirty pages from cache to disk, the database checkpoint process controls the amount
of work SQL Server needs to do during crash recovery. If SQL Server had to roll forward the changes for a
huge number of committed transactions, then the recovery process could be very lengthy.
17
19
20
Therefore, when working in FULL and BULK LOGGED recovery models, it is vital that
you perform regular transaction log backups, in addition to full backups and, optionally,
differential backups. Many novice or part-time DBAs perform full backups on their
databases, but they don't perform transaction log backups. As a result, the transaction
log is not truncated, and it grows and grows until the drive it is on runs out of disk space,
causing SQL Server to enter read-only mode.
When a log backup occurs, any VLF that is no longer active becomes eligible for
truncation, meaning simply that SQL Server can reuse its space to store new log records.
Truncation of the log will occur as soon as the log backup is taken, assuming no other
factors, such as an in-progress database backup operation, are delaying truncation. We'll
cover some of the other factors that may delay truncation of VLFs even after log backup,
as well as factors that keep large swathes of the log active that otherwise wouldn't need
to be, such as a rogue, long-running uncommitted transaction, or database mirroring, or
replication processes, in Chapter 7.
COPY_ONLY backups of the transaction log
An exception to the rule of log backups truncating the log is the COPY_ONLY log backup. A
COPY_ONLY log backup exists "independently" of the normal log backup scheme; it does not
break the log backup chain.
In a SIMPLE recovery model database, by contrast, log truncation can occur immediately
upon checkpoint. SQL Server flushes cached dirty pages to disk (after first writing the
transaction details) and then immediately truncates any VLFs that are no longer active,
so that the space can store new log records. This also explains why log backups are
meaningless for SIMPLE recovery model databases.
21
In Listing 1.1, we create a new TestDB database on a SQL Server 2008 instance, and
then immediately obtain the size of the log file using the DBCC SQLPERF (LOGSPACE)
command.
USE master ;
IF DB_ID('TestDB') IS NOT NULL
DROP DATABASE TestDB ;
CREATE DATABASE TestDB ON
(
NAME = TestDB_dat,
FILENAME = 'D:\SQLData\TestDB.mdf'
) LOG ON
(
NAME = TestDB_log,
FILENAME = 'D:\SQLData\TestDB.ldf'
) ;
DBCC SQLPERF(LOGSPACE) ;
22
Listing 1.1:
As you can see, the log file is currently about 1 MB in size, and about 30% full.
The model database
The properties of the model database determine the initial size and growth characteristics of new user
databases on an instance, as well as the default recovery model that each database will adopt (FULL, in
this case). We'll discuss the impact of these properties in more detail in Chapter 8.
We can confirm the size of the file simply by locating the physical files on disk, as shown
in Figure 1.1.
Figure 1.1:
Let's now perform a full database backup (i.e. backing up the data file) for TestDB, as
shown in Listing 1.2 (you'll first need to create the SQLBackups directory). Note that this
backup operation ensures the database truly is operating in FULL recovery model; more
on this in Chapter 3.
-- full backup of the database
BACKUP DATABASE TestDB
TO DISK ='D:\SQLBackups\TestDB.bak'
WITH INIT;
GO
Listing 1.2:
23
24
Listing 1.3:
Notice that the log file size has ballooned to almost 100 MB and the log is 93% full (the
figures might be slightly different on your system). If we were to insert more data, it
would have to grow in size to accommodate more log records. Again, we can confirm the
size increases from the physical files (the data file has grown to 64 MB).
We can perform another full database backup at this point, by rerunning Listing 1.2, and
it will make no difference to the size of the log file, or the percentage of space used in
the file. Instead, however, let's back up the transaction log file and recheck the values, as
shown in Listing 1.4.
-- now back up the transaction log
BACKUP Log TestDB
TO DISK ='D:\SQLBackups\TestDB_log.trn'
WITH INIT;
GO
DBCC SQLPERF(LOGSPACE) ;
Listing 1.4:
The log file is still the same physical size but, by backing up the file, SQL Server is able
to truncate the log, making space in the inactive VLFs in the log file available for reuse;
it can write new log records without needing to grow the log file. In addition, of course,
we've captured the log records into a backup file and can use that backup file as part
of the database recovery process, should we need to restore the TestDB database to a
previous state.
25
Summary
In this first chapter, we've introduced the transaction log, and explained how SQL Server
uses it to maintain data consistency and integrity, via a write ahead logging mechanism.
We've also described, and briefly demonstrated, how a DBA can capture the contents
of the transaction log file into a backup file, for use as part of a database recovery
operation. Finally, we stressed the importance of backups in controlling the size of
the transaction log.
In the next chapter, we'll take a closer look at the architecture of the transaction log.
26
Figure 2.1:
Transaction log files are sequential files; in other words, SQL Server writes to the transaction log sequentially (unlike data files, which SQL Server writes in a more random
fashion, as applications modify data in random data pages).
27
SQL Server stamps each log record inserted into the log file with a Logical Sequence
Number (LSN). When we create a new database, with its associated log file, the first log
record marks the logical start of the log file, which at this stage will coincide with the start
of the physical file. The LSNs are then ever increasing, so a log record with an LSN of 5
records an action that occurred immediately before the one with an LSN of 6. The most
recent log record will always have the highest LSN, and marks the end of the logical file
(discussed in more detail shortly).
Log records associated with a given transaction are linked in an LSN chain. Specifically,
log records are "backwards chained;" each log record stores the sequence number of the
log record that preceded it in a particular transaction. This enables SQL Server to perform
rollback, undoing each log record in exact reverse order.
An important concept is that of the "active log." The start of the active log is the "oldest
log record that is required for a successful database-wide rollback or by another activity or
operation in the database." The LSN of this log record is MinLSN. In other words, the
MinLSN is the LSN of the log record relating to the oldest open transaction, or to one
that is still required by some other database operation or activity. The log record with
the highest LSN (i.e. the most recent record added) marks the end of the active log. SQL
Server writes all subsequent records to the logical end of the log.
28
Figure 2.2:
Note that you may occasionally hear the MinLSN referred to as the tail of the log, and
MaxLSN as the head of the log. A VLF that contains any part of the active log is active;
a VLF that contains no part of the active log is inactive. In Figure 2.2, VLFs 1 to 3 are all
active. In short, a log record is no longer part of the active log if it is older (i.e. has a lower
LSN) than the MinLSN record and this will be true only if the following two conditions
are met:
1. No other database process, including a transaction log backup when using FULL or
BULK LOGGED recovery models, requires it to remain in the active log.
2. It relates to a transaction that is committed, and so is no longer required for rollback.
For example, consider a case where the MinLSN is a log record for an open transaction
(T1) that started at 9.00 a.m. and takes 30 minutes to run. A subsequent transaction (T2)
starts at 9.10 a.m. and finishes at 9.11 a.m.. Figure 2.3 shows a very simplified depiction of
this situation (our VLFs hold only four log records!).
29
Figure 2.3:
VLF2 contains no log records relating to open transactions, but all these log records
remain part of the active log since they all have LSNs greater than MinLSN. In Figure 2.4,
the action has moved on. A third transaction (T3) has started, and T1 has completed its
final update, and committed.
Figure 2.4:
Now, the log record for the oldest open transaction is LSN8, but the MinLSN is still LSN1,
and so VLFs 13 are all active. Why? Our database is operating in the FULL recovery
model and until the next log backup operation, all of these log records must remain in the
active log. In other words, LSN1 is the oldest log record still required by another database
process or activity, in this case a log backup.
30
Figure 2.5:
In this manner, as transactions start and are committed, we can imagine (somewhat
simplistically) the tail and head of the log moving left to right across Figure 2.5, so that
VLFs that previously contained part of the active log now become inactive (VLF1), and
VLFs that were previously untouched will become part of the active log (VLF4). In a
SIMPLE recovery model database, we would get the same situation as depicted in
Figure 2.5, if we replaced the log backup with a checkpoint operation.
31
32
33
34
Listing 2.1:
We can interrogate the VLF architecture using a command called DBCC LogInfo, as
shown in Listing 2.2.
Interrogating VLFs using DBCC LogInfo
DBCC LogInfo is an undocumented and unsupported command at least there is very little written
about it by Microsoft. We'll use it in this chapter to peek at the VLFs, but we won't go into detail about all
the information it returns.
Listing 2.2:
35
36
Listing 2.3:
Having inserted only 1,500 rows, the log has already auto-grown. We see four new VLFs
(note that the CreateLSN values are non-zero, and the same in each case, because SQL
Server created them in a single auto-grow event). The auto-growth increment was 16 MB
and each new VLF is roughly 4 MB in size. The first of the new VLFs (FSeqNo 34) is active,
but the rest are yet unused. The log is now 18 MB, as expected, and is 16% full.
37
Listing 2.4:
Thanks to the log backup, the log is now only 6% full. The first four VLFs are truncated,
and so are available for reuse. Let's grow the log a second time, adding 15K rows (simply
rerun Listing 2.3 and adjust the number of rows).
38
Figure 2.6:
SQL Server fills VLFS 3437 (arising from the previous auto-growth). The original four
VLFs (3033) are available for reuse, but SQL Server opts to grow the log immediately,
rather than reuse them first, and then starts filling the first three of the new VLFs (3840).
In this case, SQL Server grows the log simply because if it had used the original four small
VLFs it would not have had the required log reservation space necessary in the event that
it needed to roll back the transaction.
The process of rolling back a transaction requires a series of operations that will undo
the effects of that transaction. These compensating operations generate log records,
just as does any other database change. Log reservation is the amount of log space
(per transaction) that SQL Server must keep free in order to accommodate these
compensation log records.
Accordingly, big transactions that write many records will also need quite a large log
reservation. SQL Server releases this additional space once a transaction commits.
Chapter 7 (Listing 7.1) provides a query that will display log bytes written and reserved
by a transaction. If we had run the 15K-row insert inside an explicit transaction, without
39
Figure 2.7:
Notice that SQL Server fills the VLFs added by the first auto-growth (3437), and then
cycles back to the start and uses the first two of the original VLFs (now reused as 38
and 39). This happens because, here, these VLFs will accommodate the log reservation
required by the smaller initial inserts. SQL Server then grows the log again and starts to
use the first of these (40) rather the remaining two original VLFs.
40
Figure 2.8:
Now we have 536 VLFs (436 of which are in use). Note that it may seem as if SQL Server
has "overgrown" the log, but again we must factor in log reservation requirements. In
this case, SQL Server had to add considerable extra space in case it needed to roll back
the insert.
A transaction log that auto-grows frequently, in small increments, will have a very large
number of small VLFs. This phenomenon is log fragmentation. Essentially, the initial size
and relatively small growth increments we've chosen for this database are inappropriate
for this sort of data load and lead to the creation of a large number of VLFs.
It's possible that log file fragmentation can degrade the performance of SQL Server
processes that need to read the log, such as crash recovery and log backups. We will
discuss this in more detail in Chapter 8, and show how to avoid fragmentation by
correctly sizing and growing the log file.
41
Summary
This chapter provides minimal background information regarding the architecture of the
transaction log; hopefully, just enough so that you can understand the basic issues and
potential problems relating to truncation, space reuse, and fragmentation, in the log file.
In Chapter 3, we move on to a more detailed discussion of the role of the transaction log
in database restore and recovery.
42
43
44
45
Figure 3.1:
A backup strategy comprising full and differential database backups and log backups.
In this case, we could restore the most recent full backup, the differential backup and the
whole chain of log backups (files 7 to 12 in Figure 3.1) up to 12.30 a.m., and we would lose
only 30 minutes-worth of data. In fact, if we still had access to the transaction log, we
might be able to perform a tail log backup (covered in detail in Chapter 4) and minimize
our data loss to close to zero. SQL Server will roll forward all of the actions recorded
in the log backups, up to the specified point and then we can recover the database to a
consistent state at a point in time very close to the time of the disaster.
Of course, this scheme assumes that all backups are valid and non-corrupt and, in the
case of the log backups, that you have a full and complete log chain. We'll get to this in
more detail shortly, but let's say that between the eighth and ninth log backups someone
switched the database to SIMPLE recovery model, and then back to FULL, without
taking any further backups. Upon switching to SIMPLE recovery model, SQL Server
would have truncated the transaction log and we'd only be able to restore to the end of
Log 8. In addition, any subsequent log backups would fail, until we took another full (or
differential) database backup.
46
Listing 3.1:
A database will adopt the default recovery model specified by the model database. In
many cases, this will mean that the default recovery model for a database is FULL, but
different editions of SQL Server may have different defaults for the model database.
49
Listing 3.2:
name ,
recovery_model_desc
sys.databases
name = 'TestDB' ;
However, be careful with this query, as it may not always tell the whole truth. For
example, if we create a brand new database and then immediately run the command from
Listing 3.2, it would report that the database was in FULL recovery model. However, in
fact, until we take a full database backup the database will be operating in auto-truncate
mode, sometimes referred to as pseudo-SIMPLE recovery model.
We can see this in action by creating a new database on a SQL Server 2008 instance,
where the default recovery model is FULL. To make doubly sure, we explicitly set the
recovery model to FULL and then query sys.databases to confirm, as shown in
Listing 3.3.
50
Listing 3.3:
This indicates that we're in FULL recovery model, so let's insert some data, check the log
space usage, force a checkpoint operation and then recheck the log usage, as shown in
Listing 3.4.
51
Listing 3.4:
Note that the log file is roughly the same size after the checkpoint, but is only 11% full;
because of the checkpoint operation, SQL Server truncated the log and made the space
available for reuse.
Although the database is assigned to FULL recovery model, it is not actually operating
in that model until the first full database backup is taken. Interestingly, this means we
could have achieved the same effect by running that full backup of the TestDB database,
instead of explicitly forcing a CHECKPOINT. The first full backup operation triggers a
CHECKPOINT and the log is truncated. All full backups start by running a checkpoint.
This is to ensure that as much of the changed data as possible is on disk, and to minimize
the portion of the log that SQL Server needs to read at the end of the backup.
52
db_name(database_id) AS 'DatabaseName' ,
last_log_backup_lsn
master.sys.database_recovery_status
database_id = db_id('TestDB') ;
DatabaseName
last_log_backup_lsn
----------------------------------------------TestDB
Listing 3.5:
NULL
If a value of NULL appears in the column, then the database is actually in autotruncate mode, and so SQL Server will truncate the log when database checkpoints occur.
Having performed a full database backup, you will find that the column is populated
with the LSN of the log record that recorded the backup operation, and at this point the
database is truly in FULL recovery model. From this point on, a full database backup will
have no effect on the transaction log; the only way to truncate the log will be to back up
the log.
Switching models
If you ever switch a database from FULL or BULK LOGGED model to SIMPLE model, this
will break the log chain and you'll only be able to recover the database up to the point
of the last log backup taken before you switched. Therefore, always take that log backup
immediately before switching. If you subsequently switch the database back from SIMPLE
to FULL or BULK_LOGGED model, remember that the database will actually continue
operating in auto-truncate mode (Listing 3.5 will display NULL) until you perform another
full or differential backup.
53
55
56
57
Summary
In this chapter, we discussed the importance of devising the right backup regime for
the needs of your most important production databases. Most of these databases will be
operating in FULL recovery model, with regular log backups alongside full and, possibly,
differential database backups. These backups are vital, not only for database restore and
recovery, but also for controlling the size of the transaction log.
Some databases don't require point-in-time restore, or need the log backups for any other
purpose (such as database mirroring), and have data-loss objectives which can be met
with full and differential database backups only. In these cases, we can operate them in
SIMPLE recovery model, greatly simplifying log maintenance.
We also briefly discussed log backup logistics, and the need to automate log backups,
schedule them at the right frequency, verify them, and store them securely.
In Chapter 4, we begin a series of three chapters examining in more detail log
management in each of the three database recovery levels, starting with SIMPLE.
58
While SQL Server still writes to the log a complete description of all actions performed,
it does not retain them such that log backups can capture this complete description.
The LSN chain will be incomplete. In short, log backups have no meaning in SIMPLE
recovery model and, in fact, we cannot even perform transaction log backups, as
Listing 4.1 demonstrates.
USE master;
ALTER DATABASE TestDB
SET RECOVERY SIMPLE;
BACKUP Log TestDB
TO DISK ='D:\SQLBackups\TestDB_log.trn'
GO
Msg 4208, Level 16, State 1, Line 1
The statement BACKUP LOG is not allowed while the recovery model is SIMPLE. Use BACKUP
DATABASE or change the recovery model using ALTER DATABASE.
Msg 3013, Level 16, State 1, Line 1
BACKUP LOG is terminating abnormally.
Listing 4.1:
This means that our backup and restore scheme will consist entirely of full (and possibly
differential) database backups.
60
61
62
63
Listing 5.1:
64
Of course, during a normal backup regime, we do not want to overwrite existing backups.
When used in this book, it is simply a convenience to facilitate repeatable tests.
Most commonly, we'd give each subsequent backup a unique name; more on this in the
forthcoming section, Full restore to point of failure.
After each regular (e.g. daily) full backup, there will be frequent (e.g. hourly) log backups,
the basic command for which is very similar:
BACKUP LOG DatabaseName
TO DISK ='FileLocation\DatabaseName_Log.trn';
65
The NORECOVERY option puts the database in a restoring state, ensures that no further
transactions will succeed after the tail log backup, and assumes that the next action we
wish to perform is a RESTORE. We can then restore our full database (or file) backups,
followed by the full chain of log backups, finishing with the tail log backup, and then
recover the database.
However, let's say instead that the 1:45 p.m. failure causes damage so bad that the
database will not come back online. In this case, a normal log backup (or a tail log backup
using WITH NORECOVERY) will fail because it will attempt to truncate the log and, as part
of that operation, it needs to write into the database's header the new log MinLSN.
In such cases, we can still attempt to back up the tail of the log, using the NO_TRUNCATE
option instead of the NORECOVERY option.
BACKUP LOG DatabaseName
TO DISK ='FileLocation\DatabaseName_Log_tail.trn'
WITH NO_TRUNCATE
Note that we should only use this option if a failure has damaged the database severely
and it won't come online.
66
After each restore operation we perform using the WITH NORECOVERY option, SQL Server
will roll forward the contents of the applied log backups, and leave the database in a
restoring state, ready to accept further log backups.
If we omit the WITH NORECOVERY option, then by default the RESTORE command will
proceed WITH RECOVERY. In other words, SQL Server will reconcile the data and log files,
rolling forward completed transactions and then rolling back uncompleted transactions
as necessary. After restoring the last backup in the restore sequence, we can then restore
WITH RECOVERY and SQL Server will perform the necessary roll forward and roll back to
recover the database to a consistent state.
67
69
Listing 5.2:
However, using backup sets seems to be a relic from times when we backed up databases
to tape. When backing up to disk, it is a bad idea to use this scheme because, of course,
the backup file will quickly grow very large.
In practice, it is far more common that each full backup and transaction log backup file
will be individually named, and probably stamped with the time and date we took the
backup. For example, most third-party backup tools, popular community-generated
scripts, plus the maintenance plan wizard /designer in SSMS, will all create separate
date-stamped files e.g. AdventureWorks_FULL_20080904_000001.bak. For all further
examples, we'll adopt the scheme of uniquely named backups.
70
Listing 5.3:
71
INTO SomeTable
( SomeCol
)
SELECT TOP ( 10 )
REPLICATE('a', 5)
FROM
sys.columns AS c;
Listing 5.4:
Create and populate SomeTable, in the FullRecovery database, and run a log backup.
Finally, in Listing 5.5, we update a row in SomeTable, make a note of the time immediately afterwards (we'll need this for a later restore), and then perform a named, marked
transaction (again, more on this shortly) which accidentally empties SomeTable.
72
Listing 5.5:
Update a row in SomeTable then delete the table contents in a marked transaction.
For this example, we're simply going to restore over the top of the existing
FullRecovery database, to return it to the state in which it existed at the time
of the log backup. Listing 5.6 restores our full database backup over the top of the
existing database and then applies the log backup.
USE master
GO
--restore the full database backup
RESTORE DATABASE FullRecovery
FROM DISK='D:\SQLBackups\FullRecovery.bak'
WITH NORECOVERY;
--restore the log backup
RESTORE LOG FullRecovery
FROM DISK='D:\SQLBackups\FullRecovery_log.trn'
WITH RECOVERY;
Listing 5.6:
Restoring to the end of the log backup (no tail log backup).
73
SQL Server is warning us that we're about to overwrite the transaction log of the existing
FullRecovery database, and we have not done a tail log backup, so any operations in
there would be lost forever. In this example, it means that after the proposed restore
operation we'd lose forever the effects of Listing 5.5, which we haven't backed up. If we're
certain that we don't need to perform that backup, we can override this check using
WITH REPLACE when we restore the full backup, as follows, and then perform the log
restore as normal.
RESTORE DATABASE FullRecovery
FROM DISK='D:\SQLBackups\FullRecovery.bak'
WITH NORECOVERY, REPLACE;
Conversely, only use REPLACE when certain that a tail log backup is not required. In this
example, we'll take that tail log backup, even though we don't need it right away. This
pitches the database into a restoring state and we proceed immediately with the restore
operation, as shown in Listing 5.7.
74
Listing 5.7:
As you can see, we've returned the database to the state in which it existed after the first
log backup. Of course, we could restore the database to the end of the tail log backup, as
shown in Listing 5.8, but then we'd just be returning the database to the state where all
data had been lost from SomeTable.
75
Listing 5.8:
Query SomeTable and you'll see that there is no data. Let's now see how to get it back;
in order to do this, we'll need to stop our restore operation after the update in Listing 5.5,
but before the rogue delete.
Point-in-time restores
Whether DBAs like it or not, developers often have access to production databases to
perform ad hoc data loads and changes. It is the joint responsibility of the DBA and
the developer to ensure these proceed smoothly, but accidents can and do happen,
and may result in the accidental deletion or modification of data. In such an event, the
DBA may need to perform a point-in-time restore, to return a database to the state in
which it existed prior to the rogue transaction, and then copy the lost data back into the
production database. We'll cover two such techniques to perform a point-time restore:
RESTOREWITHSTOPBEFOREMARK restoring to a marked transaction.
RESTOREWITHSTANDBY restoring a standby database to specific point in time.
76
Listing 5.9:
We've left the FullRecovery_Copy database in a restoring state, and the next step is to
restore the first log backup, using WITHSTOPBEFOREMARK, because we want to return
our database to a consistent state as of the last committed transaction at the time the
marked transaction started.
RESTORE LOG [FullRecovery_Copy]
FROM DISK = N'D:\SQLBackups\FullRecovery_Log.trn'
WITH
NORECOVERY,
STOPBEFOREMARK = N'Delete_SomeTable'
GO
At this point, we'll see the following message since the log backup does not contain the
designated mark:
Processed 0 pages for database 'FullRecovery_Copy', file 'FullRecovery_dat' on file 1.
Processed 4 pages for database 'FullRecovery_Copy', file 'FullRecovery_log' on file 1.
This log file contains records logged before the designated mark. The database is being left
in the Restoring state so you can apply another log file.
RESTORE LOG successfully processed 4 pages in 0.007 seconds (4.115 MB/sec).
78
Listing 5.11:
At this point, we can recover the FullRecovery_Copy database and query it, as shown
in Listing 5.12.
RESTORE DATABASE [FullRecovery_Copy]
WITH RECOVERY
GO
USE FullRecovery_copy
SELECT TOP 1 * FROM SomeTable
Listing 5.12:
79
80
Listing 5.13:
Figure 5.1:
81
Listing 5.14: Restore the first log backup to the standby database.
Next, we can restore fully the next log backup (in this example, the tail log backup).
Having done so, we'll find that all the data in SomeTable is gone, and so we know that
this log backup contains the rogue delete transaction. Fortunately, in this case, not
only do we know that already, we also know the exact time we wish to stop the restore.
Therefore, we can proceed as shown in Listing 5.15, restoring the tail log backup using
WITH STOPAT to stop the restore at the point of the last committed transaction, at the
time specified, just before we lost the data.
USE master
Go
RESTORE LOG Standby_FullRecovery
FROM DISK = N'D:\SQLBackups\Fullrecovery_tail.trn'
WITH
STANDBY =
N'D:\SQLBackups\Standby_FullRecovery_Copy_UNDO.bak',
STOPAT = '2012-10-05 16:23:06.740'
GO
Listing 5.15:
82
*
fn_dump_dblog(DEFAULT, DEFAULT, DEFAULT, DEFAULT,
'C:\SQLBackups\FullRecovery_tail.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,
It's not pretty and it's unsupported, so use it with caution. It accepts a whole host of
parameters, the only one we've defined being the path to the log backup. It returns a vast
array of information that we're not going to begin to delve into here, but it does return
the Begin Time for each of the transactions contained in the file, and it may offer some
help in working out where to stop a restore operation.
A different technique to point-in-time restores using STOPAT, is to try to work out
the LSN value associated with, for example, the start of the rogue transaction that
83
84
Listing 5.17:
We can then proceed with the restore operation, replacing the existing database, as
discussed previously. We'll return to this topic in Chapter 6, when we discuss the
BULK_LOGGED recovery model, as there are cases there where this technique won't work.
Summary
In this chapter, we've covered the basics of backing up and restoring log files for
databases operating in FULL recovery model, which will be the norm for many
production databases.
For most DBAs, the need to perform a point-in-time restore is a rare event, but it's one of
those tasks where, if it is necessary, it is critical that it is done, and done well; the DBA's
reputation depends on it.
85
86
87
88
89
We'll use a SELECTINTO statement, which can be minimally logged in SQL Server 2008,
to insert 200 rows, of 2,000 bytes each, into a table called SomeTable. Since the page size
in SQL Server is 8 KB, we should get four rows per page and so 50 data pages in all (plus
some allocation pages). Listing 6.1 creates a test database, FullRecovery, ensures that it
is operating in FULL recovery model, and then runs the SELECTINTO statement.
USE master
GO
IF DB_ID('FullRecovery') IS NOT NULL
DROP DATABASE FullRecovery;
GO
-- Clear backup history
EXEC msdb.dbo.sp_delete_database_backuphistory
@database_name = N'FullRecovery'
GO
CREATE DATABASE FullRecovery ON
(NAME = FullRecovery_dat,
FILENAME = 'D:\SQLData\FullRecovery.mdf'
) LOG ON
(
NAME = FullRecovery_log,
FILENAME = 'D:\SQLData\FullRecovery.ldf'
);
ALTER DATABASE FullRecovery SET RECOVERY FULL
GO
USE FullRecovery
GO
90
Listing 6.1:
Now, at this point, we'd like to peek inside the log, and understand what SQL Server
recorded in the log because of our fully logged SELECTINTO statement. There are some
third-party log readers for this purpose, but very few of them offer support beyond SQL
Server 2005. However, we can use two undocumented and unsupported functions to
interrogate the contents of log files (fn_dblog) and log backups (fn_dump_dblog), as
shown in Listing 6.2.
SELECT
--
FROM
Operation ,
Context ,
AllocUnitName ,
Description ,
[Log Record Length] ,
[Log Record]
fn_dblog(NULL, NULL)
Listing 6.2:
Figure 6.1 shows a small portion of the output consisting of a set of eight pages (where the
allocation unit is dbo.SomeTable). Notice that the context in each case is LCX_HEAP,
so these are the data pages. We also see some allocation pages, in this case a Differential
Changed Map, tracking extents that have changes since the last database backup (to
facilitate differential backups), and some Page Free Space (PFS) pages, tracking page
allocation and available free space on pages.
91
Figure 6.1:
The log records describing the changes made to SomeTable are all of type LOP_FORMAT_
PAGE; they always appear in sets of 8, and each one is 8,276 bytes long. The fact that they
appear in sets of 8 indicates that SQL Server was processing the insert one extent at a
time and writing one log record for each page. The fact that each one is 8,276 bytes shows
that each one contains the image of an entire page, plus log headers. In other words,
for the INSERTINTO command, and others that SQL Server will minimally log in
BULK_LOGGED recovery, SQL Server does not log every individual row when run
in FULL recovery; rather, it just logs each page image, as it is filled.
A closer examination of the Log Record column shows many bytes containing the
hex value 0x61, as shown in Figure 6.2. This translates to decimal 97, which is the
ASCII value for 'a', so these are the actual data rows in the log file.
Figure 6.2:
So in FULL recovery model, SQL Server knows, just by reading the log file, which extents
changed and exactly how this affected the contents of the pages. Let's now compare this
92
Listing 6.3:
93
Figure 6.3:
This time, the log records for the changes made to SomeTable appear in the context
of Global Allocation Maps (GAMs) and Index Allocation Maps (IAMs), tracking extent
allocation, plus some PFS pages. In other words, SQL Server is logging the extent
allocations (and any changes to the metadata, i.e. system tables, which we don't show in
Figure 6.3), but the data pages themselves are not there. There is no reference in the log as
to what data is on the allocated pages. We do not see here the 0x61 pattern that appeared
in the log records for the FullRecovery database, and most of the log records are
around 100 bytes in size.
Therefore, we now have a clearer picture of exactly what it means for SQL Server to
minimally log an operation: it is one where SQL Server logs allocations of the relevant
extents, but not the actual content of those extents (i.e. data pages).
94
95
GO
SELECT
FROM
INTO PrimaryTable_Large
( SomeColumn
)
SELECT TOP 100000
'abcd '
FROM
msdb.sys.columns a
CROSS JOIN msdb.sys.columns b
*
sys.dm_db_index_physical_stats(DB_ID(N'FullRecovery'),
OBJECT_ID(N'PrimaryTable_Large'),
NULL, NULL, 'DETAILED');
Listing 6.4:
97
d.name ,
session_id ,
d.recovery_model_desc ,
-database_transaction_begin_time ,
database_transaction_log_record_count ,
database_transaction_log_bytes_used ,
DATEDIFF(ss, database_transaction_begin_time, GETDATE())
AS SecondsToRebuild
FROM
sys.dm_tran_database_transactions AS dt
INNER JOIN sys.dm_tran_session_transactions AS st
ON dt.transaction_id = st.transaction_id
INNER JOIN sys.databases AS d ON dt.database_id = d.database_id
WHERE d.name = 'FullRecovery'
COMMIT TRANSACTION
Listing 6.5:
98
Figure 6.4:
It took approximately 5 seconds to rebuild the index, and the rebuilds required about
166 MB of log space for 20,131 log records; this is ignoring the log reservation in case of
rollback, so the total log space required is larger.
If we run the same example in the BulkLoggedRecovery database, the output is as
shown in Figure 6.5.
Figure 6.5:
The rebuild appears to be a bit faster, at 4 seconds; however, because the index in this
example is quite small, and because the data and log files are both on a single drive, not
much can be concluded from that time difference. The main point here is the difference
in the log space used. In BULK_LOGGED recovery, that index rebuild only used about
0.6 MB of log space, compared to the 166 MB of log space in FULL recovery. That's a
major saving considering that this was quite a small table, at only 160 MB in size.
In case anyone's wondering whether the behavior will be any different in SIMPLE
recovery, look at Figure 6.6 (the code to reproduce the example in a SIMPLE recovery
database, as well as BULK_LOGGED, is included in the code download for this chapter).
99
Figure 6.6:
As expected, the behavior is the same as for BULK_LOGGED recovery, since operations
that are minimally logged in BULK_LOGGED recovery are also minimally logged in
SIMPLE recovery.
This is the major reason for running a database in BULK_LOGGED recovery; it offers both
the database recovery options of FULL recovery (mostly, see the coming sections), but it
reduces the amount of log space used by certain operations, in the same way as SIMPLE
recovery. Note also, that if the database is a log-shipping primary, we cannot switch the
database into SIMPLE recovery for index rebuilds without having to redo the log shipping
afterwards, but we can switch it to BULK_LOGGED for the index rebuilds.
Finally, note that database mirroring requires FULL recovery only and, as such, a
database that is a database mirroring principal cannot use BULK_LOGGED recovery.
Crash recovery
Crash recovery, also called restart recovery, is a process that SQL Server performs
whenever it brings a database online. So for example, if a database does not shut down
cleanly, then upon restart SQL Server goes through the database's transaction log. It
undoes any transaction that had not committed at the time of the shutdown and redoes
any transaction that had committed but whose changes had not been persisted to disk.
This is possible because, as discussed in Chapter 1, the Write Ahead Logging mechanism
ensures that the log records associated with a data modification are written to disk before
either the transaction commits or the data modification is written to disk, whichever
happens first. SQL Server can write the changes to the data file at any time, before the
transaction commits or after, via the checkpoint or Lazy Writer. Hence, for normal
operations (i.e. ones that are fully logged), SQL Server has sufficient information in
the transaction log to tell whether an operation needs to be undone or redone, and has
sufficient information to roll forward or roll back.
For operations that were minimally logged, however, roll forward is not possible as there's
not enough information in the log. Therefore, when dealing with minimally logged
operations in SIMPLE or BULK_LOGGED recovery model, another process, Eager Write,
guarantees that the thread that is executing the bulk operation hardens to disk any
extents modified by the minimally logged operation, before the transaction is complete.
This is in contrast to normal operations where only the log records have to be hardened
before the transaction is complete, and the data pages are written later by a system
process (Lazy Writer or checkpoint).
This means that crash recovery will never have to redo a minimally logged operation
since SQL Server guarantees that the modified data pages will be on disk at the time
the transaction commits, and hence the minimal logging has no effect on the crash
recovery process.
101
Database restores
SQL Server also needs to perform redo operations when restoring full, differential, or log
backups. As we've discussed, for a minimally logged operation the affected pages are on
disk at the point the transaction completes and so SQL Server simply copies those pages
into any full or differential backup files, and restores from these backups are unaffected.
Restores from log backups, however, are more interesting. If the log backup only
contained the log records relating to extent allocation, then on restoring the log backup
there would be no way to re-create the contents of the extents that the minimally logged
operation affected. This is because, as we saw earlier, the log does not contain the inserted
data, just the extent allocations and metadata.
In order to enable log restores when there are minimally logged operations, included
in the log backup are not just the log records, but also the images of any extent (set of
eight pages) affected by the minimally logged operation. This doesn't mean images
of them as they were after the minimally logged operation, but the pages as they are at
the time of the log backup. SQL Server maintains a bitmap allocation page, called the
ML map or bulk-logged change map, with a bit for every extent. Any extents affected
by the minimally logged operation have their bit set to 1. The log backup operation reads
this page and so knows exactly what extents to include in the backup. That log backup
will then clear the ML map.
102
Figure 6.7:
The log backup at 10:30 backs up the log records covering the period 10:0010:30. Since
there was a minimally logged operation within that log interval, it copies the extents
affected by the minimally logged operations into the log backup. It copies them as they
appear at the time of the log backup, so they will reflect the effects of the BULK INSERT
and the UPDATE, plus any further modifications that may have taken place between the
UPDATE and the log backup.
This affects how we can restore the log. It also affects the size of the log backups and,
under certain circumstances, may affect tail log backups, but we'll get to that in more
detail in the next section.
Let's take a look an example of how minimally logged operations can affect a pointin-time restore. Figure 6.8 depicts an identical backup timeline for two databases. The
green bar represents a full database backup and the yellow bars represent a series of log
backups. The only difference between the two databases is that the first is operating in
FULL recovery model, and the second in BULK LOGGED.
103
Figure 6.8:
The time span of the fifth log backup is 10:00 to 10:30. At 10:10, a BULK INSERT
command (1) loaded a set of data. This bulk data load completed without a hitch but, in
an unrelated incident at 10:20, a user ran a "rogue" data modification (2) and crucial data
was lost. The project manager informs the DBA team and requests that they restore the
database to a point in time just before the transaction that resulted in data loss started,
at 10:20.
In the FULL recovery model database, this is not an issue. The bulk data load was fully
logged and we can restore the database to any point in time within that log file. We
simply restore the last full database backup, without recovery, and apply the log files
to the point in time right before the unfortunate data loss incident occurred, using the
RESTORE LOG command with the STOPAT parameter, to stop the restore operation
sometime before 10:20.
In the BULK_LOGGED database, we have a problem. We can restore to any point in time
within the first four log backups, but not to any point in time within the fifth log backup,
which contains the minimally logged operations. Remember that for this log backup we
only have the extents affected by the minimally logged operation, as they existed at the
time of the log backup. The restore of the fifth log backup is "all or nothing:" either we
apply none of the operations in this log file, stopping the restore at the end of the fourth
104
Unfortunately, if we apply the whole of the fifth log file backup, this would defeat the
purpose of the recovery, since the errant process committed its changes somewhere
inside of that log backup file, so we'd simply be removing the data we were trying to get
back! We have little choice but to restore up to the end of the fourth log, recover the
database, and report the loss of any data changes made after this time.
This inability to restore the database to point-in-time if there are minimally logged
operations within the log interval is something that must be considered when choosing
to run a database in BULK_LOGGED recovery, either for short or long periods. It is easy to
identify whether or not a specific log backup contains any minimally logged operations.
A RESTORE HEADERONLY returns a set of details about the backup in question, including
a column HasBulkLoggedData. In addition, the msdb backupset table has a column,
has_bulk_logged_data. If the column value is 1, then the log backup contains
minimally logged operations and can only be restored entirely or not at all. That said,
finding this out while planning or executing a restore may be an unpleasant surprise.
105
Listing 6.6:
Next, we check current log space usage, and then back up the log.
106
Listing 6.7:
Given that the log size is only about 24 MB, you may be surprised to see the size of the log
backup, about 1 GB in my test! For a database in FULL recovery, you'll find that the log
size and log backup size are both about 1 GB.
Listing 6.8:
With the SQL Server service shut down, go to the data folder and delete the mdf file for
the BulkLoggedRecovery database, and then restart SQL Server. It's not a complete
simulation of a drive failure, but it's close enough for the purposes of this demo.
108
Listing 6.9:
In Listing 6.10, we attempt to take a tail log backup (note that NO_TRUNCATE implies
COPY_ONLY and CONTINUE_AFTER_ERROR):
BACKUP LOG BulkLoggedRecovery
TO DISK = 'D:\SQLBackups\BulkLoggedRecovery_tail.trn'
WITH NO_TRUNCATE
Processed 7 pages for database 'BulkLoggedRecovery', file 'BulkLoggedRecovery_log' on file 1.
BACKUP WITH CONTINUE_AFTER_ERROR successfully generated a backup of the damaged database.
Refer to the SQL Server error log for information about the errors that were encountered.
BACKUP LOG successfully processed 7 pages in 0.007 seconds (7.463 MB/sec).
Listing 6.10: Attempting a tail log backup using BACKUP LOGWITH NO_TRUNCATE.
Well, it said that it succeeded (and despite the warning, there were no errors in the
error log). Now, let's try to restore this database, as shown in Listing 6.11.
109
Listing 6.11:
Well, that didn't work. Let's follow the advice of the error message and see if we can
restore the log using CONTINUE_AFTER_ERROR.
110
That worked, so let's investigate the state of the restored database. Rerun Listing 6.9 and
you'll see it's reported as ONLINE, and SomeTable, the target of SELECTINTO exists,
so let's see if any of the data made it into the table (remember, the page allocations were
logged, the contents of the pages were not).
111
*
SomeTable
Listing 6.13:
Note that this was the error message from SQL Server 2008 Enterprise Edition. It's
possible you'll see different errors on other versions. In any event, this doesn't look good;
let's see what DBCC CHECKDB says about the state of the database.
DBCC CHECKDB ('BulkLoggedRecovery') WITH NO_INFOMSGS, ALL_ERRORMSGS
Msg 8921, Level 16, State 1, Line 1
Check terminated. A failure was detected while collecting facts. Possibly tempdb out of space
or a system table is inconsistent. Check previous errors.
This doesn't look good at all, unfortunately (and TempDB was not out of space). About
the only sensible option here is to restore again and leave off the tail log backup. It means
that any transactions that committed between the last normal log backup and the point
of failure are lost.
112
113
Summary
The BULK_LOGGED recovery model offers a way to perform data loads and some
database maintenance operations such as index rebuilds without the transaction log
overhead that they would normally have in FULL recovery model, but while still keeping
the log chain intact. The downsides of this include greater potential data loss if a disaster
occurs during, or over the same time span as, the bulk operation. In other words, you
won't be able to use the STOPAT option when restoring a log file that contains minimally
logged operations. It is still possible to restore the entire transaction log backup to roll the
115
Acknowledgements
Many thanks to Shawn McGehee, author of SQL Server Backup and Restore
(http://www.simple-talk.com/books/sql-books/sql-backup-and-restore/) for
contributing additional material to the Database restores section of this chapter.
116
117
Conversely, if the log file has only a few VLFs that are very large, we risk tying up large
portions of the log for long periods. Each VLF will hold a very large number of log
records, and SQL Server cannot truncate a VLF until it contains no part of the active
log. In cases where truncation is delayed for some reason (see the Lack of log space reuse
section), this can lead to rapid log growth. For example, let's assume that each VLF is 1 GB
in size and that the log is full. You perform a log backup, but all VLFs contain some part
118
119
120
Index rebuilds
Rebuilding an index offline, using ALTER INDEX REBUILD (or the deprecated
DBCC DBREINDEX in SQL Server 2000) drops the target index and rebuilds it from
scratch (online index rebuilds do not drop the existing index until the end of the
rebuild operation).
Logging and online index rebuilds
Online index rebuild is a fully logged operation on SQL Server 2008 and later, whereas it is minimally
logged in SQL Server 2005. Therefore, performing such operations in later SQL Server versions will
require substantially more transaction log space. See: http://support.microsoft.com/kb/2407439,
as well as Kalen Delaney's blog, investigating logging during online and offline index rebuilds, for both
FULL and BULK_LOGGED recovery model databases: http://sqlblog.com/blogs/kalen_delaney/
archive/2011/03/08/what-gets-logged-for-index-rebuilds.aspx.
In the FULL recovery model, index rebuilds can be a very resource-intensive operation,
requiring a lot of space in the transaction log. In the SIMPLE or BULK_LOGGED recovery
model, rebuilding an index is a minimally logged operation, meaning that only the allocations are logged, and the actual pages are not changed, therefore reducing the amount of
log space required by the operation.
If you switch to the SIMPLE model to perform an index rebuild, the LSN chain
will be immediately broken. You'll only be able to recover your database to a point
in time contained in the previous transaction log backup. To restart the chain, you'll
need to switch back to the FULL model and immediately take a full or differential
database backup.
If you switch to the BULK_LOGGED model (see Chapter 6), the LSN chain is always
maintained but there are still implications for your ability to perform point-in-time
restores, since a log backup that contains a minimally logged operation can't be used to
recover to a point in time. If the ability to perform a point-in-time recovery is paramount
121
Index reorganization
In contrast to rebuilding an index, reorganizing (defragmenting) an index, using
ALTER INDEX REORGANIZE or, in SQL Server 2000, DBCC INDEXDEFRAG (since deprecated) is always a fully logged operation, regardless of the recovery model, and so the
actual page changes are always logged. However, index reorganizations generally require
less log space than an index rebuild, although this is a function of the degree of fragmentation in the index; a heavily fragmented index will require more log space to reorganize
than a minimally fragmented one.
Furthermore, the ALTER INDEX REORGANIZE operation is accomplished using multiple
shorter transactions. Therefore, when performed in conjunction with frequent log
backups (or when working in SIMPLE model), log space can be made available for reuse
during the operation, so minimizing the size requirements for the transaction log during
the operation.
122
123
The best approach, however, is to schedule regular maintenance on only those indexes
where you can prove a positive, sustained impact on query performance. Logical fragmentation (index pages in the wrong order) thwarts SQL Server's read-ahead mechanism
(http://msdn.microsoft.com/en-us/library/ms191475(v=sql.105).aspx) and makes it
less I/O-efficient at reading contiguous pages on disk. However, this only really affects
large range scans from disk. Even for very fragmented indexes, if you are not scanning
the table, rebuilding or reorganizing indexes might not help performance. Reduced page
density (many gaps causes by page splits and deletes) will cause pages to take up more
space on disk and in memory, and require the I/O bandwidth to transfer the data. Again,
though, this form of fragmentation won't really affect infrequently modified indexes and
so rebuilding them won't help.
Before scheduling index maintenance, ask yourself what performance metrics benefited
from the maintenance? Did it reduce I/O significantly? How much did it improve the
performance of your most expensive queries? Was the positive impact a sustained one?
If the answers to these are "no" or "don't know," then it's probable that regular index
maintenance is not the right long-term answer. Finally, it's also worth noting that
124
125
126
Listing 7.1:
Figure 7.1 shows some sample output (we split the result set in two, for readability).
Figure 7.1:
Incidentally, if we rerun this example but with ALTER INDEXREORGANIZE, then the
value in the Log Bytes Used column reduces from about 159 MB to around 0.5 MB.
127
Listing 7.2:
The value of the log_reuse_wait_desc column will show the current reason why log
space cannot be reused. If you've run the previous example (Listing 7.1), then it's likely
that the FullRecovery database will display the value LOG_BACKUP in this column
(more on this in the next section).
It may be that more than one thing is preventing log reuse. The sys.databases view
will only show one of the reasons. It is therefore possible to resolve one problem, query
sys.databases again and see a different log_reuse_wait reason.
The possible values for log_reuse_wait_desc are listed in Books Online
(http://msdn.microsoft.com/en-us/library/ms178534.aspx), but we'll cover the
most common causes here, and explain how to safely ensure that space can start to
get reused.
128
129
FROM
WHERE
Listing 7.3:
name ,
recovery_model_desc ,
log_reuse_wait_desc
sys.databases
name = 'FullRecovery'
If a lack of log backups is the cause of log growth problems, the first thing to do is to
verify that the database in question really does need to be operating in FULL recovery.
This will be true if it must be possible to restore the database to an arbitrary point in
time, or to point of failure in the case of a disaster, or if FULL recovery model is required
for another reason (such as database mirroring). If the Recovery Point Objective (RPO) in
the SLA stipulates a maximum of 15 minutes potential data loss, then it's highly unlikely
you can fulfill this with only full and differential database backups and, again, log backups
will be necessary.
However, if it turns out there are no log backups simply because they are not required,
then the database should not be operating in FULL recovery; we can switch to using the
SIMPLE recovery model, where the inactive portion of the transaction log is automatically marked as reusable, at checkpoint.
If the database does need to operate in the FULL recovery model, then start taking log
backups, or investigate the need to take more frequent log backups. The frequency of the
transaction log backups depends on a number of factors such as the frequency of data
changes, and on SLAs for acceptable data loss in the event of a crash. In addition, you
130
Active transactions
If the value returned for log_reuse_wait_desc is ACTIVE_TRANSACTION, then you
are suffering from the second most common cause of a full or large transaction log in
SQL Server: long-running or uncommitted transactions. Rerun the transaction from
Listing 7.1, but without committing it, and then rerun Listing 7.2 and you should see this
value listed (don't forget to go back and commit the transaction).
As discussed in the Log Truncation and Space Reuse section of Chapter 2, a VLF inside
the transaction log can only be truncated when it contains no part of the active log. If
the database is using the FULL or BULK_LOGGED recovery models, only a log backup
operation can perform this truncation. Long-running transactions in a database delay
truncation of the VLFs that contain the log records generated after the start of the transaction, including the log records generated by changes to data in the database by other
concurrent sessions, even when those changes have been committed. Additionally, the
amount of space required by a long-running transaction will be increased by space reservations for "compensation log records," which are the log records that would be generated
if the transaction were rolled back in the system. This reservation is required to ensure
that the transaction can be reverted successfully without running out of log space during
the rollback.
Another common cause of the Active Transaction value for log_reuse_wait_desc
is the presence of "orphaned" explicit transactions that somehow never got committed.
Applications that allow for user input inside a transaction are especially prone to this kind
of problem.
131
Long-running transactions
One of the most common operations that results in a long-running transaction, which
also generates large numbers of log records in a database, is archiving or purging of data
from a database. Data retention tends to be an afterthought in database design, usually
considered after the database has been active for a period and is approaching the capacity
limits of the available storage on a server.
Usually, when the need to archive data arises, the first reaction is to remove the
unneeded data from the database using a single DELETE statement, as shown in
Listing 7.4. To produce some simple test data, this script uses a simplified version of
Jeff Moden's random data generator (see Chapter 1, Listing 1.3), modified slightly to
produce dates into 2012.
USE FullRecovery ;
GO
IF OBJECT_ID('dbo.LogTest', 'U') IS NOT NULL
DROP TABLE dbo.LogTest ;
SELECT TOP 500000
SomeDate = CAST(RAND(CHECKSUM(NEWID())) * 3653.0 + 37534.0 AS DATETIME)
INTO
dbo.LogTest
FROM
sys.all_columns ac1
CROSS JOIN sys.all_columns ac2 ;
-- delete all but the last 60 days of data
DELETE dbo.LogTest
WHERE
SomeDate < GETDATE() - 60
Listing 7.4:
Depending on the number of rows that exist in the date range to be deleted, this can
become a long-running transaction that will cause transaction log growth issues, even
when the database is using the SIMPLE recovery model. The presence of cascading
FOREIGN KEY constraints or auditing triggers exacerbates the problem. If other tables
reference the target table, via FOREIGN KEY constraints designed to CASCADE ON
DELETE, then SQL Server will also log details of the rows deleted through the cascading
constraint. If the table has a DELETE trigger on it, for auditing data changes, SQL Server
will also log the operations performed during the trigger's execution.
132
Listing 7.5:
Using this model for purging data, the duration of each DELETE transaction is only the
time required to delete a single day's data from the table, plus the time required for any
triggers or cascading constraints to perform their operations. If the database uses the
SIMPLE recovery model, the next checkpoint will truncate the log records generated by
each daily purge. If the database uses the FULL or BULK_LOGGED recovery model, the
next log backup will truncate the log records generated by each daily purge, as long as
no part of the active log exists inside the VLFs containing log records relating to the
data purge.
133
Listing 7.6:
Using the TOP operator inside the DELETE statement for data purges.
These methods work in any edition of SQL Server 2000, 2005, and 2008 to minimize
transaction duration during data purge operations.
However, if the database is SQL Server 2005 or 2008 Enterprise Edition, and the data
purging process runs regularly, then an even better way to purge the data is to partition
the table using a sliding window partition on the column used to delete the data. This will
134
Uncommitted transactions
By default, SQL Server wraps any data modification statement in an implicit transaction
to ensure that, in the event of a failure, SQL Server can roll back the changes already
made at the point of failure, returning the data to a consistent state. If the changes
succeed, the implicit transaction is committed to the database. In contrast to implicit
transactions, which occur automatically, we create explicit transactions in code to wrap
multiple changes into a single transaction, ensuring that all the changes can be undone by
issuing a ROLLBACK command, or persisted by issuing a COMMIT for the transaction.
When used properly, explicit transactions can ensure that data modifications that
span multiple tables complete successfully as a unit, or not at all. When used incorrectly,
however, orphaned transactions remain active in the database, preventing truncation
of the transaction log, and so resulting in the transaction log growing or filling up.
135
136
Listing 7.7:
DBCC OPENTRAN reports only the oldest active transaction, and the primary indicator
of whether or not the active transaction is problematic is the Start Time. Generally,
uncommitted transactions that become problematic with regard to transaction log
growth have been open for a long period of time.
The other important piece of information is the SPID (server process ID; in the
DMVs this is replaced by session_id), which identifies the session that created
the open transaction. We can use the SPID to determine whether the transaction
is actually an orphaned transaction or just a long-running one, by querying the
sysprocesses view (in SQL Server 2000) or the sys.dm_exec_sessions and
sys.dm_exec_connections DMVs in SQL Server 2005 and later, as shown in
Listing 7.8.
137
WHERE
sys.dm_exec_sessions s
JOIN sys.dm_exec_connections c ON s.session_id = c.session_id
CROSS APPLY sys.dm_exec_sql_text(c.most_recent_sql_handle) t
s.session_id = 56
138
Listing 7.8:
If the session is in a runnable, running, or suspended status, then it is likely that the
source of the problem is a long-running, rather than orphaned, transaction. However,
only further investigation will confirm. It is possible that an earlier transaction failed
and the connection was reset, for use under connection pooling, and that the currently
executing statement is not associated with the open transaction.
In SQL Server 2005 and later, we can use the sys.dm_tran_session_transactions
and sys.dm_tran_database_transactions DMVs to gather information specific
to the open transaction, including the transaction start time, number of log records used
by the open transaction, as well as the bytes of log space used, as we saw previously in
Listing 7.1. Listing 7.9 shows a simplified version, with sample output.
SELECT
st.session_id ,
st.is_user_transaction ,
dt.database_transaction_begin_time ,
dt.database_transaction_log_record_count ,
dt.database_transaction_log_bytes_used
FROM
sys.dm_tran_session_transactions st
JOIN sys.dm_tran_database_transactions dt
ON st.transaction_id = dt.transaction_id
AND dt.database_id = DB_ID('FullRecovery')
WHERE st.session_id = 56
Listing 7.9:
139
Replication
During transactional replication, it is the job of the log reader agent to read the transaction log, looking for log records that are associated with changes that need to be replicated to subscribers (i.e. are "pending replication"). Once the changes are replicated, it
marks the log entry as "replicated." Slow or delayed log reader activity can lead to records
being left as "pending replication" for long periods, during which time they will remain
part of the active log, and so the parent VLF cannot be truncated. A similar problem exists
for log records required by the Change Data Capture (CDC) feature.
In either case, the log_reuse_wait_desc column of sys.databases will show
REPLICATION as the root cause of the problem. The problem will also reveal itself in
the form of bottlenecks in the throughput performance of the transaction log disk array,
specifically, delayed read operations under concurrent write loads. Writes to the log
file occur sequentially, but read operations associated with the log reader agent and log
backups read the file sequentially as well. Having sequential reads and writes occurring
140
141
ACTIVE_BACKUP_OR_RESTORE
When the log_reuse_wait_desc column shows ACTIVE_BACKUP_OR_RESTORE as
the current wait description, a long-running full or differential backup of the database
is the most likely cause of the log reuse problems. During a full or differential backup of
the database, the backup process delays log truncation so that the active portion of the
transaction log can be included as a part of the full backup. This allows changes made to
database pages during the backup operation to be undone when the backup is restored
WITH RECOVERY, to bring the database to a consistent state. If such waits are causing
persistent problems, you'll need to investigate ways to optimize the backup process, such
as by improving the performance of the backups (via backup compression) or improving
the performance of the underlying disk I/O system.
142
DATABASE_MIRRORING
When the log_reuse_wait_desc column shows DATABASE_MIRRORING, as the
current wait description, asynchronous database mirroring operations may be the cause
of the log reuse issues.
In synchronous mirroring, transactions on the principal are only committed once their
related log records have been transferred to the mirror database. For asynchronous
database mirroring, the log records are transferred later and the principal's log can't be
truncated until they are. When mirroring problems arise, a large number of log records
on the principal can remain part of the active log, preventing log space reuse, until copied
over to the mirror.
For synchronous database mirroring, we may see a value of DATABASE_MIRRORING if
the mirror is not contactable, due to a broken or very slow connection, or suspension of
the mirroring session. For asynchronous database mirroring, we may well see this value
during normal operation, as well as during connection problems.
In such cases, I would first check the status of the mirroring session for the affected
database(s). If they are not synchronizing correctly, then you will need to troubleshoot
the cause of the failed connection between the principal and the mirror. One of the most
common problems with database mirroring, when certificates are used to secure the
endpoints, is the expiration of the certificates, requiring that they be re-created. A full
discussion of troubleshooting mirroring connectivity problems is outside the scope of
this book but, unless the databases are properly synchronizing so that the log records are
being sent to the mirror, the active portion of the transaction log on the principal will
continue to grow and not be able to be truncated without breaking the mirroring setup.
If the transaction rate on the principal greatly exceeds the rate at which log records
can be transferred to the mirror, then the log on the principal can grow rapidly. If the
mirror server is being used for reporting, by creating snapshots, verify that the disk I/O
configuration for the mirror is not saturated, by using the disk latency Perfmon counters
mentioned earlier. If this is where the problem is, eliminating use of the mirror server
143
144
In the type column, a D represents a database backup, L represents a log backup and I
represents a differential backup. If there are no log backups, or they are very infrequent,
then your best course of action is to take a log backup (assuming the database is operating
in FULL or BULK_LOGGED recovery model). Hopefully, this will free up substantial space
within the log and you can then implement an appropriate log backup scheme, and log
file growth management strategy.
If, for some reason, it is not possible to perform a log backup due to a lack of disk space,
or the time it would take to perform a log backup exceeds the acceptable time to resolve
the problem, then, depending on the disaster recovery policy for the database in question,
it might be acceptable to force a truncation of the log by temporarily switching the
database to the SIMPLE recovery model in order that inactive VLFs in the log can be
truncated on CHECKPOINT. You can then switch the recovery model back to FULL and
perform a new full database backup (or a differential backup, assuming a full backup
was taken at some previous time) to restart the log chain for point-in-time recovery.
Of course, you'll still need to investigate the problem fully, in order to make sure that
the space isn't simply devoured again. Bear in mind also that, as discussed previously,
if the problem preventing space reuse is anything other than Log Backup, then this
technique won't work, since those records will simply remain part of the active log,
preventing truncation.
145
146
147
149
150
Summary
The transaction log is critical to the operation of a SQL Server database, and the ability
to minimize data loss in the event of a disaster. In a situation where the log is growing
explosively, or is even full, the DBA needs to act very quickly to diagnose and fix the
problem, but it's also important to act calmly, and avoid unthinking reactions such as
forcing log truncation, then scheduling regular log shrinks, which will cause more harm
than good.
Acknowledgements
Many thanks to Jonathan Kehayias, lead author of Troubleshooting SQL Server
(http://www.simple-talk.com/books/sql-books/troubleshooting-sql-server-aguide-for-the-accidental-dba/), available as a free eBook, for contributing additional
material to this chapter.
151
Physical Architecture
The correct physical hardware and architecture will help ensure that you get the best
possible log throughput, and there are a few "golden rules." Others have covered this
before, notably Kimberly Tripp in her much referenced blog post, 8 Steps to better
Transaction Log throughput, (http://www.sqlskills.com/blogs/kimberly/post/8-Stepsto-better-Transaction-Log-throughput.aspx), so we won't go into detail again here.
Note that in designing the underlying physical architecture for the log file, our primary
goal is to optimize log write throughput. SQL Server writes to the log in response to
every transaction that adds, removes or modifies data, as well as in response to database
maintenance operations such as index rebuilds or reorganization, statistics updates,
and more.
152
Note that we've based the SIZE and FILEGROWTH settings for the data and log files on
those for AdventureWorks2008. The code for this example (Listings 8.18.3) is in the
YouOnlyNeed1Log.sql file, in the code download.
153
Listing 8.1:
154
GO
*
sys.objects
object_id = OBJECT_ID(N'dbo.Persons')
AND type = N'U' )
DROP TABLE dbo.Persons;
Listing 8.2:
Now, we'll add 15K rows to the table and run DBCC LOGINFO. Note that in our tests, we
filled the Persons table with data from the Person.Contact table in the AdventureWorks 2005 database. However, the code download file contains alternative code, which
will work with AdventureWorks2008 and AdventureWorks2012.
155
GO
INTO dbo.Persons
( FName ,
LName ,
Email
)
SELECT TOP 15000
LEFT(aw1.FirstName, 20) ,
LEFT(aw1.LastName, 30) ,
aw1.EmailAddress
FROM
AdventureWorks2005.Person.Contact aw1
CROSS JOIN AdventureWorks2005.Person.Contact aw2;
USE Persons
GO
DBCC LOGINFO;
Listing 8.3:
SQL Server is sequentially filling the VLFs in the primary log file (FileID 2), followed
by the secondary one (FileID 3). It has also auto-grown the primary log file (please
refer back to Chapter 2 for a more detailed example of how SQL Server auto-grows the
transaction log).
156
Figure 8.1:
In this situation, any operation that reads the log will start by reading a block of four VLFs
in the primary log (FSeqNo 36-39), followed by four blocks in the secondary log (FSeqNo
40-43), followed four blocks in the primary log, and so on. This is why multiple log files
can reduce I/O efficiency, as we'll discuss further in the next section.
The only reason to add an additional log file is in exceptional circumstances where, for
example, the disk housing the log file is full (see Chapter 6) and we need, temporarily, to
add an additional log file as the quickest means to get the SQL Server out of read-only
mode. However, as soon as it's no longer required the additional file should be removed,
as discussed later, in the section, What to Do If Things Go Wrong.
157
RAID 1+0 is a nested RAID level known as a "striped pair of mirrors." It provides redundancy by first mirroring each disk, using RAID 1, and then striping those mirrored disks
with RAID 0, to improve performance. There is a significant monetary cost increase
associated with this configuration since only half of the disk space is available for use.
However, this configuration offers the best configuration for redundancy since, potentially, it allows for multiple disk failures while still leaving the system operational, and
without degrading system performance.
A common and somewhat cheaper alternative is RAID 5, "striping with parity," which
stripes data across multiple disks, as per RAID 0, and stores parity data in order to provide
protection from single disk failure. RAID 5 requires fewer disks for the same storage
capacity, compared to RAID 1+0, and offers excellent read performance. However, the
need to maintain parity data incurs a performance penalty for writes. While this is less of
a problem for modern storage arrays, it's the reason why many DBAs don't recommend
it for the transaction log files, which primarily perform sequential writes and require the
lowest possible write latency.
If, as per our previous suggestion, you are able to isolate a single database's log file on a
dedicated array, at least for those databases with the heaviest I/O workload, then it may
be possible to use the more expensive RAID 1+0 for those arrays, and RAID 5, or RAID 1,
for lower workload databases.
To give an idea of the I/O performance offers by various RAID levels, following are three
possible RAID configurations for a 400 GB database that performs a balanced mix of
160
162
163
Note, finally, that Linchi Shea has demonstrated a big effect on the performance of data
modifications when comparing a database with 20,000 VLFs to one with 16 VLFs. See:
http://sqlblog.com/blogs/linchi_shea/archive/2009/02/09/performance-impact-alarge-number-of-virtual-log-files-part-i.aspx.
164
165
Listing 8.4:
Now, we're going to grow the log in lots of very small increments, as shown in Listing 8.5,
in order to produce a very fragmented log file.
DECLARE @LogGrowth INT = 0;
DECLARE @sSQL NVARCHAR(4000)
WHILE @LogGrowth < 4096
BEGIN
SET @sSQL = 'ALTER DATABASE PersonsLots
MODIFY FILE (NAME = PersonsLots_log,
SIZE = ' + CAST(4096+2048*@LogGrowth AS VARCHAR(10))
+ 'KB );'
EXEC(@sSQL);
SET @LogGrowth = @LogGrowth + 1;
END
USE PersonsLots
GO
DBCC LOGINFO
--16388 VLFs
Listing 8.5:
Listing 8.6:
Finally, we're ready to take a log backup and see how long it takes. We've included the
backup statistics in a comment after the backup code.
167
Listing 8.7:
For comparison, we'll repeat the same test, but this time we'll carefully size our database
log to have a reasonable number of reasonably sized VLFs. In Listing 8.8, we re-create the
Persons database, with an initial log size of 2 GB (= 16 VLFs, each 128 MB in size). We
then manually grow the log, just three steps to 8 GB in size, comprising 64 VLFs (each
roughly 128 MB in size).
USE master
GO
IF DB_ID('Persons') IS NOT NULL
DROP DATABASE Persons;
GO
CREATE DATABASE [Persons] ON PRIMARY
(
NAME = N'Persons'
, FILENAME = N'C:\SQLData\Persons.mdf'
, SIZE = 2097152KB
, FILEGROWTH = 1048576KB
)
LOG ON
(
NAME = N'Persons_log'
, FILENAME = N'D:\SQLData\Persons_log.ldf'
, SIZE = 2097152KB
, FILEGROWTH = 2097152KB
)
GO
168
Listing 8.8:
Now rerun Listings 8.2, 8.3 (with 1 million rows) and 8.6 exactly as for the previous test.
You should find that, in the absence of any log growth events, Listing 8.6 runs a lot
quicker (in half the time, in our tests). Finally, rerun a log backup.
169
Listing 8.9:
The effect on log backup time is relatively small, but reproducible, for this sized log, about
a 1520% increase in backup time for a log with 14,292 VLFs compared to one with 64, and
of course, this is a relatively small database (albeit with a very heavily fragmented log).
Lots of redo
In the first example, we reuse the PersonsLots database. Drop and re-create it,
set the recovery model to FULL, take a full backup and then insert 1 million rows, as per
previous listings. You can find the full code for the examples in this section in the files,
PersonsLots_RedoTest.sql and Persons_RedoTest.sql, as part of the code download for
this book.
Now, before we update these rows, we're going to disable automatic checkpoints.
170
When we commit the subsequent updates, we'll immediately shut down the database so
that all of the updates are hardened to the log but not to the data file. Therefore, during
crash recovery, SQL Server will need to read all of the related log records and redo all of
the operations.
USE PersonsLots
Go
/*Disable Automatic checkpoints*/
DBCC TRACEON( 3505 )
/*Turn the flag off once the test is complete!*/
--DBCC TRACEOFF (3505)
/* this took 5 mins*/
BEGIN TRANSACTION
DECLARE @cnt INT;
SET @cnt = 1;
WHILE @cnt < 6
BEGIN;
SET @cnt = @cnt + 1;
UPDATE dbo.Persons
SET
Email = LEFT(Email + Email, 7000)
END;
DBCC SQLPERF(LOGSPACE) ;
--11170 MB, 100% used
USE PersonsLots
GO
DBCC LOGINFO;
-- 22340 VLFs
Listing 8.10: PersonsLots disable automatic checkpoints and run update in an explicit transaction.
171
Listing 8.11:
After restarting the SQL Server service, try to access PersonsLots, and you'll see a
message stating that it is in recovery.
USE PersonsLots
Go
/*Msg 922, Level 14, State 2, Line 1
Database 'PersonsLots' is being recovered. Waiting until recovery is finished.*/
SQL Server has to open the log and read each VLF before it starts recovering the database.
So the impact of many VLFs is that it could extend the time between SQL Server
restarting the database, and the actual start of the recovery process.
Therefore, once the database is accessible, we can interrogate the error log for time
between these two events, as well as the total recovery time.
EXEC sys.xp_readerrorlog 0, 1, 'PersonsLots'
/*
2012-10-03 11:28:14.240
2012-10-03 11:28:26.710
2012-10-03 11:28:33.000
2012-10-03 11:28:33.010
*/
Listing 8.13:
172
Listing 8.14: Persons: disable automatic checkpoints, run and commit explicit transaction,
shut down SQL Server.
173
*/
Listing 8.15:
Note that, this time, there is less than 0.5 seconds between SQL Server starting up the
database, and recovery beginning. The recovery process took just over 9 seconds.
You will see that in these tests we haven't created a situation in which all other things
are equal, aside from the log fragmentation. For a start, the recovery process for the
fragmented log database rolls forward 140 transactions, and in the second test, only rolls
forward 1 transaction.
However, it is clear from the tests that a fragmented log can significantly delay the onset
of the actual database recovery process, while SQL Server reads in all of the VLFs.
174
Lots of undo
As an alternative example, we could execute our long update transaction, run a checkpoint and then shut down SQL Server with the transaction uncommitted, and see how
long SQL Server takes to recover the database, first when the log is fragmented and then
when it is not. In each case, this will force SQL Server to perform a lot of undo in order to
perform recovery, and we'll see the effect, if any, of an internally fragmented log.
We won't show the full code for these tests here as we've presented it all previously, but
it's available in the code download files for this book (see PersonsLots_UndoTest.sql and
Persons_UndoTest.sql).
/* (1) Recreate PersonsLots, with a fragmented log (Listing 8.4 and 8.5)
(2) Create Persons table, Insert 1 million rows (Listings 8.2 and 8.3)
*/
BEGIN TRANSACTION
/* run update from Listing 8.6*/
/*Force a checkpoint*/
CHECKPOINT;
/*In an second session, immediately Shutdown without commiting*/
SHUTDOWN WITH NOWAIT
Repeat the same test for the Persons database (as defined in Persons_UndoTest.sql).
Listing 8.17 shows the results from the error logs for each database.
175
*/
Listing 8.17: Error log database startup and recovery information for PersonsLots and Persons.
For PersonsLots the delay between database startup and the start of recovery is over 11
seconds, whereas for Persons it is about 0.5 seconds.
The overall recovery times are much longer in these undo examples, compared to the
previous redo examples. For PersonsLots, the total recovery time was 326 seconds,
compared to 279 seconds for Persons, with the non-fragmented log.
176
177
Note that the log space used has dropped from 87% to 34%, even though this is a FULL
recovery model database, and there was no log backup after the transaction committed.
SQL Server has not truncated the log in this case, merely released the log reservation
space, after the transaction committed.
Having set the initial log size, based on all of these requirements, and set a sensible autogrowth safety net, it's wise to monitor log usage, and set alerts for log auto-growth events,
since, if we've done our job properly, they should be rare. Chapter 9 will discuss log
monitoring in more detail.
179
180
Some time later, we've fixed the problem that resulted in delayed log truncation; there is
now plenty of reusable space in the primary log file, and we no longer need this secondary
log file, but it still exists. Let's restore the PersonsLots database.
USE master
GO
RESTORE DATABASE PersonsLots
FROM DISK ='D:\SQLBackups\PersonsLots_full.bak'
WITH NORECOVERY;
RESTORE DATABASE PersonsLots
FROM DISK='D:\SQLBackups\PersonsLots.trn'
WITH Recovery;
/*<output truncated>
Processed 18094 pages for database 'PersonsLots', file 'PersonsLots_log' on file 1.
Processed 0 pages for database 'PersonsLots', file 'PersonsLots_Log2' on file 1.
RESTORE LOG successfully processed 18094 pages in 62.141 seconds (2.274 MB/sec).*/
The restore took over 60 seconds. If we repeat the exact same steps, but without adding
the secondary log file, the comparative restore, in our tests, took about 8 seconds.
181
This is one problem solved, but we may still have a bloated and fragmented primary log.
While we should never shrink the log as part of our standard maintenance operations, as
discussed in Chapter 7, it is permissible in situations such as this, in the knowledge that
we have investigated and resolved the cause of the excessive log growth, and so shrinking
the log should be a "one-off" event.
The recommended approach is to use DBCC SHRINKFILE (see http://msdn.microsoft.
com/en-us/library/ms189493.aspx) to reclaim the space. If we don't specify a target
size, or if we specify 0 (zero) as the target size, we can shrink the log back to its original
size (in this case, 2 MB) and minimize fragmentation of the log file. If the initial size of
the log was large, we wish to shrink the log smaller than this, in which case we specify a
target_size, such as "1".
182
In the output from this command, we see the current database size (24128*8-KB
pages) and minimum possible size after shrinking (256*8-KB pages). This is actually an
indication that the shrink did not work fully. SQL Server shrank the log to the point
where the last VLF in the file contained part of the active log, and then stopped. Check
the messages tab for confirmation.
/*Cannot shrink log file 2 (PersonsLots_log) because the logical log file located
at the end of the file is in use.
(1 row(s) affected)
DBCC execution completed. If DBCC printed error messages, contact your system
administrator.*/
Perform a log backup and try again.
USE master
GO
BACKUP DATABASE PersonsLots
TO DISK ='D:\SQLBackups\PersonsLots_full.bak'
WITH INIT;
GO
BACKUP LOG PersonsLots
TO DISK = 'D:\SQLBackups\PersonsLots.trn'
WITH init
183
Listing 8.23: Shrinking the primary log file after log backup.
Having done this, we can now manually grow the log to the required size, as
demonstrated previously in Listing 8.8.
Summary
We started with a brief overview of the physical architecture factors that can affect log
throughput, such as the need to separate log file I/O onto a dedicated array, and choose
the optimal RAID level for this array.
This chapter then emphasized the need to manage transaction log growth explicitly,
rather than let SQL Server auto-growth events "manage" it for us. If we undersize the
log initially, and then let SQL Server auto-grow it in small increments, we'll end up with
a fragmented log. Examples in the chapter demonstrated how this might affect the
performance of any SQL Server operations that need to read the log.
Finally, we discussed the factors that determine the correct log size, and correct autogrowth increment for a given database, and we offered advice on how to recover
from a situation where a database suffers from multiple log files and an oversized and
fragmented primary log.
184
Further Reading
Manage the Size of the Transaction Log File
http://msdn.microsoft.com/en-us/library/ms365418.aspx
The Trouble with Transaction Logs
http://thomaslarock.com/2012/08/the-trouble-with-transaction-logs/
How to shrink the SQL Server log
http://rusanu.com/2012/07/27/how-to-shrink-the-sql-server-log/
Multiple log files and why they're bad
http://www.sqlskills.com/BLOGS/PAUL/post/Multiple-log-files-andwhy-theyre-bad.aspx
Acknowledgements
Many thanks to Jonathan Kehayias, who contributed the RAID section of this chapter.
185
186
Monitoring Tools
Several tools are available that will, among other things, allow us to monitor activity on
our database files, including the log. Here, we'll consider just two in brief; a built-in tool
(Perfmon) and a third-party tool (Red Gate SQL Monitor).
Windows Perfmon
A popular "built-in" tool for monitoring SQL Server activity is Windows Performance
Monitor (Perfmon). It is a Windows OS monitoring tool that provides a vast range of
counters for monitoring memory, disk I/O, CPU and network usage on a server (for
example, see http://technet.microsoft.com/en-us/library/cc768048.aspx), and
also exposes the counters maintained by SQL Server. Generally, the DBA or system
administrator would set up Perfmon to record statistics from various counters at
regular intervals, storing the data in a file and then importing it into Excel, or a
similar tool, for analysis.
Amongst its many counters, it offers a number to measure disk read and write
performance, as well as specific counters for log monitoring.
There are plenty of available tutorials on the use of Perfmon, and we won't repeat those in
any detail here. In addition to documentation on TechNet (http://technet.microsoft.
com/en-us/library/cc749249.aspx), we recommend the articles below for those new to
this tool.
SQL Server Perfmon Best Practices, by Brent Ozar
A comprehensive tutorial on use of the tools and recommended counters for
monitoring SQL Server, and how to analyze the saved data in Excel.
http://www.brentozar.com/archive/2006/12/
dba-101-using-perfmon-for-sql-performance-tuning/
187
188
189
Figure 9.1:
Setting up Perfmon.
Having clicked OK, we return to the previous screen and simply need to confirm the
collection interval (this will vary, depending on what you're monitoring; I just chose one
second for this simple example) and click Finish.
We can run this new data collection set on a schedule, of course, but here we'll simply
right-click on it and select Start (after a second or two, a green "Play" symbol will appear).
Back in SSMS, we create a reasonably heavy write load on our Persons database, as
shown in Listing 9.1.
USE Persons
GO
DECLARE @cnt INT;
SET @cnt = 1;
-- may take several minutes; reduce the number of loops, if required
WHILE @cnt < 6
BEGIN;
SET @cnt = @cnt + 1;
UPDATE dbo.Persons
SET
Email = LEFT(Email + Email, 7000)
END;
Listing 9.1:
190
Figure 9.2:
191
192
Figure 9.3:
A nice feature of SQL Monitor is that it makes it very easy, much easier than with
Perfmon, to compare the same type of activity across different periods. In the Time range
drop-down, we can change the time range, set a custom range, as well as compare values
for this metric from today (or "this week") to those captured yesterday (or "last week"),
and so on.
193
194
Listing 9.2:
Using sys.dm_io_virtual_file_stats
For each database file that SQL Server uses, data files as well as log (and full text) files, the
sys.dm_io_virtual_file_stats function gives cumulative physical I/O statistics,
indicating how frequently the file has been used by the database for reads and writes
195
INTO
FROM
DB_NAME(mf.database_id) AS databaseName ,
mf.physical_name ,
divfs.num_of_reads ,
divfs.num_of_bytes_read ,
divfs.io_stall_read_ms ,
divfs.num_of_writes ,
divfs.num_of_bytes_written ,
divfs.io_stall_write_ms ,
divfs.io_stall ,
size_on_disk_bytes ,
GETDATE() AS baselineDate
#baseline
sys.dm_io_virtual_file_stats(NULL, NULL) AS divfs
JOIN sys.master_files AS mf ON mf.database_id = divfs.database_id
AND mf.file_id = divfs.file_id
Listing 9.3:
Listing 9.4 shows a query against the #baseline table, returning some the read and
write statistics for the Persons database.
196
FROM
WHERE
physical_name ,
num_of_reads ,
num_of_bytes_read ,
io_stall_read_ms ,
num_of_writes ,
num_of_bytes_written ,
io_stall_write_ms
#baseline
databaseName = 'Persons'
Listing 9.4:
As noted, the data provided by this function is cumulative from when the server
last restarted; in other words, the values in the data columns increment continuously, from the point when the server was last restarted. As such, a single "snapshot"
of the data is rather meaningless, on its own. What we need to do is take a "baseline"
measurement, wait for a set period, perhaps while a specific set of operations completes,
then take a second measurement and subtract the two, so that you can see where I/O is
"accumulating."
Rerun Listing 9.1 to update our Persons table, and run Listing 9.5 to collect a second set
of data and subtract the baseline data values (we have omitted a few columns from the
output, due purely to space constraints).
197
currentLine
AS ( SELECT
FROM
DB_NAME(mf.database_id) AS databaseName ,
mf.physical_name ,
num_of_reads ,
num_of_bytes_read ,
io_stall_read_ms ,
num_of_writes ,
num_of_bytes_written ,
io_stall_write_ms ,
io_stall ,
size_on_disk_bytes ,
GETDATE() AS currentlineDate
sys.dm_io_virtual_file_stats(NULL, NULL) AS divfs
JOIN sys.master_files AS mf
ON mf.database_id = divfs.database_id
AND mf.file_id = divfs.file_id
)
SELECT currentLine.databaseName ,
LEFT(currentLine.physical_name, 1) AS drive ,
currentLine.physical_name ,
DATEDIFF(millisecond,baseLineDate,currentLineDate) AS elapsed_ms,
currentLine.io_stall - #baseline.io_stall AS io_stall_ms ,
currentLine.io_stall_read_ms - #baseline.io_stall_read_ms
AS io_stall_read_ms ,
currentLine.io_stall_write_ms - #baseline.io_stall_write_ms
AS io_stall_write_ms ,
currentLine.num_of_reads - #baseline.num_of_reads AS num_of_reads ,
currentLine.num_of_bytes_read - #baseline.num_of_bytes_read
AS num_of_bytes_read ,
currentLine.num_of_writes - #baseline.num_of_writes AS num_of_writes ,
currentLine.num_of_bytes_written - #baseline.num_of_bytes_written
AS num_of_bytes_written
FROM currentLine
INNER JOIN #baseline ON #baseLine.databaseName = currentLine.databaseName
AND #baseLine.physical_name = currentLine.physical_name
WHERE #baseline.databaseName = 'Persons' ;
Listing 9.5:
198
199
Using sys.dm_os_performance_counters
Generally, it's probably easiest to collect performance counters using Performance
Monitor (Perfmon), as discussed previously. However, if you prefer to save the statistics
in a database table and interrogate them using SQL, the sys.dm_os_performance_
counters DMV is a very useful tool. Just write the query to retrieve the data from the
DMV, add INSERT INTO CounterTrendingTableName and you have a rudimentary
monitoring system! In addition, it's not always possible to get direct access to Perfmon,
and accessing it from a different machine can be slow.
Unfortunately, using this DMV is far from plain sailing and a full description of its
intricacies is out of scope here. Instead, we refer you to the book, Performance Tuning
with SQL Server Dynamic Management Views (http://www.simple-talk.com/books/
sql-books/performance-tuning-with-sql-server-dynamic-management-views/),
which is available as a free eBook.
Listing 9.6, below, simply provides an example of how to report on log growth or shrink
events. The output indicates that the Persons database (initial log size 2 MB, autogrowth increment 2 MB) underwent a huge number of log growth events, due to inserting
the initial load of 1 million rows and then performing the update in Listing 9.2.
This is obviously a cause for concern and the DBA would need to investigate the log
sizing and growth settings, and possibly perform a one-off shrink followed by appropriate
resize, as described in Chapter 8.
200
object_name ,
counter_name ,
instance_name ,
cntr_value
FROM
sys.dm_os_performance_counters
WHERE
cntr_type = @PERF_COUNTER_LARGE_RAWCOUNT
AND object_name = @object_name
AND counter_name IN ( 'Log Growths', 'Log Shrinks' )
AND cntr_value > 0
ORDER BY object_name ,
counter_name ,
instance_name
Listing 9.6:
201
PowerShell
PowerShell, with Server Management Objects, forms a powerful automation tool for
managing and documenting SQL Server databases. It's a rather steep learning curve for
any DBA brought up on T-SQL and GUI management tools, but a few short scripts can
gather all manner of data across all your servers, and all SQL Server versions.
202
203
Listing 9.7:
Using PowerShell and SMO to investigate log file size, location and activity.
204
205
Listing 9.8:
206
Summary
This final chapter reviewed just a few of the tools available to the DBA for monitoring
log growth and performance, including Windows Perfmon, third-party monitoring tools,
Dynamic Management Views, and PowerShell or T-SQL scripting. We tried to offer a
reasonable feel for what each tool can do, so that you can explore further if it looks like a
good fit for your needs.
It is essential that every DBA maintains a healthy transaction log. Ideally, this will consist
of a single log file, on a dedicated RAID 1+0 array (or as close to this ideal as you can get)
in order to support maximum write performance and throughput. We must capture
"baseline" statistics that characterize log write performance under typical workload, and
then monitor this data over time, checking for abnormal activity, or sudden deterioration
in performance.
Likewise, we should also size in accordance with the current and predicted data load,
rather than let SQL Server "manage" log growth via auto-growth events. We should
enable SQL Server's auto-growth facility but only as a safeguard, and the DBA should
receive an alert when log growth occurs, and investigate. By carefully monitoring log
growth, we can avoid situations such as a full transaction log, or a highly fragmented log,
which might impede performance of operations that read the log, such as log backups and
the crash recovery process.
Further Reading
Storage Testing and Monitoring (video)
http://technet.microsoft.com/en-us/sqlserver/gg508910.aspx
Baselining and Benchmarking (video)
http://technet.microsoft.com/en-us/sqlserver/gg429821.aspx
207
Acknowledgements
We would like to thank:
Louis Davidson, who, in the Dynamic Management Objects section of
this chapter, allowed us to reproduce material from his book, which he
co-authored with Tim Ford: Performance Tuning with SQL Server Dynamic
Management Views (http://www.simple-talk.com/books/sql-books/
performance-tuning-with-sql-server-dynamic-management-views/)
Phil Factor for his help with the PowerShell script.
208