Runbook10 steps
Morning health check
The daily pass: instance state, space, FRA, last night's backups, alert log, failed jobs, invalid objects and accounts about to expire. Read-only and needs no extra license.
- Database and instance overviewOne row per instance: host, version, uptime, role, open mode, archive log mode and whether it's a CDB. The first thing to run when you land on an unfamiliar…
- Processes and sessions against their limitsCurrent and peak usage of processes, sessions and transactions since startup. A PEAK % near 100 means the next connection storm will hit ORA-00020.
- Tablespace usageUsed and free space in GB against the maximum each tablespace can autoextend to, with its type (permanent, temporary or undo), fullest first.
- Fast Recovery Area usageFRA size, used and reclaimable space, then what's using it by file type. Real used % is the number that matters: at 100% the database stops archiving and hangs.
- RMAN backup jobs in the last 7 daysStatus, duration and output size of each RMAN job, newest first.
- Datafiles without a recent backupDatafiles with no backup of any kind in the last two days, or none at all. A new datafile added after the last full backup shows up here first.
- Alert log errors in the last 24 hoursORA- and TNS- errors plus checkpoint warnings from the alert log, read through SQL. It can be slow when the alert log is very large.
- Scheduler job failures in the last 24 hoursEvery job run that didn't succeed, with the error number and the start of the error text.
- Invalid objectsEvery invalid object by owner and type, typically left behind by a deployment or patch. The recompile script fixes most of them.
- Accounts locked, expired or expiring soonApplication accounts that aren't OPEN, or whose passwords expire in the next 14 days. Catch service accounts before they break an application.
The whole runbook as one file: SQL> @morning
1-- denrepo runbook: Morning health check2-- The daily pass: instance state, space, FRA, last night's backups, alert log, failed jobs, invalid objects and accounts about to expire. Read-only and needs no extra license.3-- Source: https://denrepo.com/runbooks/morning/4 5SET ECHO OFF6 7-- Not yet verified8PROMPT9PROMPT ==== 1 of 10: Database and instance overview ====10CLEAR COLUMNS11SET LINESIZE 200 PAGESIZE 100 TRIMOUT ON TAB OFF12COLUMN inst_name HEADING "INSTANCE" FORMAT A1213COLUMN host_name HEADING "HOST" FORMAT A25 TRUNCATE14COLUMN version FORMAT A1115COLUMN status FORMAT A816COLUMN started FORMAT A1617COLUMN up_days HEADING "UP DAYS" FORMAT 9990.918COLUMN db_name HEADING "DB" FORMAT A919COLUMN role FORMAT A1620COLUMN open_mode HEADING "OPEN MODE" FORMAT A2021COLUMN log_mode HEADING "LOG MODE" FORMAT A1222COLUMN cdb FORMAT A323 24SELECT i.instance_name AS inst_name, i.host_name, i.version, i.status,25 TO_CHAR(i.startup_time, 'YYYY-MM-DD HH24:MI') AS started,26 ROUND(SYSDATE - i.startup_time, 1) AS up_days,27 d.name AS db_name, d.database_role AS role,28 d.open_mode, d.log_mode, d.cdb29FROM gv$instance i30CROSS JOIN v$database d31ORDER BY i.inst_id;32 33-- Not yet verified34PROMPT35PROMPT ==== 2 of 10: Processes and sessions against their limits ====36CLEAR COLUMNS37SET LINESIZE 120 PAGESIZE 100 TRIMOUT ON TAB OFF38COLUMN resource_name HEADING "RESOURCE" FORMAT A2239COLUMN current_util HEADING "CURRENT" FORMAT 999,99040COLUMN max_util HEADING "PEAK" FORMAT 999,99041COLUMN limit_value HEADING "LIMIT" FORMAT A1042COLUMN max_pct HEADING "PEAK %" FORMAT 990.943 44SELECT resource_name,45 current_utilization AS current_util,46 max_utilization AS max_util,47 TRIM(limit_value) AS limit_value,48 CASE WHEN TRIM(limit_value) NOT IN ('UNLIMITED', '0')49 THEN ROUND(100 * max_utilization / TO_NUMBER(TRIM(limit_value)), 1) END AS max_pct50FROM v$resource_limit51WHERE resource_name IN ('processes', 'sessions', 'transactions', 'parallel_max_servers')52ORDER BY resource_name;53 54-- Not yet verified55PROMPT56PROMPT ==== 3 of 10: Tablespace usage ====57CLEAR COLUMNS58SET LINESIZE 130 PAGESIZE 100 TRIMOUT ON TAB OFF59COLUMN tablespace_name HEADING "TABLESPACE" FORMAT A2560COLUMN contents HEADING "TYPE" FORMAT A961COLUMN used_gb HEADING "USED GB" FORMAT 999,990.9962COLUMN free_gb HEADING "FREE GB" FORMAT 999,990.9963COLUMN max_gb HEADING "MAX GB" FORMAT 999,990.9964COLUMN used_pct HEADING "USED %" FORMAT 990.965 66SELECT m.tablespace_name,67 t.contents,68 ROUND(m.used_space * t.block_size / POWER(1024, 3), 2) AS used_gb,69 ROUND((m.tablespace_size - m.used_space) * t.block_size / POWER(1024, 3), 2) AS free_gb,70 ROUND(m.tablespace_size * t.block_size / POWER(1024, 3), 2) AS max_gb,71 ROUND(m.used_percent, 1) AS used_pct72FROM dba_tablespace_usage_metrics m73JOIN dba_tablespaces t ON t.tablespace_name = m.tablespace_name74ORDER BY m.used_percent DESC;75 76-- Not yet verified77PROMPT78PROMPT ==== 4 of 10: Fast Recovery Area usage ====79CLEAR COLUMNS80SET LINESIZE 140 PAGESIZE 100 TRIMOUT ON TAB OFF81COLUMN name HEADING "LOCATION" FORMAT A3082COLUMN limit_gb HEADING "LIMIT GB" FORMAT 999,990.983COLUMN used_gb HEADING "USED GB" FORMAT 999,990.984COLUMN reclaimable_gb HEADING "RECLAIMABLE GB" FORMAT 999,990.985COLUMN real_used_pct HEADING "REAL USED %" FORMAT 990.986COLUMN files FORMAT 999,99087COLUMN file_type HEADING "FILE TYPE" FORMAT A2588COLUMN used_pct HEADING "USED %" FORMAT 990.9989COLUMN reclaim_pct HEADING "RECLAIM %" FORMAT 990.9990 91SELECT name,92 ROUND(space_limit / POWER(1024, 3), 1) AS limit_gb,93 ROUND(space_used / POWER(1024, 3), 1) AS used_gb,94 ROUND(space_reclaimable / POWER(1024, 3), 1) AS reclaimable_gb,95 ROUND(100 * (space_used - space_reclaimable) / NULLIF(space_limit, 0), 1) AS real_used_pct,96 number_of_files AS files97FROM v$recovery_file_dest;98 99SELECT file_type,100 percent_space_used AS used_pct,101 percent_space_reclaimable AS reclaim_pct,102 number_of_files AS files103FROM v$recovery_area_usage104ORDER BY percent_space_used DESC;105 106-- Not yet verified107PROMPT108PROMPT ==== 5 of 10: RMAN backup jobs in the last 7 days ====109CLEAR COLUMNS110SET LINESIZE 150 PAGESIZE 100 TRIMOUT ON TAB OFF111COLUMN session_key HEADING "KEY" FORMAT 999999112COLUMN input_type HEADING "TYPE" FORMAT A13113COLUMN status FORMAT A23114COLUMN started FORMAT A16115COLUMN ended FORMAT A16116COLUMN size_out HEADING "OUTPUT" FORMAT A10117COLUMN duration HEADING "DURATION" FORMAT A10118 119SELECT session_key, input_type, status,120 TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS started,121 TO_CHAR(end_time, 'YYYY-MM-DD HH24:MI') AS ended,122 output_bytes_display AS size_out,123 time_taken_display AS duration124FROM v$rman_backup_job_details125WHERE start_time > SYSDATE - 7126ORDER BY start_time DESC;127 128-- Not yet verified129PROMPT130PROMPT ==== 6 of 10: Datafiles without a recent backup ====131CLEAR COLUMNS132SET LINESIZE 140 PAGESIZE 100 TRIMOUT ON TAB OFF133COLUMN file# HEADING "FILE#" FORMAT 99999134COLUMN name HEADING "DATAFILE" FORMAT A75135COLUMN last_backup HEADING "LAST BACKUP" FORMAT A16136 137SELECT f.file#, f.name,138 NVL(TO_CHAR(MAX(b.completion_time), 'YYYY-MM-DD HH24:MI'), 'never') AS last_backup139FROM v$datafile f140LEFT JOIN v$backup_datafile b ON b.file# = f.file#141GROUP BY f.file#, f.name142HAVING MAX(b.completion_time) IS NULL143 OR MAX(b.completion_time) < SYSDATE - 2144ORDER BY f.file#;145 146-- Not yet verified147PROMPT148PROMPT ==== 7 of 10: Alert log errors in the last 24 hours ====149CLEAR COLUMNS150SET LINESIZE 200 PAGESIZE 200 TRIMOUT ON TAB OFF151COLUMN at_time HEADING "TIME" FORMAT A19152COLUMN message FORMAT A150 WORD_WRAPPED153 154SELECT TO_CHAR(originating_timestamp, 'YYYY-MM-DD HH24:MI:SS') AS at_time,155 SUBSTR(message_text, 1, 300) AS message156FROM v$diag_alert_ext157WHERE originating_timestamp > SYSTIMESTAMP - INTERVAL '1' DAY158 AND (message_text LIKE '%ORA-%'159 OR message_text LIKE '%TNS-%'160 OR message_text LIKE '%Checkpoint not complete%')161ORDER BY originating_timestamp DESC;162 163-- Not yet verified164PROMPT165PROMPT ==== 8 of 10: Scheduler job failures in the last 24 hours ====166CLEAR COLUMNS167SET LINESIZE 180 PAGESIZE 200 TRIMOUT ON TAB OFF168COLUMN at_time HEADING "TIME" FORMAT A16169COLUMN owner FORMAT A15170COLUMN job_name HEADING "JOB" FORMAT A30171COLUMN status FORMAT A10172COLUMN error# HEADING "ORA-" FORMAT 99999173COLUMN info HEADING "DETAIL" FORMAT A70 WORD_WRAPPED174 175SELECT TO_CHAR(log_date, 'YYYY-MM-DD HH24:MI') AS at_time,176 owner, job_name, status, error#,177 DBMS_LOB.SUBSTR(additional_info, 200, 1) AS info178FROM dba_scheduler_job_run_details179WHERE status <> 'SUCCEEDED'180 AND log_date > SYSTIMESTAMP - INTERVAL '1' DAY181ORDER BY log_date DESC;182 183-- Tested on 12c, 19c · verified Sep 2026184PROMPT185PROMPT ==== 9 of 10: Invalid objects ====186CLEAR COLUMNS187SET LINESIZE 150 PAGESIZE 200 TRIMOUT ON TAB OFF188COLUMN owner FORMAT A20189COLUMN object_type FORMAT A20190COLUMN object_name FORMAT A30191SELECT owner,192 object_type,193 object_name,194 status195FROM dba_objects196WHERE status = 'INVALID'197ORDER BY owner, object_type, object_name;198 199-- Not yet verified200PROMPT201PROMPT ==== 10 of 10: Accounts locked, expired or expiring soon ====202CLEAR COLUMNS203SET LINESIZE 140 PAGESIZE 200 TRIMOUT ON TAB OFF204COLUMN username FORMAT A25205COLUMN account_status HEADING "STATUS" FORMAT A25206COLUMN expires FORMAT A10207COLUMN locked FORMAT A10208COLUMN profile FORMAT A20209 210SELECT username, account_status,211 TO_CHAR(expiry_date, 'YYYY-MM-DD') AS expires,212 TO_CHAR(lock_date, 'YYYY-MM-DD') AS locked,213 profile214FROM dba_users215WHERE oracle_maintained = 'N'216 AND (account_status <> 'OPEN' OR expiry_date < SYSDATE + 14)217ORDER BY expiry_date NULLS LAST, username;218