Partition pruning using non-prefixed local indexes
This small post is from nice and frighten world of partitions, specifically the case of partition pruning. If you are using partitions in your application there is fair chance that your database is quite big and performance demanding. If you are not working in classic Data Warehousing manner you have to care about DML statement performance. One of the basic rules of DML performance is less indexes you have and less index columns in existing indexes then higher is the DML performance. Thus one of the architectural DBA targets is to reduce the number of indexed columns by just most critical. Another axiom statement is that smaller indexes is easier to support and manage then big one, thus local indexes usually more preferable. The conclusion from two previous axiom is that probably most indexes on partitioned tables in your application is non-prefixed local indexes. Now we came to the topic of this post how oracle optimizer deal with such indexes and what we can do with it.
Lets create list base partitioned table transactions
| Column Name | Comment |
| SERNO | Unique identifier |
| PARTITIONKEY | Partition Key |
| SGENERAL | Some indexed Field |
And create two indexes TRANSACTIONS_PK (SERNO, PARTITIONKEY) and local partitioned non-prefixed index TRANSACTIONSI (SGENERAL).
Let’s start from the most common statement
EXPLAIN PLAN FOR SELECT SGENERAL, count(1) FROM transactions WHERE PARTITIONKEY='JUL2012' AND SGENERAL is not null GROUP BY SGENERAL;
PLAN_TABLE_OUTPUT
———————————————————————————————————————————————-
Plan hash value: 2338610280
| Id | Operation | Name |
Rows |
Bytes |
Cost |
Time |
Pstart |
Pstop |
| 0 | SELECT STATEMENT |
5 |
55 |
69978 (1) |
00:14:00 |
|||
| 1 | HASH GROUP BY |
5 |
55 |
69978 (1) |
00:14:00 |
|||
| 2 | PARTITION LIST SINGLE |
5340K |
56M |
69727 (1) |
00:13:57 |
KEY |
KEY |
|
| *3 | TABLE ACCESS FULL | TRANSACTIONS |
5340K |
56M |
69727 (1) |
00:13:57 |
19 |
19 |
Predicate Information (identified by operation id):
—————————————————
3 – filter(“STGENERAL” IS NOT NULL)
Does it look like non-optimal? Optimizer has chosen to do FULL SCAN even if it has nice looking index TRANSACTIONSI. The problem is that partition pruning works only for prefixed indexes, that means you have to pay for support extra column in your index even if you do not need it because you clearly can get the partition name based from the identical rules on a table.
But luckily we have extended syntaxes. Uses it we can manually define the partition that should be used. In our case the partition name is equal to the partition key. Getting the partition name is very simple for list partitions but can be received in all other cases too.
EXPLAIN PLAN FOR
SELECT SGENERAL, count(1)
FROM TRANSACTIONS PARTITION ("JUL2012")
WHERE SGENERAL is not null
GROUP BY SGENERAL;
PLAN_TABLE_OUTPUT
———————————————————————————————————————————————-
Plan hash value: 198159339
| Id | Operation | Name |
Rows |
Bytes |
Cost |
Time |
Pstart |
Pstop |
| 0 | SELECT STATEMENT |
4 |
20 |
10 (20) |
00:00:01 |
|||
| 1 | SORT GROUP BY NOSORT |
4 |
20 |
10 (20) |
00:00:01 |
|||
| 2 | PARTITION LIST SINGLE |
8890 |
44450 |
8 (0) |
00:00:01 |
KEY |
KEY |
|
| *3 | INDEX FULL SCAN | TRANSACTIONSI |
8890 |
44450 |
8 (0) |
00:00:01 |
19 |
19 |
Predicate Information (identified by operation id):
—————————————————
3 – filter(“SGENERAL” IS NOT NULL)
This plan looks much better. According to optimizer estimations we can save 14 minutes on it. But there is even more attractive syntaxes for those who do not want to spend their time calculating the partition name
EXPLAIN PLAN FOR
SELECT SGENERAL, count(1)
FROM TRANSACTIONS PARTITION FOR ('JUL2012')
WHERE SGENERAL is not null
GROUP BY SGENERAL;
PLAN_TABLE_OUTPUT
———————————————————————————————————————————————
Plan hash value: 198159339
| Id | Operation | Name |
Rows |
Bytes |
Cost |
Time |
Pstart |
Pstop |
| 0 | SELECT STATEMENT |
4 |
20 |
10 (20) |
00:00:01 |
|||
| 1 | SORT GROUP BY NOSORT |
4 |
20 |
10 (20) |
00:00:01 |
|||
| 2 | PARTITION LIST SINGLE |
8890 |
44450 |
8 (0) |
00:00:01 |
KEY |
KEY |
|
| *3 | INDEX FULL SCAN | TRANSACTIONSI |
8890 |
44450 |
8 (0) |
00:00:01 |
19 |
19 |
Predicate Information (identified by operation id):
—————————————————
3 – filter(“SGENERAL” IS NOT NULL)
The only problem with last syntax is that it is supported only from 11.2 version. The syntax was developed to support interval partitions but can be handy for all other types.
Does it looks that with new syntax we came to the kingdom of wealth and prosperity, where we can easily avoid prefixes on local indexes and still use the effective partition pruning? But it is not. The hidden rock is that the partition keys in PARTITION FOR clause could not be defined throw the variables like in WHERE clause. Below there are examples of such attempt
Firstly traditional approach
DECLARE v_part char(7):='JUL2012'; BEGIN FOR REC IN (SELECT SGENERAL, count(1) FROM TRANSACTIONS WHERE PARTITIONKEY=v_part AND SGENERAL is not null GROUP BY SGENERAL) LOOP NULL; END LOOP; END; / Elapsed: 00:12:42.05
It works but does not looks like very performance effective. Let’s go and try 10g approach in PL/SQL
DECLARE v_part varchar2(10):="JUL2012"; BEGIN FOR REC IN (SELECT SGENERAL, count(1) FROM TRANSACTIONS PARTITION (v_part) WHERE SGENERAL is not null GROUP BY SGENERAL) LOOP NULL; END LOOP; END; / ERROR at line 2: ORA-06550: line 2, column 25: PLS-00201: identifier "JUL2012" must be declared ORA-06550: line 2, column 10: PL/SQL: Item ignored
It looks like kind of expected behavior. You could not use table name as variable, why you should be able to use partition name. Personally I put a lot of hope on the last test with PARTITIONKEY reference
DECLARE<span style="color: #000000;"> v_part char(7):='JUL2012'; BEGIN FOR REC IN (SELECT SGENERAL, count(1) FROM TRANSACTIONS PARTITION FOR (v_part) WHERE SGENERAL is not null GROUP BY SGENERAL) LOOP NULL; END LOOP; END; / ERROR at line 1: ORA-14763: Unable to resolve FOR VALUES clause to a partition number ORA-06512: at line 4
It was the first time I see the error thus have a look into the documentation
ORA-14763: Unable to resolve FOR VALUES clause to a partition number
Cause: Could not determine the partition corresponding to the FOR VALUES clause.
Action: Remove bind variables and dependencies on session parameters from the values specified in the FOR VALUES clause.
Ha-Ha-Ha nothing change we still could not pass partition key values into query.
Summarizing oracle offer the mechanism that allows to use partition pruning on non-prefixed local indexes but have not support the syntaxes in pl/sql using binds. The usage of this syntaxes have sense only in case of big queries (e.g. reports) running on a table, when gains from effective execution plan prevail over the loses of dynamic sql.
A DBA_HIST_SQLSTAT query that I am very fond of
This is a quick post to share a SQL statement I use a lot at work. The query mines the AWR tables (beware the licence implications) for a specific SQL ID and date/time range and shows a few choice statistics for each snapshot period.
awrsql.sql:
prompt enter start and end times in format DD-MON-YYYY [HH24:MI]
column sample_end format a21
select to_char(min(s.end_interval_time),'DD-MON-YYYY DY HH24:MI') sample_end
, q.sql_id
, q.plan_hash_value
, sum(q.EXECUTIONS_DELTA) executions
, round(sum(DISK_READS_delta)/greatest(sum(executions_delta),1),1) pio_per_exec
, round(sum(BUFFER_GETS_delta)/greatest(sum(executions_delta),1),1) lio_per_exec
, round((sum(ELAPSED_TIME_delta)/greatest(sum(executions_delta),1)/1000),1) msec_exec
from dba_hist_sqlstat q, dba_hist_snapshot s
where q.SQL_ID=trim('&sqlid.')
and s.snap_id = q.snap_id
and s.dbid = q.dbid
and s.instance_number = q.instance_number
and s.end_interval_time >= to_date(trim('&start_time.'),'dd-mon-yyyy hh24:mi')
and s.begin_interval_time <= to_date(trim('&end_time.'),'dd-mon-yyyy hh24:mi')
and substr(to_char(s.end_interval_time,'DD-MON-YYYY DY HH24:MI'),13,2) like '%&hr24_filter.%'
group by s.snap_id
, q.sql_id
, q.plan_hash_value
order by s.snap_id, q.sql_id, q.plan_hash_value
/
Nothing ground breaking and I’m sure many will have a similar script.
Below I have example output showing one of the scripts many successful outings, quite a dramatic plan change I’m sure you’ll agree.
SQL> @awrsql enter start and end times in format DD-MON-YYYY [HH24:MI] Enter value for sqlid: 1jjpo2i4b313g Enter value for start_time: 15-NOV-2011 Enter value for end_time: 21-NOV-2011 13:00 Enter value for hr24_filter: SAMPLE_END SQL_ID PLAN_HASH_VALUE EXECUTIONS PIO_PER_EXEC LIO_PER_EXEC MSEC_EXEC --------------------- ------------- --------------- ---------- ------------ ------------ ---------- 15-nov-2011 TUE 08:00 1jjpo2i4b313g 3133159894 129629 0 5 0 16-nov-2011 WED 08:01 1jjpo2i4b313g 3133159894 115003 0 5 .1 17-nov-2011 THU 08:01 1jjpo2i4b313g 3133159894 115741 0 5 0 18-nov-2011 FRI 07:00 1jjpo2i4b313g 3133159894 30997 0 5 .1 18-nov-2011 FRI 08:00 1jjpo2i4b313g 3133159894 81034 0 5 0 21-nov-2011 MON 00:00 1jjpo2i4b313g 790865878 16 323091.6 323128.3 36905.8 21-nov-2011 MON 01:00 1jjpo2i4b313g 790865878 29 349676.2 349713.7 48387.2 21-nov-2011 MON 02:00 1jjpo2i4b313g 790865878 35 339474.6 339509.2 34057.7 21-nov-2011 MON 03:00 1jjpo2i4b313g 790865878 37 340934.6 340970.2 35899.4 21-nov-2011 MON 04:01 1jjpo2i4b313g 790865878 38 333469.1 333503.9 35450.8 21-nov-2011 MON 05:00 1jjpo2i4b313g 790865878 35 347559.3 347595.2 35231.8 21-nov-2011 MON 06:00 1jjpo2i4b313g 790865878 32 340224.8 340260 35208.3
I also like to use the query to track number of executions or LIO per execution over a longer time frame to see if either the frequency or individual impact of the SQL is changing over time. I can use the “hr24_filter” variable to do this, for example showing me all snapshots for hour “13″ over a whole month.
Application Code Level script
With patches and fixes delivered to different sites and applying to different testing, verification and production regions, there is a need for a simple way to compare database objects in the application schema without having database link between different databases. This can be challenging as the PL/SQL code is usually encrypted and database segments have different storage attributes. Here I show the method to achieve this comparison.
The reason why I think it would be interesting to public is that the problem is very common and up to day. There is no common white paper that helps to solve it. The only note that pretends to cover the topic is 781719.1 But it speaks only about PL/SQL objects that are not enough in a real world.
The attached script use MD5 hashing algorithm to get hash value associated to the version of database object. The hashing CLOB can be get from DBMS_METADATA package or from DBA_SOURCE view for PL/SQL objects. The principals are very simple but there are few points that have been raised during development.
1. The tables, indexes and e.t.c. with different storage configuration should generate the same hash. This issue has been solved by DBMS_METADATA.SET_TRANSFORM_PARAM
2. The tables with different order of columns and constraints have to provide the same hash too. The solution was to use Simple XML format of DBMS_METADATA output and sorting the columns and constraints alphabetically using XML functions.
3. The nested and index organise service tables are included into DBA_OBJECTS as TABLES but DBMS_METADATA include their description into the master objects but failed on servant items. My solution was to use DBA_TABLES view to segregate only high level objects.
4. Java objects has no option to generate description in XML format thus we generate it in general DDL format.
5. Role description should include grants that have been granted to the role. The DBMS_METADA does not include grants into generated description thus again the XML functions was used to get proper CLOB
The following privileges required for the user who run the script
-- GRANT CREATE PROEDURE
-- GRANT EXECUTE ON DBMS_CRYPTO
-- GRANT EXECUTE ON DBMS_METADATE
-- GRANT SELECT ON DBA_SOURCE
-- GRANT SELECT ON DBA_SYS_PRIVS
-- GRANT SELECT ON DBA_TAB_PRIVS
-- GRANT SELECT ON DBA_ROLE_PRIVS
The following script take one parameter – the application schema name and provide as output list of database objects with corresponding hash values.
create or replace function verify_source(p_source_type in varchar2,
p_source_name in varchar2,
p_source_owner in varchar2) return varchar2 AUTHID CURRENT_USER as
code_source clob;
md5hash varchar2(32);
v_h NUMBER; -- handle returned by OPEN
v_th NUMBER; -- handle returned by ADD_TRANSFORM
begin
IF p_source_type in ('VIEW','ROLE','TABLE','INDEX'
,'MATERIALIZED_VIEW','MATERIALIZED_VIEW_LOG','SEQUENCE','SYNONYM') THEN
v_h := DBMS_METADATA.OPEN(p_source_type);
if p_source_type not in ('ROLE') THEN
DBMS_METADATA.SET_FILTER(v_h,'SCHEMA',p_source_owner);
END IF;
DBMS_METADATA.SET_FILTER(v_h,'NAME',p_source_name);
v_th := DBMS_METADATA.ADD_TRANSFORM(v_h,'SXML');
if p_source_type not in ('VIEW','ROLE') THEN
DBMS_METADATA.SET_TRANSFORM_PARAM (v_th,'SEGMENT_ATTRIBUTES',FALSE);
DBMS_METADATA.SET_TRANSFORM_PARAM (v_th,'STORAGE',FALSE);
DBMS_METADATA.SET_TRANSFORM_PARAM (v_th,'TABLESPACE',FALSE);
DBMS_METADATA.SET_TRANSFORM_PARAM (v_th,'PARTITIONING',FALSE);
END IF;
code_source := DBMS_METADATA.FETCH_CLOB(v_h);
IF p_source_type in ('TABLE') THEN
-- get rid off sorting misconfigurations
SELECT UPDATEXML(
UPDATEXML(SRC,'/TABLE/RELATIONAL_TABLE/COL_LIST',
XMLELEMENT("COL_LIST", (
XMLQuery (
'for $i in /TABLE/RELATIONAL_TABLE/COL_LIST/COL_LIST_ITEM
order by $i/NAME
return $i'
passing by value SRC
RETURNING CONTENT
)
) )
) ,'/TABLE/RELATIONAL_TABLE/FOREIGN_KEY_CONSTRAINT_LIST',
XMLELEMENT("FOREIGN_KEY_CONSTRAINT_LIST", (
XMLQuery (
'for $i in
/TABLE/RELATIONAL_TABLE/FOREIGN_KEY_CONSTRAINT_LIST/FOREIGN_KEY_CONSTRAINT_LIST_ITEM
order by $i/NAME
return $i'
passing by value SRC
RETURNING CONTENT
)
) )
).getClobVal() INTO code_source
FROM ( SELECT XMLQuery(
'declare function local:removeNS($e as element()) as element()
{
element { QName("", local-name($e)) }
{
for $i in $e/node()|$e/attribute::*
return typeswitch ($i)
case element() return local:removeNS($i)
default return $i
}
}; (::)
local:removeNS($SRC/child::*)'
passing XMLType(code_source) as "SRC"
returning content
) SRC
FROM dual) INITSRC;
END IF;
DBMS_METADATA.CLOSE(v_h);
ELSIF p_source_type in ('PROCEDURE','FUNCTION','TYPE','TYPE BODY','TRIGGER','PACKAGE','PACKAGE BODY')
THEN
code_source := '';
for source_record in (select text from dba_source where owner = upper(p_source_owner) and name =
upper(p_source_name) and type = upper(p_source_type) order by line)
loop
code_source := code_source||source_record.text;
end loop;
ELSIF p_source_type in ('JAVA_CLASS','JAVA_SOURCE') THEN
code_source := DBMS_METADATA.GET_DDL(p_source_type,p_source_name,upper(p_source_owner));
ELSIF p_source_type in ('ROLE') THEN
SELECT
INSERTCHILDXMLAFTER(SRC,'/ROLE','TYPE[1]',
(SELECT
XMLAgg(
XMLELEMENT("privs",
XMLFOREST(OWNER, TABLE_NAME, PRIVILEGE)))
FROM
(SELECT DISTINCT *
FROM (
SELECT OWNER, TABLE_NAME, PRIVILEGE
FROM dba_tab_privs
WHERE GRANTEE IN
(SELECT GRANTED_ROLE
FROM DBA_ROLE_PRIVS
START WITH GRANTEE=p_source_name
CONNECT BY NOCYCLE PRIOR GRANTED_ROLE=GRANTEE
UNION ALL
SELECT p_source_name FROM DUAL)
UNION ALL
SELECT NULL OWNER, PRIVILEGE TABLE_NAME, NULL PRIVILEGE
FROM dba_sys_privs
WHERE GRANTEE IN (SELECT GRANTED_ROLE
FROM DBA_ROLE_PRIVS
START WITH GRANTEE=p_source_name
CONNECT BY NOCYCLE PRIOR GRANTED_ROLE=GRANTEE
UNION ALL
SELECT p_source_name FROM DUAL))
ORDER BY 1,2,3))).getClobVal() INTO code_source
FROM ( SELECT XMLQuery(
'declare function local:removeNS($e as element()) as element()
{
element { QName("", local-name($e)) }
{
for $i in $e/node()|$e/attribute::*
return typeswitch ($i)
case element() return local:removeNS($i)
default return $i
}
}; (::)
local:removeNS($SRC/child::*)'
passing XMLType(code_source) as "SRC"
returning content
) SRC
FROM dual) INITSRC;
END IF;
md5hash := rawtohex(dbms_crypto.hash(typ => dbms_crypto.HASH_MD5,
src => code_source));
return md5hash;
exception
WHEN OTHERS THEN
return p_source_type||p_source_name;
end;
/
show error
Spool code_level_checksum.lst
set pagesize 5000
set heading off
set echo off
set feedback off
col OBJECT_TYPE FORMAT a30
col OBJECT_NAME FORMAT a30
col HASHVAL FORMAT a35
prompt ###################################
prompt # TABLES #
prompt ###################################
SELECT 'TABLE' OBJECT_TYPE, TABLE_NAME OBJECT_NAME , verify_source('TABLE',TABLE_NAME, OWNER)
HASHVAL
FROM DBA_TABLES WHERE OWNER='&1'
AND IOT_NAME IS NULL
order by 1,2
/
prompt ###################################
prompt # INDEXES #
prompt ###################################
SELECT 'INDEX' OBJECT_TYPE, INDEX_NAME OBJECT_NAME , verify_source('INDEX',INDEX_NAME, OWNER)
HASHVAL
FROM DBA_INDEXES WHERE OWNER='&1'
order by 1,2
/
prompt ###################################
prompt # ROLES #
prompt ###################################
SELECT 'ROLE' OBJECT_TYPE, ROLE OBJECT_NAME , verify_source('ROLE',ROLE, 'TCTDBS') HASHVAL
FROM DBA_ROLES
order by 1,2
/
prompt ###################################
prompt # JAVA SOURCES #
prompt ###################################
select OBJECT_TYPE, OBJECT_NAME, verify_source('JAVA_SOURCE',OBJECT_NAME, OWNER) HASHVAL FROM
dba_objects
WHERE OWNER='&1' and OBJECT_TYPE IN ('JAVA SOURCE')
order by 1,2
/
prompt ###################################
prompt # JAVA CLASSES #
prompt ###################################
select 'JAVA_CLASS' OBJECT_TYPE, NAME OBJECT_NAME, verify_source('JAVA_CLASS',NAME, OWNER) HASHVAL
FROM dba_java_classes
WHERE OWNER='&1'
order by 1,2
/
prompt ###################################
prompt # MISC #
prompt ###################################
select OBJECT_TYPE, OBJECT_NAME, verify_source(OBJECT_TYPE,OBJECT_NAME, OWNER) HASHVAL FROM
dba_objects
WHERE OWNER='&1' and OBJECT_TYPE IN ('PROCEDURE','FUNCTION','TYPE','TYPE
BODY','TRIGGER','VIEW','MATERIALIZED_VIEW','MATERIALIZED_VIEW_LOG','SEQUENCE','SYNONYM','PACKAGE','PACKAG
E BODY')
order by 1,2
/
spool off;
drop function verify_source
/
Hope you will find it usefull.
The output from this script can be compared as simple flat file using any favorite comparison tool, e.g. WinMerge.
LFPW Parameters
During some recent investigations into “log file parallel write” (LFPW) performance I found myself looking at the Oracle 11.2 Database Reference and wondering about P2 of this wait event. In case you’re not familiar with the parameters they are:
Parameter Description ------------ ------------------------------ files Number of files to be written blocks Number of blocks to be written requests Number of I/O requests ------------ ------------------------------
File is obvious, the number of log file members in the current redo log group, but for the other two parameters I wasn’t sure if it is the number of blocks/request per file or in total. I assumed total, but wanted to be sure so did a quick bit of searching. This led me to OraDBPedia where I read,
P2=The number of redo blocks to be written to each log member.
and
P3=Total number of I/O requests needed to satisfy the write load (i.e. P2).
For whatever reason, I didn’t feel 100% happy to accept this so did a quick test in 11.2.0.2 XE as shown below.
Test Details:
- Enable tracing of LGWR
- Run “alter system switch logfile” to cause LGWR to flush the redo buffer (not shown in output below)
- Run small insert statement
- Commit
- Examine trace file
Starting off with the default in XE of 1 member per group:
14:01:19 SQL> insert into t (c1, c2, c3) values (1,'DELETE', sysdate); 1 row created. 14:01:23 SQL> commit; Commit complete. 14:01:27 SQL>
Looking at the 10046 trace reveals:
*** 2011-09-29 14:01:27.315 WAIT #0: nam='log file parallel write' ela= 374 files=1 blocks=2 requests=1 obj#=-1 tim=1481335308118
Adding another member to each of the 2 groups giving 2 members per group:
14:01:27 SQL> insert into t (c1, c2, c3) values (1,'DELETE', sysdate); 1 row created. 14:04:48 SQL> commit; Commit complete. 14:04:53 SQL>
Looking at the 10046 trace reveals:
*** 2011-09-29 14:04:52.477 WAIT #0: nam='rdbms ipc message' ela= 2969787 timeout=300 p2=0 p3=0 obj#=-1 tim=1481538543689 WAIT #0: nam='rdbms ipc message' ela= 30043 timeout=3 p2=0 p3=0 obj#=-1 tim=1481538574024 WAIT #0: nam='rdbms ipc message' ela= 567670 timeout=300 p2=0 p3=0 obj#=-1 tim=1481539141991 WAIT #0: nam='log file parallel write' ela= 550 files=2 blocks=4 requests=2 obj#=-1 tim=1481539142779
Adding another member to each of the 2 groups giving 3 members per group:
14:04:53 SQL> insert into t (c1, c2, c3) values (1,'DELETE', sysdate); 1 row created. 14:07:51 SQL> commit; Commit complete. 14:07:53 SQL>
Looking at the 10046 trace reveals:
*** 2011-09-29 14:07:53.980 WAIT #0: nam='rdbms ipc message' ela= 2560082 timeout=300 p2=0 p3=0 obj#=-1 tim=1481718344261 WAIT #0: nam='log file parallel write' ela= 1658 files=3 blocks=6 requests=3 obj#=-1 tim=1481718346216 WAIT #0: nam='rdbms ipc message' ela= 438069 timeout=44 p2=0 p3=0 obj#=-1 tim=1481718784548
Adding another member to each of the 2 groups giving 4 members per group:
14:07:53 SQL> insert into t (c1, c2, c3) values (1,'DELETE', sysdate); 1 row created. 14:09:48 SQL> commit; Commit complete. 14:09:51 SQL>
Looking at the 10046 trace reveals:
*** 2011-09-29 14:09:51.687 WAIT #0: nam='rdbms ipc message' ela= 1445500 timeout=300 p2=0 p3=0 obj#=-1 tim=1481834980859 WAIT #0: nam='log file parallel write' ela= 1910 files=4 blocks=8 requests=4 obj#=-1 tim=1481834983045
I see the above a conclusive evidence that both blocks (p2) and requests (p3) are the total rather than “per file”.
Maybe things have changed since the OraDBPedia articles was originally written, but I’ve contacted the guys behind the site and expect the page to be updated shortly.
OOW11: The Come Down
I’m on the way back to London. Having had a great time at OpenWorld and fantastic weather the last two days (plus the use of the Pythian Moon Dog) I feel a bit down to be leaving San Francisco…
A few comments about my last post before I get into this one:
- DTrace support in Linux was deliberately missed off the list as it was conveyed in the Oracle Linux 6 session I attended that it was not production ready.
- I’m an Oracle database geek. The other sessions I attended were not bad, but as someone that spends a lot of time trying to learn as much as possible about the Oracle database server, when I attend a presentation on a subject I’m much less familiar, but also very keen on, I tend to get a lot out of it – The point is, don’t get the impression that the other sessions were not good.
This was my 3rd OpenWorld and the best experience for me by far. There are a number of factors contributing to that including an apparent increase in the number of technical sessions (necessary as Unconference was not run this year). I highly recommend attending OpenWorld. It is a different experience from the only other Oracle focused conference I’ve attended (UKOUG Conference), but they are both great from my point of view, which is why I plan to continue attending both… And hopefully getting to some more
Reasons to consider attending OpenWorld:
- The speakers are absolutely top notch – There are too many names to “drop” here, but you really do have the best independent and Oracle employed experts
- The evening events provide an opportunity to meet like-minded people, and “talk tech”
- You are there to hear the latest hardware and software releases as they happen
- It’s in San Francisco!
Anyone that follows me on Twitter may have noticed that I was very impressed by what I saw of Oracle Enterprise Manager 12c “Cloud Control”. While watching the presentation I planned to get it installed on a VM as soon as I got back to the UK. Martin Bach was clearly as keen as I am and he’s posted details of his install here. Boy, that man’s fast. 11.2.0.3 installed about as fast as humanly possible after the release and pretty much the same with this – Nice work Martin
Boarding call!
OOW11: Best Session So Far
I’ve been a little slack getting my words down with respect to OpenWorld 2011 so far. There are many others faster at getting the news out as it breaks, so I won’t even try to cover the big announcements. You can find that elsewhere.
Without a doubt the session that got me most excited so far was “Overview: New Features in Oracle Linux 6″. I’m a DBA, but take a very keen interest in Linux. I’ve been choosing Oracle Enterprise Linux (note that it has actually been renamed as Oracle Linux now) as my OS of choice for pretty much everything I do in my personal lab environment, not just for database servers. This is something that a couple of my Linux geek friends have questioned. They have extolled the joys of Debian and criticised Oracle for being lazy with Linux – “Just taking Red Hat and re-branding it.” I really wished they’d been in this session. I came away with a very strong impression that Oracle really do care about Linux, they do invest in Linux and they have some very smart guys onboard who really know what they are doing. It’s hard for me to cover everything that was discussed during the session here, but here’s an attempt to summarise:
- Oracle Linux is not a desktop distribution – Obvious, but worth stating
- Focus is on the kernel – Hence Unbreakable Enterprise Kernel
- Aim to stay close to the mainline as possible – Aim to release around once a year
- 9 month grace before expecting the new kernel to be used
- Network optimisation such as receive packet steering (RPS) and transmit packet steering (XPS) sound very significant
- Oracle Linux 6.2 is beta now or very very soon – This is wrong (see comment from Lenz below)
- Transcendent Memory
- Btrfs – “Build around snapshots” (Chris Mason)
- Task Control Groups (Cgroups) – Offer fine grained control of resources and particularly useful for NUMA systems
- Linux Containers – Yes, that’s right
- Ksplice – Patch your kernel with zero downtime… Oh, and you can rollback with no downtime too
If you do nothing else after reading this post then checkout Ksplice! Read the official site and this on wikipedia
I should stress that there was the usual disclaimer about the information provided and there is always the possibility that I’ve misrepresented what was said, so do additional research before making big decisions based on the above.
Also mentioned was Open vSwitch which sounds like something I should look into.
While writing this I have been prompted to think about what matter to me with respect to Linux and I’ve concluded that exactly how much money (by paying kernel developers) Oracle have put into the Linux kernel is something that I don’t have the time or inclination to work out. What matters to me is that Linux is developing, Oracle are contributing and Oracle provide me with a completely free version that I can use in my lab for database servers and anything else I want to do.
Ad-hoc Users and Undo Usage
Here is a quote from the Oracle Documentation about V$UNDOSTAT.MAXQUERYLEN:
MAXQUERYLEN NUMBER Identifies the length of the longest query (in seconds) executed in the instance during the period. You can use this statistic to estimate the proper setting of the UNDO_RETENTION initialization parameter. The length of a query is measured from the cursor open time to the last fetch/execute time of the cursor. Only the length of those cursors that have been fetched/executed during the period are reflected in the view.
And here is a quote from My Oracle Support note “V$UNDOSTAT MAXQUERYLEN Suddenly Appears With a High Value For a Simple Query [ID 1307600.1]“
the MAXQUERYLEN value is defined as “the length of a query measured from the cursor open time to the last fetch/execute time of the cursor”. Hence if the cursor is suspended (e.g. dbms_lock.sleep) or the session is switched out (session switching) then the MAXQUERYLEN time will continue to increase but will not be show as no current execution/fetch is occurring on the cursor.
…
This is expected behaviour
Nothing wrong with any of this but it is all relevant to the test that follows. In order to set up the test I change my local database to have a small undo tablespace and generate some workload. I’ve collapsed the code in case you want to jump straight to the story, in summary it generates some transactions and random queries with a duration between 1 and 10 minutes.
alter system set undo_tablespace = undo scope=both sid='*'; create undo tablespace undo_small datafile 'C:\ORACLEXE\ORADATA\XE\UNDO_SMALL.DBF' size 30m reuse autoextend on maxsize 500m; alter system set undo_tablespace = undo_small scope=both sid='*'; create or replace function slow_query(i_seconds_to_wait in number) return number as l_n number; l_pad2 bigtab.pad2%TYPE; cursor c_bigtab is select pad2 from bigtab; begin open c_bigtab; fetch c_bigtab into l_pad2; sys.dbms_lock.sleep(i_seconds_to_wait); fetch c_bigtab into l_pad2; close c_bigtab; return i_seconds_to_wait; end slow_query; / -- session #1 - generate trxns declare L_rno number; L_i number; L_pad1 bigtab.pad1%TYPE; begin while true loop L_i := L_i + 1; L_rno := round(dbms_random.value(1,1000000)); update bigtab set pad1 = pad1 where rno = L_rno; commit; dbms_lock.sleep(0.01); end loop; end; / -- session #2 - generate queries of random length betwen 1 min and 10 mins declare l_sleep number; l_slept number; begin while true loop l_sleep := round(dbms_random.value(60,600)); select slow_query(l_sleep) into l_slept from dual; end loop; end; /
Output from V$UNDOSTAT after the background workload has been running for a while is below along with the current size of the undo tablespace.
END_TIME_CHAR TXNCOUNT MAXQUERYLEN MAXQUERYID ACTIVEBLKS UNEXPIREDBLKS EXPIREDBLKS TUNED_UNDORETENTION
-------------------- ---------- ----------- ------------- ---------- ------------- ----------- -------------------
28-SEP-2011 19:45:50 38118 584 200k2np23z57q 160 912 496 1425
28-SEP-2011 19:55:50 38404 452 200k2np23z57q 160 1120 1280 1292
28-SEP-2011 20:05:50 38507 479 200k2np23z57q 160 1704 1464 1320
28-SEP-2011 20:15:50 38340 383 200k2np23z57q 160 2656 496 1224
FILE_ID FILE_NAME MB
---------- --------------------------------------------- ----------
5 C:\ORACLEXE\ORADATA\XE\UNDO_SMALL.DBF 30
Now a simple query is executed from SQL Developer and the first set of rows is fetched. V$SESSION output below.
SQL> select username,program,status,state,event,sql_id,seconds_in_wait siw 2 from v$session 3 where username = 'ADHOC'; USERNAME PROGRAM STATUS STATE EVENT SQL_ID SIW -------- --------------- -------- ------------------- --------------------------- ------------- ---------- ADHOC SQL Developer INACTIVE WAITING SQL*Net message from client 6fwqzurbc8y7k 7 SQL> select sql_text from v$sql where sql_id = '6fwqzurbc8y7k'; SQL_TEXT ----------------------------------------------------------------------- select * from bigtab
Another quote from the Oracle Documentation
SQL_ID VARCHAR2(13) SQL identifier of the SQL statement that is currently being executed
You can see from the V$SESSION output above that we have a SQL ID and therefore must be executing some SQL. But we also have a session status of “INACTIVE” and are waiting on event “SQL*Net message from client”. That is a nice way of spotting sessions that are similar to the focus of this post.
Around 15 minutes later the session is still waiting on the user to act and V$UNDOSTAT looks consistent with my steady state.
USERNAME PROGRAM STATUS STATE EVENT SQL_ID SIW -------- --------------- -------- ------------------- --------------------------- ------------- ---------- ADHOC SQL Developer INACTIVE WAITING SQL*Net message from client 6fwqzurbc8y7k 912 END_TIME_CHAR TXNCOUNT MAXQUERYLEN MAXQUERYID ACTIVEBLKS UNEXPIREDBLKS EXPIREDBLKS TUNED_UNDORETENTION -------------------- ---------- ----------- ------------- ---------- ------------- ----------- ------------------- 28-SEP-2011 19:45:50 38118 584 200k2np23z57q 160 912 496 1425 28-SEP-2011 19:55:50 38404 452 200k2np23z57q 160 1120 1280 1292 28-SEP-2011 20:05:50 38507 479 200k2np23z57q 160 1704 1464 1320 28-SEP-2011 20:15:50 38340 383 200k2np23z57q 160 2656 496 1224 28-SEP-2011 20:25:50 38557 985 200k2np23z57q 160 2592 816 1825 28-SEP-2011 20:31:28 20492 866 200k2np23z57q 160 2592 816 1706
We now fetch the next set of records and check V$SESSION and V$UNDOSTAT again.
USERNAME PROGRAM STATUS STATE EVENT SQL_ID SIW -------- --------------- -------- ------------------- --------------------------- ------------- ---------- ADHOC SQL Developer INACTIVE WAITING SQL*Net message from client 6fwqzurbc8y7k 35 END_TIME_CHAR TXNCOUNT MAXQUERYLEN MAXQUERYID ACTIVEBLKS UNEXPIREDBLKS EXPIREDBLKS TUNED_UNDORETENTION -------------------- ---------- ----------- ------------- ---------- ------------- ----------- ------------------- 28-SEP-2011 19:45:50 38118 584 200k2np23z57q 160 912 496 1425 28-SEP-2011 19:55:50 38404 452 200k2np23z57q 160 1120 1280 1292 28-SEP-2011 20:05:50 38507 479 200k2np23z57q 160 1704 1464 1320 28-SEP-2011 20:15:50 38340 383 200k2np23z57q 160 2656 496 1224 28-SEP-2011 20:25:50 38557 985 200k2np23z57q 160 2592 816 1825 28-SEP-2011 20:32:49 26370 992 6fwqzurbc8y7k 160 2592 816 1766
V$UNDOSTAT.MAXQUERYID is now that of the SQL Developer session. What if an extra 10 minutes had elapsed?
10 minutes later…
END_TIME_CHAR TXNCOUNT MAXQUERYLEN MAXQUERYID ACTIVEBLKS UNEXPIREDBLKS EXPIREDBLKS TUNED_UNDORETENTION -------------------- ---------- ----------- ------------- ---------- ------------- ----------- ------------------- 28-SEP-2011 19:45:50 38118 584 200k2np23z57q 160 912 496 1425 28-SEP-2011 19:55:50 38404 452 200k2np23z57q 160 1120 1280 1292 28-SEP-2011 20:05:50 38507 479 200k2np23z57q 160 1704 1464 1320 28-SEP-2011 20:15:50 38340 383 200k2np23z57q 160 2656 496 1224 28-SEP-2011 20:25:50 38557 985 200k2np23z57q 160 2592 816 1825 28-SEP-2011 20:35:50 41787 992 6fwqzurbc8y7k 160 3440 688 1833 28-SEP-2011 20:41:31 20088 1293 6fwqzurbc8y7k 160 3440 688 2134
The TUNED_UNDORETENTION is starting to climb but the number of expired blocks is unchanged because our UNDO tablespace is extending – it is now at 42M.
FILE_ID FILE_NAME MB
---------- --------------------------------------------- ----------
5 C:\ORACLEXE\ORADATA\XE\UNDO_SMALL.DBF 42
So let’s prevent it from extending and wait another 10 minutes before fetching the next set of rows.
alter database datafile 5 autoextend off; END_TIME_CHAR TXNCOUNT MAXQUERYLEN MAXQUERYID ACTIVEBLKS UNEXPIREDBLKS EXPIREDBLKS TUNED_UNDORETENTION -------------------- ---------- ----------- ------------- ---------- ------------- ----------- ------------------- 28-SEP-2011 20:05:50 38507 479 200k2np23z57q 160 1704 1464 1320 28-SEP-2011 20:15:50 38340 383 200k2np23z57q 160 2656 496 1224 28-SEP-2011 20:25:50 38557 985 200k2np23z57q 160 2592 816 1825 28-SEP-2011 20:35:50 41787 992 6fwqzurbc8y7k 160 3440 688 1833 28-SEP-2011 20:45:50 41534 1594 6fwqzurbc8y7k 160 4608 576 2752 28-SEP-2011 20:55:50 44669 2196 6fwqzurbc8y7k 160 5200 264 2766
We can see that the number of expired blocks is falling and the number of un-expired blocks is rising. 10 minutes later I click to fetch the next set of records and…
ORA-01555: snapshot too old: rollback segment number 17 with name "_SYSSMU17$" too small
01555. 00000 - "snapshot too old: rollback segment number %s with name \"%s\" too small"
*Cause: rollback records needed by a reader for consistent read are
overwritten by other writers
*Action: If in Automatic Undo Management mode, increase undo_retention
setting. Otherwise, use larger rollback segments
That was only after 35-40 minutes – well within reach of a normal trip to the canteen (admittedly my undo tablespace is sized modestly in order to help me prove a point).
So, here, in reverse order, are my top 3 undo related threats when giving read only access via a tool such as SQL Developer or Toad to application support staff (even though they have the best of intentions).
#3 Undo tablespace pressure.
Unbeknownst to the ad-hoc user he can quite easily cause pressure on the undo tablespace causing an AUTOEXTEND file to grow purely to satisfy their next page of data.
#2 Undo tablespace alerts
For undo tablespaces with no AUTOEXTEND files Grid Control or some other monitoring tool will alert as the number of unexpired blocks starts to fall causing unnecessary (albeit low priority) work for your first line DBAs.
#1 ORA-1555
For undo tablespaces with no AUTOEXTEND files and either a sufficiently high transaction rate or sufficiently high gap between page 1 and page X of our ad-hoc user’s query we may find that the undo required to construct page X has been overwritten. The user gets ORA-1555, thinks “no harm done” and re-starts the query. However the alert log now contains something like below and an alert is fired shortly afterwards.
Wed Sep 28 21:14:48 2011 ORA-01555 caused by SQL statement below (SQL ID: 6fwqzurbc8y7k, Query Duration=3538 sec, SCN: 0x0000.001aad4e):
If the hour is late and the company is pretty twitchy then this could involve someone getting out of bed to investigate a potentially failed batch run.
Have I missed any undo related threats?
leave a comment