• subscribe
March 23, 2009 12:00 AM

Removing Forwarded Records from Heap Tables

Uncover a hidden SQL Server performance killer
SQL Server Pro
InstantDoc ID #101572

Executive Summary:
Forwarded records are a type of fragmentation within heap tables that can cause poor SQL Server performance. Learn how to use the sys.dm_db_index_physical_stats dynamic management view (DMV) to find forwarded records and the FixForwardedRecs stored procedure to remove them. The FixForwardedRecs stored procedure removes forwarded records without permanently changing the table structure and without unloading and reloading data in a table.

So you’ve checked all the likely suspects, but your SQL Server is still performing poorly. Before you put that capital request in for more horsepower, think about a hidden performance killer you might have missed—forwarded records. Forwarded records are a type of fragmentation that’s hard to detect and harder to resolve. By automating the discovery and resolution of forwarded records, you can eliminate this performance killer from your databases. This article walks you through how to use a stored procedure that’s designed to intelligently detect and eliminate forwarded records, as well as log the affect of their removal.

How Forwarded Records Are Created
In essence, forwarded records are “fragmentation” within a permanent or temporary heap table. (Forwarded records are found only in heap tables.) Heap tables, the most simple storage arrangement, consist of unorganized data pages. Rows are inserted into a heap wherever there happens to be space. Because a heap is unordered, the data pages aren’t linked together in any way. When a heap table is accessed for information and no clustered index is available to support requests, the table might be scanned inefficiently. The scan can be even more inefficient if forwarded records exist.

A forwarded record occurs when a heap table (either permanent or temporary) is created that includes variable length fields. When a batch is called to load data into the table, some of the variable length fields might be populated with a short value (e.g., the letters A or N). A second batch might be called to transform the data in this field into something more user friendly (e.g., A = Acceptable and N = Not Acceptable) but that is a larger value than was initially used. This is exactly what creates the forwarded records.

When a larger value is updated in a variable length field in a record, SQL Server will first try to expand the row of the existing page on the chance that there’s enough room to continue to use the original page. If that fails and SQL Server can’t find an existing page with enough room for the larger value, a new page has to be created for the record. The data is moved to the new page and is assigned a new Relative Identifier (RID). The old page now contains a forwarding pointer (forwarded record) that tells SQL Server to jump to the new RID. The new record location also has a back pointer to the old record. Forwarded records prevent non-clustered indices on heap tables from being updated with the RID of the new row. The overhead of updating indices is worse than replacing the old records with a pointer. But the drawback to forwarded records is that their presence could result in a large amount of I/O. For each forwarded record, SQL Server has to jump to the target page, then back to the original one. This is a very inefficient way to read a row, and it also wastes space.

Although forwarded records can occur on all heap tables they don’t always and you can take steps to alleviate them. For example, you could initially populate tables with dynamic columns with long values. This approach would initially set the record to a large enough size to accommodate most updates and create minimal forwarded records. Updating a dynamic column from a long value to a shorter value won’t create any forwarded records. Developers should be very aware of how temporary tables are created and populated. Often, no one is aware of the existence of forwarded records in temporary tables because the objects are destroyed when batches are completed. Web Listing 1 shows how forwarded records are created.

Finding Forwarded Records
Now that you know how forwarded records are created, let’s look at how you find forwarded records and determine if they’re causing poor performance. Use the dynamic management view (DMV) sys.dm_db_index_physical_stats, as shown in Listing 1, to find forwarded records. The results from this DMV (displayed in Table 1) show that there are now 24,142 forwarded records in FRTable. If FRTable is called in a query from a user’s web application, that user would start to experience slow response because SQL Server had to do many more reads to satisfy the request. After doing a DBCC DROPCLEANBUFFERS and a DBCC FREEPROCCACHE, a quick check of STATISTICS IO of the query “SELECT * FROM FRTable” before the forwarded records are created show that it performed 75 logical reads, 0 physical reads and 21 read-ahead reads. After the forwarded records are introduced, the same query performs 24,432 logical reads, 0 physical reads and 46 read-ahead reads. This is a 32,576 percent increase in logical reads and a 119 percent increase in read-ahead reads, which translates to much more work for SQL Server.



ARTICLE TOOLS

Comments
  • IJeb
    3 years ago
    Aug 26, 2009

    @gnanau

    DBCC SHOWCONTIG WITH TABLERESULTS can be used to determine forwarded records in SQL Server 2000

    IJeb Reitsma

  • Douglas
    3 years ago
    Jul 06, 2009

    code doesn't work!

  • Gabriela
    3 years ago
    Apr 20, 2009

    Very good article, indeed! I have question though: Is there a way to determine the forwarded records in SQL Server 2000?

  • Megan
    3 years ago
    Apr 17, 2009

    Hi pelsql,

    Thanks so much for your question. Lori Brown's response is below.

    "I chose to create a clustered index on a sequential value column because 1) it is a best practice to set clustered indexes on columns that are in some type of sequential order (either numeric or alphabetic) and 2) because a clustered index on a column that is already in sequential order would cause the least amount of change to the heap table. One of the challenges of having a procedure that makes a structural change to a table is realizing that clustered indexes can have a profound effect on the order of the data in the table. I have experienced times when adding even a very innocuous non-clustered index has changed the sort order of data returned in queries resulting in user complaints that an application was suddenly returning incorrect data to users. In an effort to not disturb data in the heap table in any way except to remove fragmentation, I decided that an autoincrementing numeric column would be the best fit for a clustered index to be introduced to a heap table. You can create a clustered index on just about any column and still achieve the same effect of removing forwarded records but you would change the sort order of the data."


    Megan Keller

    Associate Editor, SQL Server Magazine

    mkeller@sqlmag.com

  • Maurice
    3 years ago
    Apr 17, 2009

    I just wonder why creating a non-unique clustered index on any column with few null values is enough by itself to obtain the same result. Why is it necessary to have some identity value, or any kind of sequential value key, to achieve the same result? Aside of that this is a great discovery for me. I got a great argument against the absence of a clustered index here. I usually discourage developer to use heap because the naturally become fragmented. However they are some case where heap are interesting, especially when there is mostly just inserts to it, and they are never updated and there is few access on it that would benefit from a clustered index.

You must log on before posting a comment.

Are you a new visitor? Register Here