An interesting optimization has been done with Nested Loops in Oracle recently. In 11 in you may see plans like this:
SQL> set autotrace traceonly explain
SQL> select /*+ use_nl (d e) */ e.empno, e.sal, d.deptno, d.dname
2 from emp e, dept d
3 where e.deptno = d.deptno ;
Execution Plan
----------------------------------------------------------
Plan hash value: 1688427947
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 13 | 312 | 6 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 13 | 312 | 6 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | DEPT | 4 | 52 | 3 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | EMP_DEPT_IDX | 4 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| EMP | 3 | 33 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("E"."DEPTNO"="D"."DEPTNO")
filter("E"."DEPTNO" IS NOT NULL)
Why the double Nested Loop? We certainly expect to see the number of joins to be one less then the total number of tables, so with only 2 tables we'd expect to see just 1 join not 2.
What's happening here is that Oracle is "carrying" less in the inner loop. It's joining the DEPT table just to index on EMP. The results from this inner join is a row from the DEPT table and the ROWID from the EMP table, much less stuff to work with in the inner loop. Joining the row of DEPT with just the index on EMP is certainly less then working with the entire row from both.
The outer loop then really isn't a nested loop at all, but rather a "lookup" into the EMP table to find the row using the ROWID that it retrieved from the inner loop. Now it has all the data, the row from DEPT and the row from EMP joined together.
Kinda cool eh?
Wednesday, November 28, 2012
Monday, November 19, 2012
Which trace file is mine?
A common issue when tracing is finding your trace file after the tracing is done. Back in the bad old days I would do something like "select 'Ric Van Dyke' from dual;" in the session I was tracing. Once I was finished I'd then do a search for a trace file with "Ric Van Dyke" in the file to find mine. It worked, but today we have a much more elegant way to find a trace file:
SQL> alter session set tracefile_identifier='MY_TRACE';
SQL> exec dbms_monitor.session_trace_enable(null,null,true,true,'ALL_EXECUTIONS');
SQL> select 'hello' from dual;
'HELL
-----
hello
SQL> exit
Disconnected from Oracle Database
11g Enterprise Edition Release 11.2.0.2.0 - 64bit Production
With the Partitioning, OLAP, Data
Mining and Real Application Testing options
C:\app\hotsos\diag\rdbms\hotsos\hotsos\trace>dir
*MY_TRACE*
11/15/2012 04:30 PM 10,220 hotsos_ora_5540_MY_TRACE.trc
11/15/2012 04:30 PM 94 hotsos_ora_5540_MY_TRACE.trm
2 File(s) 10,314 bytes
You can make this more elaborate for example this is an anomomys PL/SQL block I have used in the past to turn on tracing, which dynamicly sets TRACEFILE_IDENTIFIER to the current time and user:
declare
t_trc_file varchar2(256):=
'alter session
set tracefile_identifier='||chr(39)||
to_char(sysdate, 'hh24miss’)||'_'||user||chr(39);
t_trc_stat varchar2(256):=
'alter session set timed_statistics=true';
t_trc_size varchar2(256):=
'alter session set max_dump_file_size=unlimited';
t_trc_sql
varchar2(256):=
'alter session set events '||chr(39)||
'10046 trace name context forever, level 12'||chr(39);
begin
execute immediate t_trc_file;
-- Set tracefile identifier
execute immediate t_trc_stat;
-- Turn on timed statistics
execute immediate t_trc_size;
-- Set max Dump file size
execute immediate t_trc_sql;
-- Turn on Trace
end;
Friday, November 9, 2012
Is this index being used?
When teaching the Optimizing Oracle SQL, Intensive class I’m
often asked how to tell if an index is being used or not. Oracle does have a feature for this called
(oddly enough) index monitoring. However
it’s a less than ideal. First off it’s
just a YES or NO switch. Either the
index has been used since you turned it on or it hasn’t. Also it’s not exactly 100% accurate. Sometimes
an index will be used and not counted and other times the way it’s used isn’t
really what you want, for example collecting stats will typically be counted as
“using” the index. However this is not
the type of use most folks are interested in.
We’d like to know when it’s used as part of a query.
We can see this. With
a rather simple query you can easily see which indexes are in use and how many
times, and even how the index is being used. Something like this does the trick; of course you’d
want to change the owner name.
The "#SQL" column is the number of different SQL statements currently in
the library cache that have used the index and the "#EXE" column shows
the total number of executions.
select count(*) "#SQL", sum(executions) "#EXE",
object_name, options
from v$sql_plan_statistics_all
where operation = 'INDEX' and
object_owner = 'OP'
group by object_name, options
order by object_name, options
/
object_name, options
from v$sql_plan_statistics_all
where operation = 'INDEX' and
object_owner = 'OP'
group by object_name, options
order by object_name, options
/
An example run:
#SQL #EXE OBJECT_NAME OPTIONS
------ ------ ------------------------ ------------------------
4 4 BIG_OBJTYPE_IDX RANGE SCAN
2 4 EMP_EMPNO_PK FULL SCAN
1 3 EMP_UPPER_IDX RANGE SCAN
1 2 LIC_OWN_CTY_LICNO_IDX SKIP SCAN
1 3 SALESORDER_PK FAST FULL SCAN
1 2 SALESORDER_PK FULL SCAN (MIN/MAX)
1 1 SALESORDER_PK RANGE SCAN DESCENDING
1 3 SALESORDER_PK UNIQUE SCAN
2 2 USERNAME_PK FULL SCAN
2 2 USERNAME_PK UNIQUE SCAN
SQL>
From this simple output we can see that the BIG_OBJTYPE_IDX is used in four different statements that each ran just once. And that the SALEORDER_PK is used a variety of ways. Each one from the same statement but some run more times then others.
A couple of other things to keep in mind, this will only
show what’s happened “recently”. Being a
V$ view the data isn’t kept in here for any particular length of time. In a
production environment you might want to run this a few times in the day to
capture what’s going on. Also this view is
fairly expensive to select from, so doing this once in a while is fine, just
don’t set this up to poll the view every second for the day. It
will likely show us as a “top SQL” statement in every monitoring tool.
Friday, July 20, 2012
Does the block size really matter?
A classic debate over the years in Oracle Land is that of
block size. There are two concepts in
this debate, smaller block size for transactional systems and larger for data
ware house systems.
The idea that smaller is good for a transactional system is
that with fewer rows per block there is a better ratio of interested
transaction list (ITL) entries in the block header to rows. Since an ITL is needed to lock one or more
rows in the block and a transactional system would generally be doing many
single (or very few) row DML actions concurrently, this ratio of few rows to
ITLs is good. This means that with just
a few ITLs in any given block there should be enough to lock any amount of rows
in a block. If there were a lot of rows
per block and many folks were trying to lock different rows in the same block
at the same time, then it’s possible the block might run out of ITLs and hence
folks have to wait more.
For a data ware house things are generally opposite. Folks tend to do lots of full table scans and
there is little (if any) concurrent DML.
So having many rows per ITL isn’t as big a deal. Some systems only have one process that does
some sort of scheduled DML operations.
Since there is just the one process running, having just one ITL per
block is fine.
This argument is valid, and is worth considering in your
design. Using it as the sole reason for
picking a block size is rather limited.
It will be difficult to see if this ratio of rows to ITLs is causing
really issues. The really issue that is foremost on most folks minds is
performance, as in, does the block size make a difference in how fast a query
will run?
The basic argument here is twofold. First, for index scans, the argument is that
the larger the block size the shorter the index (its height, BLEVEL) hence the
faster the index. Second, for full
table scans, the larger the block the faster since there are more rows per
block hence fewer blocks to read.
Let’s take a look at some numbers to see how these arguments
stack up. Granted this is a simple example,
but if we can see a performance gain or not in a simple example that’s a pretty
good indicator of what will happen in a complex one. First the index question.
Our test data is a table in an 8K block size tablespace, 9,254,528
rows, 131,840 blocks and about 1G in size.
We will do an index range scan on this table in three tests. The block size of the tablespace for the indexes
will be 4K, 8K and 16K. The index stats
for each are:
4K – Levels 4, Leaf blocks 197,987
8K – Levels 3, Leaf blocks 95,873 (reduction of 52%)
16K – Levels 2, Leaf blocks 47,177 (reduction of 51%, and 76% from the 4K)
8K – Levels 3, Leaf blocks 95,873 (reduction of 52%)
16K – Levels 2, Leaf blocks 47,177 (reduction of 51%, and 76% from the 4K)
The run time statistics of each of the index scans:
4K Block 8K Block 16K Block
Consistent Gets 14,017 13,904 13,846
Buffer Pinned Ct 12,065 12,065 12,065
Time* 22,172 22,297 22,165
Buffer Pinned Ct 12,065 12,065 12,065
Time* 22,172 22,297 22,165
(* The time is a representative time from multiple runs in
micro-seconds)
Notice that the performance hardly was affected by the block
size change. Almost no matter which way
you look at the performance it wasn’t significantly changed by the block
size. The Buffer Pinned Count stayed
the same which makes perfect sense, this was the number of rows retrieved from
the table and those same ROWIDs would be retrieved from the index regardless of
the block size.
So, what can one say about a full table scan? Here I recreated the table in a tablespace of
4K, 8K and 16K with no indexes.
The table had 9,254,784 rows in all three incarnations. The number of blocks in each table was:
4k - 269,824
8K - 131,840 (51% reduction from 4K)
16K - 65,216 (50% reduction from 8K, 75% reduction from 4K)
8K - 131,840 (51% reduction from 4K)
16K - 65,216 (50% reduction from 8K, 75% reduction from 4K)
The size of the table in Megs didn’t change a lot, 1,054M
for 4K, 1,030M for 8K and 1,019M for 16K.
About a 2% drop each time with just over 3% drop from 4K to 16K.
The run time statistics of a full table scan for each block
size:
4K Block 8K Block
16K Block
Consistent Gets 534,811 261,354 121,494
Time* 11,590,478 15,411,618 14,280,009
Time* 11,590,478 15,411,618 14,280,009
(* The time is a representative time from multiple runs in
micro-seconds)
Not surprising the LIOs (consistent gets) dropped off right
in line with the smaller number of blocks for the table. This is rather nice and likely good for
contention in particular. However it is
interesting to note that the time went up as the block size got larger. It seems the reasonable explanation for this
is that although it’s reading fewer blocks, each block is larger and hence over
all takes longer to read.
So what’s the bottom line?
It seems rather doubtful that a larger block size will have much (if
any) impact on performance. It does seem
that it could reduce the overall size of objects (tables and indexes) which in
itself is a good thing. However don’t
expect the performance of you application to change significantly just because
you have a larger block size.
Tuesday, May 8, 2012
And just for fun.... A couple weeks ago I was in San Francisco and got to meet the Myth Busters! What a hoot. In real life they are very much like what you see on TV. They were working with a friend of mine who does stunt work on an upcoming show. I would tell you what it is, but it has to do with jumping into water.... :-)

http://www.neooug.org/generaltab/seminar_2011/seminar.html
See you there!!
Thursday, February 2, 2012
Subscribe to:
Posts (Atom)

