Oracle Tips and Tricks — David Fitzjarrell

September 26, 2023

Getting Fresh

Filed under: stats — dfitzjarrell @ 07:24

Statistics are the backbone of the CBO so it would make sense to keep them fairly current. Oracle does this with is scheduled statistics job but even that can not produce current statistics for every table every tun. Database size is usually the reason due to the way the scheduled job executes. Let’s look at at that ans see where it leads.

As installed and scheduled by Oracle the daily statistics job runs during an eight hour window; the issue here is that the job will stop processing tables/indexes when that window closes. Depending upon the size of the table/index Oracle may choose to pause the execution before the eight-hour execution time ends for the day. This, of course, leaves the tables in a mixed state where some are analyzed that day and others either have been recently analyzed or are waiting until the next window opens.
Let us take, for example, an Oracle database that’s 12 TB in size — there will be several, if not many, large to very large tables, with indexes, to be processed by the automated statistics job. Let us also say that Oracle can process 3 TB of data wit the statistics job; it can be seen that it will take at least four executions of the statistics job to process every table/index in the database. Wouldn’t it be convenient to generate a report listing those tables and indexes? Siince that data is already in the database it’s a simple matter of extracting it in a usable form.

Regardless of whether the Partitioning option has been purchased there will be partitioned tables/indexes present in the data dictionary. To obtain an accurate listing the query in question will need to access:

DBA_TABLES
DBA_TA\B_PARTITIONS
DBA_INDEXES
DBA_IND_PARTITIONS

[Remember that ALL tables/indexes in the database will be included in the statistics job and these Oracle-owned partitioned tables will be interspersed throughout the entire table/index listing.] Information in that output should include:

The analyze date
The object owner
The count of objects analyzed for that owner
A complete listing of the tables/partitions/indexes analyzed on that date

The script should also allow for the user to provide a number of days prior to the current date to obtain information from (sysdate -2, sysdate -3, etc.) so that useful investigations can be performed. With that information in mind a possible script to create such a report would be:

rem
rem Set paraemters
rem
set linesize 230 pagesize 10000 verify off trimspool on
break on last_analyzed skip 1
col owner format a35
col table_name like owner
col partition_name like owner

rem
rem Get overall table count
rem

col tabcount new_value tab_ct noprint
col sys_date new_value sysdt noprint

select count(*) tabcount from dba_tables;
select to_char(sysdate, ‘MMDDRRRR’) sys_date from dual;

rem
rem Generate counts, bu owner, for offset provided
rem Spool output to a fle named with the current date
rem

select nvl(trunc(t.last_analyzed), trunc(p.last_analyzed)) last_analyzed, t.owner, count() tables, round((count()/&tab_ct)100,2) pct_analyzeed from dba_tables t left outer join dba_tab_Partitions p on p.table_owner = t.owner and p.table_name = t.table_name where t.last_analyzed is not null and t.last_analyzed >= trunc(sysdate – &&1) group by nvl(trunc(t.last_analyzed), trunc(p.last_analyzed)), t.owner union all select nvl(trunc(t.last_analyzed), trunc(p.last_analyzed)) last_analyzed, t.owner, count() indexes, round((count()/&tab_ct)100,2) pct_analyzeed
from dba_indexes t left outer join dba_ind_Partitions p
on p.index_owner = t.owner and p.index_name = t.index_name
where t.last_analyzed is not null
and t.last_analyzed >= trunc(sysdate – &&1)
group by nvl(trunc(t.last_analyzed), trunc(p.last_analyzed)), t.owner
order by 1
/

pause

rem
rem Produce listing of owner, table_name, partition_name analyzed during the oiffset period
rem

select t.owner, t.table_name, p.partition_name, nvl(t.num_rows, p.num_rows) num_rows, nvl(trunc(t.last_analyzed), trunc(p.last_analyzed)) last_analyzed
from dba_tables t left outer join dba_tab_Partitions p
on p.table_owner = t.owner and p.table_name = t.table_name
where t.last_analyzed is not null
and t.last_analyzed >= trunc(sysdate – &&1)
union all
select t.owner, t.index_name, p.partition_name, nvl(t.num_rows, p.num_rows) num_rows, nvl(trunc(t.last_analyzed), trunc(p.last_analyzed)) last_analyzed
from dba_indexes t left outer join dba_ind_Partitions p
on p.index_owner = t.owner and p.index_name = t.index_name
where t.last_analyzed is not null
and t.last_analyzed >= trunc(sysdate – &&1)
order by 5, 1, 2, 3
/

undefine 1
spool off

Going through the code let’s see what it does. The first block sets column formatting and report breaks so that the output fits on a single line per object and each date of analysis has its own block of data. This should make it easier to find objects and analysis dates for troubleshooting purposes.

Next up is the count by owner report for all owners in the database. The total count includes tables and indexes owner by each listed account.

The last section of code generates the full list of analyzed objects — tables, partitions and indexes — processed on the reported date., This portion of the report is the “meat and potatoes” of the output, where the curious DBA can access to see when a table in question was last analyzed, useful when performance issues arise.

The output can be spooled to a file for later use since the itemized list can be rather lengthy. Please note that after the owner/count report is generated the script pauses so the DBA can get a sense of the volume processed during the requested time period. Hitting the Enter key resumes the output generation.

THe first part of the report could look like this:

AST_ANALYZED OWNER TABLES PCT_ANALYZEED


22-SEP-2023 00:00:00 SYS 8 .29
PLANINURTIAN 1 .04
BUBA 4 .15
PLANINURTIAN 1 .04
GRAKNOR 1 .04
SYS 12 .44
BUBA 8 .29
GRAKNOR 3 .11

23-SEP-2023 00:00:00 SYS 18 .66
PLANINURTIAN 1 .04
BUBA 1 .04
BUBADW 1 .04
SYS 23 .84
PLANINURTIAN 1 .04
BUBA 2 .07
BUBADW 1 .04

24-SEP-2023 00:00:00 BUBADW 67 2.44
SYSTEM 3 .11
SMORTT 1 .04
BUBA 14 .51
SYS 489 17.83
BUBA 8 .29
SMORTT 2 .07
BUBADW 53 1.93
SYS 493 17.97
SYSTEM 1 .04
DBSNMP 2 .07

