Resolving and/or Avoiding Allocation Page Contention Problems
All the code in this section uses the Ch8_4TempDBContentionResolution.sql code file.
Once you’ve determined that you’re suffering from
allocation page contention in tempdb (or even if you’re not sure), you
have a few different ways to reduce the likelihood of it happening.
Multiple Tempdb Data Files
If you’re a DBA rather than a
developer, you might be tempted to opt for this solution first. Recall
that there is a set of allocation pages at the start of each data file,
so if you have more than one file and can balance the load between them
you’ll be less likely to get a hotspot on the allocation pages compared
to a single file.
It’s a good best practice to have multiple tempdb
files for your instance anyway because doing so is a simple, risk-free
way of reducing the likelihood of contention occurring.
Tempdb works with multiple data files by using a proportional fill
algorithm to try to balance the amount of free space across all the
files. The effect of this is to favor the file with the most free space
until it equals all the other files. This is a bad scenario if you’re
trying to balance the allocation requests evenly across the files, so
you need to ensure that all the tempdb data files are the same size.
This is illustrated in Figure 3.
To determine whether simply adding more files can
make a measurable difference to the contention example from the
previous section, you can configure the server to have four equally
sized tempdb data files. It’s not important for them to be on separate
drives because you’re not doing it to improve I/O performance but
simply to have more allocation pages.
You can modify the following the script for your own environment to configure the data files, which are all on the same disk.
ALTER DATABASE tempdb
MODIFY FILE (name=tempdev,size=512MB) ;
GO
ALTER DATABASE tempdb
ADD FILE (name=tempdev2,size=512MB,filename='D:\data\tempdev2.ndf') ;
GO
ALTER DATABASE tempdb
ADD FILE (name=tempdev3,size=512MB,filename='D:\data\tempdev3.ndf') ;
GO
ALTER DATABASE tempdb
ADD FILE (name=tempdev4,size=512MB,filename='D:\data\tempdev4.ndf') ;
Run through the demo again and see if it makes a difference. Try adding more and more files to see the effect.
Adding more files in this example will help
reduce contention and will even remove it if you add enough files, but
you can get easier gains by checking the code.
Temporary Object Reuse
This optimization is a little-known feature called temporary object reuse.
If you’re a developer and you manage the code rather than the server,
the first thing you’ll likely look at is optimizing the code, rather
than reviewing server best practices. In most scenarios changing the
code yields the best performance improvements anyway, so it’s not a bad
starting approach.
Beginning with SQL Server 2005, it’s possible for
SQL Server to cache temporary object definitions so that they can be
reused if the same object needs to be created again. To be more
specific, one IAM page (Index Allocation Map) and one extent are cached.
Objects that are reused don’t have to be
allocated new space and therefore won’t contribute to any allocation
problems. Optimizing your code to ensure that your temporary tables are
being cached will help to reduce any potential problems.
SQL Server tries to cache temporary tables by
default, so the first thing you need to check is whether or not SQL
Server is caching yours. To do so, you can run your code in a loop and
monitor the difference between the “temp table creation rate”
Performance Monitor counter at the start and end of the loop.
Fortunately, Sunil Agarwal from Microsoft has written a nice wrapper
script that does it for us:
SET NOCOUNT ON ;
GO
DECLARE @table_counter_before_test BIGINT ;
SELECT @table_counter_before_test = cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Temp Tables Creation Rate' ;
DECLARE @i INT = 0 ;
WHILE ( @i < 10 )
BEGIN
EXEC tempdbdemo.dbo.usp_loop_temp_table ;
SELECT @i += 1 ;
END ;
DECLARE @table_counter_after_test BIGINT ;
SELECT @table_counter_after_test = cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Temp Tables Creation Rate' ;
PRINT 'Temp tables created during the test: '
+ CONVERT(VARCHAR(100), @table_counter_after_test
− @table_counter_before_test) ;
To use the script yourself simply change the stored procedure name you want to test from usp_loop_test_table to whatever code you want.
If the code you want to test is complicated, you
might want to set the loop iterations to 1 the first time you run this
script just to be sure how many different temporary tables are created. Once you know that, you can set it back to 10 loop iterations as in the example.
The example code indicates that only one
temporary table creation statement is called many times, so if the
value returned from the above script is more than 1, then you can be confident that you’re not getting temporary object reuse.
Running the script provides the following result:
Temp tables created during the test: 1000
During 10 executions, 1,000 temporary
tables were created, so you can conclude that the table isn’t being
cached (remember that the looping procedure executes the procedure
creating the temp table 100 times, 10 * 100 = 1,000).
There’s obviously a problem in the example code
somewhere, so what you need to determine now is under what
circumstances SQL Server will cache temporary tables, so you know whether any changes can be made to the code.
Temporary objects will be cached as long as the following obtains:
- Named constraints are not created.
- DDL (Data Definition Language) statements that affect the table, such as CREATE INDEX or CREATE STATISTICS, are not run after the table has been created.
- The object is not created using dynamic SQL; using sp_executesql, for example.
- The object is created inside another object such as the following:
- Stored procedure
- Trigger
- User-defined function
- The return table of a user-defined table-valued function
If you look back at the code for usp_temp_table
you’ll notice that a unique clustered index is created after the table
definition, which breaks the rules for cached temporary objects:
CREATE UNIQUE CLUSTERED INDEX cix_c1 ON #tmptable ( c1 ) ;
All is not lost, however, because you
can utilize a constraint within the temporary table definition to
achieve the same results without breaking the rules for temporary
object caching. The next code listing shows the new definition with the
old CREATE INDEX statement commented out.
USE [tempdbdemo] ;
GO
CREATE PROCEDURE [dbo].[usp_temp_table]
AS
CREATE TABLE #tmpTable
(
c1 INT UNIQUE CLUSTERED,
c2 INT,
c3 CHAR(5000)
) ;
--CREATE UNIQUE CLUSTERED INDEX cix_c1 ON #tmptable ( c1 ) ;
DECLARE @i INT = 0 ;
WHILE ( @i < 10 )
BEGIN
INSERT INTO #tmpTable ( c1, c2, c3 )
VALUES ( @i, @i + 100, 'coeo' ) ;
SET @i += 1 ;
END ;
GO
Here, a unique clustered constraint has been
added to the c1 column, which SQL Server will enforce internally by
using a clustered index, so you can keep exactly the same functionality.
Testing the new stored procedure using the temporary table creation test now returns the following result:
Temp tables created during the test: 1
This example has shown you two ways to tackle a
tempdb page contention issue: adding more data files and temporary
object reuse. Taking advantage of temporary object reuse doesn’t remove
the issue because of the large number of concurrent connections trying
to use the object name, so adding additional tempdb data files is still
required to balance the allocation requests.
Trace Flag 1118
This trace flag was introduced in SQL
Server 2000 to help alleviate contention on the SGAM page (2:1:3) by
disabling mixed extent allocations in all databases.
Every time you create a new table that’s not big enough to fill an
extent (which happens a lot in tempdb), the SGAM page is read to find a
mixed extent with enough free space to allocate to your table.
The effect of enabling this trace flag is that every object you create will be allocated its own extent (a uniform
extent). The only downside to this is the extra disk space that’s
needed because every table needs at least 64KB; although that’s
unlikely to be an issue on most systems.
SQL Server 2008 introduced an improved algorithm
for allocating space in mixed extents, so you’ll be unlikely to
encounter this issue often if at all with SQL Server 2012.
Even though you’re unlikely to find SGAM
contention in SQL Server 2012, trace flag 1118 still works exactly the
same: It disables mixed extent allocations.
NOTE
While you
probably won’t ever need to use 1118 to reduce SGAM contention, the
fact that it forces only uniform extents to be allocated can be used to
increase performance under very heavy workloads, so it might be worth
testing if you’re tuning that kind of environment.