This is a follow up from an earlier blog "Finding Index usage". In that blog, I described a very simple way to list how indexes are being used. In this blog, the query is much extended so that it now shows your Navision keys, listed by either number of updates, or by their cost divided by their usage, and it shows when an index was last used for reading. The idea is to show a list of indexes that are being maintained, but never or rarely being used.
The query uses SQL Server Dynamic Managament Views (DMW), which means it will only work for for SQL Server 2005 and later.
Feel free to add comments to this blog about how useful (or not) this query is. And about any problems you may find, and suggestions to improve it. All comments will be welcome!
To use it, copy the query below into SQL Server Management Studio. Remember to set the database to your Microsoft Dynamics NAV database (not Master or any other database). Then run it. Depending on the size of your database, it may take a few minutes to run it. First time you run it, I would recommend that you do it when the SQL Server is not otherwise busy, until you konw how long it takes:
-- use NavisionDB
IFOBJECT_ID ('z_IUQ_Temp_Index_Keys', 'U') IS NOT NULL
DROP TABLE z_IUQ_Temp_Index_Keys;
IFOBJECT_ID ('zIUQ_Temp_Index_Usage', 'U') IS NOT NULL
DROP TABLE zIUQ_Temp_Index_Usage
-- Generate list of indexes with key list
[F_Obj_ID] [int]NOT NULL,
[F_Obj_Name] [nvarchar](128) NULL,
[F_Ind_ID] [int]NOT NULL,
[Index_Column_ID] [int]NOT NULL,
[Index_Key] [nvarchar](128) NULL,
[Index_Key_List] [nvarchar](MAX) NULL,
CONSTRAINT [z_IUQ_TempPK] PRIMARY KEY(
-- populate key string
declareIndexCursor cursor FOR
select F_Obj_ID, F_Ind_ID from z_IUQ_Temp_Index_Keys
FORUPDATE of Index_Key_List
set@KeyString = NULL
fetchnext from IndexCursor into @ObjID, @IndID
while@@fetch_status = 0 begin
SET @KeyString = ''
SELECT @KeyString = COALESCE(@KeyString, '') + Index_Key + ', '
where F_Obj_ID = @ObjID and F_Ind_ID = @IndID
ORDER BY F_Ind_ID, Index_Column_ID
SET @KeyString = LEFT(@KeyString,LEN(@KeyString) - 2)
set Index_Key_List = @KeyString
where current of IndexCursor
fetch next from IndexCursor into @ObjID, @IndID
-- Generate list of Index usage
[F_Table_Name] [nvarchar](128) NOT NULL,
[F_Ind_ID] [int]NOT NULL,
[F_Index_Name] [nvarchar](128) NULL,
[Index_Type] [nvarchar](56) NOT NULL,
[Index_Keys] [nvarchar](255) NULL
US.user_seeks + US.user_scans + US.user_lookups User_Reads,
when (ISNULL(US.last_user_seek,'00:00:00.000') >= ISNULL(US.last_user_scan,'00:00:00.000')) and (ISNULL(US.last_user_seek,'00:00:00.000') >= ISNULL(US.last_user_lookup,'00:00:00.000')) then US.last_user_seek
when (ISNULL(US.last_user_scan,'00:00:00.000') >= ISNULL(US.last_user_seek,'00:00:00.000')) and (ISNULL(US.last_user_scan,'00:00:00.000') >= ISNULL(US.last_user_lookup,'00:00:00.000')) then US.last_user_scan
fromsys.dm_db_index_usage_stats US, sys.indexes SI where SI.object_id = US.object_id and SI.index_id = US.index_id
orderby No_Of_Updates desc
-- Select and join the two tables.
when TIU.User_Reads = 0 then TIU.No_Of_Updates
else TIU.No_Of_Updates / TIU.User_Reads
fromzIUQ_Temp_Index_Usage TIU, z_IUQ_Temp_Index_Keys TIK where TIK.F_Obj_Name = TIU.F_Table_Name and TIK.F_Ind_ID = TIU.F_Ind_ID and TIK.Index_Column_ID = 1
andTIU.F_Table_Name not in ('zIUQ_Temp_Index_Usage','z_IUQ_Temp_Index_Keys')
orderby No_Of_Updates desc
--order by Cost_Benefit desc
The query will show you one line for each index in the SQL Database. It shows you the table name, and a list of fields in the index. Note that any non-clustered index also contain the clustered index. For example on SQL Server, the key "Document No." in the "Cus. Ledger Entry table" is "Document No.","Entry No.". Also note that the indexes shown by SQL Server is not always shown in the same order as you have defined them in NAV.
The column "No_Of_Updates" basically shows you the cost of this index, since every update requires a lock as well as a write to the database. The next column, "User_Reads", shows you how often this index has been used, either from the UI, or by C/AL code. Compare these two, and you have way to compare the cost against the benefits of each index, as shown in the column "Cost_Benefit", which is simply "No_Of_Updates" / "User_Reads". The column "Last_Used_For_Reads" shows you when an index was actually used for reading.
The query sorts the indexes by "No_Of_Updates", with the most updated (most costly) index first. At the last line of the query you can change the sorting to "order by Cost_Benefit desc", and you are likely to see a different picture.
Finally, the query shows you whether each index is clustered or non-clustered.
The query will create two new tables called "z_IUQ_Temp_Index_Keys" and "zIUQ_Temp_Index_Usage". Although highly unlikely, if you already have tables with these names in your database, then the query will overwrite those without warnings. These tables collect index usage statistics, so if you need to run the query again, for example because you lost the results, or wat to run it with a different sorting, you don't have to run the whole query. Just run the last part of the query - from the section "-- Select and join the two tables.", and it will run much faster. Only after you change indexes, or want an updated view of index usage, you need to run the whole query again.
The data shown by the query is reset every time SQL Server restarts. So if you have recently restarted SQL Server, then the query may not show you the most precise picture of how the indexes are being used over time. Also consider that some indexes may only ever be used for example at end of the month / end of fiscal year, etc. So just because the query shows that a certain index was not used since SQL Server was last restarted, then this index may still be required for specific jobs.
These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.