Adsense

Saturday, September 22, 2012

Identify Missing Indexes

Running the statement below will bring up a list of indexes which the SQL Server query optimizer believes would have produced a better query plan.  The list is ordered based on total cumulative benefit (for example a frequently run query with and index that would have improved performance by 25% would show up before an index for a one time query that would have improved performance by 50%).


-- get the missing indexes that would be beneficial for speeding up above queries
SELECT
      (avg_total_user_cost * (avg_user_impact)) * (user_seeks + user_scans) as total_cost_savings,
      [statement] AS full_object_name,
      d.equality_columns,
      d.inequality_columns,
      unique_compiles,
      avg_user_impact,
      avg_total_user_cost,
      user_scans,
      user_seeks
FROM  
      sys.dm_db_missing_index_groups G
      inner JOIN sys.dm_db_missing_index_group_stats GS ON G.index_group_handle = GS.group_handle
      inner JOIN sys.dm_db_missing_index_details D ON G.index_handle = D.index_handle
order by (avg_total_user_cost * (avg_user_impact)) * (user_seeks + user_scans) desc

Monday, September 17, 2012

Shrinking Excess Free Space From the Database

The script below will scan through all files in all the databases on a given instance and identify the files with the most free space.  It will shrink that file down in a small increment and then identify the next file with the most free space to shrink (might be that same file still).

I use this script after a big release to identify excess free space and remove it.

It also comes in hand when a drive is out of space and you need to clean it up fast.



SET NOCOUNT ON

DECLARE @dbname sysname
DECLARE @stmt varchar(1000)
DECLARE @shrink_increment integer
DECLARE @volume char(1)
DECLARE @space_to_free integer
DECLARE @FileID integer
DECLARE @TotalMB integer

SET @shrink_increment = 250 --Increment which you would like to shrink files in.
SET @space_to_free = 100000 --Total amount of space you want to free.
SET @volume = null --If you would like to shrink a particular drive just enter it here.  If you want to do all drives set it to null.

IF EXISTS (SELECT 1 FROM tempdb.dbo.sysobjects WHERE name like '##data%')
      DROP TABLE ##data

CREATE TABLE ##data (
      Fileid int NOT NULL,
      FileGroup int NOT NULL,
      TotalExtents int NOT NULL,
      UsedExtents int NOT NULL,
      Name sysname NOT NULL,
      FileName varchar(300) NOT NULL,
      DatabaseName sysname NULL,
      TotalMB integer NULL,
      UsedMB integer NULL)

DECLARE db_curs CURSOR FOR
SELECT name
FROM master.sys.databases
WHERE state = 0 --Only get ONLINE databases
and database_id > 4

OPEN db_curs
FETCH NEXT FROM db_curs INTO @dbname

WHILE @@FETCH_STATUS = 0
BEGIN
      SET @stmt = 'USE ' + @dbname + char(10) + 'DBCC showfilestats with no_infomsgs'

      INSERT INTO ##data (Fileid, FileGroup, TotalExtents, UsedExtents, Name, FileName)
      EXECUTE(@stmt)

      UPDATE ##data
      SET DatabaseName = @dbname
      WHERE DatabaseName is null

      SET @stmt = 'USE ' + @dbname + char(10) + 'dbcc sqlperf(logspace) with no_infomsgs'

      FETCH NEXT FROM db_curs INTO @dbname     
END

CLOSE db_curs
DEALLOCATE db_curs

UPDATE ##data
SET TotalMB = (TotalExtents * 64.0)/ 1024.0,
UsedMB = (UsedExtents * 64.0)/ 1024.0

WHILE @space_to_free > 0
BEGIN
      SELECT TOP 1
            @FileID = FileID,
            @TotalMB = TotalMB - @shrink_increment,
            @dbname = DatabaseName
      FROM ##data
      WHERE substring(FileName, 1, 1) = @volume or @volume is null
      and totalmb > 1000
      ORDER BY (cast(UsedMB as float)/cast(TotalMB as float))
     
      SET @stmt = 'USE ' + @dbname + char(10) + 'DBCC SHRINKFILE(' + cast(@FileID as varchar) + ', ' + cast(@TotalMB as varchar) + ')'

      PRINT @stmt

      execute(@stmt)

      Update ##data
      SET TotalMB = @TotalMB
      WHERE FileID = @FileID
      and DatabaseName = @dbname

      SET @space_to_free = @space_to_free - @shrink_increment
END

Monday, September 10, 2012

Identify likely candidates for indexing


Everytime that a query is executed by SQL Server and the query optimizer believes that a certain index would improve the query plan it makes records this information in the query plan.  All query plan which exist in the cache are stored in the management view dm_exec_cached_plans.  

You can pull out the 100 most commonly used plans which have potentially missing indexes using the query below.  You'll need to click on the xml to bring up the graphical query plan where the missing index will appear.  

If for some reason you just see the xml when clicking the link save of the xml with the extension .queryplan and double click on it and you should see the graphical representation.


