Thursday, May 2, 2013

SQL Cursors and TempDB

During a meeting yesterday the question was asked "Aren't cursors stored in tempDB?"  My initial response was no they will be rendered in memory but the correct answer is..."It depends!" Let me preface the rest of this blog post by saying when possible use set-based operations rather than cursors as cursors may be a hit on performance, but as in all things SQL it is best to test.

According to Books Online, Insensitive/Static cursors create a temporary copy of the data being accessed by the cursor in TempDB.  Likewise a Keyset cursor will build a keyset containing the uniquely identifiying columns for the cursor in TempDB.  Other cursors Fast_Forward, Forward_Only, and Dynamic do not incur the overhead associated with storing all of the cursor data in TempDB.  By using the dmv, sys.dm_session_space_usage, I was able to view the page allocations in TempDB using the different cursor types.  I noticed that there were page allocations to TempDB no matter which type of cursor was chosen but there were far more page allocations for a Keyset cursor  versus a Fast_Forward or Static cursor.

When programming cursors I typically declare a cursor as Fast_Forward which creates a forward only static cursor.  This option performs better than other cursor options and seems to work well for me as I rarely have to move backwards through a cursor or modify the cursor data.  If I am updating or deleting data through a cursor then I will use a Forward_Only, Dynamic cursor.  I often have seen developers use the ISO syntax when creating cursors such as follows.

DECLARE cur_DBs CURSOR
FOR
SELECT Name
FROM sys.databases d
WHERE d.state_desc = 'ONLINE';

By omitting the T-SQL cursor model in the declaration statement a Keyset, optimistic, non-scrollable cursor is created which will have its keyset materialized in TempDB.

So there you have it.  Cursors will have some page allocations in TempDB but how many will depend on the type of cursor being declared.  When declaring cursors Fast_Forward cursors have less overhead and impact on TempDB.  So when reviewing or writing code remember to declare the type of cursor being created to reduce overhead.

Tuesday, March 5, 2013

Dynamically Setting Max Server Memory on Cluster

I was recently given the task of adding a new node to our one node cluster with two SQL instances.  Due to politics within the company what was originally going to be a two-node SQL cluster had lingered around as a single node cluster for over a year. The issue I was confronted with was how to maximize memory usage on both nodes of the cluster but ensure that the two instances do not compete for memory if they happen to be running on the same node.

The solution I came up with was to query the server to determine the physical node name that each of the SQL instances was running on.  If after running both queries it is determined that the instances are running on the same node then the max server memory for both servers is reduced to prevent contention.  Conversely, if both instances are running on different nodes then the max server memory is increased to leave just enough for the operating system.  I remembered that changing the max server memory on an instance causes a dump of the procedure cache so I had to add logic to determine if the max server memory needed to be changed or if it could be left as it was.

There are several ways to get the code I created to run at startup.  One option is to create a stored procedure that runs the code and to have the stored procedure run at startup for the instance.  To execute the procedure this way the server must be configured to scan for startup procedures and you must run sp_procoption to set the stored procedure to run at startup. I chose to create a SQL Agent job to run the code and set the job schedule to run at agent startup to keep from having to reconfigure the server.  This way is also more transparent for any future DBAs.

In order to change the max server memory setting I had to create a linked server on both servers that I was sure would have ALTER SETTINGS permission.

Below is the code I created.



DECLARE @standalonememory        int            = 125000
DECLARE @sharedmemory            int            = 55000
DECLARE @SQLRPTHost              nvarchar(50)
DECLARE @SQLDWHost               nvarchar(50) 
DECLARE @SQLRPTmaxmemory         int 
DECLARE @SQLDWmaxmemory          int 
 
--Retrieve the host name for SQLRPT host    
SELECT @SQLRPTHost = CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS nvarchar(50))--Returns Node Name

-- Retrieve the host name for SQLDW        
SELECT @SQLDWHost = ComputerNamePhysicalNetBIOS
FROM OPENQUERY (SYS_SQLDW,'SELECT CAST(SERVERPROPERTY(''ComputerNamePhysicalNetBIOS'') AS nvarchar(50)) AS ComputerNamePhysicalNetBIOS');

--Retrieve the max server memory on current server    
SELECT @SQLRPTmaxmemory = CAST(value  as int)     
FROM sys.configurations    
WHERE name = 'max server memory (MB)' 

