What happens on the SQL Server engine side when I'm deleting an index from one of my tables?
Details: I have a database running into production.
In this database, I have a query that creates deadlocks on a regular basis. I've found the query creating the deadlock, ran it on my computer, showing its execution plan. SQL Server Management Studio proposes to add an index on one specific table.
The index makes sense to me but my problem is that, on this table I already have 3 indexes and, to be honest, I cannot be sure if they're properly used or if they've been created for a specific role.
I could simply add one more index on the table but I'm concerned about the cost I'll pay each time I add/update/delete data on the table.
I made a few attempts on my machine and it seems that I need to delete at least two other indexes to make the engine select the index I'm creating today (looks odd to me). As soon as I force the engine to take my index (because I deleted everything else), the query runs 10 times faster.
Can I simply use the DROP Index command without much problem? I don't have to rebuild or anything?
A non-clustered index is a secondary data structure - it's not "integrated" into the main data structure of the clustered index or heap.
As such, deleting one should be a relatively fast and pain-free experience, since all the system should need to do is remove metadata about the index and mark the index's pages as unallocated.
There shouldn't be a need to "rebuild" or do anything else.
You can run this query to get know whether the indexes of a table are used or not
SELECT TOP 50
o.name AS ObjectName
, i.name AS IndexName
, i.index_id AS IndexID
, dm_ius.user_seeks AS UserSeek
, dm_ius.user_scans AS UserScans
, dm_ius.user_lookups AS UserLookups
, dm_ius.user_updates AS UserUpdates
, p.TableRows
, 'DROP INDEX ' + QUOTENAME(i.name)
+ ' ON ' + QUOTENAME(s.name) + '.'
+ QUOTENAME(OBJECT_NAME(dm_ius.OBJECT_ID)) AS 'drop statement'
FROM sys.dm_db_index_usage_stats dm_ius
INNER JOIN sys.indexes i ON i.index_id = dm_ius.index_id
AND dm_ius.OBJECT_ID = i.OBJECT_ID
INNER JOIN sys.objects o ON dm_ius.OBJECT_ID = o.OBJECT_ID
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
INNER JOIN (SELECT SUM(p.rows) TableRows, p.index_id, p.OBJECT_ID
FROM sys.partitions p GROUP BY p.index_id, p.OBJECT_ID) p
ON p.index_id = dm_ius.index_id AND dm_ius.OBJECT_ID = p.OBJECT_ID
WHERE OBJECTPROPERTY(dm_ius.OBJECT_ID,'IsUserTable') = 1 and o.name='your_table_name'
AND dm_ius.database_id = DB_ID()
AND i.type_desc = 'nonclustered'
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0
ORDER BY (dm_ius.user_seeks + dm_ius.user_scans + dm_ius.user_lookups) ASC
GO
Then go with the DROP index statement if any of them are not used.
Before dropping, ensure the server has been UP for a long time enough so the indexes got the chance to get used.
Also, having three indexes on a table is not many. Just check they are useful to your queries.
Related
In order to create a Dashboard I need to work on a database that I do not have information about. The data in the DataBase is updated through a particular management interface and I do not know where it is updated on the DB. Is there a way to check for data updates without the tables names or columns names?
Thanks
I agree that knowing nothing about the database will not get you very far, but you can get some information about table updates without knowing what you're looking for. Whether or not it's useful is a different question.
The included code leverages sys.dm_db_index_usage_stats to identify the last time a user update was performed on the table. This works for heaps as well as indexed tables and the user_update value includes inserts, deletes, and updates. The sys.dm_db_index_usage_stats view will include information only for tables that have been interacted. To work around that I UNION ALL with a second query to get the relevant object information for tables that aren't found in sys.dm_db_index_usage_stats. This gives a complete view of the table objects regardless of whether or not they've been used since the service started. You may not care about that at all and could strip it out.
Again, this may not be helpful, but your question was just "Is there a way to check for data updates without the tables names or columns names?" and the answer to that specific question is yes.
Caveats:
The user_update value is simply an incrementing counter of update actions. This will not let you know what the update was or how many updates have occured since X point in time, but it will let you know the last time a table was updated.
This information does not persist beyond a service restart, so if a table was updated right before a restart, you wouldn't know.
The provided script is database specific, meaning it only returns information about the datbase it runs in. You could use something like sp_MSforeachdb to run against everything though.
And the code...
SELECT * FROM
(
SELECT
##servername as servername
, DB_NAME(database_id) as DatabaseName
, u.object_id
, SchemaName = OBJECT_SCHEMA_NAME(u.object_id, database_Id)
, TableName = OBJECT_NAME(u.object_id, database_id)
, Writes = SUM(user_updates)
, LastUpdate = CASE WHEN MAX(u.last_user_update) IS NULL THEN CAST('17530101' AS DATETIME) ELSE MAX(u.last_user_update) END
FROM sys.dm_db_index_usage_stats u
JOIN sys.indexes i
ON u.index_id = i.index_id
AND u.object_id = i.object_id
WHERE u.database_id = DB_ID()
GROUP BY database_id, u.object_id
UNION ALL
SELECT
##servername as servername
, DB_NAME() as DatabaseName
, o.object_id
, OBJECT_SCHEMA_NAME(o.object_id, db_id())
, object_name(o.object_id, db_id())
, 0
, CAST('17530101' AS DATETIME)
FROM sys.indexes i
JOIN sys.objects o ON i.object_id = o.object_id
WHERE o.type_desc in ('user_table')
and i.index_id NOT IN (select s.index_id from sys.dm_db_index_usage_stats s where s.object_id=i.object_id
and i.index_id=s.index_id and database_id = db_id(db_name()) )
) AS temp
ORDER BY writes DESC
I have created indexes on my DB the last 2 months. Everything works fine. However, now I have to move those indexes to the UAT ENV. I kept track of the indexes I created. However I want to be sure I didn't miss any. How do I know the indexes created in the last 2 months using a query?
SQL Server does not maintain index creation information for indexes in a DMV. This is not information you can query.
You can get this information for PK, unique indexes or unique constraints.
See Kendra Little's blog here for some good info on this topic.
From Kendra's post:
But if you’re not looking for the create date of a Primary Key, unique index, or unique constraint, you’re out of luck.
What you probably need is to check the indexes in the database through the catalog views. Maybe something like this can help you:
SELECT
t.name AS TableName,
ind.name AS IndexName,
col.name AS ColumnName,
STATS_DATE(t.object_id,ind.index_id)
FROM sys.indexes ind
INNER JOIN sys.index_columns ic ON ind.object_id = ic.object_id and ind.index_id = ic.index_id
INNER JOIN sys.columns col ON ic.object_id = col.object_id and ic.column_id = col.column_id
INNER JOIN sys.tables t ON ind.object_id = t.object_id
WHERE STATS_DATE(t.object_id,ind.index_id) > DATEADD(mm,-2,GETDATE())
ORDER BY TableName, IndexName, ColumnName
Remember to run in the database you have to check your indexes and not in the master.
Thanks for all the responses. There is really no good way to find the indexes created in last two months. The best way is to load the indexes in a table and compare them with DEV Indexes.
SQL Server 2012
I wanted to compress tables and indexes. I did a search to find tables that weren't compressed and manually checked accuracy of script by looking at table properties/storage prior to compressing. I generated scripts for tables as follows:
ALTER TABLE [R_CompPen].[CP2507BodySystem]
REBUILD WITH (DATA_COMPRESSION=PAGE);
After the script ran I verified compression through SMS however, the script I ran to find the uncompressed tables and generate scripts still showed them as uncompressed.
So the question is why didn't the Alter Table script update system tables and if it actually is but showing indexes, how can the script be written to only show tables and conversely a separate script to only show indexes?
SELECT distinct 'ALTER TABLE ['
+ sc.[name] + '].[' + st.[name]
+ '] REBUILD WITH (DATA_COMPRESSION=PAGE);'
FROM sys.partitions SP
INNER JOIN sys.tables ST ON st.object_id = sp.object_id
INNER JOIN sys.Schemas SC on sc.schema_ID = st.schema_ID
WHERE sp.data_compression = 0
The 'DISTINCT' is the culprit here. Once you have multiple indexes, you also have multiple entries in sys.partitions. But the distinct hides the other entries.
Here I have a table called Album with 2 indexes, which I compressed using
ALTER TABLE Album REBUILD WITH (DATA_COMPRESSION = PAGE);
After running this statement, the non clustered index remains uncompressed and keeps appearing in the list.
EDIT:
Turns out that when you only want to know about table level compression, you simply filter for index_id 0 or 1. Higher numbers refer to non clustered indexes. Shameless copy from Barguast's solution on his own question:
SELECT [t].[name] AS [Table], [p].[partition_number] AS [Partition],
[p].[data_compression_desc] AS [Compression]
FROM [sys].[partitions] AS [p]
INNER JOIN sys.tables AS [t] ON [t].[object_id] = [p].[object_id]
WHERE [p].[index_id] in (0,1)
I'm a little worried about the default retention period in SQL Server 2008 Change Tracking (which is 2 days).
Is it a good idea to set this period to eg. 100 years and turn auto cleanup off or will it bite me back in the future with excessive storage usage and/or performance degradation? Anyone has experience in that matter?
If you set auto cleanup off, it's best to periodically go through and remove the change tracking information yourself, by disabling and then re-enabling change tracking for each table. Otherwise, yes, the tracking data will continue to grow and grow.
You can't query the underlying tables directly, but you can poke at their metadata. The following query shows relative row counts:
select
s.name as schema_name
, t.name as table_name
, (select sum(rows) from sys.partitions x where o.parent_object_id = x.object_id) as rows_in_base_table
, o.name as tracking_table
, p.rows as rows_in_tracking_table
from sys.objects o
join sys.tables t on o.parent_object_id = t.object_id
join sys.schemas s on t.schema_id = s.schema_id
join sys.partitions p on o.object_id = p.object_id
where o.name like 'change[_]tracking%'
and o.schema_id = schema_id('sys')
order by schema_name, table_name
Run that in your database, and you should get a rough sense of current overhead.
The change tracking tables all follow a standard schema. For example:
select
c.name, c.column_id
, type_name(user_type_id) as type_name
, c.max_length, c.precision, c.scale
, c.is_nullable, c.is_identity
from sys.columns c
where object_id = (
select top 1 object_id from sys.objects o
where o.name like 'change[_]tracking%'
and o.schema_id = schema_id('sys')
)
The k_% columns vary by table and correspond to the primary keys of the tracked table. You are looking at a base minimum overhead of 18 bytes + (primary key length) per row. That adds up!
For example, I'm tracking some skinny base tables that are only 15 bytes wide, with a 7-byte composite key. That makes the tracking tables 18+7=25 bytes wide!
How do I list tables without indexes in my SQL 2008 database?
Edit
I want the Schema name and the Table name.
This should cover what your looking for. i.e. tables that are heaps (no clustered index) and do not have any non-clustered indexes. It uses the new sys. table objects used in 2005/2008.
in addition, you probably want to look for tables that do have a clustered index, but have no nonclustered indexes (this is the 2nd part of the statement which I've left commented out.
SELECT
schemaname = OBJECT_SCHEMA_NAME(o.object_id)
,tablename = o.NAME
FROM sys.objects o
INNER JOIN sys.indexes i ON i.OBJECT_ID = o.OBJECT_ID
-- tables that are heaps without any nonclustered indexes
WHERE (
o.type = 'U'
AND o.OBJECT_ID NOT IN (
SELECT OBJECT_ID
FROM sys.indexes
WHERE index_id > 0
)
)
-- OR
-- table that have a clustered index without any nonclustered indexes
--(o.type='U'
-- AND o.OBJECT_ID NOT IN (
-- SELECT OBJECT_ID
-- FROM sys.indexes
-- WHERE index_id>1))
Here's an example:
select SCHEMA_NAME(schema_id), name from sys.tables
where OBJECTPROPERTY(object_id, 'IsIndexed')= 0
In addition to #Philip Fourie's suggestion you might want to think about which indexes to create.
Once you have been accessing your data, SQL Server 2008 keeps track of places where it thinks indexes will be helpful (it refers to these as "missing indexes." There are a hand full of new Dynamic Managed Views which can show these missing indexes and some info about them.
From MSSQlTips:
sys.dm_db_missing_index_details - Returns detailed information about a missing index
sys.dm_db_missing_index_group_stats - Returns summary information about missing index groups
sys.dm_db_missing_index_groups - Returns information about a specific group of missing indexes
sys.dm_db_missing_index_columns(index_handle) - Returns information about the database table columns that are missing for an index. This is a function and requires the index_handle to be passed.
select shema = s.name, table_name = o.name
from sys.objects o
join sys.schemas s on o.schema_id = s.schema_id
where type = 'U'
and not exists (select i.index_id
from sys.indexes i
where i.type <> 0 --ignore default heap index row
and o.object_id = i.object_id )
Edit:
I have updated the SQL to include the Schema name as requested. (Note I had to sys.objects instead of sysobjects to cater for schemas that were introduced in SQL 2005)
The catalog tables are documented in the SQL Server documentation, see this link.
This FAQ contains more samples and might also be useful.
Note that these are system tables and can change between SQL server versions, where possible rather use the system table-independent views called Information Schema Views.
This code gives all the details about the indexes for all the tables:
SELECT
sch.name AS [Schema],
obj.name AS TableName,
indx.name AS IndexName,
CASE
WHEN indx.type_desc = 'HEAP' THEN 'N/A'
ELSE indx.type_desc
END AS IndexType
FROM sys.objects obj
JOIN sys.indexes indx ON indx.object_id = obj.object_id
JOIN sys.schemas AS sch ON sch.schema_id = obj.schema_id
WHERE
obj.type = 'U'
ORDER BY
obj.name