Wednesday, February 5, 2014

Find the unused database tables in SQL Server

I have a customer right now that needs a LOT of cleanup... BIG fragmentation!  The tables have not been properly maintained, and the fragmentation really is tremendous.  We backed up the db and restored it to a dev server so that I could test out my 'solution'.  About 3 hours into it, I realized that I probably needed to revisit my approach.  At the very minimum, I wanted to exclude the older, unused objects. No sense spending any time on the tables they are not using.
How do I know which objects are not being used anymore?  This is a very quick way to show me all of the tables that have NOT been touched since the last service restart.

   USE DBName;
   SELECT        
       ao.[name] [Table],
       s.[name] [Schema],
       [create_date] [Created],
       [modify_date] [LastModified]
   FROM
        sys.all_objects ao JOIN sys.schemas s
          ON ao.schema_id = s.schema_id
   WHERE
        OBJECT_ID NOT IN (
             SELECT OBJECT_ID
             FROM sys.dm_db_index_usage_stats
       )
       AND [type] = 'U'
   ORDER BY
       [modify_date] DESC

  
Check this out for more detailed information on the sys.dm_db_index_usage_stats DMV:
             http://technet.microsoft.com/en-us/library/ms188755.aspx

Another thing to look for would be the empty tables.  Many times tables are created and developed in production, but never actually implemented.  You can use this newer post of mine to bring back all tables that are empty.  
   http://www.sqlfingers.com/2014/10/list-all-empty-tables-in-your-sql.html

REMEMBER -- just being empty does not necessarily mean it isn't used.  It could be a working table that is purged regularly as it is being used.  Keep that in mind before you go and drop something just because it is empty.

Read the SQL Server Database Transaction Log

Who deleted that data?! Or worse -- who dropped that table?!  It really frustrates me when something like this occurs on the servers that I am responsible for... yet I am unaware.  Here is a quick piece to show you that yes, we can read the database transaction log, and we can figure out who made that unauthorized change.
 
First we need to create a test table.

   -- create test table
   CREATE TABLE [Personnel] (
     [UserID] INT IDENTITY(1,1),
     [UserName] VARCHAR(55),
     [Dept] CHAR (25)
   );

Ok. Now let us look in the log file to see that new table.

   -- look in log for that table creation
   SELECT
     [Current LSN],
     [Operation],
     [Transaction Name],
     [SPID],
     [Begin Time]
   FROM  
     fn_dblog(NULL,NULL)
   WHERE 
     [Transaction Name] = 'CREATE TABLE'



Current LSN
Operation Transaction Name SPID BeginTime
00000020:00000f70:002c LOP_BEGIN_XACT CREATE TABLE 64 2014/02/05 12:10:41:093

Sweet!  Now let us write something into the table.

   -- write into test table

   INSERT dbo.Personnel (UserName,Dept)
   SELECT 'John Doe','Accounting'
   UNION
   SELECT 'Jane Doe','Mangement'

Look at our data:

   SELECT * FROM dbo.Personnel

Ok. Now we are going to do that unauthorized DELETE.

   /* delete something */

   DELETE dbo.Personnel
   WHERE UserName = 'Jane Doe'

Check the log... it is there.

   -- read the log
   SELECT
      [Transaction ID],
      [Operation],
      [Context],
      [AllocUnitName]
   FROM  
      fn_dblog(NULL,NULL)
   WHERE  
      Operation = 'LOP_DELETE_ROWS'
      AND AllocUnitName = 'dbo.Personnel'

Transaction IDOperationContextAllocUnitName
0000:00000557LOP_DELETE_ROWSLCX_HEAPdbo.Personnel

Now, we need to use that Transaction ID returned above to get the Transaction SID.
This will help us find the actual user who performed this action.

   -- get the transaction sid
   SELECT
      [Transaction ID],
      [Transaction SID],  
      [Operation],
      [AllocUnitName]
   FROM  
      fn_dblog(NULL,NULL)
   WHERE   
      [Transaction ID] = '0000:00000550'

 

Transaction IDTransaction SID  OperationAllocUnitName
0000:00000557 0x010500000000000515000000EFEE1C9FBC493493D723D1D8E8030000 LOP_DELETE_ROWS  dbo.Personnel

Ok. This is the gold! Now we use the Transaction SID to figure out who actually performed the delete!

   -- find the offending user

   USE master;
   SELECT SUSER_SNAME(0x010500000000000515000000EFEE1C9FBC493493D723D1D8E8030000) [Login]

Login
MyMachine\MyName
 
Alright. One better. Let us drop that Personnel table.

   -- drop table
   DROP TABLE dbo.Personnel

Just like before, let us get our Transaction ID, and then our Transaction SID.

   -- get transaction id
   SELECT
      [Transaction ID],
      [Transaction SID],
      [Transaction Name],
      [Operation],
      [Description]
   FROM  
      fn_dblog(NULL,NULL)
   WHERE
      [Transaction Name] = 'DROPOBJ'

And now... we find the guy who dropped the table.


   -- pass transaction sid to get user name

   USE master;
   SELECT SUSER_SNAME(0x010500000000000515000000EFEE1C9FBC493493D723D1D8E8030000) [Login]


It is undocumented, so you need to be cautious.  But, it is very easy method for reading into your transaction log files.

When was the last time your SQL Server Service was restarted?

Every DBA needs to know the last time the SQL Server Service was restarted! Best case, your restarts are scheduled, known events.  But... we all know that is not always the case.  How do you know when your service was last restarted?  See the statements below.  The first couple are using the newer DMV objects, and that last one is just looking at the create_date on the TempDB. No matter what version you have, that one will always work.

I am actually working with another DBA right now, across the country, on the same instance.  Waiting for him to apply a patch and restart the service.  This is great!  Now I can just run this, and know when he has restarted the service!

    -- 1. sys.dm_os_sys_info  
   SELECT sqlserver_start_time
   FROM sys.dm_os_sys_info; 

   -- 2. sys.dm_exec_sessions  
   SELECT login_time
   FROM sys.dm_exec_sessions
   WHERE session_id = 1; 

   -- 3. TempDB
  
   SELECT create_date
   FROM sys.databases
   WHERE name = 'tempdb'



Useful information regarding your SQL Server instance and available resources:
sys.dm_os_sys_info

Details regarding all active user connections: