SQL Server HotFix History Analysis

By:   |   Updated: 2018-05-22   |   Comments   |   Related: More > Database Administration


Problem

As a DBA, we may be asked by management or other stakeholders, like developers and application architects whether a new SQL Server version is stable or when a new of version of SQL Server can be considered mature and stable. Is there a way we come up with a solid approach to answer this question?

Solution

This is actually a very interesting question, we may not be able to predict the future, but we can at least look back and do some analysis of previous SQL Server releases and try to get some insights to make informed recommendations.

What I propose is that we look at the SQL Server Cumulative Updates (CUs) and see how many fixes each CU contains and normalize the number of fixes by day. For example, we can check that after SQL Server 2012 RTM was released, within about 100 days, how many CUs are released and sum the hotfixes in these CUs, and then average out the hotfixes within 100 days, so we can calculate average daily hotfixes. We can do this to SQL Server 2014/2016/2017 as well. This applies to hotfixes after each Service Pack as well (there are no more Service Packs for SQL Server 2017, all updates are done using CUs).

I am excluding SQL Server 2008R2 and earlier versions because Microsoft started to taking the Cumulative Update approach only since SQL Server 2012. Previously, SQL Server mainly used Service Packs, which aggregates all hotfixes since the last SP/RTM, as a standard patch approach. (Of course, Hotfixes are available based on needs). 

Solution Algorithm

We will first retrieve SQL Server CU release history by using my previous tip “Creating a SQL Server Build and Patch Release Table”, and then we will retrieve all hotfix information using another tip “Extract Hot Fix Details from a SQL Server CU KB Articles”. Once we get all the information, we can start to do some interesting calculations, such as:

  • Total / Average daily hotfixes in first [X] days since RTM (let’s say X=100 or 365)
  • Total hotfix numbers for the life cycle of each Service Pack (including RTM)
  • In SQL Server 2017 (the latest SQL Server), with each CU releases (every month), calculate the total # of hotfixes in each CU, see whether there is any trend.

Repository and Analysis Tables

We first create two repository tables, one is for the CU release list and another is hotfix list in each CU, and we then we create another table for analysis purpose. The two repository tables are a direct copy from the previous two tips (I put them together here for easy reading).

-- inventory table for SQL Server Version build numbers and the web URL to the each release
use MSSQLTips -- change it to your own db 
drop table if exists dbo.SQLVer;
create table dbo.SQLVer (
  Build varchar(32)
, Description varchar(256)
, ReleaseDate varchar(30)
, WebLink varchar(512) -- web url to the MS CU/SP release 
);
 
-- store the fix information in CU KB article
if object_id('dbo.SQLCU_Detail', 'U') is not null
   drop table dbo.SQLCU_Detail;
create table dbo.SQLCU_Detail (
 VSTSBugNumber varchar(50)
 , KBNumber varchar(12)
 , [Description] varchar(1024)
 , [FixArea] varchar(50)
 , [Platform] varchar(50)
 , KB_URL varchar(256)
 , SQLCU_URL varchar(256)
 , LogDate datetime default current_timestamp
 , id int identity primary key
);	

The analysis table is like the following:

use MSSQLTips
 
if object_id('dbo.SQLVer_Analysis', 'U') is not null
   drop table dbo.SQLVer_Analysis;
 
create table dbo.SQLVer_Analysis (
  id int identity primary key
, build varchar(20)
, [Description] varchar(200)
, ReleaseDate datetime
, [Name] varchar(100)
, ParentID int -- indicates the basis of the CU, such as RTM or SP, because we say CU1 for SP2 or CU1 for RTM,  
, RootID int -- indicate original SQL Version
);	

Populate Report Tables

We can easily populate the two repository tables using the PowerShell scripts in the previous two tips.

