Index Usage Query
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
IF
OBJECT_ID ('z_IUQ_Temp_Index_Keys', 'U') IS NOT NULL
DROP TABLE z_IUQ_Temp_Index_Keys;
IF
OBJECT_ID ('zIUQ_Temp_Index_Usage', 'U') IS NOT NULL
DROP TABLE zIUQ_Temp_Index_Usage
-- Generate list of indexes with key list
create
table z_IUQ_Temp_Index_Keys(
[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(
[F_Obj_ID]
,
[F_Ind_ID]
,
[Index_Column_ID]
)
)
Insert
into z_IUQ_Temp_Index_Keys
select
object_id,
object_name(object_id),
index_id
,
Index_Column_ID
,
index_col(object_name(object_id),index_id,Index_Column_ID),
''
from
sys.index_columns
go
-- populate key string
declare
IndexCursor cursor FOR
select F_Obj_ID, F_Ind_ID from z_IUQ_Temp_Index_Keys
FOR
UPDATE of Index_Key_List
declare
@ObjID int
declare
@IndID int
DECLARE
@KeyString VARCHAR(MAX)
set
@KeyString = NULL
open
IndexCursor
set
nocount on
fetch
next from IndexCursor into @ObjID, @IndID
while
@@fetch_status = 0 begin
SET @KeyString = ''
SELECT @KeyString = COALESCE(@KeyString, '') + Index_Key + ', '
FROM z_IUQ_Temp_Index_Keys
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)
update z_IUQ_Temp_Index_Keys
set Index_Key_List = @KeyString
where current of IndexCursor
fetch next from IndexCursor into @ObjID, @IndID
end
;
close
IndexCursor;
deallocate
IndexCursor;
-- Generate list of Index usage
create
table zIUQ_Temp_Index_Usage(
[F_Table_Name] [nvarchar]
(128) NOT NULL,
[F_Ind_ID] [int]
NOT NULL,
[F_Index_Name] [nvarchar]
(128) NULL,
[No_Of_Updates] [int]
NULL,
[User_Reads] [int]
NULL,
[Last_Used_For_Reads] [datetime]
NULL,
[Index_Type] [nvarchar]
(56) NOT NULL,
[last_user_seek] [datetime]
NULL,
[last_user_scan] [datetime]
NULL,
[last_user_lookup] [datetime]
NULL,
[Index_Keys] [nvarchar]
(255) NULL
)
insert
into zIUQ_Temp_Index_Usage
select
object_name
(US.object_id) Table_Name,
US
.index_id Index_ID,
SI
.name Index_Name,
US
.user_updates No_Of_Updates,
US
.user_seeks + US.user_scans + US.user_lookups User_Reads,
case
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
else US.last_user_lookup
end
as Last_Used_For_Reads,
SI
.type_desc Index_Type,
US
.last_user_seek,
US
.last_user_scan,
US
.last_user_lookup,
''
from
sys.dm_db_index_usage_stats US, sys.indexes SI where SI.object_id = US.object_id and SI.index_id = US.index_id
order
by No_Of_Updates desc
go
-- Select and join the two tables.
select
TIU
.F_Table_Name Table_Name,
--TIU.F_Ind_ID Index_ID,
--TIU.F_Index_Name Index_Name,
TIK
.Index_Key_List,
TIU
.No_Of_Updates,
TIU
.User_Reads,
case
when TIU.User_Reads = 0 then TIU.No_Of_Updates
else TIU.No_Of_Updates / TIU.User_Reads
end
as Cost_Benefit,
TIU
.Last_Used_For_Reads,
TIU
.Index_Type
from
zIUQ_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
and
TIU.F_Table_Name not in ('zIUQ_Temp_Index_Usage','z_IUQ_Temp_Index_Keys')
order
by 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.
Lars Lohndorf-Larsen
Escalation Engineer
These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.
Anonymous
March 27, 2008
SQL Server 2005 keeps (some) history of the number and time that blocking occurred on indexes. This blogAnonymous
March 27, 2008
SQL Server 2005 keeps (some) history of the number and time that blocking occurred on indexes. This blogAnonymous
June 11, 2008
Please refer to this post about what I mean with "modern troubleshooting". This post describes methodsAnonymous
June 12, 2008
Please refer to this post about what I mean with "modern troubleshooting". This post describes