The complete listing will not be shown due to its large size. (User names were altered for privacy reasons.).

Given that such a script would be executed for multiple days a single table can occur more than once is a single output file. Since the analyze date is also provided it should be a simple task to determine the last time a given object was processed.

Simply because a table or index was processed, say, a week ago that is not a reliable indicator of stale statistics. The DBA_TAB_STATISTICS and DBA_IND_STATISTICS views both contain a column named STALE_STATS that can be queried to see if Oracle considers the statistics stale. Three possible values are found for STALE_STATS:

YES
NO

Rows with a value for STALE_STATS can occur for these reasons:

1) Sufficient statistics may not exist for the listed table
2) The table may be a fixed object
3) The listed table may be partitioned

[Fixed object statistics are generated by using dbms_stats.gather_fixed_objects_stats() as they are NOT gathered during a normal statistics run]. Since the data in partitioned tables lies in the partitions, and some partitions may have stale statistics a determination for the entire table cannot be made, hence the value. This is why the code above queries the partitioned tables view. The ability to generate a listing of all objects analyzed on a given date can be of great help to a DBA investigating issues regarding performance.

Regular executions of the provided code, and generating output files for later reference, can produce a pattern of analysis that can help the DBA resolve problems such ass slow queries creating performance bottlenecks. Knowing what is causing the slowness is half the battle a DBA can face; with that data in hand a plan of attack can be formulated to resolve the problem.

Fresher IS better, as far as statistics go.

September 20, 2023

Out Of Sorts

Filed under: disaster recovery,General,replication — dfitzjarrell @ 18:27

Occasionally the unthinkable can occur and the DBA can be left with a standby database that is no longer synchronizing with the primary. A plethora of “advice”will soon follow that discovery, most of it much like this:

“Well, ya gotta rebuild it.”

Of course the question to ask is “how far out of synch is the standby>” That question is key in determining how to attack this situation. Let’s go through the two most common occurrences of this and see how to address them.

Let’s start with the most drastic — you’re over 500 logs behind the primary (don’t laugh, I’m aware of at least one site where this occurred). Depending upon how RMAN is configured the DBA may not be able to restore all of the missing logs. At this point it will most likely be just as fast to recreate the standby from the primary using RMAN. Since that process is well-documented it won’t be discussed here. Please note that this is an act to perform when the next method can’t be used.

The standby is a “reasonable” number of logs behind — the fastest and least intrusive way to fix this is to recover the missing archivelogs from RMAN backups. Again this allows RMAN to come to the rescue. Going through the process step by step should make this easy for most DBAs. Let’s begin.

Step one is to determine how many logs comprise the gap that prevents the standby from getting caught up — Oracle 0provides that information in the V$ARCHIVE_GAP view:

THREAD# NUMBER
LOW_SEQUENCE# NUMBER
HIGH_SEQUENCE# NUMBER
CON_ID NUMBER

This view provides the log sequence information for the recovery gap in the form of the lowest sequence missing and the highest sequence missing. Using this view provides the information for the RMAN archive restore command shown below:

run {
allocate channel d1 device type disk;
restore archivelog from logseq &1 until logseq &2;
}

Putting that command into a script allows the DBA to pass in the two sequence values n the RMAN command line:

RMAN > @restore_logs.rman 1000 1230

This will find and restore archivelogs with the provided sequence numbers. Now the question becomes “where do these logs et restored to?” The commonly expected destination is the standby server, which is where these logs eventually need to go. The DBA can to that, but t hen the DBA must also create a script to register those logs with the standby database after they have been restored. A simpler and cleaner approach is to restore those logs to the primary and let the standby configuration handle all of the work. Restoring to the primary writes the restored logs to the configured archive log destination; the ARCH process then performs its usual duties of copying those logs to the standby server and registering them automatically. With this technique all the DBA needs to do is get the sequence range and restore them to the primary — Oracle takes over and completes the tasks at hand and the standby starts recovering as soon as the first restored log is transferred to the standby server.

This behavior can be verified by executing the following query on the standby database:

SELECT *
FROM V$MANAGED_STANDBY
WHERE THREAD# > 0
AND GROUP# <> ‘N/A’;

The output will show all of the logs being applied to the standby, regardless of whether they are archived redo logs or standby redo logs. Repeated execution of that query will show the log sequence begin applied by either LGWR or ARCH. As the log restores progress and the apply process works through them the standby closes the gap and again becomes synchronized with the primary.

This is why at least one additional standby redo log is configured for the standby — depending upon how large the gap has become that “extra” standby redo log wil get used to hold the LGWR data sent to the standby. There is nothing preventing the DBA from configuring MORE than one extra standby redo log, but it is expected that the DBA is actually monitoring the standby and can address a gab before it overflows the standby redo logs already configured. To see how many standby redo logs are involoved the following query ncan be executed:

select group#, thread#, sequence#, used, round(used*100/bytes, 2) pct_full, archived, status, first_change#, first_time, last_change#, last_time
from v$standby_log
where exists (select 1 from v$database where database_role = ‘PHYSICAL STANDBY’);

which produces output similar to:

  GROUP#  THREAD#   SEQUENCE#        USED PCT_FULL ARC STATUS       FIRST_CHANGE# FIRST_TIME              LAST_CHANGE# LAST_TIME

      10    1      300465   271206912    25.86 YES ACTIVE        110812847402 05-SEP-2023 16:46:29        110812981549 05-SEP-2023 16:47:56
      11    1       0       0     0.00 NO  UNASSIGNED
      12    1       0       0     0.00 YES UNASSIGNED
      13    1       0       0     0.00 YES UNASSIGNED
      14    1       0       0     0.00 YES UNASSIGNED

During a gap situation there will likely be several standby redo logs populated simultaneously. As the gap closes these logs will be read and the transactions applied, releasing them to return to UNASSIGNED status.

Once the archivelog restore has started on the primary the entire process can be monitored from the standby server using the two queries provided above. An occasional query of V$ARCHIVE_GAP will show the gap, or gaps, closing as the logs are restored and shipped to the standby. If a gap remains simply repeat the process, using the newest log sequence numbers. In most cases only a single execution of this process will be required to set things right.

