SQL, PL/SQL Tips and Tricks

Transcription

SQL, PL/SQL Tips and Tricks
SQL, PL/SQL Tips and Tricks
Plamen Petkov
References:
Expert Oracle Database Architecture.
Oracle Documentation
Oracle MetaLink
SQL Cookbook.
Thomas Kyte
ORACLE
ORACLE
Anthony Molinaro
Personal experience
......................................................
Plamen Petkov
Agenda
●
Mining alert/logs files
●
ORA-01555: snapshot too old
●
ORA-01002 fetch out of sequence
●
Optimistic Lock and ORA_ROWSCN
●
Selecting from LONG columns
●
Row Generation
●
Different Timezones support
Mining alert and/or other logs
●
Identify the place of the alert log
\mining_alert\locate_alert.sql
●
Create external tables
●
Select from alert.log
●
Select from apache access.log
Create external tables
It is actually a file located at
the ORACLE server file
system
●alert_dir “directory” is an
oracle object, mapped to a
file system directory
●Now, alert_log appears to
us as normal Oracle table,
for reading only, containing
all lines of the DB alert log
●
create or replace directory alert_dir as
'D:\Documentation\PL_SQL_Tips_Tricks\mining_alert';
CREATE TABLE alert_log(
text_line varchar2(255)
)
ORGANIZATION EXTERNAL(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY alert_dir
ACCESS PARAMETERS(
records delimited by newline
fields
REJECT ROWS WITH ALL NULL FIELDS
)
LOCATION(
'alert_gbdb.log'
)
)
REJECT LIMIT unlimited
;
mining_alert\create_external.sql
Select from alert.log – uptime report
we are looking for
●
Starting ORACLE
instance literal;
at the previous line
is the time of the
startup;
●so, at the line
before the previous
one is the time of
the previous
shutdown
●
Mon May 22 07:51:37 2006
Starting ORACLE instance (normal)
....................
Mon May 22 08:15:14 2006
ALTER DATABASE DISMOUNT
..................
Archive process shutdown avoided: 0 active
Mon May 22 08:15:44 2006
Starting ORACLE instance (normal)
..................
mining_alert\alert.log_Uptime.sql
Select from alert.log – ORA Events
Mon May 22 07:54:23 2006
Errors in file /u01/app/oracle/admin/gbdb/bdump/gbdb_dbw0_22656.trc:
ORA-01157: cannot identify/lock data file 201 - see DBWR trace file
ORA-01110: data file 201: '/u16/app/oracle/oradata/gbdb/temp01.dbf'
we are looking for ORA-xxx
messages, which are usually errors;
●at the previous line is the time of the
event;
●last predicate filters all additional error
messages;
●
mining_alert\alert.log_ORA.sql
Additional info on mining into alert.log: http://asktom.oracle.com/~tkyte/alert_arch.html
Select from apache.log – performance issues
apache_log is an external table,
pointing to
app.access_200607102312 file
●From each record there we derive
the request, request type and
response time per request
●Aggregating we see our slowest
modules at the 23rd hour were
cc_deposits and dowithdraw
●
mining_alert\apache.log.sql
Write to external Data Pump Files
create table apache_log_unload organization external(
type oracle_datapump
default directory alert_dir
location ('apache.log.unload.pump')
)
as select count(1) cnt_Req, max(RT) max_RT,
to_char(trunc(req_Time, 'HH24'), 'dd.mm.yy HH24:MI') req_Time_HH, req_type, Request
from (
select req_Time, req_type,
....................
\mining_alert\apache.log.datapump.unload.sql
mining_alert\APACHE.LOG.UNLOAD.PUMP
Feature of Oracle 10.1 and above
●Using the previous select we
create a data pump format file,
containing the aggregations from
apache_log table
●It contains the RS in binary format
plus XML meta data (not
CREATE/INSERT)
●
Read from external Data Pump Files
As usual external table, but of
oracle_datapump format
●
Created with the
statement from the
previous slide
The new tools IMPDP/EXPDP
designed for VERY HIGH-SPEED
movement of data between DBs
●
New tools IMPDP/EXPDP benefits from the
meta data:
●
Oracle stores character set in the file,
so IMPDP it performs Character Set
conversion on the fly when transferring
data to another DB
●
they easily replace table names etc.
●
Can be suspended/resumed
●
Perform parallel operation
●
............
●
mining_alert\mining_alert\apache.log.datapump.load.sql
ORA-01555: snapshot too old error
NOTE: ORA-01555 is NOT related to data corruption.
It is an error during READING of data.
Current transaction is rolled back.
●
UNDO is too small for the work we do
/Our program(s) fetch through commits/
●
Our program commits through fetches
●
Delayed Block Cleanout
UNDO is in fact, too small
/Our program(s) fetch through commits/
We will update/commit a relatively big table using relatively small
Undo Tablespace and will try to read from it in the same time
create undo tablespace undo_small datafile
'D:\oracle\oradata\orcl\undo_small.dbf' size 2 m
autoextend off;
alter system set undo_tablespace = undo_small;
create table big_Table_Random as select * from all_objects
order by dbms_random.random;
alter table big_Table_Random add constraint
pk_big_Table_Random primary key (object_id) using index
tablespace users;
exec dbms_stats.gather_table_stats(user, 'BIG_TABLE_RANDOM',
cascade => true);
ORA-01555\scripts\undo_small.sql
A process updates
Big_Table_Random....
\ORA-01555\scripts\Update_Commit.sql
...While another process tries
to fetch from it...
....and in some point of time hits
a block, modified and committed
after the point of time, reading
has started (read consistency)
What to do:
1. Increase rollback /undo retention time/
2. tune queries
ORA-01555\scripts\random_Fetch.sql
Our program commits through fetches
alter database datafile 'D:\oracle\oradata\orcl\undo_small.dbf'
resize 150m;
Even with bigger Undo...
...committing too often,
while fetching the same
data, at some point at time
we hit modified and
committed by us block.
As it is modified AFTER the
query has started, oracle
can not continue due to read
consistency
ORA-01555\scripts\random_Fetch_Commit.sql
What to DO to avoid Ora-01555
1. Size Undo properly
2. Commit when needed
3. Do it as simple as possible
4. Tune the application
(queries)
Performing mass update with improperly sized Undo, we may hit
ORA-30036: unable to extend segment by 8 in undo tablespace 'UNDO_SMALL'
but the good news however is that the data is in the consistent state as before
the mass update.
As an alternative, we can split the data by some business criteria and perform
the update on each distinctive part of it.
What is Delayed Block Cleanout
One of the things oracle does during
commit is to clear the transaction info
from the block header.
However, when modified blocks are
more than 10% of the buffer cache, or
the block is not available, oracle
leaves this action to the next query
touching the block.
This is called Delayed Block Cleanout .
Side effects:
●redo generated by select statement;
●dirty blocks => DBW writes to disk
again
●
●
ORA-01555\scripts\t_500_dbc.sql
2 MB UNDO
4 MB buffer cache
Delayed Block Cleanout and ORA-01555
0. A block is changed by a transaction TRN0, committed and not cleaned out
That means its header still points to the UNDO tablespace where its transaction entry
is, having Commit SCN = SCN0 given during the commit.
1. A long running transaction SCN1 begins. SCN1 is the read
consistent SCN, this transaction must rollback in order to achieve
read consistency
UNDO lowest SCN
slot0
TRN0 | Commit SCN0 | Transaction Entry
Tablaspace USERS
Block0
TRN0
2. The DB is alive – means there is a lot of processing. This processing has nothing to do
with block0 but uses a lot of Undo, thus reusing the slot0.
Because of the commits, the lowest SCN, recorded in UNDO goes bigger than SCN1
UNDO lowest SCN > SCN1
3. Meanwhile, nobody touches our block0...so it is still not cleaned out.
Commit SCNx | slot0 | Transaction Entry for SCNx
4. Our transaction (SCN1) eventually touches block0. Seeing the pointer in the header, oracle
goes to UNDO to see what to do with this block
We can not find
our SCN0 in the
UNDO....
Commit SCN0 found in UNDO
yes
no
...so we do not
whether it is
committed before or
after SCN1
lowestSCN < SCN1
no
ORA-01555: Snapshot too old
Commit SCN0 < SCN1
yes
yes
no
Use the block from the buffer cache and clean it
Rollback the block before using it
end
SCN0 is cycled out...so it is less than the lowest SCN recorded in the
UNDO. That means the lowest UNDO SCN is a good upper bound.
Now, if or query read consistent SCN1 is bigger than the lowest SCN,
that means it started AFTER the commit over BLOCK0. So it can use it
from the buffer cache.
Its header will be cleaned as well...but this is not the case (p.2)
ORA-01555\images\Delayed Cleanout and 1555.odg
We will use previously created t_500
table to produce around 500 dirty
blocks.
After update and COMMIT, we open a
ref cursor – this is the read consistent
time for this ref cursor.
begin
for i in 1..1000 loop
update t_Small s set s.y = i where s.id
= &1;
commit;
end loop;
end;
/
exit;
Using t_Small we utilize the whole
undo in 9 simultaneous sessions...
...and got ora-01555 when fetching
the result set
ORA-01555\scripts\ora_01555_dbc.sql
Prevent: touch the modified blocks:
DBMS_STATS.gather_table_stats(...)
ORA-01002 fetch out of sequence
create or replace procedure upd_Commit(i_Obj_Id in number, i_Obj_Name in varchar2,
i_Count in number) is
begin
update big_table_random set object_name = lower(i_Obj_Name) where object_id = i_Obj_Id;
if(i_Count = 101)then
commit;
dbms_output.put_line(i_Count || ', ' || i_Obj_Id || ' committed');
end if;
exception
when others then
dbms_output.put_line(i_Count || ', ' || i_Obj_Id || ': ' || i_Obj_Name || ', Exc: '
|| sqlerrm);
rollback;
end upd_Commit;
A PL/SQL processing which at
some point at time ends the
transaction for whatever reason
ORA-01002\upd_Commit.prc
A for update for loop
unexpectedly got ORA-01002
Interesting:
in 9.2.0.7 the effect is missing
ORA-01002\for_loop.pdc
ORA-01002 – bulk collect workaround
The cursor is CLOSED before the
transaction is closed – either by
commit or rollback.
This prevents ORA-01002
ORA-01002\bulk_Collect.sql
The code in the loop body (too
complex sometimes) is actually not
changed.
The first COMMIT/ROLLBACK ends the transaction, so all
selected for updates records so far are unlocked again.
ORA-01002 – autonomous transaction wrapper
create or replace procedure upd_Commit(i_Obj_Id in number, i_Obj_Name in varchar2, i_Count
in number) is
begin
update big_table_random_a set object_name = lower(i_Obj_Name) where object_id = i_Obj_Id;
if(i_Count = 101)then
commit;
dbms_output.put_line(i_Count || ', ' || i_Obj_Id || ' committed');
end if;
exception
when others then
dbms_output.put_line(i_Count || ', ' || i_Obj_Id || ': ' || i_Obj_Name || ', Exc: ' ||
sqlerrm);
rollback;
end upd_Commit;
create or replace procedure upd_Commit_Autonomous(i_Obj_Id in number, i_Obj_Name in
varchar2, i_Count in number) is
pragma autonomous_transaction;
begin
upd_Commit(i_Obj_Id, i_Obj_Name, i_Count);
commit;
exception
when others then
rollback;
raise;
end upd_Commit_Autonomous;
ORA-01002\upd_Commit_Autonomous.prc
Dead Lock is possible if it is about the
same object
In both cases we have prerequisites to
leave data inconsistent – it is just bad
practice to control the transaction
inside the procedure.. its the initiator's
job to do it.
ORA-01002\for_loop.pdc
Wrapping in autonomous
transaction means
rollback/commit ends another
transactions, so is unable to
close our for update cursor
Optimistic Lock and ORA_ROWSCN
Optimistic lock (not DB lock) is a method preventing LOST UPDATES.
It is an alternative to the Pessimistic Locking.
It is Optimistic Lock, related to web applications.
With pessimistic lock the thin client can leave the DB object locked for
ever (until the DBA takes care).
Here we are about to compare two methods to perform optimistic lock:
Using a special column, maintained by a trigger or
application to tell us the version of the record.
●Using the new Oracle 10g feature ORA_ROWSCN
●
Optimistic Lock using Version Column
We create table dept as a copy of
scott.dept with additional version
column “last_mod”.
●As it is with a precision of fractional of
the second, it is quite enough in a
human being environment.
●The idea is to check if the row is
modified just before we modify it in the
DB.
●Sequence number can be used as
well, but:
●
It does not give us the additional
info of the actual time,
modification has happened
●
We will an additional sequence to
maintain
●
Optimistic Locking\Version Column\dept.tab
Lost Update Prevention
create or replace package body manage_dep is
-----------------------------------------------------------------------function derive_Dep_Data(i_Deptno in dept.deptno%type) return
Ref_Cursor is
v_RC Ref_Cursor;
begin
open v_RC for select * from dept where deptno = i_Deptno;
return v_RC;
end;
------------------------------------------------------------------------procedure modify_Dep(i_deptno in dept.deptno%type, i_dname in
dept.dname%type,
i_loc in dept.loc%type, i_las_mod in dept.last_mod%type) is
begin
update dept set dname = i_dname, loc = i_loc, last_mod =
CURRENT_TIMESTAMP
where deptno = i_deptno and last_mod = i_las_mod;
Optimistic Lock\Version Column\upd_Dept.sql
dbms_output.put_line(sql%rowcount || ' Departments changed!');
end;
end manage_dep;
Optimistic Lock\Version Column\manage_dep.pck
In a multiuser environment, we have two (or
more) clients editing the same department.
Both see the same data.....
Optimistic Lock\Version Column\upd_Dept.sql
...But only the first one succeeds in
her update...
Optimistic Lock\Version Column\upd_Dept.sql
... The second updates nothing
as it is already updated. I it is up
to the business how to describe
this to the unhappy user.
Optimistic Lock\Version Column\upd_Dept.sql
Optimistic Lock using ORA_ROWSCN
ORA_ROWSCN is actually a version column, but provided by ORACLE – starting from 10g Release 1.
ORA_ROWSCN is based on SCN. As we know that means based in always advancing at commit time SCN.
This is still not the pain killer. ORA_ROWSCN is, by default, maintained at block level, not at row level.
However we have the power to change this, but only at the time of creating the segment!
In a block we have two rows...
...both maintained with one
and the same ROWSCN
Optimistic Lock\ORA_ROWSCN\dept_rowscn_block.tab
“ROWDEPENDENCIES (row
dependency tracking) was to
the database with Oracle9i in
support of advanced replication to
allow for better parallel
propagation of changes.”
Starting with Oracle 10g we can
use it to implement effective
Optimistic Locking using
ORA_ROWSCN. This will add 6
bytes overhead to each row. So,
it DOES NOT save space
depending with do-it-by-yourself
Version Column
Optimistic Lock\ORA_ROWSCN\dept_rowscn_row.tab
Converting an SCN to wall clock time
Another benefit of the
transparent ORA_ROWSCN
column is that we can convert it
to wall clock time using
SCN_TO_TIMESTAMP built-in
However, there are some limitations:
1. SCN_TO_TIMESTAMP works approximately +/- 3 seconds.
2. It works about 5 days of database uptime
Optimistic Locking\ORA_ROWSCN\scn_to_timestamp.sql
Selecting from LONG columns
It is always an issue to identify
which view(s) references some
table or another view.
And this is not only about
views, but a lot of more bloody
dictionary objects using LONG
data type columns
\LONG\all_views.sql
Benefit from dbms_sql.column_value_long
create or replace package body long_help is
g_cursor number := dbms_sql.open_cursor;
g_query varchar2(32765);
procedure bind_variable( p_name in varchar2, p_value in varchar2 ) is
begin
if ( p_name is not null )then
dbms_sql.bind_variable( g_cursor, p_name, p_value );
end if;
end;
function substr_of( p_query in varchar2,
p_from in number,
p_for
in number,
p_name1 in varchar2 default NULL, p_bind1 in varchar2 default NULL,
p_name2 in varchar2 default NULL, p_bind2 in varchar2 default NULL,
p_name3 in varchar2 default NULL, p_bind3 in varchar2 default NULL,
p_name4 in varchar2 default NULL, p_bind4 in varchar2 default NULL ) return
varchar2 as
l_buffer
varchar2(4000);
l_buffer_len
number;
begin
if ( nvl(p_from,0) <= 0 )then
raise_application_error(-20002, 'From must be >= 1 (positive numbers)' );
end if;
if ( nvl(p_for,0) not between 1 and 4000 )then
raise_application_error(-20003, 'For must be between 1 and 4000' );
end if;
if ( p_query <> g_query or g_query is NULL )then
if ( upper(trim(nvl(p_query,'x'))) not like 'SELECT%')then
raise_application_error(-20001, 'This must be a select only' );
end if;
dbms_sql.parse( g_cursor, p_query, dbms_sql.native );
g_query := p_query;
end if;
bind_variable( p_name1, p_bind1 );
bind_variable( p_name2, p_bind2 );
bind_variable( p_name3, p_bind3 );
bind_variable( p_name4, p_bind4 );
dbms_sql.define_column_long(g_cursor, 1);
if (dbms_sql.execute_and_fetch(g_cursor)>0)then
dbms_sql.column_value_long(g_cursor, 1, p_for, p_from-1, l_buffer,
l_buffer_len);
end if;
return l_buffer;
end substr_of;
end long_help;
LONG\long_help.pck
The query provided (p_query) must select at
most one record.
●We parse only if we need to.
●We bind only if we need to.
●We use dbms_sql.column_value_long to
extract a requested portion of the selected
LONG column
●We use invoker rights for this package
●
create or replace package long_help authid current_user is
function substr_of(
p_query in varchar2,
p_from in number,
p_for
in number,
p_name1 in varchar2 default NULL,
p_bind1 in varchar2 default NULL,
p_name2 in varchar2 default NULL,
p_bind2 in varchar2 default NULL,
p_name3 in varchar2 default NULL,
p_bind3 in varchar2 default NULL,
p_name4 in varchar2 default NULL,
p_bind4 in varchar2 default NULL
)return varchar2;
end long_help;
Searching into view definition
LONG\Search_Longs.sql
Searching into TRIGGERS
LONG\Search_Triggers.sql
Partition's HIGH VALUE
LONG\Identify_Partition_HV.sql
Row Generation
RowGen\row_generate.sql
RowGen\Last_N_Years
Aggregate Statistics...
We want to generate statistics in
BET_LEAGUE_STAT from 01.08 till now.
Modification_Time shows when for the
statistics has been generated for the last
time.
The statistics is grouped per day, so
we need resultset giving us all Dates
in that period
RowGen\generate_statistics.pdc
...W/O losing any modifications
C_PB_BEGINING constant date := to_date('01.08.2006', 'DD.MM.YYYY');
gv_This_Aggregation date;
begin
select nvl(MIN(to_date(tr.START_TIME, 'MM/DD/YY HH24:MI:SS')), sysdate) into gv_This_Aggregation from
v$transaction tr;
for rec_Date in (
select last_aggregated+level-1 for_Date
from (select nvl(trunc(max(bsc.MODIFICATON_TIME)), C_PB_BEGINING) last_aggregated from bet_stat_cust bsc)
connect by last_aggregated+level-1 <= trunc(gv_This_Aggregation)
order by for_date desc
)loop
GENERATE_LEAGUE_STATS_VBS(rec_Date.for_Date);
end loop;
end;
/
-------------------------------------------------------- fills in the league statistics for virtual bets per day
function GENERATE_LEAGUE_STATS_VBS (i_for_date DATE) return number IS
BEGIN
delete adm.bet_league_stat bls where bls.month = i_for_date and bls.type = C_STAT_TYPE_VBS;
insert into adm.bet_league_stat(
month, bs_parent_id, bs_league,
bs_etype, type, bs_stake, bs_payout,
modificaton_time
)
select i_for_date, League.Cat_Parent_Id bs_Parent_ID, League.Cat_Name_En bs_league,
ett.ogr_name, C_STAT_TYPE_VBS, sum(bc.bet_stake), sum(bc.bet_return), gv_This_Aggregation
from bet.bet_child bc, bet.bet b, bet.outcome o, bet.gb_event e, bet.event_type_text ett, bet.catalog c,
bet.catalog League, bet.t_user u
where bc.bet_settle_time >= i_for_date and bc.bet_settle_time < i_for_date+1
and b.bet_id = bc.bet_id and u.id = b.bet_customer_id and u.type != 'T'
and o.o_id = bc.o_id and e.gb_id = o.o_gb_event_id and ett.ogr_id(+) = e.gb_ogr_id and ett.lang_id(+) =
'EN'
and c.cat_id = e.gb_egroup_id and League.cat_id = c.cat_parent_id
group by League.Cat_Parent_Id, League.Cat_Name_En, ett.ogr_name
;
return sql%rowcount;
END;
RowGen\generate_statistics.pdc
v$transactions helps us not to loose any
updates started just before statistics
aggregation begun
Different Timezones support
TZ\allowed_TZ.sql
c_Timestamp_
Local_TZ is
timestamp.
It is converted
to DB
timezone.
Only for the
same session
TZ it is actually
the same as
other date
columns,
because there
was no
conversion....
C_Timestamp_Local_TZ for Sofia's users, however contains the actual time in Sofia...
...As well as those from Berlin