When was a table last changed?

February 7th, 2009

I frequently get a question about how to find out when was a table last modified. I’m talking about table data, not table structure, the latter would be detectable from dba_objects.last_ddl_time.

Unless you have some table level DML auditing already turned on, then as one option you could use LogMiner and “just” work through the redo/archivelogs in the range of interest with it. This could be very time consuming (especially if the last change was done a while back), so here’s another option for getting last table modification info very easily, however it comes with some restrictions.

http://www.tanelpoder.com/files/scripts/lastchanged.sql

(read instructions and limitations from the script header).

The idea is following:

  1. Oracle has an ORA_ROWSCN pseudocolumn which reports the last known change time for a row in a table. The “time” shows a commit SCN number of last transaction modifying the row, not a real timestamp though. It is important to note that unless the ROWDEPENDECIES are enabled, then the last SCN is known only at data block level, not row level, rowscn’s for all rows in a block would report whatever SCN is in the last change SCN in block header.
  2. SCN is ever-increasing internal “time” used by Oracle recovery and transaction layers and it is possible to map this to real time with reasonable accuracy using few Oracle’s tables which store SCN to wallclock time mappings. My script reports the time range in which the last change to datablock/row occurred so you’ll know how accurate it is.
    I use sys.smon_scn_time and v$log_history views for SCN to real time mapping. So my script reports two times, first one may be more accurate, second one has longer history on the other hand.You can use whatever other datasource for doing this mapping, as long as it has SCNs and corresponding timestamps in it. For example, if you have log_checkpoints_to_alert parameter set to true, you can grep the SCN/timestamp pairs out with command like this:

    cat alert_win10g.log | egrep -e "^Beginning.*checkpoint|[[:alpha:]]{3} [[:alpha:]]{3} [[:digit:]]{2} "
    
    

Here are few usage examples.

First I’ll check some data which was probably last changed a long time ago:

SQL> @lastchanged sys.obj$ name='DBMS_STANDARD'