#assume the latest SQL Server PS module is installed, if not
#go to https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-ps-module
# first populate CU repository table 
Import-Module sqlserver -DisableNameChecking;
for ($i=1; $i -le 4; $i++) #extract sql server 2017 to SQL Server 2012 build history
{
    $t = Get-SQLBuildHistory -TableNumber $i -IncludeLink;
    Write-SqlTableData -ServerInstance localhost -DatabaseName MSSQLTips `
    -SchemaName dbo -TableName SQLVer -InputData $t;
}			

Once we get dbo.SQLVer table populated, we need to process a few specific entries as there are two items in one column as shown below.

select * from dbo.SQLVer
where weblink is not null
and charindex(';', weblink) > 1;	
web link

We are only interested in the CU links not the Security update links, so we will do the following cleanup by removing the non-CU parts from both the [Description] and [WebLink] columns.

use mssqltips
set nocount on;
 
declare @build varchar(30), @description varchar(600), @ReleaseDate varchar(30), @weblink varchar(600);
declare @loc int, @loc2 int, @crlf char(2) = char(0x0d) + char(0x0a);
declare @cur cursor;
 
set @cur = cursor for
select * from dbo.sqlver 
where charindex(';', weblink) > 0;
 
open @cur;
fetch next from @cur into @build, @description, @releasedate, @weblink;
while @@FETCH_STATUS = 0
begin
   if @description like 'CU%'
   begin
      select @loc = charindex(@crlf, @description), @loc2 = charindex(';', @weblink);
      update  dbo.sqlver--dbo.SQLVer
      set [Description] = SUBSTRING([Description], 1, @loc-1), WebLink = substring(Weblink, 1, @loc2-1)
      where current of @cur;
      insert into dbo.sqlver (build, [Description], ReleaseDate, WebLink)
      select @build, substring(@description, @loc+2, 200), @ReleaseDate, substring(@weblink, @loc2+1, 200)
   end
   else
   begin
      select @loc = charindex(':', @description), @loc2 = charindex(';', @weblink);
      update dbo.sqlver --dbo.SQLVer
      set [Description] = SUBSTRING([Description], 1, @loc-1), WebLink = substring(Weblink, 1, @loc2-1)
      where current of @cur;
 
      insert into dbo.sqlver (build, [Description], ReleaseDate, WebLink)
      select @build, substring(@description, @loc+2, 200), @ReleaseDate, substring(@weblink, @loc2+1, 200);
 
 
   end
 
   fetch next from @cur into @build, @description, @releasedate, @weblink;
 
end
close @cur;
deallocate @cur;
 
-- cleanup some depreciated (or replaced) CUs
delete from dbo.sqlver
where description like '%(replaced)%'

Now we will extract the hotfix list in each CU with this code:

import-module sqlserver -DisableNameChecking;
$qry = @"
select WebLink from dbo.SQLVer
where Description like 'cu%'
and weblink is not null;
"@;
$links = invoke-sqlcmd -ServerInstance localhost -Database MSSQLTips -Query $qry;
 
$repo_dt = new-object system.data.datatable;
$col = new-object System.Data.DataColumn ('bugnum', [system.string]);
$repo_dt.Columns.Add($col);
 
$col = new-object System.Data.DataColumn ('kbnum', [system.string]);
$repo_dt.Columns.Add($col);
 
$col = new-object System.Data.DataColumn ('description', [system.string]);
$repo_dt.Columns.Add($col);
 
$col = new-object System.Data.DataColumn ('fixarea', [system.string]);
$repo_dt.Columns.Add($col);
 
$col = new-object System.Data.DataColumn ('platform', [system.string]);
$repo_dt.Columns.Add($col);
 
$col = new-object System.Data.DataColumn ('kb_url', [system.string]);
$repo_dt.Columns.Add($col);
 
$col = new-object System.Data.DataColumn ('cu_url', [system.string]);
$repo_dt.Columns.Add($col);
 
foreach ($url in $links.weblink) #{$url}
{
    try 
    {
        Write-Host $url -ForegroundColor Yellow;
        $dt=Get-SQLCUDetail -SQLCU $url  -IncludeLink;  
 
        if ($dt.Columns.count -eq 7) #contains [platform] column
        {  
            foreach($r in $dt.Rows)
            {
               $dr = $repo_dt.NewRow();
               $dr[0]=$r[0];
               $dr[1]=$r[1];
               $dr[2]=$r[2];
               $dr[3]=$r[3];
               $dr[4]=$r[4]
               $dr[5]=$r[5];
               $dr[6]=$r[6];
               $repo_dt.Rows.add($dr);
            }
        }
        elseif ($dt.columns.Count -eq 6) #deos not contain [platfrom] but contains [fix area] col
        {
           foreach ($r in $dt.rows)
           {
               $dr = $repo_dt.NewRow();
               $dr[0]=$r[0];
               $dr[1]=$r[1];
               $dr[2]=$r[2];
               $dr[3]=$r[3];
               $dr[5]=$r[4];
               $dr[6]=$r[5];
               $repo_dt.Rows.add($dr);
            }
        } #elseif
        else #only 5 columns, no [platform] and [fix area]
        {
           foreach ($r in $dt.rows)
           {
               $dr = $repo_dt.NewRow();
               $dr[0]=$r[0];
               $dr[1]=$r[1];
               $dr[2]=$r[2];
               $dr[5]=$r[3];
               $dr[6]=$r[4];
               $repo_dt.Rows.add($dr);
            }
        } #else
        # Write-SqlTableData -ServerInstance localhost -DatabaseName mssqltips -SchemaName dbo -TableName SQLCU_Detail -InputData $dt;
    } #try
    catch
    { Write-Error $_; }
}
#write to sql table dbo.SQLCU_Detail
Write-SqlTableData -ServerInstance localhost -DatabaseName mssqltips -SchemaName dbo -TableName SQLCU_Detail -InputData $repo_dt;
			

Now we need to manipulate the collected data, and populate our analysis table dbo.SQLVer_Analysis.

-- populate dbo.SQLVer_Analysis
use MSSQLTips;
 
insert into dbo.SQLVer_Analysis (build, [Description], ReleaseDate, [Name])
select build, [description], [ReleaseDate]
, brief= case when [Description] like 'CU%' then replace(substring([description], 1, charindex(' (KB', description)), ' Microsoft ', ' ') 
              when [Description] like '%RTM' then [Description]  
           when [Description] like '% Service Pack [1-9]%' then substring(replace([Description], 'Microsoft ', ''), 1, iif(charindex(' (',replace([Description], 'Microsoft ', ''))=0, 100,charindex(' (',replace([Description], 'Microsoft ', '')) ))
        else substring([description], 1, charindex(' (KB', description)) end
from dbo.SQLVer
where (Description like '%CU[1-9]%' or [Description] like '%RTM' or [Description] like '%Service Pack [1-9]%')
and convert(decimal(2,0),left(build,2)) >=11
and  [Description] not like 'MS%' and  [Description] not like 'security%' and  [Description] not like 'fix%' and  [Description] not like 'on%'
order by  build --convert(decimal(2,0),left(build,2)) asc--, -- cast(Releasedate as datetime) asc
 
-- a small cleanup
update dbo.SQLVer_Analysis set Name=replace(name, ' RTM', '')
where name like 'CU% 2016 RTM'
go
 
 
-- populate ParentID and RootID
 
declare @cur cursor;
declare @id int, @name varchar(100); 
declare @first_id int, @last_id int
 
select top 1 @first_id = id from dbo.SQLVer_Analysis
where name like '%rtm'
order by id asc
 
set @cur = cursor for 
select id
from dbo.SQLVer_Analysis
where id > @first_id
and name like '%rtm'
order by id
 
open @cur;
fetch next from @cur into @id
while @@fetch_status = 0
begin
    set @last_id = @id
   update dbo.SQLVer_Analysis
   set RootID = @first_id
   where id >= @first_id and id < @last_id;
   
   set @first_id = @last_id
   fetch next from @cur into @id
 
end
select @last_id = max(id) + 1 from dbo.SQLVer_Analysis;
 
   update dbo.SQLVer_Analysis
   set RootID = @first_id
   where id >= @first_id and id < @last_id;
close @cur;
 
 
-- now we need to update parentid
 
select top 1 @first_id = id from dbo.SQLVer_Analysis
where name like '%rtm'
order by id asc
 
set @cur = cursor for 
select id
from dbo.SQLVer_Analysis
where name like '%rtm' or name like '% Service Pack [1-9]'
and id > @first_id
order by id;
open @cur;
fetch next from @cur into @id
while @@fetch_status = 0
begin
    set @last_id = @id
   update dbo.SQLVer_Analysis
   set ParentID = @first_id
   where id > @first_id and id < @last_id;
   
   set @first_id = @last_id
   fetch next from @cur into @id
 
end
select @last_id = max(id) + 1 from dbo.SQLVer_Analysis;
 
   update dbo.SQLVer_Analysis
   set ParentID = @first_id
   where id > @first_id and id < @last_id;
close @cur;
go

With all data collected, we can do some analysis.

We first check the total # of hotfixes within around 100 days after RTM release. Here “around 100 days” means finding the CU whose release date (T day) is closest to 100 days after RTM release date and calculate all hotfixes inside CUs up to (T day).

-- find the total # and avg daily # of hotfixes within [X] days after RTM release
use MSSQLTips;
 
declare @X int = 100;
; with c as (select id, rtmdate = ReleaseDate from dbo.SQLVer_Analysis where name like '%rtm')
, c2 as (select a.build, a.Description, a.releasedate, a.RootID, dist= datediff(day, c.rtmdate, a.ReleaseDate)  
from dbo.SQLVer_Analysis a
inner join c
on a.parentid = c.id 
)
, c3 as (select rootid,  mindist=min(abs(@X-dist))
from c2
group by rootid
) 
 
, c4 as (select c3.rootid, c2.build, c2.Description, c2.ReleaseDate, c2.dist 
from c3
inner join c2 
on c3.RootID = c2.RootID
and abs(@X-c2.dist) = c3.mindist ) -- c4 give out the closest CU build which was release about [X] days after RTM
, c5 as (  -- c5 should give out all builds before c4.release but after rtm
select a2.*, c4.dist
from dbo.SQLVer_Analysis a1
inner join c4 
on a1.build = c4.build --) select * from c5
inner join dbo.SQLVer_Analysis a2
on a1.ParentID = a2.ParentID
and a2.build <= a1.build
) 
 
select  [Version]=case left(v.build, 2) 
            when '11' then 'SQL2012'
            when '12' then 'SQL2014'
            when '13' then 'SQL2016'
            when '14' then 'SQL2017'
            else 'Other'
           end
, Daily_Avg_Bugs= cast(count(*)*1.0/c5.dist as decimal(5,2))
, Total_Bugs = count(*)
, Days_Since_RTM=c5.dist
from dbo.SQLCU_Detail d
inner join dbo.SQLVer v
on d.SQLCU_URL = v.WebLink
inner join c5 
on c5.build = v.Build
and v.Description like 'CU%'
group by left(v.build, 2), c5.dist
order by 1
go

The result is:

version
daily bugs

Find the Average Daily Error Rate for each Service Pack (including RTM):

--2. find the avg error rate among each SPs
use MSSQLTips;
select p.Build, p.Description, count(*) as Total_Errors, max(datediff(day, p.releasedate, a.releasedate)) as [Days] 
, Daily_Errors = cast(count(*) *1.0 / max(datediff(day, p.releasedate, a.releasedate)) as decimal(5,2))
from dbo.SQLVer_Analysis a
inner join dbo.SQLVer_Analysis p
on a.ParentID = p.id
inner join dbo.sqlver v
on v.Build = a.build
and a.Description = v.Description
inner join dbo.SQLCU_Detail d
on d.SQLCU_URL  = v.WebLink
group by p.build, p.Description
order by p.build;	

The result is:

description

If we use the daily errors for post-RTM hotfixes, we can see, SQL Server 2012 is lowest at 0.41 while SQL Server 2017 seems to be highest at 1.18.

daily errors

I have two personal explanations for this trend:

  1. SQL Server becomes more complex with more features, thus likely more bugs.
  2. The more quickly a new SQL Server version is released, more errors it may contain. SQL Server 2014 and SQL Server 2016 have similar daily error rates because they were each released two years after the previous version. SQL Server 2017 has more daily errors when it has only a 1-year gap from the previous version.

Calculate the number of hotfixes in each CU of SQL Server 2017:

use MSSQLTips;
select  CU=substring(v.Description,1,3), Total_Hotfix=count(*)
from dbo.SQLCU_Detail d
inner join dbo.SQLVer v
on v.WebLink = d.SQLCU_URL
and v.Build like '14%'
group by substring(v.Description,1,3)
order by [CU]
go

The result is:

hotfix

No surprise, CU1 has the most hotfixes, doesn’t this conform to people’s “superstition” that it is better to wait for the first CU (or SP) before putting the new version into production?

Summary

In this tip, we have explored a way to do some SQL Server CU / Hotfix release history analysis based on public data from Microsoft.

Different calculations with different perspectives may give us different insights into our SQL Server stability concerns. But generally speaking, the later you adopt a new version, the more stable the product is as this can be shown in the daily average bugs data (Figure 3).

Of course, this method of calculation is not necessarily satisfactory, for example, security hotfixes are not counted, hotfixes inside Service Pack are not calculated, etc. Also, the hotfixes do not necessarily carry the same weight of importance, for example, some hotfixes are just about adding a new feature instead of really fixing an issue.

Next Steps

You can build your own questions and try to get the answer from the data. For example, you can count the hotfixes of each FixArea (i.e. SSRS, SSAS, SQL Server Database Engine, etc.) and compare them between different Service Packs or SQL Server versions.

This tip is based on the previous tips and you may review them here.



sql server categories

sql server webinars

subscribe to mssqltips

sql server tutorials

sql server white papers

next tip



About the author
MSSQLTips author Jeffrey Yao Jeffrey Yao is a senior SQL Server consultant, striving to automate DBA work as much as possible to have more time for family, life and more automation.

This author pledges the content of this article is based on professional experience and not AI generated.

View all my tips


Article Last Updated: 2018-05-22

Comments For This Article

















get free sql tips
agree to terms