Recently, I’ve needed to optimize some SQL Server queries. I decided to look out there what to do to tune SQL Server and SQL queries. This is what I found.
At the database level, turn on automatic update of statistics, increase the file size autogrowth and update the compatibility level. At the table level, delete your unused indexes and create the missing ones, keeping around 5 indexes per table. And, at the query level, find and fix implicit conversions.
While looking up what I could do to tune my queries, I found Pinal Dave from SQLAuthority. Chances are you have already found one of his blog posts when searching for SQL Server tuning tips. He’s been blogging about the subject for years.
These are six tips from Pinal’s blog and online presentations I’ve applied recently. Please, test these changes in a development or staging environment before making anything on your production servers.
1. Enable automatic update of statistics
Turn on automatic update of statistics. You should turn it off if you’re updating a really long table during your work-hours.
This is how to enable automatic update of statistic update. [Source]
USE<YourDatabase>;GO-- Enable Auto Create of StatisticsALTERDATABASE<YourDatabase>SETAUTO_CREATE_STATISTICSONWITHNO_WAIT;-- Enable Auto Update of StatisticsALTERDATABASE<YourDatabase>SETAUTO_UPDATE_STATISTICSONWITHNO_WAIT;GO-- Update Statistics for whole databaseEXECsp_updatestatsGO
2. Fix File Autogrowth
Add size and file growth to your database. Make it your weekly file growth. Otherwise, change it to 200 or 250MB.
From SQL Server Management Studio, to change the file autogrowth, go to your database properties and then to Files. Click on the three dots in the Autogrowth column. And, change the file growth.
3. Find and Fix Implicit conversions
Implicit conversions happen when SQL Server needs to convert between two data types in a WHERE or in JOIN.
For example, the query below with OrderNumber as a VARCHAR(20) has implicit warning when we compare it to a INT parameter.
To run this query, SQL Server has to go through all the rows in the dbo.Orders table to convert the OrderNumber from VARCHAR(20) to INT.
To decide when implicit conversion happens, you can check Microsoft Data Type Precedence table. Types with lower precedence convert to types with higher precedence. For example, VARCHAR will be always converted to INT and to NVARCHAR.
Use the below script to indentify queries with implicit conversion. [Source].
After updating your SQL Server, make sure to update the compatibility level of your database to the highest level supported by the current version of your SQL Server.
You can change your SQL Server compatibility level using SQL Server Management Studio or with TSQL query. [Source].
Create your missing indexes. But, don’t create them all. Create the first 10 missing indexes in your database. Stick to having around 5 indexes per table.
You can use the next script to find the missing indexes in your database. [Source]. But, don’t blindly add new indexes. Check the indexes you already have and the estimated impact of the missing indexes.
SELECTTOP25dm_mid.database_idASDatabaseID,dm_migs.avg_user_impact*(dm_migs.user_seeks+dm_migs.user_scans)Avg_Estimated_Impact,dm_migs.last_user_seekASLast_User_Seek,OBJECT_NAME(dm_mid.OBJECT_ID,dm_mid.database_id)AS[TableName],'CREATE INDEX [IX_'+OBJECT_NAME(dm_mid.OBJECT_ID,dm_mid.database_id)+'_'+REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.equality_columns,''),', ','_'),'[',''),']','')+CASEWHENdm_mid.equality_columnsISNOTNULLANDdm_mid.inequality_columnsISNOTNULLTHEN'_'ELSE''END+REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.inequality_columns,''),', ','_'),'[',''),']','')+']'+' ON '+dm_mid.statement+' ('+ISNULL(dm_mid.equality_columns,'')+CASEWHENdm_mid.equality_columnsISNOTNULLANDdm_mid.inequality_columnsISNOTNULLTHEN','ELSE''END+ISNULL(dm_mid.inequality_columns,'')+')'+ISNULL(' INCLUDE ('+dm_mid.included_columns+')','')ASCreate_StatementFROMsys.dm_db_missing_index_groupsdm_migINNERJOINsys.dm_db_missing_index_group_statsdm_migsONdm_migs.group_handle=dm_mig.index_group_handleINNERJOINsys.dm_db_missing_index_detailsdm_midONdm_mig.index_handle=dm_mid.index_handleWHEREdm_mid.database_ID=DB_ID()ORDERBYAvg_Estimated_ImpactDESCGO
6. Delete most of your indexes
Indexes reduce perfomance all the time. They reduce performance of inserts, updates, deletes and selects. Even if a query isn’t using an index, it reduces performance of the query.
Delete most your indexes. Identify your main table and check if it has more than 5 indexes. But, don’t create indexes on every key of a JOIN.
Also, keep in mind if you rebuild an index for a table, SQL Server will remove all plans cached related to that table.
Rebuilding your indexes is the most expensive way of updating statistics.
You can find your unused indexes with the next script. [Source]. Look for indexes with zero seeks/scans and lots of updates. They’re good candidates to drop.
SELECTTOP25o.nameASObjectName,i.nameASIndexName,i.index_idASIndexID,dm_ius.user_seeksASUserSeek,dm_ius.user_scansASUserScans,dm_ius.user_lookupsASUserLookups,dm_ius.user_updatesASUserUpdates,p.TableRows,'DROP INDEX '+QUOTENAME(i.name)+' ON '+QUOTENAME(s.name)+'.'+QUOTENAME(OBJECT_NAME(dm_ius.OBJECT_ID))AS'drop statement'FROMsys.dm_db_index_usage_statsdm_iusINNERJOINsys.indexesiONi.index_id=dm_ius.index_idANDdm_ius.OBJECT_ID=i.OBJECT_IDINNERJOINsys.objectsoONdm_ius.OBJECT_ID=o.OBJECT_IDINNERJOINsys.schemassONo.schema_id=s.schema_idINNERJOIN(SELECTSUM(p.rows)TableRows,p.index_id,p.OBJECT_IDFROMsys.partitionspGROUPBYp.index_id,p.OBJECT_ID)pONp.index_id=dm_ius.index_idANDdm_ius.OBJECT_ID=p.OBJECT_IDWHEREOBJECTPROPERTY(dm_ius.OBJECT_ID,'IsUserTable')=1ANDdm_ius.database_id=DB_ID()ANDi.type_desc='nonclustered'ANDi.is_primary_key=0ANDi.is_unique_constraint=0ORDERBY(dm_ius.user_seeks+dm_ius.user_scans+dm_ius.user_lookups)ASCGO
VoilĂ ! These are six tips I learned from Pinal Dave to start tuning your SQL Server. Pay attention to your implicit conversions. You can get a surprise.
I gained a lot of improvement only by fixing implicit conversions. In a store procedure, we had a NVARCHAR parameter to compare it with a VARCHAR column. Yes, implicit conversions happen between VARCHAR and NVARCHAR.