1

Situation:
Large amount of data (millions to tens of millions of rows) in several tables that is not changed often, but when it does, there are usually lots of changes.

Tables each have a "summary" table associated with them that needs to be scheduled for update when any changes are made to main table.

Current Proposed Solution:
Update/Insert/Delete Trigger created on each main table that inserts the name of a table into a table called UpdatedTables.

A job is scheduled to run each night that checks UpdatedTables, updates the summary tables for the tables listed there, and then deletes the entries from UpdatedTables.

Questions:

  1. Does my solution make sense? Is there anything wrong with it?
  2. Could this cause issues if a large import/update was still going on when the job was scheduled?
  3. Would it be worth disabling the trigger after the first insert and then re-enabling it after the job runs at night? I know the per transaction cost is minimal, but I don't see the point in it executing 5 million times when it really needs to only run once.
6
  • Do you have a column in those tables (most likely timestamp) that can identify if table was modified without using trigger at all? Or use the solution described by Pinal Dave here. Commented Jun 1, 2017 at 16:30
  • @SqlWorldWide For updated and inserted, that might work, but not for deleted
    – Kevin
    Commented Jun 1, 2017 at 16:33
  • Why not? It will work for both heap and clustered index in case of delete. Check this code here. Commented Jun 1, 2017 at 16:52
  • @SqlWorldWide - the Pinal Dave solution would tell you the most recent time any of the table's indexes had been updated since the last restart. It's not hard to imagine a scenario where a 6 hour load/update gets run, then the server is restarted to apply patches, then the summary table update job runs - and thinks it has nothing to do.
    – RDFozz
    Commented Jun 1, 2017 at 23:22
  • @RDFozz totally agree and valid point. I thought the question was just to know the 1st instance of any change. May be running this in a short loop will be better than executing triggers millions of times. Commented Jun 1, 2017 at 23:33

1 Answer 1

2

Here are three four options:

Option 1 - consider how long the jobs that populate the summary tables take to run. If that time is not prohibitively long, then consider running the jobs nightly regardless of whether there's been a change or not. That actual frequency of changes, especially in the biggest tables, would influence your decision here.

Option 2 - @SQLWorldWide suggested an alternative from a Pinal Dave article; using the sys.dm_db_index_usage_stats view to detect the last time the table was changed. (Clustered indexes have to change whenever any column changes; for tables without a clustered index, a "dummy" index (index_id = 0) will be shown, again showing the time when anything was inserted, updated, or deleted in the table). However, this has one flaw - these views are reset each time the server is restarted. So, if the server was restarted five minutes before your job ran, you might not update any of the summary tables.

(See "UPDATE" below, for an even better alternative):

Again, depending on how long it takes to refresh all the summary tables, and how often your server is restarted, there is an alternative:

SELECT MAX(last_user_update) as last_change
  FROM (SELECT last_user_update
          FROM sys.dm_db_index_usage_stats
         WHERE database_id = DB_ID('<dbname>')
           AND OBJECT_ID=OBJECT_ID('<tablename>')
        UNION ALL
        SELECT sqlserver_start_time FROM sys.dm_os_sys_info
       ) q
;

(Replace <dbname> and <tablename> with the appropriate values for your database and table, of course.)

This query will return the time of the most recent change to <tablename> of the last time your server was restarted, whichever came last.

Have your job store the time and date when it started running as its last step, and retrieve that last run time as its first step. If the last run time is less than last_changed, then update the summary table.

As noted, this will mean that all the summary tables will be updated after each restart; if that is a major issue, then this option wil lprobably not work for you.

Option 3 - This is your idea, tweaked slightly.

Instead of writing the table name down each time the trigger runs, I'd have UpdatedTables have two columns (more if needed, for instance for the name of the procedure that updates the summary table, or whatever you'd need to run that process when appropriate): tablename, and is_changed, a bit column set to 0 by default. Your trigger would update the bit column to one where the tablename matched and the bit column was zero; after the summary tables are updated, just reset the bit column to 0.

Note that you'd want to lock the table while updating the summary table - otherwise, a change might happen during that update, and no record would be made of it. That's a problem that would hold with your solution as originally written if you prefer that - and is the only thing that jumps out at me as a possible problem with it.

If locking the table isn't an option (and it probably isn't, if a large import/export could be going on when the job that checks for updates runs), you could reset is_changed before updating the summary table. However, in that case, you'd want to be sure to set is_changed back to one if the summary table update failed for some reason, so it would get run again next time.

With the tweaked solution, hopefully the trigger would run very quickly - and, you don't run the risk of have millions of records in UpdatedTables that all say 'Name_of_Updated_Table' - that does consume disk space, after all.

The last few paragraphs answer your first two questions. I would not turn the trigger off while a large job is updating a table (if you are using a trigger), unless the job manually updates your table noting it was changed when it finishes. If the summary table update job ran while your large import update was running, but after your first change had run the trigger, the summary job would clear the update flag for the table - and the rest of the import/update wouldn't get flagged as a change.

UPDATE:

Option 4 - Use a combination of options 2 and 3.

Credit to @SQLWorldwide again, as his comment made me think of this option.

For this option, UpdatedTables should be as described in option 3.

As in option two, record the time when you start the summary table update process each night after it's successfully completed. Record it in table LastSummaryRun, column RunDate. TRUNCATE the table and INSERT the value, so the table only has one row.

At the end of that process, clear the is_changed flags in UpdatedTables and run this:

UPDATE ut
   SET is_changed = 1
  FROM UpdatedTables ut
         CROSS JOIN LastSummaryRun ls
 WHERE EXISTS (SELECT 1
                 FROM sys.dm_db_index_usage_stats
                WHERE database_id = DB_ID('<dbname>')
                  AND OBJECT_ID=OBJECT_ID(ut.tablename)
                  AND last_user_update > ls.RunTime
              )
;

(Again, replace <dbname> and <tablename> with the appropriate values for your database and table, of course.)

This will catch any changes that happened while your job was running, and will make sure we've primed all the values, in case there's a restart.

Execute this same statement in a job that runs every 5 minutes or so.

UpdatedTables should be correct unless:

  • the server restarted, AND
  • the only change that day happened in the five minutes before the restart.

If that seems like an acceptable risk, then this is probably the best of all the options. Note that the job can be run more often than every 5 minutes

1
  • Thanks for the in-depth answer. I'm gonna give it some thought and see what works best for us. Gonna leave this unanswered a little longer to see if anyone else chimes in, but will probably accept it later today
    – Kevin
    Commented Jun 2, 2017 at 12:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.