SELECT TOP 100
st.text,
cp.cacheobjtype,
cp.objtype,
DB_NAME(st.dbid)AS DbName,
cp.usecounts,
qp.query_plan
FROM sys.dm_exec_cached_plans cp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp
WHERE CAST(qp.query_plan AS NVARCHAR(MAX))LIKE '%<MissingIndexes>%'
ORDER BY cp.usecounts DESC

Thursday, September 6, 2012

Script out all permissions on a SQL Server Database

In SQL Server Management Studio put your results into text only (shortcut CTRL + T) and run the following script.  Save the results off and you will have a backup of all permissions in your databases.  I use this script when I migrate databases between instances.



/*
This script will script out the following permissions for all user databases:
      Database users
      Database roles
      Database role member
      Table level permissions
*/

set nocount on

declare @DBName varchar(100)

DECLARE curs CURSOR for
SELECT distinct name
FROM master.dbo.SYSDATABASES



OPEN CURS

FETCH Next FROM CURS into @dbname

WHILE @@FETCH_STATUS = 0
BEGIN    
       
      EXEC('        
       SELECT ''IF NOT EXISTS(SELECT 1 FROM ' + @DBName + '..sysusers where name='''''' + sysusers.Name COLLATE Latin1_General_CI_AS + '''''')'' + CHAR(13) +  ''EXEC ' + @DBName +'..sp_grantdbaccess '''''' + syslogins.name + '''''', '''''' + sysusers.name + ''''''''+ char(10) + ''go''        
        FROM ' + @DBName + '..sysusers sysusers        
        JOIN master..syslogins syslogins        
          ON syslogins.sid = sysusers.sid
       WHERE issqlrole=0        
         AND sysusers.Name Not in (''Guest'', ''dbo'')        
      ')    
         
      --Roles    
      EXEC('    
      SELECT ''IF NOT EXISTS(SELECT 1 FROM ' + @DBName + '..sysusers where name='''''' + Name + '''''' and issqlrole=1)'' + CHAR(13) + ''exec ' + @DBName + '..sp_addrole '''''' + Name + '''''', ''''dbo''''''+ char(10) + ''go''    
        FROM ' + @DBName + '..sysusers     
       WHERE issqlrole=1     
         AND name not like ''db_%''    
      ')    
           
      --Role Members        
           
      EXEC('        
      SELECT ''EXEC ' + @DBName + '..sp_addrolemember '''''' + role.name + '''''', '''''' + member.name + ''''''''+ char(10) + ''go''        
        FROM ' + @DBName + '..sysmembers sysmembers        
        JOIN ' + @DBName + '..sysusers role        
          ON groupuid=role.uid        
        JOIN ' + @DBName + '..sysusers member        
          ON memberuid=member.uid        
       WHERE member.name<>''dbo''        
      ')     
           
      --Object Permissions        
      EXEC('        
      SELECT ''USE ' + @DBName + ''' + CHAR(13) +     
       ''IF EXISTS(SELECT 1 FROM ' + @DBName + '..sysobjects where name='''''' + o.name COLLATE Latin1_General_CI_AS  + '''''')'' + CHAR(13) +      
       ''GRANT '' + v.name + '' ON ['' + u2.name + ''].['' + o.name + ''] TO ['' + u1.name + '']
go''    
        FROM ' + @DBName + '..sysprotects p          
        JOIN master..spt_values v        
          ON action = v.number         
         AND v.type = ''T''   
        JOIN ' + @DBName + '..sysobjects o        
          ON o.id = p.id        
        JOIN ' + @DBName + '..sysusers u1         
          ON u1.uid=p.uid        
        JOIN ' + @DBName + '..sysusers u2        
          ON u2.uid=o.uid        
       WHERE p.id > 100        
         AND protecttype IN  (204,205)        
      ')    
           

      FETCH Next FROM CURS into @dbname
END   
   
close curs    

DEALLOCATE curs

Tuesday, September 4, 2012

Retrieve Query Plan From a SPID

Have you ever had the need to view a query plan in SQL Server for a slow running process put weren't sure how to pull it up?  The query below will bring up the query plan for you.  Simply update @spid with the spid of the process you'd like to view the query plan for and execute.  Click on the blue XML and SQL Server Management Studio will bring up a a graphical view of the query plan.

DECLARE @spid AS INT;
SET @spid = 86;   -- UPDATE VARIABLE SPID ID

SELECT session.session_id, sql_text.text as most_recent_requested_query, plan_text.query_plan
FROM sys.dm_exec_sessions as session WITH (NOLOCK)
LEFT OUTER JOIN sys.dm_exec_requests as request WITH (NOLOCK) ON session.session_id = request.session_id
OUTER APPLY sys.dm_exec_sql_text(request.sql_handle) as sql_text
OUTER APPLY sys.dm_exec_query_plan(request.plan_handle) as plan_text
WHERE session.session_id = @spid;

If for some reason when you click on the XML it simply bring up the XML in a new query window you can save the xml in a file with the extension .sqlplan and then open the file.  SSMS should then display the graphical plan.