--Retrieve the max server memory on remote server    
SELECT @SQLDWmaxmemory = value     
FROM OPENQUERY(SYS_SQLDW, 'SELECT CAST(value as int) AS value
       FROM sys.configurations
       WHERE name = ''max server memory (MB)''')    

IF @SQLRPTHost = @SQLDWHost            --Both instances running on same host       
  BEGIN           
       --Max Memory should be set to sharedmemory if not set it         
       IF @SQLRPTmaxmemory != @sharedmemory          
         BEGIN             
        
              EXEC sp_configure 'Max Server Memory', @sharedmemory             
              RECONFIGURE           

         END          --Check the memory setting for the SQLDW instance         

       IF @SQLDWmaxmemory != @sharedmemory
         BEGIN             
      
              EXEC ('sp_configure ''Max Server Memory'', ' + @sharedmemory + '; RECONFIGURE') AT SYS_SQLDW           
  
         END           

       END     
              ELSE --Servers are each running on their own node       
                BEGIN             
                    --Max Memory should be set to stand alone memory if not set it         
                     IF @SQLRPTmaxmemory != @standalonememory          

                       BEGIN             
                         
                           EXEC sp_configure 'Max Server Memory', standalonememory             
                           RECONFIGURE            

                       END         
                      
                       --Check the memory setting for the SQLDW instance         
                       IF @SQLDWmaxmemory != @standalonememory          

                         BEGIN             
                                
                                  EXEC ('sp_configure ''Max Server Memory'', ' + @standalonememory + '; RECONFIGURE') AT SYS_SQLDW           
                              

                           END           
                        END 

The job is then basically duplicated on the second SQL instance with the servers being reversed so SQLRPT becomes the remote server and SQLDW becomes the local server.

Wednesday, October 31, 2012

Who Did that? The Default Trace Knows

We had an issue at work the other day where a database name was changed from MyDatabase to MyDatabase DO NOT USE. This change caused the overnight ETL process to fail. The question immediately arose, “When did this change and who made the change?” Using the default trace in SQL Server I was quickly able to determine when and who made the change.

The default trace is a system trace that is enabled by default in SQL Server 2005,2008, and 2012. According to BOL this trace captures information mostly relating to configuration changes. The 2012 documentation mentions that this functionality will be removed in a future version and we should use extended events instead. I however, find the default trace particularly useful because it is not something that I have to configure and enable on a server-by-server basis . For those of you that have viewed the database disk usage report in Management Studio, you are already familiar with the default trace. The autogrow / autoshrink events section of the database disk usage report is pulled from the default trace information.

To view the default trace you can open it in SQL Profiler or use the function fn_trace_gettable to query the trace file. It should be noted that the default trace rolls over after restarts and after the trace file reaches 20MB. Only 5 trace files are maintained so on a busy system the default trace will not hold a lot of history but for the instance we had the other morning the default trace was perfect.

Here is a copy of the script I use to query the default trace. I choose to filter out the backup and restore information, event_id 115, to make the results easier to analyze.

    DECLARE @Path        NVARCHAR(250);

    SELECT
        @Path = REVERSE(SUBSTRING(REVERSE([path]),
        CHARINDEX('\', REVERSE([path])), 260)) + N'log.trc'       
    FROM    sys.traces
    WHERE   is_default = 1;

    SELECT DatabaseName,
        Filename,
        (Duration/1000)             AS Duration,
        t.StartTime,
        EndTime,
        EventClass,
        te.name
        TextData,
        LoginName,
        ApplicationName,
        Hostname,
        (IntegerData * 8.0/1024)    AS ChangeInSize
    FROM ::fn_trace_gettable(@Path, DEFAULT) t
    JOIN sys.trace_events te
        on t.EventClass = te.trace_event_id
    WHERE  t.StartTime > '20121028'
        AND EventClass != 115
    ORDER  BY t.StartTime DESC;


By passing the second parameter for fn_trace_gettable as “Default” the function reads the information from all of the trace files. Joining the trace table with the sys.trace_events system view allows me to pull the name of the trace event.
Here are the results after changing my database name from “Sample” to “Sample DO NOT USE”.

The default trace can be a very useful tool for finding information about your server instance. I urge you to investigate it further for yourself. Bear in mind when using the default trace that depending on service restarts and the level of activity on the server that information captured by the default trace may not stick around very long.

Thursday, October 25, 2012

SQL Server NUMA Surprise


Today I welcome myself back to my blog after months of neglect, sorry blog. 

Today as part of my MCM prep I decided to dive into Software NUMA and SQL Server.  I have read a lot of information regarding Software NUMA over the years but felt that I needed to dive deeper into NUMA to better understand exactly how and when to configure it. 

As part of my studies I came across two blog posts from Johnathan Kehayias (Blog | Twitter) regarding NUMA.  In his second blog on NUMA SQL Server and Soft NUMA  Johnathan does a great job of walking through how he calculated the CPU mask to divide a 24 CPU server into 4 NUMA nodes.  After reading through the blog post I was shocked to find out at the end that Johnathan discovered NUMA does not work as reported in BOL and MSDN. 
 
"The benefits of soft-NUMA include reducing I/O and lazy writer bottlenecks on computers with many CPUs and no hardware NUMA. There is a single I/O thread and a single lazy writer thread for each NUMA node. Depending on the usage of the database, these single threads may be a significant performance bottleneck. Configuring four soft-NUMA nodes provides four I/O threads and four lazy writer threads, which could increase performance."
After configuring 4 Soft NUMA nodes SQL Server still only created one lazy writer thread.  This is contrary to BOL which as you can see states that SQL Server will create a lazy writer for each Soft NUMA node.  In his post Johnathan references a post from Bob Dorr, an escalation engineer at Microsoft in the Product Support division, called How It Works: Soft NUMA, I/O Completion Thread, Lazy Writer Workers and Memory Nodes. In his post Bob explains that additional lazy writer threads are only created for Hard NUMA nodes and not Soft NUMA nodes.

I find this particularly interesting because this goes against everything I studied for the MCITP: Database Administrator certification.  Given this new information from Bob Dorr and Johnathan Kehayias, the next question is why would I implement Soft NUMA?  Soft NUMA will handle I/O completion threads.  When we think of I/O completion threads we would generally think this means writing data to transaction and data files but as Bob Dorr points out, I/O completion threads actually handle connection requests and TDS data. 

If you are considering implementing Soft NUMA or just want to learn more about NUMA I urge you to read the aforementioned blog posts from Johnathan and Bob.