Enhancing WSUS database cleanup performance SQL script

There have been many reports of slow and sluggish clean up and removal of expired updates. An old WSUS database can include 1000’s of updates that need to be removed.

Turns out that adding two non-clustered indexes on SUSDB can DRAMTICALLY improve removal of expired updates.

This is a fine example of a community solution.

Some background reading:

Wsus Delete Obsolete Updates – where the key is the addition of the added indexes to SUSDB.

Fixing WSUS – When the Best Defense is a Good Offense – Johan does an excellent job of documenting the many things folks have tried, with varying degrees of success.

Finally, an awesome solution that includes creating the Indexes needed if they do not exist. Heavily commented. Copy and paste the following into SQL Server Management Studio.

Authored by Benjamin Reynolds (Microsoft) – be sure to follow him on Twitter.

USE [SUSDB];

DECLARE @BatchSize int = NULL; -- NULL = do all;  <<-- Control batches here

SET NOCOUNT ON;

-- Check to see if the delete indexes exist; if not don't try to run the script:
DECLARE @IndexRetry bit = 1;
IndexRetry:
IF INDEXPROPERTY(OBJECT_ID(N'tbRevisionSupersedesUpdate'),N'IX_tbRevisionSupersedesUpdate',N'IndexID') IS NULL
   OR INDEXPROPERTY(OBJECT_ID(N'tbLocalizedPropertyForRevision'),N'IX_tbLocalizedPropertyForRevision',N'IndexID') IS NULL
GOTO NeedIndexes;

-- Create tables/variables:
DROP TABLE IF EXISTS #Results; -- This will only work on newer versions of SQL Server 2016+
DECLARE  @UpdateId int
        ,@CurUpdate int
        ,@TotalToDelete int
        ,@Msg varchar(2000);
CREATE TABLE #Results (RowNum int IDENTITY(1,1) PRIMARY KEY CLUSTERED NOT NULL, UpdateId int NOT NULL);
INSERT INTO #Results (UpdateId)
EXECUTE dbo.spGetObsoleteUpdatesToCleanup;

-- If a batch size was provided update the table so we only take care of that many items during this run:
IF @BatchSize IS NOT NULL
DELETE #Results
 WHERE RowNum > @BatchSize;

-- Assign working variables:
SELECT @TotalToDelete = MAX(RowNum)
  FROM #Results;
-- 
SELECT @Msg = 'Total Updates to Delete: '+CONVERT(varchar(10),@TotalToDelete);
RAISERROR (@Msg,0,1) WITH NOWAIT;