When a standby databse is “out of sorts” with the primary most likely all that needs to occur is to restore the missing archivelogs to the primary and let Oracle take over. Rarely will the synchronization be so far “out of whack”: that a rebuild of the standby is required. Provided the DBA has properly configured the backup and recovery process for the database in question it should be a trivial matter to restore the standby to it’s rightful state.

Be prepared. It’s not just for the Boy Scouts.

September 13, 2023

What Constraints?

Filed under: General — dfitzjarrell @ 12:20

Constraints help keep data quality high by preventing conditions that can “contaminate” the data pool. They come in various forms:

  • Not null constraints
  • Check constraints
  • Unique constraints
  • Referential constraints

All have a purpose, when sued properly, but sometimes conditions change and a constraint that was once perfectly acceptable now interferes with application functionality or performance. Dropping constraints is easy, rebuilding them may not be. Oracle has thought the constraint issues and has provided ways to extract existing definitions so constraints won’t be lost.

The DBMS_METADATA.GET_DDL function is probably the fastest and easiest method to extract such definitions from the data dictionary. The basic parameters to provide are the object_type, the name and the schema:

select dbms_metadata.get_ddl(‘CONSTRAINT’,”MY_CHECK_CONSTRAINT’,’BOB’) from dual;

If the named constraint exists the definition wil be displayed. Properly formatted the definition can be saved to a SQL file for later use.

What if you don’t know the constraint name? The DBA_CONSTRAINTS view comes to the rescue:

select constraint_name, constraint_type, owner from dba_constraints where owner = ‘BOB’ and table_name = ‘SNORPLE’;

That query will return all constraints created against the table of interest. Of course you could let SQL*Plus create the commands for you:

select ‘select dbms_metadata.get_ddl(”CONSTRAINT”,”’||constraint_name||”’,”’||owner||”’)i from dual;’
from dba_constraints
where table_name = ‘ENROLLMENT_PERIOD’
and constraint_type <> ‘R’
union
select ‘select dbms_metadata.get_ddl(”REF_CONSTRAINT”,”’||constraint_name||”’,”’||owner||”’)i from dual;’
from dba_constraints
where table_name = ‘ENROLLMENT_PERIOD’
and constraint_type = ‘R’
/

ave the output to a file and it can be used to generate commands to replace every constraint for a given table, even referential constraints. DBMS_METADATA.GET_DDL knows w hat to extract for each type of constraint, including referenced tables. Add these lines to the start of the file to ensure all of the output is visible and SQL statements are properly terminated:

column text format a230
set linesize 230 pagesize 0
set long 100000

begin
dbms_metadata.set_transform_param(dbms_metadata.session_transform, ‘SQLTERMINATOR’, true);
dbms_metadata.set_transform_param(dbms_metadata.session_transform, ‘PRETTY’, true);
end;
/

These additions set the SQL*Plus formatting and set DBMS_METADATA to include the command terminator and make the output a bit more user-friendly. Notice that two different constraint types are used — referential constraints have a different definition in DBMS_METADATA.a Examining the output created we see:

ALTER TABLE “BOB”.”SNOOPLE” MODIFY (“SNOOPLE_KEY” NOT NULL ENABLE);

ALTER TABLE “BOB”.”SNOOPLE” MODIFY (“BEGIN_DATE” NOT NULL ENABLE);

ALTER TABLE “BOB”.”SNOOPLE” MODIFY (“DATE_CREATED” NOT NULL ENABLE);

ALTER TABLE “BOB”.”SNOOPLE” ADD CONSTRAINT “EP_BEGIN_END_DATE_CHK” CHECK (end_date IS NULL OR end_date >= begin_date) ENABLE NOVALIDATE;

ALTER TABLE “BOBDW”.”SNOOPLE” MODIFY (“SNOOPLE_KEY” NOT NULL ENABLE);

ALTER TABLE “BOBDW”.”SNOOPLE” MODIFY (“BEGIN_DATE” NOT NULL ENABLE);

ALTER TABLE “BOBDW”.”SNOOPLE” MODIFY (“DATE_CREATED” NOT NULL ENABLE);

ALTER TABLE “ED”.”SNOOPLE” MODIFY (“SNOOPLE_KEY” NOT NULL ENABLE);

ALTER TABLE “ED”.”SNOOPLE” MODIFY (“START_DATE” NOT NULL ENABLE);

ALTER TABLE “ED”.”SNOOPLE” MODIFY (“END_DATE” NOT NULL ENABLE);

ALTER TABLE “ED”.”SNOOPLE” ADD CONSTRAINT “SNARD_FK” FOREIGN KEY (“SNARD_KEY”)
REFERENCES “ED”.”CLAIMANT_STATUS” (“SNARD_KEY”) ENABLE;

ALTER TABLE “BOB”.”SNOOPLE” ADD CONSTRAINT “SNOOPLE_PK” PRIMARY KEY (“SNOOPLE_KEY”)
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE “BOB_X” ENABLE;

ALTER TABLE “BOBDW”.”SNOOPLE” ADD CONSTRAINT “SNOOPLE_PK” PRIMARY KEY (“SNOOPLE_KEY”)
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE “BOBDW_DATA” ENABLE;

ALTER TABLE “ED”.”SNOOPLE” ADD CONSTRAINT “SNOOPLE_PK” PRIMARY KEY (“SNOOPLE_KEY”)
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE “ED_DATA” ENABLE;

All statements are complete and contain the configured SQL terminator. These constraints can now be dropped, if necessary, and rebuilt just as they were.

Databases change over time because data requirements can change over the lifetime of an application. Because of this constraints can be added, dropped or altered because of those data changes. Having a mechanism to generate code for existing constraints should be in every DBA’s back pocket. It’s good that Oracle recognizes this and provides the DBMS_METADATA package to retrieve this information into an immediately usable form in the unfortunate event that a constraint gets, or needs to be, dropped. Using this package as the first step in managing constraints may keep the DBA from losing all of his or her hair should the unmentionable occur.

It’s a thought worth having.

September 12, 2023

Externally yours

