When I investigate a new SQL Server instance for the first time, I perform a number of standard checks to get a handle on the current health of the system. One of the checks I like to perform is a VLF count on each transaction log file associated with each database on the system.
Right now, some of you may be wondering what I’m talking about and some of you may be nodding your head in confirmation. Neither of these responses is surprising, as this topic is somewhat obscure, unless you just like reading about SQL Server internals as much as I do.
What is a VLF?
The transaction log of a SQL Server database is always most evident as one or more physical files on disk; however, the transaction log is internally managed as a set of virtual log files (VLFs) within the physical files. This type of internal configuration makes it easier for the storage engine to manage the transaction log and control the reuse of internal space efficiently and effectively.
How is the Number of VLFs Determined?
When you create the transaction log for your database, it will have between 2 and 16 VLF’s. The number of VLF’s is determined by the size of the log when it is first created. SQL Server uses a simple formula to determine the number of VLF’s created.
If the file size is between 1 MB and 64 MB, the storage engine will allocate 4 VLFs inside the file. If the file size is greater than 64 MB, but less than or equal to 1 GB in size, the storage engine will allocate 8 VLFs inside the file. If the file size is greater than 1 GB, then the storage engine will allocate 16 VLFs inside the file.
This formula is also used when “growing” the transaction log too. Furthermore, the transaction log will always grow and shrink along full VLF boundaries. It’s the smallest unit of change allowed within the file.
Visualizing our VLF Count
Now that we a good understanding of how the storage engine will allocate VLFs inside our transaction log, let’s take a look at how we actually see the VLFs in an active database. We’ll be using an undocumented DBCC command to have a look.
First, let’s create a simple database using the following settings.
USE [master]
GO
CREATE DATABASE [myTestDatabase] ON PRIMARY
( NAME = N’myDatabase’, FILENAME = N’C:\myDatabase.mdf’
,SIZE = 3072KB
,MAXSIZE = UNLIMITED
,FILEGROWTH = 1024KB )
LOG ON
( NAME = N’myDatabase_log’, FILENAME = N’C:\myDatabase_log.ldf’
,SIZE = 1024KB
,MAXSIZE = 2048GB
,FILEGROWTH = 1024KB)
GO
Now that the database has been created, let’s take a look at the number of VLFs that were created inside the transaction log.
We’ll be using the undocumented DBCC command called “LOGINFO” to view the VLF count for our database.
USE myTestDatabase;
GO
DBCC LOGINFO;
GO
The execution of the DBCC LOGINFO command outputs the results listed below.

The results of DBCC LOGINFO have a lot to tell us; however, to determine the number of VLFs in our database, we only need to count the number of records. Each record returned from the command represents a single VLF in our transaction log. You can see that the database we created earlier has a total of 4 VLFs.
As you can see, the formula illustrated previously has been used, and since the initial size of the log was between 1 MB and 64 MB, the storage engine created exactly 4 VLFs in this transaction log.
VLFs and Auto-growth
The SQL Server storage engine uses the same formula when allocating additional space to the transaction log file during an auto-growth event. Let’s simulate 5 auto-growth events of 1 MB each and then have a look at our VLF count.
If you remember the formula from above, you know that the storage engine will allocate 4 VLFs per auto-growth event, or a total of 20 additional VLFs.

When we execute DBCC LOGINFO again, we can see that our estimate was correct and we now have a total of 24 VLFs in our transaction log. We have 4 VLFs from the original creation of the 1 MB transaction log and we have an additional 20 VLFs from the five 1 MB auto-growth events.
At first glance, this type of behavior may not alarm you. In fact, this database may go on to have a happy, healthy life under the right workload. However, if this database is placed into production as a data warehouse or as the backend for a high transaction OLTP application then we’ll have problems.
For example, if this transaction log were to grow to 50 GB, or 51,200 MB, it would have a total of 204,824 VLFs. We got 4 VLFs during the initial file creation, then an additional 20 VLFs during the five 1 MB auto-growth events. Then, to top it all off, we got another 204,800 VLFs from the numerous auto-growth events needed to increase the log by 50 GB.
If this database continues to experience auto-growth at this rate, we can expect to see significant performance problems. We need to get this transaction log under control and implement the proper configuration changes to reign in this excessive VLF creation.
Implications and Mitigations
Simply put, having too many, or too few, VLFs in your transaction log file can seriously degrade all transaction log file related processes. These impacts will most likely occur during transaction log backups, service restarts when database recovery is running, transaction log clearing, etc. The good news is that this problem is easy to address, and with proper pre-allocation, unlikely to reoccur.
Generally speaking, if you find that you have a database transaction log with more than 50 VLFs you may want to perform some maintenance to reduce the count. Also, you’ll need to review the auto-growth settings and make the changes necessary to alleviate the problem from reoccurring in the future. The steps required to make these changes are fairly simple; however, you may need to experiment a bit in order the get the VLF count set properly.
You can use the following steps to adjust the VLF count for your transaction log. These steps should only be performed during a period of low activity.
First, perform a transaction log backup.
BACKUP LOG [myTestDatabase] TO DISK =
N’C:\myTestDatabase.bak’
GO
Once the transaction log backup has been completed, you are ready to shrink the transaction log and prepare for the pre-allocation steps.
USE myTestDatabase;
GO
DBCC SHRINKFILE(‘myTestDatabase_log’, TRUNCATEONLY)
GO
After this step, the transaction log has returned to its default initial size of 1 MB and is ready for pre-allocation.
I’m not going to go into all the factors used to determine the proper size for this transaction log. Instead, we’ll make an assumption that 60 GB is large enough, paired with consistent transaction log backups, to hold the largest individual transaction without requiring an auto-growth.
We’ll grow the transaction log to 60 GB by executing the following three queries.
ALTER DATABASE myTestDatabase
MODIFY FILE
(
NAME = myTestDatabase_log
, SIZE = 20 GB
)
GO
ALTER DATABASE myTestDatabase
MODIFY FILE
(
NAME = myTestDatabase_log
, SIZE = 40 GB
)
GO
ALTER DATABASE myTestDatabase
MODIFY FILE
(
NAME = myTestDatabase_log
, SIZE = 60 GB
)
GO
Based on our knowledge of the storage engine formula for VLF allocation, we know we will get 16 VLFs after executing each of the queries list above. This equals a total of 48 VLFs in our transaction log. When we add this to the already existing 4 VLFs, we have a total of 52 VLFs. We’re right on target.
Assuming our transaction log configuration is appropriate for our workload, and no auto-growth is required, we can expect our new VLF count to remain stable; however, if auto-growth should occur, we will add some number of additional VLFs to our transaction log. Due to this, it’s important to configure the initial size of our transaction log, and our auto-growth increment, to values that won’t cause our VLF count to spiral out of control again.
Conclusion
I encourage you to continue to research the internals of the transaction log. A thorough knowledge of this critical mechanism will be highly beneficial as you strive to enhance you own database environments.
Scott Caldwell, SQL Server DBA
