Christian Antognini descibed in his book Troubleshooting Oracle Performance how to gather the system statistics in a very proficient way. The main idea of gathering system stats is to collect the system stats in a user’s table for a certain time period and to set the different system stats manually based on the gathered values. If you collect the system stats into the system table sys.aux_stats$ the optimizer will use the gathered values to „optimize“ the sql statements. Therefore if you gather the system stats into a user’s table the the optimizer will not be affected by these values – during the period of gathering system stats unless you set the values manually.
To implement the collection of gathered system stats you need four elements:
- a table you gather your system stats into
- a table you save all the gathered system stats
- a small pl/sql procedure called „gather“ to gather the system stats into a user table 1) and save the values to 2)
- a database job calling the procedure every hour (the code will not be shown here)
To create 1) and 2) you need to execute the following pl/sql block:
1 2 3 4 5 6 7 8 9 | DECLARE c_tab CONSTANT VARCHAR2(20) := 'SYSSTATS_1'; c_tab_hist CONSTANT VARCHAR2(20) := c_tab||'_HIST'; BEGIN DBMS_STATS.CREATE_STAT_TABLE (ownname => USER, stattab => c_tab); DBMS_STATS.CREATE_STAT_TABLE (ownname => USER, stattab => c_tab_hist); EXECUTE immediate('alter table '||c_tab_hist||' add norecs number'); END; / |
The procedure 3) may look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | CREATE OR REPLACE PROCEDURE scott.gather IS c_tab CONSTANT VARCHAR2(20) := 'SYSSTATS_1'; c_tab_hist CONSTANT VARCHAR2(20) := c_tab||'_HIST'; c_cmd CONSTANT VARCHAR2(4000) := 'insert into '||c_tab_hist|| ' select s.*, (select count(*) from '|| c_tab_hist||') NORECS '|| ' from ' ||c_tab ||' s'; BEGIN -- start gathering system stats dbms_stats.gather_system_stats(gathering_mode => 'START' , INTERVAL => NULL , statown => 'SCOTT' , stattab => c_TAB); -- wait 57 Minutes dbms_lock.sleep( 60*60 - 3*60 ); -- stop gathering system stats dbms_stats.gather_system_stats(gathering_mode => 'STOP' , INTERVAL => NULL , statown => 'SCOTT' , stattab => C_TAB); -- save / append all values from table 1) to table 2) EXECUTE immediate(c_cmd); commit; EXCEPTION WHEN others THEN NULL; -- here you should have an exception handler to log all errors END gather; / |
This procedure „gather“ will start the gathering of system statistics into the table SCOTT.SYSSTATS_1. Then it will wait for 57 Minuten and stop the gathering of system stats. Thereafter the gathered system stats will be inserted into the table SCOTT.SYSSTATS_1_HIST. The column NORECS will count the number of previous inserted rows.
NOTE: you need the privelege to execute the package dbms_lock
Some days before at customers site I did not had the privilege to execute this procedure. So, I had to change the procedure and used the INTERVAL method. Instead of execute first dbms_stats.gather_system_stats by gathering_mode => ‚START‘ I used gathering_mode => ‚INTERVAL‘ and specify the minutes the procedure has to gather the system stats. I changed my procedure inside to
1 2 3 4 5 6 7 8 9 | BEGIN dbms_stats.gather_system_stats(gathering_mode => 'INTERVAL' , INTERVAL => 57 , statown => 'SCOTT' , stattab => c_TAB); EXECUTE immediate(c_cmd); commit; END gather; / |
But, this new procedure did not work! This procedure did not collect any values. All essential columns are NULL without the start and end time. My mistake was: I was thinking that the procedure will wait for 57 minutes to gather all values, however the procedure will not wait. When execute the procedure the gathering process would be started in background while the procedure finished after 1 or 2 seconds. Therefore the INSERT directly after the procedure cannot find any data.
So, I switched the order of the INSERT and the procedure: everything works fine. Every hour when the procedure is calling by a job scheduler of the database the table 2) is filled. The INSERT will transfer the data of the previous run to the HIST table. Executing the procedure thereafter will empty the SYSSTATS_1 table and the gathering of new records of system statistics started again. Here the final code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | CREATE OR REPLACE PROCEDURE scott.gather IS c_tab CONSTANT VARCHAR2(20) := 'SYSSTATS_1'; c_tab_hist CONSTANT VARCHAR2(20) := c_tab||'_HIST'; c_cmd CONSTANT VARCHAR2(4000) := 'insert into '||c_tab_hist|| ' select s.*, (select count(*) from '|| c_tab_hist|| ') NORECS '|| ' from ' ||c_tab ||' s'; BEGIN -- save the data of the previous run EXECUTE immediate(c_cmd); commit; -- start gathering again dbms_stats.gather_system_stats(gathering_mode => 'INTERVAL' , INTERVAL => 57 , statown => 'SCOTT' , stattab => c_TAB); EXCEPTION WHEN others THEN NULL; -- here you should have an exception handler to log all errors end gather; END gather; / |
Using the view „ALL_SYSSTATS_1“ you can see gathered values saved in SYSSTATS_1_HIST:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | CREATE OR REPLACE VIEW ALL_SYSSTATS_1( INTERVAL , START_DATE , END_DATE , CPUSPEED , SREADTIM , MREADTIM , MBRC , MAXTHR , SLAVETHR) AS SELECT 'sysstats_1' INTERVAL , C2 START_DATE , C3 END_DATE , round(N3,2) CPUSPEED , round(N1,2) SREADTIM , round(N2,2) MREADTIM , round(N11,2) MBRC , round((SELECT sa.N1 FROM SYSSTATS_1_HIST sa WHERE sa.C4 = 'PARIO' AND sa.NORECS = s.NORECS),2) MAXTHR , round((SELECT sb.N2 FROM SYSSTATS_1_HIST sb WHERE sb.C4 = 'PARIO' AND sb.NORECS = s.NORECS),2) SLAVETHR FROM (SELECT * FROM SYSSTATS_1_HIST s WHERE s.N1 IS NOT NULL AND s.N2 IS NOT NULL) s WHERE s.c4 = 'CPU_SERIO' AND N3 IS NOT NULL AND N11 IS NOT NULL ORDER BY s.NORECS DESC ; |
Based on these values you choose the values you will use to set the different system statistics manully. A proposal will give you the view „SYSSTATS_AVG“:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | CREATE OR REPLACE VIEW SYSSTATS_AVG( NO_OF_RECS , CPUSPEED , SREADTIM , MREADTIM , MBRC , MAXTHR , SLAVETHR) AS SELECT COUNT (*) NO_OF_RECS , ROUND (AVG (CPUSPEED), 2) CPUSPEED , ROUND (MAX (SREADTIM), 2) SREADTIM , ROUND (MAX (MREADTIM), 2) MREADTIM , ROUND (MAX (MBRC), 2) MBRC , ROUND (AVG (MAXTHR), 2) MAXTHR , ROUND (AVG (SLAVETHR), 2) SLAVETHR FROM ALL_SYSTATS_1 s ; |
Thanks to Christian Antognini, Franck Pachot and Iradj Atchatchloui for their support.
Rainer Hartwig
Very helpful – will check this advice asap …