Filed under: General — dfitzjarrell @ 13:53

Every so often it may be necessary to provide data that can change on a regular basis. Unfortunately for a “standard” table this can be complicated and tedious especially if the definition changes. Oracle provides a somewhat unique solution with the external table. Let’s see what that id, now to create one and how relatively simple it can be to alter the definition and load data.

An external table takes advantage of a tool named SQL*Loader, Oracle’s bulk data loading utility using flat files. Shown below is a scriipt to create just such a table:

CONNECT / AS SYSDBA;
drop table ch33.sec_audit_ext_tbl;
— create the external table
CREATE TABLE ch33.sec_audit_ext_tbl
(audit_dt varchar2(20),
db varchar2(12),
emp_name varchar2(80),
email varchar2(80),
acct_status varchar2(24),
priv_granted varchar2(45)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY data_pump_dir
ACCESS PARAMETERS
(
records delimited by newline
badfile data_pump_dir:’loader_tst%a_%p.bad’
logfile data_pump_dir:’loader_tst%a_%p.log’
fields terminated by ‘;’
missing field values are null
( audit_dt, db, emp_name,email, acct_status, priv_granted
)
)
LOCATION (‘user_privs_semicolon.lst’)
)
PARALLEL
REJECT LIMIT UNLIMITED;

It looks … interesting and possibly confusing, so let’s discuss each part in some detail. It starts, as any other table creation script, with the create table statement, including column names and data types:

CREATE TABLE ch33.sec_audit_ext_tbl
(audit_dt varchar2(20),
db varchar2(12),
emp_name varchar2(80),
email varchar2(80),
acct_status varchar2(24),
priv_granted varchar2(45)
)

From here it differs as it uses features found in SQL*Loader. The next section describes the table as external and provides the relevant information the utility needs to build and populate the table, like record delimiters,column delimiters, the directory where the source flat file is located and files to log the build and log bad records in the source file. Explaining this section further:

 ORGANIZATION EXTERNAL                                                                  <== Declare this an external table
 ( 
   TYPE ORACLE_LOADER                                                                   <== Use SQL*Loader to read and populate data
   DEFAULT DIRECTORY data_pump_dir                                                      <== Oracle directory where files will be read and written
   ACCESS PARAMETERS 
   ( 
     records delimited by newline                                                       <== Newlines define records
     badfile data_pump_dir:'loader_tst%a_%p.bad'                                        <== File where bad records will be recorded    
     logfile data_pump_dir:'loader_tst%a_%p.log' a                                      <== Log file
     fields terminated by ';'                                                           <== Column values are separated with semicolons
     missing field values are null                                                      <== "Missing" values are considered NULL
     ( audit_dt, db, emp_name,email, acct_status, priv_granted                          <== columns to populate in each row
     ) 
   ) 
   LOCATION ('user_privs_semicolon.lst')                                                <== File name to load
 ) 

Next is whether to use serial or parallel processing followed by the reject limit to reach before failing the table creation. Siince SQL*Loader is the tool of choice it will follow the usual rule for bulk data loading.

As this table relies upon the flat file used as the source the data can be re-loaded by simply modifying the file specified in the LOCATION parameter. Using this example let’s assume there are two possible flat files:

   user_privs_semicolon.lst  
   user_privs_semicolo2n.lst  

This table can use one, the other, or both files as the data source. Obviously changing the file name is the first way to change the table data loaded. The second is to add file names, enclosed in single-quotes and separated by commas, to load multiple files at the same time. To make this a bit clearer presume a numeric data is used as part of the file name:

   user_privs_semicolon_01012023.lst  
   user_privs_semicolon_01312023.lst  