-- LastChanged.sql v1.0 by Tanel Poder ( http://www.tanelpoder.com )

Running this query:

.   select MAX(ora_rowscn)
.   from sys.obj$
.   where name='DBMS_STANDARD';

DATA_SOURCE       LAST_CHANGED
----------------- -------------------------------------------------------
sys.smon_scn_time Before  2008-12-31 16:05:25 (earlier than 21 days ago)
v$log_history     Before  2008-10-27 03:58:16 (earlier than 86 days ago)

2 rows analyzed.

As both sys.smon_scn_time and v$log_history don’t have records dating back to that old time when the database was created (when DBMS_STANDARD and other object records in that datablock were created), they just show that the change happened before the oldest SCN to time mapping they have.

Here’s a check on a regular table, but we are only interested in rows/datablocks where the “sal” column is bigger than 1000:

SQL> @lastchanged scott.emp sal>1000

-- LastChanged.sql v1.0 by Tanel Poder ( http://www.tanelpoder.com )

Running this query:

.   select MAX(ora_rowscn)
.   from scott.emp
.   where sal>1000;

DATA_SOURCE       LAST_CHANGED
----------------- ---------------------------------------------------------------------
sys.smon_scn_time Between 2009-01-21 08:56:57 and 2009-01-21 09:02:19 (5 minute range)
v$log_history     Between 2009-01-21 08:26:43 and 2009-01-21 08:57:14 (31 minute range)

14 rows analyzed.

Lets change something (and commit too, as ORA_ROWSCN is updated at commit time, any non-committed change doesn’t really exist yet).

SQL> update scott.emp set sal = sal * 2;

14 rows updated.

SQL> commit;

Commit complete.

Let’s see the last change time again:

SQL> @lastchanged scott.emp sal>1000

-- LastChanged.sql v1.0 by Tanel Poder ( http://www.tanelpoder.com )

Running this query:

.   select MAX(ora_rowscn)
.   from scott.emp
.   where sal>1000;

DATA_SOURCE       LAST_CHANGED
----------------- ---------------------------------------------------------------
sys.smon_scn_time After   2009-01-21 09:02:19 (between 6 minutes ago and now)
v$log_history     After   2009-01-21 08:57:17 (between 11 minutes ago and now)

14 rows analyzed.

As you see, the last change time range has changed.

So, this script can be very useful first round diagnosis tool when you want to know when some table/data was changed the last (in case you don’t have fine grained auditing enabled already). That would help you to correctly determine if some data/row has not been modified since some time in past and also it can show you when some data/row has been modified in past, but this approach is subject to false positives if rowdependencies option is not enabled for that table.

Nevertheless, even with those false positives, this approach gives you the information which archivelogs you would need to study to find the latest changes to the data (instead of starting to mine through all the archivelogs generated in past in hope to find the change).

  • Share/Bookmark

Tanel Poder
Administration, Oracle, Tools, Troubleshooting

  1. Trackbacks

  1. Comments

  2. February 7th, 2009 at 23:07 | #1

    Tanel,

    You are seriously the man. I swear, I love your blog.

    One thing I would like to add is that I think setting rowdependencies is that it will take an extra 6 bytes per row. Basically the same thing as having a char(6) to the table. A small price to pay and a great way to do auditing if you want to know when data changed.

  3. Martin
    February 8th, 2009 at 04:20 | #2

    Tanel,
    at least for very recent updates the function SCN_TO_TIMESTAMP would give a much more precise information concerning which timestamp is associated with the scn.
    If the scn is too far in the past, the function raises an exception, so here your approach would come in very handy.
    Best regards,
    Martin
    PS: Thank you again for your 2008-presentation at Austrian Oracle User Group together with Christian Antognini

  4. Boris
    February 8th, 2009 at 05:03 | #3

    Why don’t you use just SCN_TO_TIMESTAMP function?

  5. February 8th, 2009 at 19:54 | #4

    Thanks Martin, Boris… Yep, SCN_TO_TIMESTAMP function would give better precision (and it is easier to use).

    But my approach works also on 9i, there’s no SCN_TO_TIMESTAMP function in 9i.

  6. February 8th, 2009 at 20:34 | #5

    Correction, this script itself does not work on 9i as there’s no ORA_ROWSCN function in 9i if I recall correctly. I have used a script somewhat similar to lastchanged.sql in 9i where I manually paste in a SCN taken from datablock header.

    So, the only benefit of using v$log_history is that it may give you longer history than the scn_to_timestamp() does. Also, by querying smon_scn_time directly you’ll at least know when the changed happened *before* the earliest SCN to time record in smon_scn_time table (scn_to_timestamp function will return an exception if it doesnt find a matching record).

    I have uploaded an improved version of lastchanged.sql script to http://www.tanelpoder.com/files/scripts/lastchanged.sql

    It will now use the SCN_TO_TIMESTAMP() function first and only when its unable to resolve the SCN to time I run my other 2 queries to get the timestamp.

    Thanks for the suggestions…

    
    
    SQL> @lastchanged obj$ 1=1
    
    -- LastChanged.sql v1.0 by Tanel Poder ( http://www.tanelpoder.com )
    
    Running this query:
    
    .   select MAX(ora_rowscn)
    .   from obj$
    .   where 1=1;
    
    DATA_SOURCE       LAST_CHANGED
    ----------------- ----------------------------------------------------------------------
    scn_to_timestamp  2009-02-08 19:48:53
    
    51957 rows analyzed.
    SQL>
    SQL>
    SQL> @lastchanged rule$ 1=1
    
    -- LastChanged.sql v1.0 by Tanel Poder ( http://www.tanelpoder.com )
    
    Running this query:
    
    .   select MAX(ora_rowscn)
    .   from rule$
    .   where 1=1;
    
    DATA_SOURCE       LAST_CHANGED
    ----------------- ----------------------------------------------------------------------
    scn_to_timestamp  SCN_TO_TIMESTAMP couldn't convert SCN to time. See next section below
    
    DATA_SOURCE       LAST_CHANGED
    ----------------- ----------------------------------------------------------------------
    sys.smon_scn_time Before  2009-01-28 20:51:19 (earlier than 11 days ago)
    v$log_history     Between 2008-10-21 03:30:25 and 2008-10-21 16:00:58 (751 minute range)
    
    4 rows analyzed.
    
    
  7. Chris
    February 9th, 2009 at 18:39 | #6

    Another possibility is 11g’s Flashback Archive. My experience is that Oracle has integrated the 10g Flashback query, flashback versions query, and flashback_transaction_query view with the Flashback Archive. So, the information that is limited in 10g to the depth of undo data is limited in 11g only by the size of your flashback archive retention period.

    Of course, you have to create the Flashback archive and enable archiving for your tables first …

  8. Anand
    February 22nd, 2009 at 10:45 | #7

    Hi Tanel.

    This was something i was looking from longtime.I was questioned for the last dml on 9i for which neither the audit was enabled nor monitoring was enabled.

    As, i think the lastest post that you have posted on point 5. works on 9i too.

    But when i am trying it on 9i it gives me an error
    {quote}
    Running this query:

    . select MAX(ora_rowscn)
    . from rule$
    . where 1=1;

    SP2-0310: unable to open file “C:\Documents.sql”
    old 4: FROM &1
    new 4: FROM and
    old 5: WHERE &2;
    new 5: WHERE Settings\db.admin\Desktop\anand\scripts\saveset;
    FROM and
    *
    ERROR at line 4:
    ORA-06550: line 4, column 11:
    PL/SQL: ORA-00903: invalid table name
    ORA-06550: line 2, column 6:
    PL/SQL: SQL Statement ignored
    {quote}

    One more thing that i wan’t to know is that what if i don’t want to give

  9. February 22nd, 2009 at 11:04 | #8

    Hi Anand,

    ORA_ROWSCN is available only from 10g as far as I know (I don’t have 9i db handy right now).

    So, you would need to
    1) dump the datablock containing the row of interest and get the scn from block header ( it looks something like this: “scn: 0×0000.016a75be” the scn is reported in hex )
    2) Run a query similar to my example which takes the SCN as a parameter instead of from ORA_ROWSCN

  10. February 22nd, 2009 at 11:04 | #9

    Btw, You can comment out the @@loadset and @@saveset commands from the script, if you’re not using my full TPT script set ( http://www.tanelpoder.com/files/TPT_public.zip )

  11. Anand
    February 22nd, 2009 at 11:47 | #10

    Hi Tanel.

    Yes, ORA_ROWSCN is not available in 9i.In 9i auditing and table monitoring are the 2 possible ways of getting the last dml as far as i know.

    Secondly, suppose that i want don’t want to use in
    @laschanged scott.emp then should i use 1=1 in

    like ,
    @lastchanged scott.emp 1=1

  12. February 22nd, 2009 at 11:55 | #11

    …or you can dump the datablock and get the SCN from block header, that’s exactly where ORA_ROWSCN gets the SCN from if you don’t have rowdependencies enabled.

    lastchanged.sql always requires two parameters, table name and the rows to be examined. When you use 1=1 then all rows are examined and you get the latest time of any row changed in the table.

    But instead of 1=1 you can use where ID=xyz or whatever other condition, in this case the script reports when was a last change done to any of the rows matching the where condition..

    If you look into script header, there are use cases and explanation in there…