SYS.STATS_TARGET$

Franck Pachot
5 min readJan 16, 2019

Here is a little note about the SYS.STATS_TARGET$ table used by the automatic statistics gathering job run at maintenance window, or when running it manually with:

exec dbms_auto_task_immediate.gather_optimizer_stats

This table is not documented and has no view on it, so those are only my guesses about what I observed, and comments are welcome. Basically, this table is used by the Auto Stats job to list the tables to process, from one execution to the other.

Note that in 12c the same information is updated into DBA_OPTSTAT_OPERATION_TASKS and visible through DBMS_STATS.REPORT_STATS_OPERATIONS. But I still use STATS_TARGET$ so see in real-time what is currently processed.

Columns description

STATUS

When the Auto Stats job lists the objects to process, they are in state PENDING (STATUS=0).

Then, when it is its turn to be processed, the START_TIME is set to current timestamp and it can be SKIPPED (STATUS=4) or processed IN PROGRESS (STATUS=1)

Then, when processing ends the END_TIME is set to current timestamp and the status can be COMPLETED (STATUS=2) if successful, or FAILED (STATUS=3) in case of error. If it ends before completion at the end of the maintenance window, the IN PROGRESS and PENDING become TIMED OUT (STATUS=5)

Note that START_TIME and END_TIME have been introduced in 12c

STALENESS

This is similar, but more precise, than the STALE column in DBA_TAB_STATISTICS. Rather than YES/NO we have here an evaluation of staleness (comparing the modifications from DBA_TAB_MODIFICATIONS with the number of rows) in a logarithmic scale between -1.0 and 1.0 where the lowest is the more stale.

TYPE# and OBJ#

This is the OBJECT_TYPE and OBJECT_ID from DBA_OBJECTS. The decode of TYPE# to OBJECT_TYPE is in the DBA_OBJECTS view on OBJ$ definition. Rather than hardcoding it in my queries, I get it from:

select /*+ RESULT_CACHE (SYSOBJ=TRUE)*/ 
distinct type#,object_type
from (select object_id obj#,object_type from dba_objects)
natural join sys.obj$

OSIZE

This is the estimated object size, from the known number of blocks and blocksize, and can be used to estimate roughly the time it takes to gather statistics.

BO# and PART#

Those are the physical identifiers for partitions and subpartitions, tables: it makes the links with the parent object, and between index and table, probably for the purpose of CASCADE gathering.

FLAGS

This is a bitmap with some information about the staleness and the status. Here is my decode (which may be wrong as it is not documented)

ltrim(
case when bitand(flags,1)=1 then ',RETRY' end
|| case when bitand(flags,2)=2 then ',NON-SEGMENT' end
|| case when bitand(flags,4)=4 then ',?' end
|| case when bitand(flags,8)=8 then ',TIMED OUT' end
|| case when bitand(flags,16)=16 then ',?' end
|| case when bitand(flags,32)=32 then ',GATHER GLOBAL' end
|| case when bitand(flags,64)=64 then ',GATHER PARTITION' end
|| case when bitand(flags,128)=128 then ',MISSING COL STATS' end
|| case when bitand(flags,256)=256 then ',STALE STATS' end
|| case when bitand(flags,512)=512 then ',NO STATS' end
|| case when bitand(flags,1024)=1024 then ',HAS DIRECTIVES' end
|| case when bitand(flags,2048)=2048 then ',MISSING EXTENSION' end
|| case when bitand(flags,4096)=4096 then ',MISSING HISTOGRAM' end
|| case when bitand(flags,8192)=8192 then ',CASCADED' end
|| case when bitand(flags,16384)=16384 then ',REPORTING RUN' end
|| case when bitand(flags,32768)=32768 then ',FIXED TABLE' end
|| case when bitand(flags,65536)=65536 then ',SYNOPSIS MISMATCH' end
,',') flags

RETRY is for the gathering which was IN PROGRESS and was stopped with TIMED OUT.

SID, SERIAL#

The last session running the Auto Stats (i.e PENDING, IN PROGRESS, TIMED OUT) is identified here so you can join with V$SESSION or even V$ACTIVE_SESSION_HISTORY to get more information afterward about the duration (which is END_TIME-START_TIME). You can also find the long-running ones in V$SQL_MONITOR.

Query examples

details

Here is an example while the Auto Stats job is running. This shows which table is currently gathered (IN PROGRESS), already done (COMPLETED) or to be done (PENDING). The flags indicate that the previous execution was TIMED OUT, and which is global level gathering.

I also join with V$SESSION in order to see the currently running job (and its login time) as I’m in 11g without the START_TIME:

Here is the query I used for this output:

select logon_time
--,start_time,end_time,end_time-start_time duration
--,start_time-lag(end_time)over(partition by sid,serial# order by start_time) after_previous,
,object_type,owner,object_name,subobject_name,sid||','||serial# "sid/ser#",round(osize/1024/1024/1024) GBytes
,rtrim(case status when 0 then 'PENDING' when 1 then 'IN PROGRESS' when 2 then 'COMPLETED' when 3 then 'FAILED'
when 4 then 'SKIPPED' when 5 then 'TIMEOUT' end) STATUS,status status#
,case staleness when -100 then 'MISSING' when -99 then null
else rtrim('STALE '||lpad(' ',round(2*(1+(staleness))),'+')) end staleness
,ltrim(
case when bitand(flags,1)=1 then ',TIMED OUT' end
|| case when bitand(flags,2)=2 then ',NON-SEGMENT' end
|| case when bitand(flags,4)=4 then ',4' end
|| case when bitand(flags,8)=8 then ',TIMED_OUT' end
|| case when bitand(flags,16)=16 then ',16' end
|| case when bitand(flags,32)=32 then ',GATHER GLOBAL' end
|| case when bitand(flags,64)=64 then ',GATHER PARTITION' end
|| case when bitand(flags,128)=128 then ',MISSING CSTATS' end
|| case when bitand(flags,256)=256 then ',STALE STATS' end
|| case when bitand(flags,512)=512 then ',NO STATS' end
|| case when bitand(flags,1024)=1024 then ',HAS DIRECTIVES' end
|| case when bitand(flags,2048)=2048 then ',MISSING EXTENSION' end
|| case when bitand(flags,4096)=4096 then ',MISSING HISTOGRAM' end
|| case when bitand(flags,8192)=8192 then ',CASCADED' end
|| case when bitand(flags,16384)=16384 then ',REPORTING RUN' end
|| case when bitand(flags,32768)=32768 then ',FIXED TABLE' end
|| case when bitand(flags,65536)=65536 then ',SYNOPSIS MISMATCH' end
,',') flags
from (
select *
from STATS_TARGET$
natural left outer join (select /*+ result_cache */ distinct type#,object_type from (select object_id obj#,object_type from dba_objects) natural join sys.obj$)
natural left outer join (select object_id obj#,owner,object_name,subobject_name from dba_objects)
natural left outer join (select sid,serial#,program,sql_id,logon_time from gv$session)
left outer join (select bo#,part#,hiboundval,analyzetime,obj# part_obj# from sys.tabpart$) using(bo#,part#)
) v
--order by end_time desc nulls last, start_time desc nulls last
order by logon_time desc nulls last,status#
/

summary

Here is another query which checks the global status while the Auto Stats job is running:

select logon_time,status,staleness,count(*),sum(GBytes) GBytes
,object_type,round(100*sum(GBytes)/max(TotGB)) "%"
from (
select logon_time,object_type,round(osize/1024/1024/1024) GBytes,sum(osize/1024/1024/1024)over(partition by logon_time,object_type) TotGB
,rtrim(case status when 0 then 'PENDING' when 1 then 'IN PROGRESS' when 2 then 'COMPLETED' when 3 then 'FAILED' when 4 then 'SKIPPED' when 5 then 'TIMEOUT' end) STATUS
,case staleness when -100 then 'MISSING' when -99 then null else 'STALE' end staleness
from STATS_TARGET$
natural left outer join (select /*+ result_cache */ distinct type#,object_type from (select object_id obj#,object_type from dba_objects) natural join sys.obj$)
natural left outer join (select sid,serial#,program,sql_id,logon_time from gv$session)
)
group by logon_time,object_type,status,staleness having logon_time is not null
order by logon_time nulls last,status,object_type,staleness;
LOGON_TIME STATUS STALENE COUNT(*) GBYTES
----------------- ----------- ------- ---------- ----------
15-JAN-2019 08:44 COMPLETED STALE 288 2177
15-JAN-2019 08:44 IN PROGRESS STALE 1 210
15-JAN-2019 08:44 PENDING MISSING 58744 18311
15-JAN-2019 08:44 PENDING STALE 2567 8579
15-JAN-2019 08:44 PENDING 55 0

Processing order

As this table is used to list the tables to process, the columns are used to order it. We know that the Auto Stats starts with the most stale (and MISSING is the first there). But before that, the tables are listed before the partitions, and partitions before subpartition. And tables are processed before indexes.

--

--

Franck Pachot

Developer Advocate for YugabyteDB (Open-Source, PostgreSQL-compatible Distributed SQL Database. Oracle Certified Master and AWS Data Hero.