`
Now we know the extract dates for this data and we can load either file individually or load them both. If the data changes regularly there may be weekly extracts:

   user_privs_semicolon_01012023.lst  
   user_privs_semicolon_01082023.lst  
   user_privs_semicolon_01152023.lst  
   user_privs_semicolon_01222023.lst  
   user_privs_semicolon_01292023.lst  
  ...

Given that frequency of file creation it’s possible to load data for roughly an entire month by defining LOCATION as follows:

   LOCATION ('user_privs_semicolon_01012023.lst','user_privs_semicolon_01082023.lst', user_privs_semicolon_01152023.lst','user_privs_semicolon_01222023.lst','user_privs_semicolon_01292023.lst')

Presuming no errors occur during the load phase of the external table creation all of the rows will appear in the external table. Changing the data is a matter of dropping the current table definition, editing the script to change flat files and executing the modified script.

External tables build quickly, yet return data a bit slower than a conventional table the first time they are accessed, due to the nature of SQL*Loader. Once a session as accessed the table no additional loading is necessary and results return with the same speed as data from permanent, physical tables. As long as the user maintains his or her current session queries will be speedy. Should the session be disconnected due to inactivity or a network interruption the table will need to be reloaded. As each session gets a memory-resident copy of the data many users can access the same data without any impact to query speed.

Just as simply the external table definition can change so if fields are added or removed from the extract simple edits to the source script can easily add or remove columns. Since external tables load the definition into memory they consume no physical storage and leave no “bodies” behind when dropped. For example if the Oracle username is to be included in the next extract the script would be changed to:

CONNECT / AS SYSDBA;
drop table ch33.sec_audit_ext_tbl;
— create the external table
CREATE TABLE ch33.sec_audit_ext_tbl
(audit_dt varchar2(20),
db varchar2(12),
username varchar2(35),
emp_name varchar2(80),
email varchar2(80),
acct_status varchar2(24),
priv_granted varchar2(45)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY data_pump_dir
ACCESS PARAMETERS
(
records delimited by newline
badfile data_pump_dir:’loader_tst%a_%p.bad’
logfile data_pump_dir:’loader_tst%a_%p.log’
fields terminated by ‘;’
missing field values are null
( audit_dt, db, username, emp_name,email, acct_status, priv_granted
)
)
LOCATION (‘user_privs_semicolon.lst’)
)
PARALLEL
REJECT LIMIT UNLIMITED;

The column is added to the definition and to the list of columns to populate on load. The table is now changed to include the Oracle username value and the column is located exactly where the developer wants it. It really is that simple.

Creating and using external tables is a fairly operation, once the basic structure of the external table creation command is understood. Simple edits make it convenient to change the table structure, the data loaded, or both, allowing the suer to tailor the table or tables as the data and/or query requirements dictate.

Data in external tables cannot be updated using the traditional “UPDATE schplitzenflibber …” syntax as they are read-only objects. Updating data requires that the source file be edited and the table rebuild/reloaded. This, of course, ensures that the data in= an external table cannot be inadvertently changed bu an errant insert/update/delete statement:

33prap22(uat001-wl-oradb): SYS > update ch33.sec_audit_ext_tbl set db = ‘SNARD’;
update ch33.sec_audit_ext_tbl set db = ‘SNARD’
*
ERROR at line 1:
ORA-30657: operation not supported on external organized table

Inserts and deletes will be met with the same error, making external tables ideal for storing reference data that cannot be changed by the end user.

Trailing spaces in fields are not removed by a VARCHAR2 declaration so such spaces must be removed. Edits to a source file can be made after the table is created and will be reflected in the output the next time the table is queried.

External tables aren’t for every situation, but they can be extremely handy when reference data can change frequently and the data is not to be updated by end users. They may take some practice in their setup but they load quickly, they can be modified with little effort, they occupy no physical storage and the data can’t be changed by conventional insert/update/delete statements. One down side to external tables is the data cannot be encrypted –no tablespace contains them so TDE can’t be used. Oracle support reports that there is no method to encrypt external table data, so external tables are not a good choice when PHI and/or PII is to be used. However since an external table can be used to store non-sensitive data any PII or PHI can be stored in a conventional table using TDE and encrypted tablesapces external tables are well-suited for non-sensitive lookup data that references any PII/PHI values.

External tables can be very useful when it’s necessary to augment table data with values not otherwise stored in the database. They can eliminate the tedious task of matching values from flat file to database columns by putting such data into a form SQL*Plus can access, saving time, energy and the patience of the end user. And, the more the end users can do for themselves the less likely they are to go to the DBA. It’s a win/win situation.

And we all like winning.

September 8, 2023

“It’s your undoing…”

Filed under: General — dfitzjarrell @ 14:55

Transactions are the lifeblood of a relational database, Oracle included. Every insert, update and delete is important, but so is the ability to rollback, or undo, any current transaction should an error or mistake occur. Getting some transactions ‘right’ can involve some trial and error on the part of the developer as queries used to provide the data set to modify are tuned and adjusted. Oracle automatically provides the ability to rollback uncommitted transactions and, in the process, allows the DBA to monitor the undo area for usage. Although automatic undo shouldn’t be an issue, provided the UNDO tablespace is properly sized for the workload, it can be helpful to see which SQL statements are consuming undo, and how much they are consuming. Let’s look into the ways a DBA can find this information.

Automatic undo has been in use for quite a while, utilizing a locally managed tablespace created specifically for this purpose. Undo segments are created automatically to meet the changing demand. As these segments are used the number of extents increases allowing even the largest transactions to generate sufficient undo to rollback the transaction if the need arises. Of course there is the dreaded ORA-01555 error, thrown when changes overwrite undo records being used by a long-running transaction. Manual rollback is also possible, should a transaction update more data than intended and need to be modified to provide correct results, provided the action has not yet been committed. This should be common knowledge to the DBA.

Four views can give the DBA information on the undo operations and usage:

DBA_HIST_UNDOSTAT
DBA_UNDO_EXTENTS
GV_$UNDOSTAT
V_$UNDOSTAT

DBA_HIST_UNDOSTAT is part of the Diagnostic and Tuning Pack views used by AWR and ASH reports. It collects limited historical data about the undo stream and its usage; snapshots are collected from the data in GV_$UNDOSTAT and kept in accordance with the configured retention period for the workload repository.

DBA_UNDO_EXTENTS provides information on the allocated undo extents, including segment name, extent number and the associated size.

GV_$UNDOSTAT, along with V_$UNDOSTAT, allow the DBA to monitor undo activity through statistical information such as the blocks consumed, the transaction count for the reported interval, various “steal” counts, active, unexpired and expired block counts and the tuned undo retention value. Such data helps the DBA track usage by date and time so that periods of peak activity can be determined.

The following views also report on undo, using the older rollback terminology in their naming:

DBA_ROLLBACK_SEGS
GV_$ROLLSTAT
KU$_ROLLBACK_VIEW
V_$ROLLNAME
V_$ROLLSTAT

These do provide different information than the %UNDO% views, adding another dimension to the reporting data. All nine should be considered when reporting on UNDO usage.

Other views outside of those nine can also contribute to UNDO reporting; the following query produces a useful report on undo usage and what SQL, if any, is responsible:

set linesize 240
COLUMN sid FORMAT 99999
COLUMN command_or_action FORMAT A90 HEADING ‘SQL Command or Action’ WRAP
COLUMN current_time FORMAT A19 HEADING ‘Current Time’
COLUMN rollback_mb FORMAT 99,990.90 HEADING ‘Undo|MB Used’
COLUMN rows_per_sec FORMAT 99,999,990.90 HEADING ‘TXN Rows|Per Sec’
COLUMN start_time FORMAT a19 HEADING ‘TXN Start Time’
COLUMN status FORMAT A8 HEADING ‘Status’
COLUMN used_urec FORMAT 9,999,999,990 HEADING ‘Undo Rows|Written’
COLUMN username FORMAT A15 HEADING ‘Username’
COLUMN program format a25

SELECT s.sid
, TO_CHAR(TO_DATE(t.start_time, ‘MM/DD/YY HH24:MI:SS’), ‘DD-MON-YY HH24:MI:SS’) start_time
, TO_CHAR(sysdate, ‘DD-MON-YY HH24:MI:SS’) current_time
, CASE WHEN (sysdate – TO_DATE(t.start_time, ‘MM/DD/YY HH24:MI:SS’)) = 0
THEN t.used_urec
ELSE t.used_urec / NVL(((sysdate – TO_DATE(t.start_time, ‘MM/DD/YY HH24:MI:SS’)) * 86400), 1)
END rows_per_sec
, SUBSTR(s.username, 1, 15) username
, program
, DECODE(BITAND(t.flag,128), 0, NULL, ‘Rollback’) status
, t.used_urec
, ROUND(r.rssize / 1024 / 1024, 2) rollback_mb
, DECODE(sq.sql_text, NULL, DECODE(aa.name, NULL, ‘UNKNOWN’, aa.name), sq.sql_text) command_or_action
FROM v$transaction t
, v$session s
, v$rollstat r
, v$sql sq
, audit_actions aa
WHERE (t.xidusn = r.usn)
AND (t.addr = s.taddr (+))
AND ( s.sql_hash_value = sq.hash_value (+)
AND s.sql_address = sq.address (+))
AND (s.command = aa.action)
ORDER BY t.start_time, s.sid;

Looking at the data the above query returns we find a wealth of information:

                         TXN Rows                                 Undo Rows       Undo

SID TXN Start Time Current Time Per Sec Username PROGRAM Status Written MB Used SQL Command or Action


145 01-JUN-22 15:38:06 01-JUN-22 15:38:16 3,755.30 BING sqlplus@a633a772bc3b (TNS 37,553 4.99 delete from t1 where mod(id,100) >= 10
V1-V3)

145 01-JUN-22 15:38:06 01-JUN-22 15:38:16 3,755.30 BING sqlplus@a633a772bc3b (TNS 37,553 4.99 delete from t1 where mod(id,100) >= 10
V1-V3)

145 01-JUN-22 15:38:06 01-JUN-22 15:38:16 3,755.30 BING sqlplus@a633a772bc3b (TNS 37,553 4.99 delete from t1 where mod(id,100) >= 10
V1-V3)

The DBA now has the undo rows written, the megabytes of UNDO space consumed and the SQL statement generating this undo usage. Repeated execution of this query will return the current UNDO usage for all active transactions generating UNDO, including recursive operations executed by the database engine such as data dictionary management processes. Such actions are likely to be listed with a SQL command as UNKNOWN in the query output since no name is assigned in the AUDIT_ACTIONS view. This query allows the DBA to dynamically monitor UNDO usage by transaction so long-running operations, that can consume large quantities of space, can be identified and possibly tuned to reduce the UNDO demand.

how does one go about tuning UNDO? With releases of Oracle starting with 10.2 the DBMS_UNDO_ADV package is available:

FUNCTION BEST_POSSIBLE_RETENTION RETURNS NUMBER
Argument Name Type In/Out Default?


STARTTIME DATE IN
ENDTIME DATE IN
FUNCTION BEST_POSSIBLE_RETENTION RETURNS NUMBER
FUNCTION BEST_POSSIBLE_RETENTION RETURNS NUMBER
Argument Name Type In/Out Default?


S1 NUMBER IN
S2 NUMBER IN
FUNCTION LONGEST_QUERY RETURNS NUMBER
Argument Name Type In/Out Default?


STARTTIME DATE IN
ENDTIME DATE IN
FUNCTION LONGEST_QUERY RETURNS NUMBER
FUNCTION LONGEST_QUERY RETURNS NUMBER
Argument Name Type In/Out Default?


S1 NUMBER IN
S2 NUMBER IN
FUNCTION RBU_MIGRATION RETURNS NUMBER
Argument Name Type In/Out Default?


STARTTIME DATE IN
ENDTIME DATE IN
FUNCTION RBU_MIGRATION RETURNS NUMBER
FUNCTION REQUIRED_RETENTION RETURNS NUMBER
Argument Name Type In/Out Default?


STARTTIME DATE IN
ENDTIME DATE IN
FUNCTION REQUIRED_RETENTION RETURNS NUMBER
FUNCTION REQUIRED_RETENTION RETURNS NUMBER
Argument Name Type In/Out Default?


S1 NUMBER IN
S2 NUMBER IN
FUNCTION REQUIRED_UNDO_SIZE RETURNS NUMBER
Argument Name Type In/Out Default?


RETENTION NUMBER IN
STARTTIME DATE IN
ENDTIME DATE IN
FUNCTION REQUIRED_UNDO_SIZE RETURNS NUMBER
Argument Name Type In/Out Default?


RETENTION NUMBER IN
FUNCTION REQUIRED_UNDO_SIZE RETURNS NUMBER
Argument Name Type In/Out Default?


RETENTION NUMBER IN
S1 NUMBER IN
S2 NUMBER IN
FUNCTION REQUIRED_UNDO_SIZE RETURNS NUMBER
Argument Name Type In/Out Default?


RETENTION DBMS_UADV_ARR IN
UTBSIZE DBMS_UADV_ARR IN/OUT
STARTTIME DATE IN
ENDTIME DATE IN
FUNCTION REQUIRED_UNDO_SIZE RETURNS NUMBER
Argument Name Type In/Out Default?


RETENTION DBMS_UADV_ARR IN
UTBSIZE DBMS_UADV_ARR IN/OUT
FUNCTION REQUIRED_UNDO_SIZE RETURNS NUMBER
Argument Name Type In/Out Default?


RETENTION DBMS_UADV_ARR IN
UTBSIZE DBMS_UADV_ARR IN/OUT
S1 NUMBER IN
S2 NUMBER IN
FUNCTION UNDO_ADVISOR RETURNS VARCHAR2
Argument Name Type In/Out Default?


STARTTIME DATE IN
ENDTIME DATE IN
INSTANCE NUMBER IN
FUNCTION UNDO_ADVISOR RETURNS VARCHAR2
Argument Name Type In/Out Default?


INSTANCE NUMBER IN
FUNCTION UNDO_ADVISOR RETURNS VARCHAR2
Argument Name Type In/Out Default?


S1 NUMBER IN
S2 NUMBER IN
INSTANCE NUMBER IN
FUNCTION UNDO_AUTOTUNE RETURNS BOOLEAN
Argument Name Type In/Out Default?


AUTOTUNE_ENABLED BOOLEAN OUT
FUNCTION UNDO_HEALTH RETURNS NUMBER
Argument Name Type In/Out Default?


PROBLEM VARCHAR2 OUT
RECOMMENDATION VARCHAR2 OUT
RATIONALE VARCHAR2 OUT
RETENTION NUMBER OUT
UTBSIZE NUMBER OUT
FUNCTION UNDO_HEALTH RETURNS NUMBER
Argument Name Type In/Out Default?


STARTTIME DATE IN
ENDTIME DATE IN
PROBLEM VARCHAR2 OUT
RECOMMENDATION VARCHAR2 OUT
RATIONALE VARCHAR2 OUT
RETENTION NUMBER OUT
UTBSIZE NUMBER OUT
FUNCTION UNDO_HEALTH RETURNS NUMBER
Argument Name Type In/Out Default?


S1 NUMBER IN
S2 NUMBER IN
PROBLEM VARCHAR2 OUT
RECOMMENDATION VARCHAR2 OUT
RATIONALE VARCHAR2 OUT
RETENTION NUMBER OUT
UTBSIZE NUMBER OUT
FUNCTION UNDO_INFO RETURNS BOOLEAN
Argument Name Type In/Out Default?


TABLE_SPACE_NAME VARCHAR2 OUT
TABLE_SPACE_SIZE NUMBER OUT
AUTO_EXTEND BOOLEAN OUT
UNDO_RETENTION NUMBER OUT
RETENTION_GUARANTEE BOOLEAN OUT

Two functions appear to be quite useful — undo_advisor and undo_health. These functions perform transactions to test the UNDO tablespace so they cannot be executed using selects. Taking undo_health “for a spin” we have:

1 declare
2 PROBLEM VARCHAR2(4000);
3 RECOMMENDATION VARCHAR2(4000);
4 RATIONALE VARCHAR2(4000);
5 RETENTION NUMBER;
6 UTBSIZE NUMBER;
7 retval number;
8 begin
9 retval :=dbms_undo_adv.undo_health(sysdate-1, sysdate,problem,recommendation,rationale,retention,utbsize);
10 dbms_output.put_line(problem);
11 dbms_output.put_line(recommendation);
12 dbms_output.put_line(rationale);
13 dbms_output.put_line(retention);
14 dbms_output.put_line(utbsize);
15* end;
SQL > /
No problem found
0
0

PL/SQL procedure successfully completed.

The function can return up to five values, depending upon what, if any, problem is discovered. As can be seen from the above example no issues are present in the undo tablespace. In similar fashion the undo_advisor is called, and it returns one value. Taking that function for a test drive we find:

1 declare
2 retval varchar2(4000);
3 begin
4 retval :=dbms_undo_adv.undo_advisor(sysdate-1, sysdate,1);
5 dbms_output.put_line(retval);
6* end;
SQL > /
Finding 1:The undo tablespace is OK.

PL/SQL procedure successfully completed.

The output is fairly sparse when no issues are present, but when the UNDO tablespace is healthy effusive output is unnecessary.

Other functions, like best_possible_retention, can be executed through a query. This one outputs, obviously, the best possible undo_retention setting to use:

SQL> select dbms_undo_adv.best_possible_retention(sysdate-1, sysdate) from dual;

DBMS_UNDO_ADV.BEST_POSSIBLE_RETENTION(SYSDATE-1,SYSDATE)

                     2514577

The value it can generate may seem a bit … large when compared to the default setting of 900. Remember this is a recommended setting, not a value chiseled in stone, so it may be accepted or ignored. As long as such recommendations are not implemented with a guarantee there should be no issues with setting a retention value that large.

Part of the tuning process is monitoring the undo system after changes are made. DBA_UNDO_EXTENTS shows the available extents and their expiration status; the larger the undo_retention the longer extents will remain unexpired. Unexpired extents are not an issue, as Oracle is prepared to “steal” unexpired undo extents if they are needed, unless the retention is guaranteed. Guaranteeing undo retention runs the risk of errors in query execution since Oracle cannot “steal” unexpired extents when they are needed, even if they are not being used.

How do you know if undo is being “stolen”? The v$undostat view reports on that and more:

SQL > desc v$undostat
Name Null? Type


BEGIN_TIME DATE
END_TIME DATE
UNDOTSN NUMBER
UNDOBLKS NUMBER
TXNCOUNT NUMBER
MAXQUERYLEN NUMBER
MAXQUERYID VARCHAR2(13)
MAXCONCURRENCY NUMBER
UNXPSTEALCNT NUMBER
UNXPBLKRELCNT NUMBER
UNXPBLKREUCNT NUMBER
EXPSTEALCNT NUMBER
EXPBLKRELCNT NUMBER
EXPBLKREUCNT NUMBER
SSOLDERRCNT NUMBER
NOSPACEERRCNT NUMBER
ACTIVEBLKS NUMBER
UNEXPIREDBLKS NUMBER
EXPIREDBLKS NUMBER
TUNED_UNDORETENTION NUMBER
CON_ID NUMBER

UNDOBLKS, TXNCOUNT, MAXQUERYLEN, MAXQUERYID and UNXPSTEALCNT are worth monitoring since these reflect the volume of UNDO generated and managed by Oracle. Oracle “stealing” unexpired undo extents often indicates an UNDO tablespace that may not be large enough to handle the transaction load users are generating. Let’s look at some typical output from that view:

BEGIN_TIME END_TIME UNDOTSN UNDOBLKS TXNCOUNT MAXQUERYLEN MAXQUERYID MAXCONCURRENCY UNXPSTEALCNT UNXPBLKRELCNT UNXPBLKREUCNT EXPSTEALCNT EXPBLKRELCNT EXPBLKREUCNT SSOLDERRCNT


NOSPACEERRCNT ACTIVEBLKS UNEXPIREDBLKS EXPIREDBLKS TUNED_UNDORETENTION CON_ID


18-OCT-2022 16:51:20 18-OCT-2022 16:54:42 2 0 41 0 0 0 0 0 0 0 0 0
0 160 221312 16512 444650 0

18-OCT-2022 16:41:20 18-OCT-2022 16:51:20 2 0 12 0 0 0 0 0 0 0 0 0
0 160 221312 16512 437282 0

18-OCT-2022 16:31:20 18-OCT-2022 16:41:20 2 17 150 1580 f3yfg50ga0r8n 2 0 0 0 0 0 0 0
0 160 221312 16512 415260 0

The MAXQUERYLEN is in seconds, and the MAXQUERYID is the SQL_ID for that query. Notice that all of the various count variables record 0, an indication that UNDO is properly sized. Should any counts increase it is likely that the UNDO tablespace will need one or more additional data files. When data files are added to an UNDO tablespace ensure they are set to autoextend, to minimize the attention that tablespace may need (it is easier to let Oracle automatically extend the available data files than it is to constantly monitor file size and manually extend one or more files after an error is thrown).

Querying v$undostat along with regular execution of the two DBMS_UNDO_ADV scripts provided can provide a fairly robust suite of checks for UNDO health. The scripts take very little time to run and do not affect currently running transactions so regular executions throughout the work day are not a concern.

A “quick and dirty” check on undo consumption can be generated with this query:

et linesize 230 pagesize 30
col username format a30
col target format a67

select s.username, s.sid, s.serial#, t.ubablk, t.ubarec, lo.sofar, lo.totalwork, lo.target
from v$session s join v$transaction t on s.taddr = t.addr
left outer join v$session_longops lo on lo.sid = s.sid and lo.serial# = s.serial#
/

It reports the username, session identifiers, undo blocks used, undo records written, the table being accessed, the work done so far and the total work for the operation reported. Sample output follows:

USERNAME SID SERIAL# UBABLK UBAREC SOFAR TOTALWORK TARGET


CH33_USER 1710 57816 56446 67 1140540 1140540 CH33.TIMS_DOCUMENT
CH33_USER 15 9561 233585 58
CH33_USER 3134 11670 326184 35
CH33_USER 3416 7942 1929966 33
CH33_USER 4294 40398 295056 17
CH33_USER 635 33966 289612 53
SWEAMS_USER 581 36936 295056 17
SWEAMS_USER 3969 62960 56446 67
CH33_USER 3428 1681 1009409 33

9 rows selected.

This query can be useful when seeing large volumes of undo that are not released in a short period of time, signalling the DBA to monitor the UNDO tablespace allocations and the undo segments in use. Should the UNDO tablespace usage pass 90% many monitoring tools will provide a space warning — of course, depending upon the autoextend state of the datafiles the warning may be ignorable. A query like this:

set linesize 132 tab off trimspool on
set pagesize 105
set pause off
set feedb on

column “TOTAL AVAIL (MB)” format 999,999,999,990.00
column “TOTAL PHYS ALLOC (MB)” format 9,999,990.00
column “USED (MB)” format 9,999,990.00
column “FREE (MB)” format 9,999,990.00
column “% USED OF AVAIL” format 990.00
column “% USED OF ALLOC” format 990.00

select a.tablespace_name,
nvl(b.tot_used,0)/(10241024) “USED (MB)”, a.physical_bytes/(10241024) “TOTAL PHYS ALLOC (MB)”,
a.bytes_alloc/(10241024) “TOTAL AVAIL (MB)”, (nvl(b.tot_used,0)/a.physical_bytes)100 “% USED OF ALLOC”,
(nvl(b.tot_used,0)/a.bytes_alloc)100 “% USED OF AVAIL” from ( select tablespace_name, sum(bytes) physical_bytes, sum(decode(autoextensible,’NO’,bytes,’YES’,maxbytes)) bytes_alloc from dba_data_files group by tablespace_name ) a, ( select tablespace_name, sum(bytes) tot_used from dba_segments group by tablespace_name ) b where a.tablespace_name = b.tablespace_name (+) — and (nvl(b.tot_used,0)/a.bytes_alloc)100 > 10
and a.tablespace_name not in (select distinct tablespace_name from dba_temp_files)
order by 6
/

reports tablespace usage relative to the currently allocated space and also calculates usage based upon the maximum size the autoextend files can attain:

TABLESPACE_NAME USED (MB) TOTAL PHYS ALLOC (MB) TOTAL AVAIL (MB) % USED OF ALLOC % USED OF AVAIL


USERS 6.13 5,521.00 32,767.98 0.11 0.02
SYSTEM 1,994.13 2,010.00 32,767.98 99.21 6.09
SYSAUX 2,150.25 13,400.00 32,767.98 16.05 6.56
UNDOTBS1 42,492.50 118,781.00 131,071.94 35.77 32.42

Monitoring tools usually trigger oh the value in the first percent used column, which can differ significantly from value in the second percent used column. A query like this one can help the DBA distinguish the between an actual alert and “noise” and save him or her from needlessly adding storage. It also keeps management from worrying about essentially nothing, and everyone can appreciate that.

In the event the DBA must add space to the UNDO tablespace note that the datafiles are set to autoextend. This is easily accomplished using the following syntax:

alter tablespace undotbs1 add datafile ‘/my/data/files/undotbs4.dbf’ size 20g autoextend on next 128M maxsize unlimited;

Of course the UNDO tablespace name and the fully qualified file name are unique to each database installation, so before adding space verify the file names and locations already configured for the UNDO tablespace. What may seem odd here is the 128M autoextend allocations; checking what Oracle created will confirm that value. Do not change that value as it may adversely affect UNDO performance.

Even with automatic undo management in the database it still falls on the shoulders of the DBA to regularly monitor UNDO space ans usage. Regular database health checks should include such checks to provide at least a daily glimpse in the UNDO arena. If UNDO fails the database stops processing transactions until the issue is resolved, which would indeed be a business disaster visible to all. Being in the “hot seat” is NEVER comfortable, and anything someone can do to prevent that from occurring is definitely effort well-spent. A few minutes each day monitoring UNDO is cheap insurance that can prevent a grinding halt to data processing at the most inopportune time

UNDO is a critical part of the transaction pathway and should be given a prominent place in any database monitoring script. Not monitoring UNDO is a flaw that needs correction — the queries provided here can give the DBA the “heads up” notice needed to ensure every transaction can complete as expected. And they will give the DBA knowledge of the UNDO usage patterns so that at peak times of activity there is sufficient UNDO space to handle the load. A calm DBA is a useful DBA, and a prepared DBA is, obviously, calm. So monitor UNDO before it can become an issue.

Unless you LIKE being Chicken Little.

Create a free website or blog at WordPress.com.