-- Create the loop to delete the updates one at a time:
WHILE EXISTS (SELECT * FROM #Results)
BEGIN
    -- Grab the "current" item:
    SELECT  TOP 1 @CurUpdate = RowNum
           ,@UpdateId = UpdateId
      FROM #Results
     ORDER BY RowNum;
    
    -- Provide some info during the script runtime:
    SELECT @Msg = CONVERT(varchar(30),GETDATE(),20) + ': Deleting ' + CONVERT(varchar(5),@CurUpdate) + '/' + CONVERT(varchar(5),@TotalToDelete) + ' = ' + CONVERT(varchar(10), @UpdateId);
    RAISERROR(@Msg,0,1) WITH NOWAIT;
    
    -- Delete the current update from the DB:
    EXECUTE dbo.spDeleteUpdate @localUpdateID = @UpdateId;
    
    -- Delete the current update from the table so we can get the next item:
    DELETE #Results
     WHERE RowNum = @CurUpdate;
END;
GOTO EndScript;

NeedIndexes:
-- If the indexes don't exist we'll try to create them and start over or end if we already tried once:
IF @IndexRetry = 0
BEGIN
    PRINT N'Indexes Required to run this script do not exist! Create them and re-run for optimal performance!';
    GOTO EndScript;
END;
ELSE
BEGIN
    IF INDEXPROPERTY(OBJECT_ID(N'tbRevisionSupersedesUpdate'),N'IX_tbRevisionSupersedesUpdate',N'IndexID') IS NULL
    BEGIN
        SELECT @Msg = CONVERT(varchar(30),GETDATE(),20) + ': Index "IX_tbRevisionSupersedesUpdate" does not exist; Creating it...';
        RAISERROR(@Msg,0,1) WITH NOWAIT;
        EXECUTE (N'USE [SUSDB]; CREATE NONCLUSTERED INDEX IX_tbRevisionSupersedesUpdate ON dbo.tbRevisionSupersedesUpdate(SupersededUpdateID);');
        SELECT @Msg = CONVERT(varchar(30),GETDATE(),20) + ': ..."IX_tbRevisionSupersedesUpdate" created.';
        RAISERROR(@Msg,0,1) WITH NOWAIT;
    END;
    IF INDEXPROPERTY(OBJECT_ID(N'tbLocalizedPropertyForRevision'),N'IX_tbLocalizedPropertyForRevision',N'IndexID') IS NULL
    BEGIN
        SELECT @Msg = CONVERT(varchar(30),GETDATE(),20) + ': Index "IX_tbLocalizedPropertyForRevision" does not exist; Creating it...';
        RAISERROR(@Msg,0,1) WITH NOWAIT;
        EXECUTE (N'USE [SUSDB]; CREATE NONCLUSTERED INDEX IX_tbLocalizedPropertyForRevision ON dbo.tbLocalizedPropertyForRevision(LocalizedPropertyID);');
        SELECT @Msg = CONVERT(varchar(30),GETDATE(),20) + ': ..."IX_tbLocalizedPropertyForRevision" created.';
        RAISERROR(@Msg,0,1) WITH NOWAIT;
    END;
    
    SET @IndexRetry = 0;
    GOTO IndexRetry;
END;

EndScript:
DROP TABLE IF EXISTS #Results;

This entry was posted in SCCM, SQL Server, Uncategorized, WSUS. Bookmark the permalink.

31 Responses to Enhancing WSUS database cleanup performance SQL script

  1. Pingback: Enhancing WSUS database cleanup performance SQL script — Steve Thompson [MVP] – Being Configuration Manager Administrator

  2. Ryan Plume says:

    How can this be made to work with WSUS SQL Servers that aren’t 2016+?

    • ConfigMgrMVP says:

      You’ll need to remark the lines that perform the existence checks for the Indexes objects, prior to creating them. If time permits I’ll document this… or, you upgrade to SQL 2016 🙂

  3. Ryan Plume says:

    So comment out “DROP TABLE IF EXISTS #Results;” or does it need to be replaced with something else? You suggest lines (plural) but I’m not quite sure which other ones may need consideration. Thanks for your help, our PFE has talked this up quite a bit!

    • ConfigMgrMVP says:

      Yes that one:
      DROP TABLE IF EXISTS #Results; — This will only work on newer versions of SQL Server

  4. Markus Klocker says:

    Will this work on WID as well?

  5. Pingback: Maintaining the WSUS Catalog by Declining Updates for Better Update Scanning – Justin Chalfant's TechNet Blog

  6. Pingback: Enhancing WSUS database cleanup performance SQL script | System Center

  7. Peter says:

    Can I just run Benjamin’s script to delete obsolete updates from my wsus?
    I have never done any maintenance to my wsus for approximately 3 years now.

    I can’t make out what the script exactly does.

  8. So I only need to use this script to clean out my SUSDB? I have used this one that Kent Agerlund put together here:

    https://blog.ctglobalservices.com/configuration-manager-sccm/kea/house-of-cardsthe-configmgr-software-update-point-and-wsus/.

    Can the one from Benjamin Reynolds replace this one, or do they both do something completely different?

    I am not an SQL guru.

    Thanks

    • ConfigMgrMVP says:

      This script will take of cleaning your SUSDB. This once enhances performance by adding missing indexes.

      • Oh okay, I am asking because when I ran the one you have here in your blog post it showed in the messages tab of SSMS that it was deleting something that looked like this “2018-09-13 07:27:29: Deleting 16/16 = 364143”. The script shows this:

        — Delete the current update from the DB:
        EXECUTE dbo.spDeleteUpdate @localUpdateID = @UpdateId;

        Just wanted some clarification so I don’t run redundant scripts.

      • ConfigMgrMVP says:

        Sounds right to me. Benjamin’s script adds the indexes if they do not exist, then loops through the deletion process one update at a time.

  9. Pingback: Latest Software Maintenance Script: Making WSUS Suck Slightly Less – Dam Good Admin

  10. BRANDON LEHMAN says:

    Thank you so much for this script! I barely able to use our WSUS DB due to continually getting the dreaded “reset server node” message. After running this script, **BOOM** I was able to open different update lookups that have been broken for a long time.

  11. Pingback: Fixing WSUS – When the Best Defense is a Good Offense – Deployment Research

  12. Pingback: MMS 2019: What’d We Learn? – Dam Good Admin

  13. Sander says:

    Hi Steve,

    I tried to run this script as a query in the SQL management studio.
    I received the following error:
    Msg 156, Level 15, State 1, Line 15
    Incorrect syntax near the keyword ‘IF’.
    Msg 156, Level 15, State 1, Line 89
    Incorrect syntax near the keyword ‘IF’.

    SQL Server Management Studio 18.1
    WIndows Server 2016 Version 1607 (Build 14393.3025) with WSUS role
    WID database

    Any idea how I could fix this error or what I am doing wrong here?

    Thanks!

    Sander

  14. Faaiq says:

    Hi there. I was trying to run this script on our SQL DB as a query and it keeps showing me the following three lines every time. These are the only three lines i see:

    2019-09-27 16:10:06: Index “IX_tbRevisionSupersedesUpdate” does not exist; Creating it…
    Msg 1088, Level 16, State 12, Line 1
    Cannot find the object “dbo.tbRevisionSupersedesUpdate” because it does not exist or you do not have permissions.

    Is there something i am doing wrong? I am running this under a user who is set to dbowner for the database.

    your help is very much appreciated! 🙂

  15. JE says:

    Thanks very much for the post and script, extremely useful. I’ve executed this query on a pre SQL 2016 SUDB database, after commenting out the “Drop table” checks. It runs correctly, but unfortunately I have so many old updates to remove (20,680) that it eventually hit a lock and stopped running. When I try to re-run the script, it’s showing a message that it won’t run, as a “Results” table already exists. How do I drop the results table Pre 2016, so I can re-run the query?

  16. Alan Levy says:

    I tired the script commenting out the “DROP TABLE IF” but I am still getting the following errors running on a WSUS DB on SBS 2011 server.
    Msg 139, Level 15, State 1, Line 0
    Cannot assign a default value to a local variable.
    Msg 139, Level 15, State 1, Line 0
    Cannot assign a default value to a local variable.
    Msg 137, Level 15, State 2, Line 25
    Must declare the scalar variable “@BatchSize”.
    Msg 137, Level 15, State 2, Line 27
    Must declare the scalar variable “@BatchSize”.
    Msg 137, Level 15, State 2, Line 60
    Must declare the scalar variable “@IndexRetry”.
    Msg 156, Level 15, State 1, Line 65
    Incorrect syntax near the keyword ‘ELSE’.
    Msg 137, Level 15, State 1, Line 84
    Must declare the scalar variable “@IndexRetry”.
    Msg 156, Level 15, State 1, Line 89
    Incorrect syntax near the keyword ‘IF’.

    • ConfigMgrMVP says:

      This was created primarily for MEMCM instances running SUSDB. So, it could be an issue with the version you have on SBS 2011 (is that still supported?).

      • Alan Levy says:

        Not supported at all, but there are those of us who like the featureset and continue to hold on until it stops working. Thanks for the reply.

  17. Pingback: WSUS: llenado de base de datos SUSDB SQL Server Express | rm-rf.es

  18. Mpilo says:

    So I ran this script, 5.5 days later, it’s still running, and soon there’ll be powering down the servers for some power maintenance, will it start again from scratch if I stop it or pick up where it left off, should I start it again or follow a different route?

    server 2012 R2, Xeon E5-2630, 32GB RAM, MS SQL Server 2014 SP3, 43GB susdb.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.