Formal SQL Tuning For Oracle Databases PDF
Formal SQL Tuning For Oracle Databases PDF
Hanno Ernst
Victor Chupis
Formal
SQL Tuning
for Oracle
Databases
Practical Efficiency - Efficient Practice
www.allitebooks.com
Formal SQL Tuning for Oracle Databases
www.allitebooks.com
ThiS is a FM Blank Page
www.allitebooks.com
Leonid Nossov • Hanno Ernst •
Victor Chupis
www.allitebooks.com
Leonid Nossov Hanno Ernst
ORACLE Deutschland B.V. & Co. KG T-SYSTEMS INTERNATIONAL GMBH
M€unchen, Germany Bamberg, Germany
Victor Chupis
Vodafone GmbH
D€usseldorf, Germany
www.allitebooks.com
I know that I know nothing
Socrates
www.allitebooks.com
ThiS is a FM Blank Page
www.allitebooks.com
Foreword by Watson
The mantra I chant relentlessly is “you must understand your data; you must
understand your queries.” By this I mean that if a developer has a complete
understanding of the information he is processing, he will be able to write code
that will run efficiently. The critical decisions that Oracle’s cost-based optimizer
must make are join order, join method, and access method. Use of structures such as
DISTINCT to remove duplicates when none exist, or OUTER JOIN when INNER
JOIN will do, or needless functions around predicate columns, or inappropriate use
of NULL will force the CBO to develop plans that do not make the best decisions
and therefore cripple performance. There are more complex issues such as whether
views are mergeable or predicate pushable, whether correlated subqueries can be
factored out into common table expressions—any number of other optimizations.
The CBO (cost based optimizer) will do its best, but even though it is probably the
most complex software artifact with which you will ever work, all it can do is
follow rules. It does not have the knowledge of what is actually possible given the
business environment that a developer will (ideally) have.
If the developer has a thorough understanding of his data and queries, he will be
able to write code that lets the optimizer make the best decisions. However, this
knowledge may take an immense amount of time to acquire, and the necessary
information may not be available. Certainly an outside consultant on a short-term
contract has no chance of gaining it, and all too often the DBA (database
adminstrator) cannot do so either. The methodology presented in this book will
allow any competent DBA to tune SQL, even if he has minimal knowledge of the
environment. That is the power of this method: You do NOT need to have a
comprehensive understanding of the data or the SQL. You do not even need to
know what the SQL being executed is or be able to rewrite it.
The approach taken in this book is vital to the division of responsibilities in an
Oracle environment. Historically, Oracle’s approach to SQL tuning was that the
DBA should identify the statements that have (or are causing) problems and throw
them back to the developer for tuning. However, all too often when asked to tune a
statement, a developer would reply “how do I do that?” and as a result many DBAs
spend 8 hours a day tuning SQL. In later releases of the database, Uncle Oracle has
realized this, and now Oracle’s approach appears to be that developers should
concentrate on writing code that fulfills a business need and DBAs should be
vii
www.allitebooks.com
viii Foreword by Watson
responsible for making it run efficiently. The formal method described will help
DBAs to do just that.
The step-by-step method will identify points in an execution plan that are
problematic for performance and suggest how to tune them. Comprehensive
instruction on how to capture, read, and interpret an execution plan is essential
for this, and the book delivers this in spades. Essential reading for all DBAs.
www.allitebooks.com
Foreword by Gosejacob
Leonid Nossov’s current work deals specifically with the central aspect of perfor-
mance tuning for Oracle databases, the analysis and acceleration of SQL
statements. I was, of course, delighted when Leonid asked me to write a few
introductory lines for this book too.
I can well imagine some potential readers being somewhat deterred by the title
“Formal SQL Tuning,” which sounds very dry and theoretical. In my view,
however, it best reflects the nature of the method described. The term “formal
tuning” can also be seen here as a synonym for a structural approach.
The current version of the Oracle database provides a comprehensive arsenal of
analysis possibilities and tools, which simplify, or even automate, the tuning of
SQL statements. However, situations repeatedly occur in which these possibilities
can only be exploited to a limited extent or, in some cases, not at all. One of my
colleagues jokingly calls this the expert mode. We regularly encounter disbelieving
faces when this method achieves amazing runtime improvements. The colleague in
question has now been awarded the title Dr. SQL by some clients.
Particularly when swift action and, hopefully, positive results are expected,
stress levels for DBAs can increase sharply. From my own experience, I remember
situations in which the DBA was trying to solve performance problems in front of
the screen at the keyboard while the rest of the room was filled with a dozen people,
including the production manager. The consistent formal approach presented in this
book can be a lifeline in such a situation.
Leonid and his co-authors succeed in serving up this rather dry material in an
easily digestible and appetizing manner in the lively dialogues with Peter Smith,
with whom I, as a reader, can easily identify. This is made possible by the use of
numerous examples which present the material under consideration in a clear
manner and make the relevant information directly accessible to the reader. Possi-
ble solutions for the diagnosed problem are supplied at the same time.
ix
www.allitebooks.com
x Foreword by Gosejacob
Let me congratulate you Leonid on another fine book, and I wish you the reader
an enjoyable reading experience and the time to learn formal SQL tuning at your
leisure, so that you can shine with this knowledge when the next critical situation
comes up.
I must admit that this is the first time I have held one of Leonid Nossov’s books in
my hands. I became aware of this book through my colleague Martin, as I myself
am often confronted with questions about SQL tuning or database monitoring. SQL
tuning is also “trendy” and has long been a popular topic in the database commu-
nity. Almost anyone who is anyone in this field has a blog on the subject of SQL
tuning. Especially after the release of new database patches or even a new version,
there is an increase in the amount of information on the Internet about new
optimization methods with associated tips and tricks.
Consequently, I was very curious about this book by Leonid and his co-authors
Victor Chupis and Hanno Ernst with the title “Formal SQL Tuning,” which was so
different from other books and articles I had read. I had no idea what to expect. As a
mathematician, I was, of course, familiar with formal methods. Mathematical
methods are often regarded as very “dry” and of little interest to nonscientists. I
was, therefore, all the more surprised by the relaxed, easily readable “question and
answer” style which Leonid uses in his book. This enables one to familiarize
oneself with the material quickly and with a minimum of effort. If you really read
every chapter—even those designated for beginners—you can even learn how
execution plans are interpreted. In this way, database administrators and developers
get a chance to gain positive, personal, hands-on experience of tuning tasks even
though they are not SQL tuning experts and do not possess a lot of previous
knowledge about data modeling. It is then also interesting to read what
co-authors Victor Chupis and Hanno Ernst have to say. They put the formal SQL
tuning method to the test and successfully solve real-life problems.
The three authors are very successful in focusing on the essentials of SQL
tuning. Equipped with formal SQL tuning methodology, one can then calmly and
confidently cope with changes to the database due to new optimizer releases or
in-house application changes. I hope all readers enjoy this book, which I heartily
recommend.
xi
ThiS is a FM Blank Page
Preface
When I wrote the chapter “Formal SQL Tuning” in [1], I never thought that there
would be a sequel to it. In my opinion, the method was clear and obvious. For this
reason the above-mentioned chapter seemed more than adequate for the under-
standing and application of the proposed formal method.
As is often the case, things turned out differently in practice. Firstly, this method
is not at all common—at least not among the participants of the SQL tuning
workshop which I have been conducting for some time. Up to now I have never
found anyone who previously knew of this method, although there were enough
experienced specialists among the participants. Secondly, this method cannot be
quickly understood. I had to use a lot of examples and exercises in order to teach the
others formal SQL tuning. Thirdly, this method proved to be very efficient. I have to
admit that I prefer manual SQL tuning and don’t use Oracle’s automatic method.
Instead, I have been applying the above-mentioned method for years now, always
with success. Some of the workshop participants have said that this method has also
been of benefit to them. I have been pleased to hear that it helps them to analyze and
solve performance problems quickly.
To my amazement, I found no book which described this method. Perhaps I
missed something or perhaps other authors had neglected this simple method.
However, I now believe that this method is an important element of practical
SQL tuning.
Two excellent database specialists, Victor Chupis and Hanno Ernst, are also of
this opinion, and so the idea of writing a book together was born. I wrote the
chapters in which formal SQL tuning is described. In contrast to [1], this method is
presented here in a much more structured and detailed way. It was also very
important for us to present our practical experience. Victor Chupis and Hanno
Ernst were responsible for this part of the book.
xiii
ThiS is a FM Blank Page
Contents
1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Aims and Target Groups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 An Overview of the Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Some Thoughts on the Term “SQL Tuning” . . . . . . . . . . . . . . . . . . 5
2.1 SQL Tuning: Definitions and Objectives . . . . . . . . . . . . . . . . . . 5
2.2 SQL Tuners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.1 Oracle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.2 Developer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.3 Database Administrator . . . . . . . . . . . . . . . . . . . . . . . . . 9
3 Minimum Minimorum on the Subject of the “Execution Plan” . . . 11
3.1 Can You Read Execution Plans? . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2 Some Important Details . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2.1 Sections of the Execution Plan . . . . . . . . . . . . . . . . . . . . 12
3.2.2 Optimizer Estimations and Costs . . . . . . . . . . . . . . . . . . 20
3.2.3 Runtime Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.3 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4 Approaches to Formal SQL Tuning . . . . . . . . . . . . . . . . . . . . . . . . 25
4.1 The Objective: Effective SQL Tuning . . . . . . . . . . . . . . . . . . . . 25
4.2 The Principle: Elimination of “Brakes” in the Execution Plan . . . 26
4.3 The Method: Analysis of Runtime Statistics in the Execution
Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.4 The Main Criterion: The Runtime Statistic “Cardinality” . . . . . . 27
4.5 The Procedure: An Iterative Process . . . . . . . . . . . . . . . . . . . . . 28
4.6 The Guideline: Tuning Without Changing the SQL Statement . . . 28
5 Bottlenecks in the Execution Plan . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.1 “Local” Problems in the Execution Plan . . . . . . . . . . . . . . . . . . 31
5.1.1 A Missing Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.1.2 A Nonselective Index . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.1.3 An Index with a Large Clustering Factor . . . . . . . . . . . . 38
xv
xvi Contents
Literature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Introduction
1
This chapter contains a brief description of our aims and the readership to which
this book is addressed. A brief overview is provided here to help potential readers
decide whether to purchase the book. This overview will also provide a useful
orientation aid to those who already have a copy. A number of people assisted us in
writing this manuscript. We would like to take this opportunity to thank them for
their help.
Another important point to mention is that Peter Smith, a friend of Leonid’s, was
a character in his first book. Several chapters were written in the form of dialogues
with Peter. This had the effect of making the dry material a bit more palatable for
the reader. His questions, input, and suggestions were extremely helpful in
presenting a difficult topic like performance tuning in an easier and more compre-
hensible manner for the reader. In the meantime, Hanno and Victor have also made
friends with Peter. This gave us the idea of inviting Peter Smith to participate again.
As he had enjoyed his first performance in front of an audience so much, he agreed
to appear again. With the first book, he increased his knowledge of performance
tuning. Now it is the turn of SQL tuning. For those who are not yet acquainted with
Peter, he would like to introduce himself:
Peter: “As you already know, my name is Peter Smith. I am still working as an
Oracle database administrator for a medium-sized enterprise. As a result of my
participation in the book on performance tuning, I have developed a keen interest in
this topic. I have learned a great deal, and the difficult times when I was confronted
with two suboptimally performing databases and didn’t know what to do are a thing
of the past. Although I have improved my knowledge of SQL tuning, it is, unfortu-
nately, far from adequate. This is what prompted me to take on the role of
Dr. Watson once again.”
The main objective of this book is to popularize formal SQL tuning. This method
has a number of advantages:
We, the authors, are fascinated by this method. Consider how often you have
been confronted by a SQL problem and didn’t know where to begin. Formal SQL
tuning offers an action plan, which normally provides a quick solution to the
problem.
A lot of books on SQL tuning don’t actually describe a tuning method, but more
a method for efficient SQL programming. This assumes good SQL skills and data
model knowledge. Such books are primarily aimed at developers because they
possess this knowledge. It is different with database administrators, who usually
know little or nothing about the relevant data model. Their SQL skills are often
relatively modest compared to that of developers.
The formal method enables database administrators to carry out SQL tuning too,
as the technique does not require knowledge of data models. Good SQL know-how
is always an advantage when carrying out SQL tuning. If, however, your knowledge
of SQL is not perfect, this is no major obstacle to the successful use of formal SQL
tuning because, in practice, most SQL problems can be solved by relatively simple
means. For this reason, this book is mainly directed at database administrators.
Each of the authors can name several examples of the successful use of formal
SQL tuning. Quite often the authors proved to be even faster and more efficient
tuners than the developers. Developers can therefore use formal SQL tuning to
supplement their own methods and, in some cases, as a better alternative.
This is not a SQL tuning textbook. The authors try to present the idea of SQL tuning
in as concise and as comprehensible manner as possible. Before we begin with SQL
tuning, we have to clarify what we understand by SQL tuning, what aims we are
pursuing, etc. This is the topic of Chap. 2.
1.3 Acknowledgments 3
1.3 Acknowledgments
The authors would like to thank their families for the patience and understanding
they have shown.
We are very grateful to Anna Nosova for her creative and humorous illustrations.
We imagine muses to be females who gather around the authors and stimulate their
creative talents. Our muse is of a completely different kind. It is male and heftily built but
is nevertheless an inexhaustible source of inspiration for us. Many thanks to Peter Smith!
Leonid Nossov would especially like to thank Wolfgang M€uller for his constant
support and motivation.
Some Thoughts on the Term “SQL Tuning”
2
Before we really get started, it makes sense to clarify what we mean by SQL tuning.
We will do that in this chapter.
A.: “Wait a minute Peter, I’m not finished yet. We still have to make clear
that what we mean by SQL tuning in this book is the tuning of individual
SQL statements.”
P.: “Isn’t that obvious?”
A.: “No. Imagine that your system is performing suboptimally after an
Oracle migration. In this case it doesn’t help to tune the individual
SQL statements because there are usually too many of them. You have
to find a reason for this problem (often it is a new optimizer feature).
That is a different process, although you can use a similar principle to
formal SQL tuning for this analysis (see the example in the appendix).”
P.: “Could you please define what you understand by SQL tuning? Criticism
should be constructive.”
A.: “Before I do that, I’d like to mention one more aspect of SQL tuning.
The improvement measures for one executive plan must not be to the
detriment of another. This can be especially critical with acute perfor-
mance problems where there is no time to test the improvement
measures. Now I can formulate my understanding of SQL tuning. By
SQL tuning we mean a process by which an acceptable performance of a
SQL statement is achieved on a system. Improvement measures for one
SQL statement must not adversely affect another. Let’s begin directly
with a suboptimally performing SQL statement. How you identify the
problematical SQL statement as described in detail in [1].”
There are three categories of SQL tuners: Oracle, developers, and database
administrators. In this section we will briefly discuss how each of these tuners
operates.
2.2.1 Oracle
Oracle notices that the real cardinality in the first execution differs
greatly from the estimation, the optimizer calculates a new execution
plan in which the cardinality gained in the first execution is taken into
consideration. It then uses this plan in the next executions. Let’s consider
another feature: adaptive plans. At the time of execution, Oracle decides
which join method it is best to use (nested loop or hash join).”
P.: “Those are only a few elements of SQL tuning.”
A.: “Oracle also has SQL tuning advisor. This advisor can function in two
modes: automatic and manual. During tuning, Oracle makes some
improvement proposals (e.g., regarding access paths), calculates statis-
tics which are specific to the SQL statements in question (that is to say
for the relevant predicates), and saves these statistics in the form of
optimizer hints in a SQL profile. These statistics represent adjustments
or corrections to the optimizer estimations in the relevant execution plan.
We’ve already discussed SQL profiles in some detail in [1]. I assume
you’ve forgotten.”
P.: “Actually, I have forgotten. I take back my objection: Oracle does do
SQL tuning. Can you say something about the quality of this tuning?”
A.: “The quality of automatic tuning in Oracle 11 and 12 has improved
noticeably. The basic principle of calculating specific statistics is effec-
tive and absolutely correct. It is no wonder the same principle is used in
some other methods of SQL tuning (e.g., in [4]).”
P.: “What use are such methods if Oracle has already implemented that?”
A.: “These methods are similar but not identical to Oracle’s SQL tuning.
One shouldn’t forget that Oracle’s automatic SQL tuning requires a
license. The other methods can be used if one has no tuning pack license,
for example.”
P.: “If such methods are so good, why do you still need formal SQL
tuning?”
A.: “I can give you a couple of reasons. Firstly, every method has its
disadvantages and doesn’t always help. Secondly, calculating specific
optimizer statistics can take an unacceptably long time (e.g., in the case
of a join of many large tables). In our opinion, such gathering of statistics
can be avoided if runtime statistics are already available in the execution
plan. This saves a lot of time. There is another reason. Formal SQL
tuning is very easy. The analysis of runtime statistics in the execution
plan is based on some simple rules and can be carried out by any
developer or database administrator. For this reason, we go our own
way in SQL tuning (Fig. 2.1).”
8 2 Some Thoughts on the Term “SQL Tuning”
2.2.2 Developer
P.: “Obviously. But I hope you can list me a few more disadvantages.”
A.: “Imagine that software developed for relatively small companies is
being used at a large company. In this case, the data volume and data
distribution are different from the standard a developer is used to. Then
at least some of the advantages the developer has to offer for SQL tuning
are lost. I have often seen developers having problems in such situations.
They may then point out alleged hardware bottlenecks or incorrect
parameterization of the database. However, when a database administra-
tor is brought in, and the situation analyzed in more detail, it often
emerges that there are problems in the SQL area.”
P.: “That is something I have also experienced.”
A.: “It is not unusual for a developer to carry out unnecessary alterations to
the SQL text during SQL tuning.”
P.: “Is that bad?”
A.: “When a performance problem is acute, it has to be remedied as quickly
as possible. For organizational reasons, a program change usually
requires several days. As the person responsible for the database, you
have to survive these days somehow.”
P.: “How should SQL tuning be practiced then?”
A.: “I’ll explain that in the next section.”
Author: “I often hear that no SQL tuning is possible without knowledge of data
models. Unfortunately, this opinion is very widespread. What do you
think, Peter?”
Peter: “Well, yes, you do have to know something about the data model.”
A.: “I, on the other hand, am sure that it is unnecessary. The formal method
which we describe in this book does not require any knowledge of data
models.”
P.: “I can’t imagine that.”
A.: “In practice I often see that database administrators are very keen to
leave SQL tuning to the developers because they are convinced that they
have no chance of succeeding with SQL tuning. The developers, on the
other hand, don’t mind if the database administrators perform this task
(Fig. 2.2).”
P.: “So who should carry out SQL tuning?”
A.: “The database administrator is responsible for the database. So it is
primarily in his interest for the database to run efficiently.”
P.: “So you think the database administrator should perform SQL tuning?”
A.: “The database administrator should start SQL tuning. That is particularly
important when there are no developers available or if the relevant
performance problem is acute. Formal SQL tuning enables him to
10 2 Some Thoughts on the Term “SQL Tuning”
Ask yourself this question. If you can’t, or are unsure and hesitant with your answer,
this chapter is for you.
It has a single objective: To impart the minimum knowledge of the execution
plan necessary to enable you to read this book. In this respect, this chapter can be
regarded as an introduction to the topic of the “execution plan.” This minimum
knowledge is also sufficient to gain a command of the method of formal SQL
tuning.
Imagine you have an execution plan in front of you. What do you see? What
information could be useful for SQL tuning? How can this be applied practically?
Let’s ask our helper Peter Smith.
Let’s take the output of this function as a basis for our little study of
the execution plan as it’s relatively complete and contains practically all
useful details required for SQL tuning. With the argument FORMAT,
one can define the output of individual sections of the execution plan.
The value ‘ADVANCED’ triggers the output of all sections. We already
discussed this in [1]. I suggest that we start with these sections and
discuss the most important ones.”
3.2.1.1 Plan
Author: “This is the first section. What do you see there, Peter?”
Peter: “I thought the first section was the SQL text.”
A.: “You’re right of course, but an unformatted SQL text is not very
important for us. So let’s start with the plan.”
P.: “Alright. In this section the execution plan itself is shown.”
A.: “As you see, it is in the form of a table. The ‘operation’ column contains
the relevant operations or steps of the execution plan, e.g., ‘index range
scan’ or ‘table access full.’ These steps are in numerical order (see
column ‘Id’).”
P.: “Some operations are indented to the right.”
A.: “Yes, because the plan is hierarchical and is displayed accordingly.
An operation A (e.g., ‘index range scan’) is subordinate to the other
operation B (e.g., ‘table access by index rowid’) if it is located below B
in the execution plan, if it is further indented to the right, and if
no operation exists between A and B that is indented to the right as
far as B.”
P.: “To what extent is the hierarchy for SQL tuning important?”
3.2 Some Important Details 13
No Id Operation Name
03 0 SELECT STATEMENT
02 1 TABLE ACCESS BY INDEX ROWID T1
01 * 2 INDEX RANGE SCAN I_T1
Fig. 3.1 Order of operations in the case of table access via an index range scan
• When two operations are equally indented to the right and subordinated
to the same operation, the upper one is executed first. One can therefore
say that the order of operations is from top to bottom.
• When one operation is subordinated to another, the subordinated one
is executed first. As the subordinated operation is indented further to
the right, the order of operations in this case goes from right to left.
Let’s apply these rules to a simple example (see Fig. 3.1). Let’s start
with step 0. That is the select statement itself. ‘Table access by index
rowid’ is subordinate to this operation. The operation ‘index range scan,’
in turn, is subordinate to ‘table access by index rowid.’ As there is no
further subordinate operation, ‘index range scan’ is executed first.”
P.: “So you start with the operation that is indented the furthest to the right.”
A.: “Yes, following the ‘right to left’ rule. The order of operations in our
example (and, incidentally, in the next one too) can be seen in the ‘No.’
column. In Fig. 3.2, the order is displayed in a join. Can you understand
this sequence, Peter?”
P.: “If one combines two rules, ‘from top to bottom’ and from ‘right to left,’
one obtains the order. Do these rules always apply?”
A.: “You can find an exception in [2], for example. We have slightly modified
this example and placed it in the ‘Runtime Statistics’ section. A second
example with scalar subquery expressions is shown in Fig. 3.3.”
P.: “I’m not really sure if I understand this example.”
A.: “Right at the top of this execution plan, there are two subqueries from the
table T2. According to the ‘top to bottom’ rule, they have to be executed
first. This is impossible, however, because they correlate to the main
query. So they only have to be executed after the ‘hash join’ (step 5 in the
execution plan).”
P.: “To be honest with you, I’m a bit unsure about this rule now.”
A.: “Normally it works (a few exceptions don’t represent any major
problems). Incidentally, I don’t know of any exceptions to the rule
‘from right to left.’”
P.: “When an execution plan is complex, it is not so easy to recognize how
far an operation is indented to the right.”
A.: “Yes, that’s right. If there had been an additional column ‘LEVEL’ for a
numerical degree of indentation in the output of DBMS_XPLAN.
DISPLAY_CURSOR, it would have been much easier. But let’s get
back to the execution plan. The next column there is ‘NAME.’ This
column contains object names for the respective operations. For ‘table
14 3 Minimum Minimorum on the Subject of the “Execution Plan”
No Id Operation Name
0 SELECT STATEMENT
… …
14 10 NESTED LOOPS OUTER
11 11 NESTED LOOPS OUTER
08 12 NESTED LOOPS
05 13 NESTED LOOPS
02 * 14 TABLE ACCESS BY INDEX ROWID PICKLISTEN
01 * 15 INDEX RANGE SCAN PIL_PK
04 16 TABLE ACCESS BY INDEX ROWID PICKRUND
03 * 17 INDEX RANGE SCAN PR_PL_FK_I
07 18 TABLE ACCESS BY INDEX ROWID PICKAUF
06 * 19 INDEX RANGE SCAN PI_PR_FK_I
10 * 20 TABLE ACCESS BY INDEX ROWID QUANTEN
09 * 21 INDEX RANGE SCAN QT_LE1_FK_I
13 * 22 TABLE ACCESS BY INDEX ROWID PRUEFGRUENDE
12 * 23 INDEX RANGE SCAN PG_NR_LE_I
… …
Fig. 3.3 Order of operations in the execution plan. An exception to the rule
access by index rowid,’ for example, one finds the appropriate table
name and an index name for ‘index range scan.’ In addition, there are
columns which are relevant for operations on partitioned objects, for
parallel operations, and for operations on remote databases. We refer to
some of these columns in the examples. The remaining columns contain
optimizer costs, optimizer estimates, and runtime statistics which are
described later. We’ve almost forgotten to mention the plan hash value.
Can you still remember how this value can be used in SQL tuning,
Peter?”
P.: “This value can be used to compare execution plans. When execution
plans have different hash values, they are in fact different. When the
hash values are the same, it is highly likely that the plans are identical.”
www.allitebooks.com
3.2 Some Important Details 15
Fig. 3.4 Query block names and table aliases can be used for hints
16 3 Minimum Minimorum on the Subject of the “Execution Plan”
Peter: “You can enter these outlines into a SQL statement as optimizer hints to fix
the relevant execution plan. Is that right?”
A.: “In principle, that’s right. One has to be careful with parallel operations,
however, because Oracle doesn’t normally generate any outlines for them.
In this case, you have to complete the outlines with the appropriate parallel
hints.”
P.: “In [1] we learned that one can either create outlines as stored outlines or
store them in a SQL profile. Can you use outlines in any other way for SQL
tuning?”
A.: “First of all, I’d like to point out that the stored outlines in 12c are no
longer supported. You can use the outlines from the section ‘Outline Data’
as patterns for your own hints. That is especially helpful when you only
need to modify the outlines slightly. There are outlines in execution plans
of cursors in the SQL area and in the AWR. They are missing in the
statspack, however.”
Bind values can be found in execution plans from the SQL area and
from the AWR. They are missing in the statspack.”
Peter: “Two lines: filter and access. I assume that a table access with the predicate
POINT_OF_SALE¼:B1 is being carried out. An index may be used for
access in the second step of the execution plan. All rows which have been
selected will be filtered out in the first step of the execution plan.
Predicates which belong to the filter are used for this purpose. For exam-
ple, in the case of a table access by rowid.”
A.: “Quite right, Peter. Please have a look below:”
P.: “But how does this help me with SQL tuning? I can take the relevant
predicates from the SQL statement.”
A.: “Not always. For example, Oracle can generate predicates from constraints.
In this case, they are missing in the SQL statement. If a SQL statement is
large and not transparent, and also contains a few views, this task is then
relatively complex. In this case, you can only assume which predicates
Oracle is using. The execution plan section ‘Predicate Information’ describes
this in precise detail.”
P.: “I see. How can I use this information for SQL tuning?”
A.: “You can assess how selective the various predicates are. It is important that
one uses selective predicates for accesses (in our example they are listed with
‘access’ in the execution plan). This accelerates these accesses and reduces
the number of hits. Predicates with filters can also reduce the number of hits,
which accelerates the subsequent execution plan steps. The performance of
the execution plan step in which the relevant filtering takes place is hardly
influenced by this, however. We will discuss this in detail when we describe
18 3 Minimum Minimorum on the Subject of the “Execution Plan”
formal SQL tuning. With exadata, predicates can also be allocated to storage
as well as filter and access. In this case, predicates are transferred to the
storage layer and the relevant data access optimized by means of exadata
features (e.g., with storage indexes).”
P.: “If I’m not mistaken, information about predicates is not maintained either in
the AWR or in the statspack.”
A.: “That’s right. You have to obtain this information from an execution plan of a
cursor from the SQL area. As a workaround against some Oracle bugs, the
parameter setting "_cursor_plan_unparse_enabled"¼false is used often. In
this case, Oracle does not generate any information about predicates. They
are then also missing in the SQL area. The same applies for the information
from the next section of the execution plan ‘Column Projection Information.’
And just one final important comment. You will no doubt have noticed
already that the execution plan steps with predicates are marked with an
asterisk in the ‘Id’ column.”
to avoid the table access, that would have been no problem. The informa-
tion about the columns to be selected helps in making the right decision. I
hope this example has changed your opinion.”
3.2.1.8 Note
Author: “The section ‘Note’ is very easy to explain. It contains information
which can also be very useful for SQL tuning. For example, one can
establish whether dynamic sampling or user bind peeking has been
used.”
Peter: “What information is particularly important?”
A.: “In principle, it is all important. It depends on the specific case. For
example, I always check whether an inefficient execution plan is fixed
with a SQL profile or with the SQL plan baselines. During SQL tuning, it
makes sense to disable the relevant profile or the baselines, because they
tend to be more detrimental than helpful. Sometimes the optimizer finds
a better execution plan itself if no SQL profiles and SQL plan baselines
are used.”
20 3 Minimum Minimorum on the Subject of the “Execution Plan”
Author: “Runtime statistics in the execution plan are extremely important for
formal SQL tuning.”
Peter: “You’ve already said that we can compare the runtime statistics with the
optimizer estimations.”
3.2 Some Important Details 21
A.: “That’s only one possibility in SQL tuning. What is much more impor-
tant is that the runtime statistics help to recognize a bottleneck in the
execution plan. We will discuss this in the chapter ‘Bottlenecks in the
Execution Plan.’ In Oracle there are a number of ways of obtaining
runtime statistics (we will describe these possibilities in the section ‘The
Method: Analysis of Runtime Statistics in the Execution Plan’). One of
these possibilities is that the runtime statistics are generated directly in
the execution plan. This does not occur per default because it is a costly
option. In the abovementioned section, we discuss how this is done. In
order to display all runtime statistics with the function DBMS_XPLAN.
DISPLAY_CURSOR, the argument FORMAT has to be supplemented
with the word ‘ALLSTATS’ (it is also possible to have these statistics
displayed singly). In order to display runtime statistics for the last
execution of a cursor, one has to supplement the argument FORMAT
with the word ‘LAST’ (otherwise the statistics will be listed summarily
for all executions). With the following command, you can identify an
execution plan with all runtime statistics for the last execution of a cursor
from the SQL area.”
P.: “Do you really need all runtime statistics for SQL tuning?”
A.: “It is difficult to say in advance what you need in a concrete case. For this
reason, I always display them all. When the optimizer estimations and the
relevant runtime statistics have the same name (like ‘rows’ and ‘time’), these
names are supplemented with ‘E-’ (estimated) and with ‘A-’ (actual). Unlike
the optimizer estimation ‘rows,’ the relevant runtime statistic is calculated for
all executions of the execution plan step it belongs to.”
P.: “But we want to compare these two values with each other. How is that
possible?”
A.: “The runtime statistic ‘starts’ makes this possible. That is the number of
executions for each execution plan step. If we divide the runtime statistic ‘A-
rows’ for a step, by the number of executions, we obtain the number of rows
for one execution of this step. We can compare this value with the relevant
estimation ‘rows.’ This applies to non-partitioned objects. With partitioned
objects it is somewhat more difficult because the statistic ‘starts’ includes
‘scanned’ partitions or sub-partitions. I think we can now present the example
from [2] which was promised in the section ‘Plan’ (Fig. 3.5).”
P.: “What does this example tell us?”
A.: “Step 6 of the above execution plan has to be executed after step 2 because
these steps are equally indented to the right and step 6 is below step 2. In fact,
this step is executed first.”
P.: “How can you see that?”
22 3 Minimum Minimorum on the Subject of the “Execution Plan”
Fig. 3.5 Order of operations in the execution plan. An example from J. Lewis
A.: “From the runtime statistic ‘starts’: Step 6 has been executed once, whereas
step 2 has never been executed.”
P.: “Very interesting. Without runtime statistics it would have been impossible
to recognize that.”
A.: “One could have assumed that because the first subquery does not correlate
with the main query and can be executed first for this reason. That can easily
be recognized with the runtime statistics. Let’s continue with the runtime
statistics. The next 3 are ‘buffers,’ ‘reads,’ and ‘writes.’ That is the number of
data blocks which is respectively read from the buffer cache or from the hard
disk or is written on the hard disk. These statistics are cumulative. In addition
to the runtime statistics mentioned, some work area statistics are listed, e.g.,
‘used-tmp’—the size of the used temporary tablespace. These statistics refer
to the relevant execution plan steps and are not cumulative. I have forgotten
to mention that the runtime statistic ‘time’ displays runtime in hours, minutes,
and seconds and is cumulative.”
3.3 Summary
• Execution plans are hierarchical and are displayed accordingly. This hierarchy
determines the order of operations:
– When two operations are equally indented to the right, the upper of these two
is executed first. That is the “top to bottom” rule.
– When one operation is subordinate to another, the subordinate operation is
executed first. That is the “right to left” rule, because the subordinate opera-
tion is indented further to the right. There are a few exceptions to the “top to
bottom” rule, but in most cases it is correct.
3.3 Summary 23
• Information from the section “Query Block Name/Object Aliases” of the execu-
tion plan can be used for optimizer hints.
• Outlines are special optimizer hints, which are designed to fix the relevant
execution plan. Oracle does not normally generate any hints in the outlines for
parallel operations. There are no outlines in the statspack.
• The section “Peeked Bind” can be used as a source of bind values for execution
of the relevant SQL statement. These bind values are missing in the statspack.
• There are two types of “predicate information”: access and filter. In access,
predicates are listed which Oracle uses when accessing data in the relevant
execution plan step. In contrast to access, predicates from filter are only used
for filtering the rows found. The section “Predicate Information” is very impor-
tant for formal SQL tuning.
• In the section “Column Projection Information,” the tables or index columns
which are to be selected in the relevant execution plan step are listed.
• The section “Note” contains some additional information, for example,
concerning the use of dynamic sampling or cardinality feedback.
Approaches to Formal SQL Tuning
4
This is the first of the three chapters in which the formal method is described. In this
chapter, we formulate the objective, principle, method, main criterion, approach,
and guideline for formal SQL tuning.
Oracle’s optimizer always tries to generate the best execution plan. This is actually
unnecessary for SQL tuning. It is perfectly adequate to upgrade a poor execution
plan to an acceptable one. It is irrelevant whether the improved plan is the optimum
plan. This makes the task of SQL tuning easier.
This is precisely the principle by which formal SQL tuning functions: One finds
problematical steps in the execution plan and eliminates them. In this way, one very
often achieves astonishingly good solutions.
In order to find problematical execution plan steps, one can either use optimizer
estimations or runtime statistics. In the case of optimizer estimations, one has to be
careful. They can be relatively imprecise for a number of reasons. If optimizer
statistics of the objects involved in the SQL statement are incorrect, then the
optimizer estimations are also far from realistic. It’s possible for optimizer statistics
to be exact but estimations not. This is because the optimizer makes assumptions in
some places. One must be particularly careful with optimizer estimations in poor
execution plans because it is often precisely these which cause suboptimal plans.
Optimizer estimations are, therefore, not a reliable basis for the analysis of
execution plans. The runtime statistics in execution plans are much more suitable.
They provide information on the actual course of individual execution plan steps.
How one can request Oracle to display runtime statistics in the execution plan has
already been described in the section “Runtime Statistics.” As the generation of
runtime statistics is expensive, this is omitted by default. It can, however, be activated
for test runs, either for one session (with the parameter setting statistics_level¼all) or
with the hint GATHER_PLAN_STATISTICS (also as a hidden hint, see [1]), for a
SQL statement.
The other possibility of accessing the statistics in the execution plan is the
feature “SQL monitoring,” which is available from Oracle 11 onward. SQL moni-
toring is described in detail in [1]. Unfortunately, no information is listed on
predicates and on column projections in the SQL monitoring reports. This has to
be obtained separately in the execution plans from the SQL area. It should not be
forgotten that SQL monitoring requires a license.
SQL tracing is another source of runtime statistics in the execution plan. As it is
relatively complicated to generate and analyze a SQL tracing (at least more
complicated than the other two features described above), this option is not used
as often as the others.
4.4 The Main Criterion: The Runtime Statistic “Cardinality” 27
Cardinality is the main criterion in the analysis of execution plans with the formal
method (Fig. 4.1). In the output of DBMS_XPLAN.DISPLAY_CURSOR, this
statistic is referred to as “A-rows”, in the SQL monitoring report as “rows (actual),”
and in SQL tracing as “rows.”
This statistic contains the number of rows which have been selected in the
relevant execution plan step. A high cardinality indicates the processing of a
large number of rows in an execution plan step. Accordingly, this step can be
expensive. That is the first reason why cardinality is selected as the main criterion.
The rows selected in an execution plan step are processed further in the subsequent
steps. If there are a large number of these, this makes the next execution plan steps
more expensive. That is the second reason.
It is possible for a large number of rows to be processed in one step although
its cardinality is low, e.g., if a full table scan (FTS) is used to find data about very
selective predicates from a large table. In such a case, one also has to consider
other runtime statistics for the analysis (e.g., buffer gets or buffers in the output
of DBMS_XPLAN.DISPLAY_CURSOR). But in this case too, cardinality is
very important for the analysis: In the case of an FTS on a large table with a
low cardinality, one can immediately assume that the relevant predicates are
selective, and one can create an index for the relevant columns as an improvement
measure.
Although the other runtime statistics play a subordinate role in the formal
method, they can also be helpful for the analysis of the execution plan.
Unfortunately, it is seldom the case that the first improvement already results in an
acceptable execution plan. As a result, one has to be prepared for several executions
of the problematical SQL statement or the problematical process.
One can, for example, execute the problematical SQL statement in SQL*Plus. If
this statement contains bind variables, these can be defined as variables in
SQL*Plus and set to corresponding values. This is possible for some common
data types (e.g., VARCHAR2 or NUMBER).
When tuning a data manipulation language (DML) statement, it is sometimes
possible to extract a select statement, to tune it, and to use the results of the tuning
on the DML statement (see [1]). As a result, it is no longer necessary to execute this
DML statement for tuning purposes and to change the relevant data.
When one tunes a process which is executed repeatedly, it is often very practical
to use SQL monitoring for tuning (e.g., see [1]). In this case, no separate executions
of individual SQL statements of this process are necessary. This saves time and
effort.
Here we have outlined a number of possibilities of how problematical SQL
statements can be executed for the purpose of SQL tuning. When carrying out SQL
tuning, it is important to consider these possibilities in advance and to select a
suitable one.
With SQL tuning, one must also consider how to implement the relevant
improvements. Very often, changes to the SQL text are not possible because the
application developers are not readily available (e.g., in the case of a standard
application). If a performance problem is acute, one must act very quickly. In this
case, it does not help if the developers are in a neighboring office, because any code
change cannot be made immediately—mostly for organizational reasons.
It therefore makes sense if you are prepared for SQL tuning without changes to
the SQL statement from the outset. The following improvements are welcome in
SQL tuning:
1. Creation of new indexes. One has to be careful and only create an index when
there is no other solution possible because each new index influences other
execution plans and can, in some cases, affect them adversely.
2. Extension of existing indexes. Here there is also a risk that the extended index
could adversely affect other execution plans. However, this risk is generally
much lower than is the case with a completely new index.
3. Use of optimizer hints. One can use optimizer hints as hidden hints (see [1]).
Alternatively, the plan optimized with the hints can be fixed (some methods to
4.6 The Guideline: Tuning Without Changing the SQL Statement 29
achieve this can be found in [1]). A change to the SQL statement is unnecessary
in either of these cases.
4. Change of database parameters. If one optimizes a SQL statement with database
parameters, it makes sense to fix the optimized plan instead of implementing the
relevant parameter settings throughout the system. Alternatively, one can try
setting the relevant parameters with the hint OPT_PARAM dedicated to a
problematical SQL statement (see [1]). According to the documentation, this is
only possible for a handful of parameters. In reality, this hint works for many
parameters.
5. Gathering of optimizer statistics. New optimizer statistics can also influence
other plans. The risk of other plans deteriorating is relatively low, however. In
order to gather optimizer statistics without any risk, one can generate new
statistics but not publish them (set preference PUBLISH to FALSE). In a
separate session, one can then allow these pending statistics with the parameter
setting optimizer_use_pending_statistics¼true and fix a plan optimized with
these statistics.
In this chapter we will describe the core elements of the formal method: The
recognition of bottlenecks in the execution plan. Formal SQL tuning provides a
very simple procedure for recognizing bottlenecks (even for large, complex execu-
tion plans). One could say that this method is your Ariadne thread in the labyrinth of
execution plans (Fig. 5.1).
Local problems are those which are “localized” to one or more (as a rule two)
execution plan steps.
Author: “Peter, how would you recognize that an index is missing in an execution
plan in case of an FTS?”
Peter: “A low cardinality of an FTS can be an indication of a possible selective
index.”
A.: “What do you mean by a low cardinality? 1, 2, or 100 maybe?”
P.: “I can’t give you a concrete figure.”
A.: “So when can you say that the cardinality of an FTS is low?”
P.: “Wait a minute. When the table in question is relatively large (and those
are the ones that interest us), Oracle has to do quite a lot of buffer gets or
physical reads with an FTS. I would compare the cardinality with the
number of buffer gets.”
A.: “Would you check every FTS in the execution plan in this way?”
P.: “Only the steps with a long runtime.”
Fig. 5.1 Formal SQL tuning is your Ariadne thread in the labyrinth of execution plans
Fig. 5.2 An indication of a missing index: FTS with a low cardinality and a large number of
buffer gets
A.: “Let’s use a test case to show how one identifies a missing index. For this
purpose, we will create a table T1 with 100,000 rows. After that we will
execute a SQL statement and display the relevant execution plan with
runtime statistics (Fig. 5.2).”
P.: “In the second execution plan step, an FTS was carried out. 185 buffer
gets were done and one row found. An index for column B is intended to
5.1 “Local” Problems in the Execution Plan 33
Fig. 5.3 An index access substantially reduces the number of buffer gets
reduce the number of buffer gets and thereby improve the execution
plan.”
A.: “You took column B from the filter in the section ‘Predicate Informa-
tion,’ didn’t you?”
P.: “That wasn’t necessary in our simple example. If the SQL statement had
been much more complex, I would have done that.”
A.: “Let’s create the index:
Index created.
Fig. 5.5 An index as a quick source of help with FTS on a large sparse table
5.1 “Local” Problems in the Execution Plan 35
Author: “In this section we will discuss a very important and interesting topic:
How does one recognize a nonselective index in the execution plan?
How would you do that, Peter?”
Peter: “When a nonselective index is used, the relevant index scan has to return
a large number of rows. The relevant cardinality must therefore be high.”
A.: “What do you regard as a high cardinality? How can you recognize that
an index isn’t selective and that something can be improved?”
P.: “You ask difficult questions. I don’t know.”
A.: “Let’s start with the high cardinality. It’s true that it isn’t possible to
say which cardinality is high when we talk about it abstractly and not
in relation to any execution plan. The cardinality therefore has to be
high for a concrete execution plan, for example, the highest one there.
How can one decide that the index in question isn’t selective? The
answer to this question can also be found in the execution plan. When
the SQL statement for a table access contains selective predicates,
and these predicates are only partly present in an index (which makes
this index nonselective), the remaining predicates have to be queried
elsewhere in the execution plan and the cardinality substantially
reduced. This normally occurs in the filter of the subsequent table
access by rowid.”
P.: “Stop, stop, stop! I can’t follow you.”
A.: “Basically, it’s very simple. I’ll demonstrate it using the following
example (Fig. 5.6).”
P.: “I can’t say that I understand it any better now.”
A.: “First let’s look for the steps with a high cardinality. Which are those,
Peter?”
P.: “Those are steps 16 and 14. There an index range scan of the index
IDX_PROCESS_PERSON has been executed and 10M and 12M rows
found, respectively.”
A.: “Is this index selective?”
P.: “In step 14, an average of approximately 24,000 rows (12M/500) are
selected per execution. That is no small value. We do not know, how-
ever, how large the table TA_PROCESS is. I don’t know if this index is
selective.”
A.: “But look what happens in the next execution plan step.”
P.: “The table TA_PROCESS is read by rowid. Oracle needed 7.53 seconds
for the index range scan and substantially longer 35.94 seconds for the
table access in step 13.”
A.: “It’s good that you noticed that. It often happens that a high cardinality
only really has an effect in one of the following execution plan steps.
36 5 Bottlenecks in the Execution Plan
Have you noticed that the cardinality in the 13th step falls from 12M to
41?”
P.: “Do you mean that that happens in this step due to the filter?”
A.: “Exactly.”
P.: “That indicates a strong selectivity of these filter predicates. I would
create an index for the relevant columns.”
A.: “That might solve the problem, but I have one objection to this solution.
In the section ‘The Guideline: Tuning Without Changing the SQL
Statement,’ we have already learned that a new index is more likely to
influence other execution plans than an extension of the existing index.”
P.: “So you are for an index extension?”
A.: “Yes, if that is possible, as it is in our case. The index
IDX_PROCESS_PERSON had two columns: PERSON_NUMBER
and CS_ID. It has been extended to include the column
PROCESS_CREATION because precisely this column was selective.
The extended index simultaneously improved step 16 (see the filter for
this step) and reduced the total runtime to a fraction of a second. Was this
analysis complicated for you?”
P.: “On the contrary.”
A.: “Then I suggest that you carry out the next analysis yourself. The next
example is interesting for two reasons. It shows that a ‘local’ problem in
the execution plan can have a very negative effect on the runtime. In
contrast to example 1, the second execution plan with runtime statistics
www.allitebooks.com
5.1 “Local” Problems in the Execution Plan 37
was taken from a SQL monitoring report. The runtime of the following
execution plan was 770 seconds (Fig. 5.7).”
P.: “The differences between the SQL monitoring report and the output of
dbms_xplan.display_cursor are not so great. ‘Execs’ means ‘starts’ and
‘rows (actual)’ is equivalent to ‘A-rows.’ Where did you get the
predicates from?”
A.: “These predicates were obtained additionally with dbms_xplan.
display_cursor because they are missing in the SQL monitoring report.”
P.: “The step with the highest cardinality is 15. 1G rows with an index range
scan of FXBAL_FPFON_FK were found in this step. I assume that this
step is relatively expensive. Step 14, in which the table FX_BALANCE
is read by rowid, is even more expensive, however (at least according to
the information in the column ‘Activity (%)’). The cardinality of this
step falls from 1G to 1241. Therefore, the filter in step 14 should be very
selective. I would extend the index FXBAL_FPFON_FK by the relevant
columns. However, I don’t know which columns from the filter belong to
table FX_BALANCE.”
A.: “You can see from the plan section ‘Query Block Name/Object Alias’
that the table FX_BALANCE has the alias B. Accordingly, one must
extend the index to include the columns FXBAL_ID and KTABA_ID.
As the index FXBAL_FPFON_FK only had a single column
FPFON_ID, it was possible to extend this index by two columns without
difficulty. This improved the runtime to 0.2 seconds. Your analysis is
therefore correct.”
38 5 Bottlenecks in the Execution Plan
Author: “Peter, we discussed in [1] what the clustering factor is in detail. Could
you tell us briefly what clustering factor means and why it’s important
for SQL tuning?”
Peter: “I can try. The clustering factor is an optimizer statistic which Oracle
calculates with an index full scan for the relevant index as follows: When
two successive index entries refer to rows which are located in different
table blocks, the value of the clustering factor is increased by 1. If they
belong to the same block, this value remains unchanged. The initial
value of the clustering factor in this calculation is equal to 1.”
A.: “How can one use the clustering factor in SQL tuning?”
P.: “This statistic shows how far apart index entries and corresponding table
entries are.”
A.: “I think I know what you mean. But your interpretation is not quite right.
For which operations can the clustering factor be important?”
P.: “In [1] we see that the clustering factor is equal to the number of buffer
gets in table accesses by rowid after a full index scan. With the clustering
factor, one can assess the effectiveness of table accesses by rowid after a
full or range index scan. The larger the clustering factor, the less
effective such table accesses are.”
A.: “Correct. What is a good value and what is a bad value for the clustering
factor?”
P.: “A good value is close to the number of filled table blocks. A poor value
is close to the number of index entries.”
A.: “Very good, Peter. Can one recognize an index with a large clustering
factor in the execution plan?”
P.: “To do that, I would compare the cardinality of the index scan with the
number of buffer gets of the subsequent table access by rowid. If these
values are close together, the clustering factor of the index is presumably
large.”
A.: “The extract from an execution plan demonstrates that (Fig. 5.8). We
will describe this example in full in the section ‘Joins with a Large Hit
Quantity.’
P.: “The number of buffer gets in step 18 is even larger than the cardinality
in step 19. How is that possible?”
A.: “You’ve forgotten that the runtime statistic ‘buffers’ is cumulative (see
section ‘Runtime Statistics’). This means that only 4252K 162K ¼
4090K buffer gets have occurred with table access by rowid. This
number is smaller than the cardinality of 4159K in step 19.”
P.: “Can one reduce the clustering factor of an index?”
5.1 “Local” Problems in the Execution Plan 39
Fig. 5.8 The index PI_PR_FK_I presumably has a large clustering factor
A.: “Yes, if one enters the data into the table assorted in the same way as in
the index. This solution has some disadvantages and is not always
practicable (see [1]). In some situations, one can extend the relevant
index to render table access by rowid unnecessary (‘index only’ access).
This eliminates the problematical step in the execution plan.”
Author: “A sparse index has considerably more leaf blocks than necessary. Its
leaf blocks are sparsely filled with data. Some blocks may even be
empty. Sparse indexes, their identification, and performance problems
which they cause are described in [1]. Peter, during which operations can
sparse indexes cause performance problems?”
Peter: “During index scans, because Oracle has to read more blocks than
necessary.”
A.: “How can one identify whether an index has too many leaf blocks?”
P.: “That can be done by means of optimizer statistics. With these statistics
one can calculate the number of leaf blocks necessary for the index
and compare this number with the relevant index statistic ‘leaf
blocks.’ The script sparse_norm_idx9i.sql, which one can download
from the Internet website www.tutool.de/book, functions according
to this principle. There one can also find the second script
estimate_sparse_norm_idx10g.sql, which needs no optimizer statistics
but obtains all necessary data for the calculation itself. This script is
more precise than the first one but needs considerably more time and
resources. Both scripts are described in [1].”
A.: “One has to be careful with the index statistic ‘leaf blocks’ because it
only takes into account the filled index blocks. Leaf blocks which are
totally empty are omitted from this statistic (see [1]).”
P.: “When an index has a lot of empty leaf blocks, the difference between
the number of all allocated index blocks from the view
DBA_SEGMENTS for this index and the index statistic ‘leaf blocks’
must be considerable. One can identify such empty leaf blocks in this
way.”
40 5 Bottlenecks in the Execution Plan
A.: “Correct, Peter. But let’s return to the execution plan. How would you
recognize a sparse index in an index scan there?”
P.: “When an index has too many leaf blocks, Oracle has to read a large
number of blocks during the index scan. Normally, in this case, the
runtime statistic ‘buffers’ is relatively large compared to the
cardinality.”
A.: “I have a little bit to add to what you have just said. The statistic
cardinality contains the number of selected rows. It is also possible
that many rows are accessed in an index scan but only a few are
selected.”
P.: “I don’t completely understand that.”
A.: “Please think back to the predicates in ‘access’ and in ‘filter’ from the
section ‘Predicate Information.’ The data is accessed with the predicates
in ‘access.’ If no filter predicates exist to the relevant index scan, then the
cardinality is almost equal to the number of accessed rows. In this case,
one can compare the cardinality and the statistic ‘buffers’ in order to
identify a sparse index, as in a practical example shown in Fig. 5.9. Can
you recognize a sparse index there?”
P.: “That’s very easy. In step 4, 326 rows were found by the index range
scan. Oracle did 1751 buffer gets for that. The index
PROVIS_TRANSACTION_1IX seems to me to be a sparse index.
Does one have to check every index scan in the execution plan? That
could be quite a lot.”
A.: “Not each one, of course. Only problematical execution plan steps are
interesting for us. Those are steps with a large runtime (column ‘A-
Time’ in an execution plan with runtime statistics) or with a large
activity (column ‘Activity (%)’ in a SQL monitoring report).”
P.: “Has the index PROVIS_TRANSACTION_1IX been rebuilt?”
A.: “Yes, you can see the result in Fig. 5.10.”
P.: “Can one rebuild every sparse index without problems?”
A.: “When there is competing access to an index, its rebuilding can cause
serious waiting time on ‘latch: cache buffers chains’ (see [1]). This is not
the only possible negative effect of a rebuilding of an index, but pre-
cisely this can have a severely adverse effect on performance. Peter, I
have already indicated that a large number of buffer gets and a relatively
low cardinality in an index range scan don’t always indicate a sparse
index.”
5.1 “Local” Problems in the Execution Plan 41
Fig. 5.10 The execution plan after the index rebuilding of PROVIS_TRANSACTION_1IX
After that we will execute the following SQL statement (Fig. 5.11).
As you can see, all leaf blocks have been read by the index range scan.
The number of selected rows is low thanks to the high selectivity of
column C. The rows have been filtered out by the filter ((C110 AND
C100)).”
P.: “It does actually look very similar to a sparse index. With the hint
NO_INDEX_SS, you have suppressed an index skip scan. Why?”
A.: “We are mainly concentrating on index range scan, because it occurs
more often than other index scans in practice. When the statistic ‘buffers’
is large in comparison to the cardinality of an index skip scan, this
normally also indicates that either the relevant index is a sparse index or
the index skip scan is simply not efficient, although the associated filter is
selective. Because each index skip scan has a filter, one has to check both
42 5 Bottlenecks in the Execution Plan
2 - access("A"=1)
filter((TO_NUMBER("C")>=100 AND TO_NUMBER("C")<=110))
Fig. 5.12 Selective filter with index range scan due to type conversion
Fig. 5.13 Selective filter with index range scan with a wide “between” interval for the leading
column in the index
Fig. 5.14 Index skip scan is more efficient than index range scan when there is a wide “between”
interval of the leading column in the index, if this column only has a few different values
P.: “As column A has only four different values, the interval between 1 and
3 is relatively wide, so Oracle has to read a lot of leaf blocks when
carrying out the index range scan. The data from these blocks are
filtered out with the filter predicate ‘B¼20000.’ The cardinality is
presumably so low because this filter is selective. But I don’t understand
how an index skip scan can help here. I would have created an index for
the column from the filter in such a situation.”
A.: “Imagine that the index I_T1 is quasi-partitioned for column A. Each of
these partitions has the same value for column A. In our example there
are four partitions. With index skip scan, Oracle executes an index range
scan in each of the partitions with ‘access (A¼<I> and B¼20000),’
whereby I ¼ 1, 2, 3. As the condition B¼20000 is selective, this access
is very efficient. For index skip scan, Oracle only has to carry out three
such index range scans. This explains why the index skip scan is very
efficient (Fig. 5.14). I find this a much more elegant solution than to
create a new index.”
5.1.5 Nested Loop Join Instead of Hash Join and Vice Versa
Author: “In this section we will discuss how one can recognize in the execution
plan that a hash join is more effective than an applied nested loop join
and vice versa.”
Peter: “Doesn’t that happen automatically in Oracle?”
A.: “You are right. Oracle uses the ‘adaptive plan’ feature for this. Oracle
already calculates both variants (hash join and nested loop join) during
parsing and only decides on one of these variants during runtime. It is
important to know that these join methods are not always an alternative
to each other.”
P.: “Why?”
A.: “In contrast to nested loop join, the hash join can only be used with equi-
joins.”
44 5 Bottlenecks in the Execution Plan
P.: “Now I understand: The hash join is based on a hash algorithm for which
join conditions with equalities are the only option.”
A.: “Correct. Before we analyze the nested loop join and the hash join in the
execution plan, we have to agree on the terminology. A nested loop join
consists of two cycles: Outer loop and inner loop. In the first cycle, the
rows are read from the outer table. For each row selected in the first
cycle, the second cycle is executed. The data in the second cycle is
selected from the second table (inner table) with the join condition. In
the case of the hash join, this functions as follows: Firstly, a hash table is
formed for the join columns from the first table (build table) in the
memory. Then all rows from the second table (probe table) are checked
against the hash table. Peter, could you please tell me under which
conditions a nested loop join makes sense.”
P.: “I think the cardinality of the outer loop should be relatively low.
Otherwise, too many inner loops will be executed.”
A.: “That’s correct, but not enough.”
P.: “I think I know which condition is still missing. The cardinality of the
inner loop must also be low. The lower it is, the more effective the nested
loop join is.”
A.: “Very good, Peter. When the cardinality of the outer or inner loop is
high, the relevant nested loop join is not efficient. If it is an equi-join, one
can try to use the hash join instead of the nested loop join. Let’s take the
following execution plan of a SQL statement from Oracle’s SYSMAN
schema as an example (Fig. 5.15).
The runtime of the relevant SQL statement with this execution plan
was 573 seconds. Can you please analyze this plan, Peter?”
P.: “The problematical steps in this plan are 18 and 19. In step 19, 584
million rows were selected. For each of these rows, the table access to
the table EM_METRIC_ITEMS was done by rowid in step 18. The
cardinality sank to eight million. This step is the most expensive in the
execution plan. We could try to reduce the cardinality in step 19.”
A.: “Peter, I fear that your analysis is heading in the wrong direction. Even
though you’re right, we can’t reduce the cardinality in step 19 because
SYSMAN is a schema from Oracle itself, and we are not allowed to
make any changes there, such as index extension. Please concentrate on
nested loops.”
P.: “OK. Steps 18 and 19 belong to the nested loop join in step 6. In this
nested loop join, 966 rows from the join in step 7 were linked to the table
EM_METRIC_ITEMS. According to the relevant predicates, that is an
equi-join. The cardinality of the inner loop in this join was eight million
(see step 18). As that is a large value, one could try to replace the nested
loop by a hash join.”
5.1 “Local” Problems in the Execution Plan 45
))
19 - access("I"."METRIC_KEY_ID"="K"."METRIC_KEY_ID")
…
A.: “The hash join can eliminate both problematical steps 18 and 19.
According to the optimizer statistics, table EM_METRIC_ITEMS had
approximately 7.5 million rows. As the table was not inordinately large,
access to this table with an FTS was tried. With these two optimizer
hints, the problematical nested loop join is replaced by a hash join:
In the execution plan, there is another similar nested loop join in step
33. Although this join was not as suboptimal as the first one, the decision
here was also to use a hash join. The following hints were used:
Fig. 5.16 The execution plan after replacing the nested loop joins by hash joins
cardinality of the inner join is also low. If these two conditions are
fulfilled, one can replace the respective hash join by a nested loop.”
A.: “Quite right. As this analysis is fairly simple, we don’t need to give an
example.”
48 5 Bottlenecks in the Execution Plan
Author: “One must be careful when changing any table order in a join as one can
easily create a Cartesian product.”
Peter: “This happens when two tables are not joined.”
A.: “Yes, there are no join predicates for two tables. Oracle then has to join
each row of the first table with each row of the second table. The number
of hits of the Cartesian product is N1 N2 rows, whereby N1 and N2 are
the cardinalities of the first and second table, respectively.”
P.: “Is a Cartesian product in an execution plan always a bad thing?”
A.: “Not always. The optimizer sometimes decides on a Cartesian product
even if an execution plan would be possible without a Cartesian
product.”
P.: “In such a case, the cardinalities of the two tables involved in the
Cartesian must be very low.”
A.: “Correct. In most cases, however, a Cartesian product causes poor
performance because its result is usually high or very high. For this
reason, one should avoid this situation wherever possible. First let’s look
at inner joins and use a simple example to show how to move a table in
the join sequence without giving rise to a Cartesian product. In the
following SQL statement, table T4 is joined to T2 as well as T0
(Fig. 5.17).
5.2 “Global” Problems in the Execution Plan 49
P.: “I don’t think table sequences T0, T4, T1, T2, T3, T5 and T4, T0, T1, T2,
T3, T5 should cause any Cartesian product either.”
A.: “That’s true. You can check that yourself. Are you now able to formulate
a rule for the movement of a table in a join?”
P.: “I’ll try. One can move a table in a join without causing a Cartesian
product if this table is joined to a preceding table in the new position. One
can move a table to the first position if it is joined to the second table.”
A.: “Such a movement alone does not guarantee any improvement in per-
formance. In the next sections, we will learn how to move a table in a
join to achieve an improvement in performance.”
P.: “I have a question. In the case of a table chain in which each table is only
joined to the next one, there are not so many variants for table
movements. We can only reverse the table order. Is that right?”
A.: “Let’s look at that together. Let’s take the following table chain:
T0 ) T1 ) T2 ) T3 ) T4. Here the symbol ‘)’ means a join.
According to our rule, we can move T1 to the first position: T1, T0,
T2, T3, T4. We can then either move T2 to the first position too or to the
second position, i.e., T2, T1, T0, T3, T4 or T1, T2, T0, T3, T4, etc. As
you see, there are quite a lot of variants for a table movement in a table
chain. I therefore see no reason to regard table chains as a special case.
Figure 5.19 shows such a change in table sequence in a table chain.”
P.: “Agreed. Have we finished with inner joins?”
A.: “Yes, we can move on to outer joins. Can you remind us what those are
please, Peter?”
P.: “The result of a left outer join (or a left join) of two tables T1 and T2
consists of the result of the inner join of these tables and the remaining
rows of table T1, for which the inner join produces no result (in this case,
the column values of table T2 contain null values). A right outer join
(or a right join) of two tables T1 and T2 is a left join of T2 and T1. A full
outer join of two tables T1 and T2 is a combination of a left and a right
join of these tables.”
A.: “So it’s enough if we only consider left and full outer join. A left join is a
rigid construction in which no change of table order is possible
(Fig. 5.20).”
P.: “What about the full outer join?”
A.: “One can change the table order there because that is a symmetrical
operation (see Fig. 5.21). This change doesn’t do anything to improve
performance, however.”
P.: “Then the change of table order is no option for outer joins. It is either
impossible or of no use.”
A.: “In principle you’re right. But if we have a combination of inner and
outer joins, there is quite a lot we can do. For example, we can move the
complete outer join in the join. This is demonstrated in Figs. 5.22 and
5.23.
It is also possible to insert a table between the tables of an outer join in
the table sequence. In the SQL statement in Fig. 5.24, table T4 is joined
Fig. 5.24 Placement of a table between the tables of an outer join (1)
Fig. 5.25 Placement of a table between the tables of an outer join (2)
to table T1 with an inner join. We can therefore insert this table between
tables T2 and T3 (which are joined together with an outer join) without
producing a Cartesian product (see Fig. 5.25). If table T4 has a high
cardinality, however, I would advise against this movement: After table
T4 has been joined with T1 and T2, the outer join with T3 is executed
and produces at least as many rows as the cardinality of T4 in step 6. In
this way, we also obtain a high cardinality in a further step of the
execution plan (in step 7 in Fig. 5.25). This is precisely what happens
in a practical example in the section ‘Joins with a Large Number of Hits.’
To conclude this section, I would like to mention that Oracle replaces
outer joins with inner joins if possible (see an example in Fig. 5.26).”
5.2 “Global” Problems in the Execution Plan 53
Fig. 5.26 Oracle replaces outer joins with inner joins if possible (1)
Fig. 5.27 Oracle replaces outer joins with inner joins if possible (2)
P.: “The condition ‘t2.b is not null’ changes a left join to an inner join.”
A.: “Quite right. There is another example shown in Fig. 5.27. The replace-
ment of outer joins by inner joins gives the optimizer more table
sequence variants to consider during parsing. This is also advantageous
for SQL tuning.”
Author: “First let’s consider joins with an inappropriate table order in the execu-
tion plan and with a low number of hits. The important thing is that this
low number of hits does not result from an aggregation or similar
operation.”
Peter: “And what if an aggregation reduces the number of hits of the join?”
A: “Then one has to analyze the number of hits immediately before this
aggregation.”
P.: “Which operation is to reduce the cardinality then?”
A.: “The cardinality is to be reduced by a join.”
P.: “Can a join with a small number of hits be accelerated?”
A.: “One can at least try. The chances of obtaining a considerable accelera-
tion are fairly good in this case.”
P.: “What do you have to do to achieve that?”
54 5 Bottlenecks in the Execution Plan
4 - filter("T1"."B"=10)
5 - access("T1"."A"="T2"."A")
6 - filter("T2"."B"=40)
Fig. 5.29 Change of table order in the join reduces the cardinality of the execution plan steps
A.: “Change the table order in the join in such a way that the cardinality is as
low as possible in each execution plan step.”
P.: “That sounds a bit too general, though. According to what criterion
should one change the table order?”
A.: “I would like to propose a simple heuristic method for this. Let’s start
with a nested loop join of two tables. Here we take a nested loop join in
particular because fast joins mainly use this kind of join. What do you
notice in the execution plan in Fig. 5.28?”
P.: “The cardinality in step 4 is 8001. In step 5, it falls to 1.”
A.: “Can one achieve a low cardinality in each execution plan step (at least
considerably lower than 8001) by changing the table order in this join?”
P.: “I don’t know.”
A.: “That’s the right answer. The execution plan doesn’t provide us with
enough information to be able to say that for sure. We have to check.
How would you do that, Peter?”
P.: “If we change the table sequence, table T2 will be queried first. I would
check how high the cardinality of filter ‘T2.B¼40’ is. If it is low, one can
change the table order.”
A.: “As a rule, I prefer checking directly with the hint LEADING.”
P.: “Why?”
A.: “A direct check is easier and, for that reason, less error-prone for the
tuner. It also provides more information which one can use during SQL
tuning. So let’s check directly with the hint LEADING(T2 T1) to see if
the changed table order reduces the cardinality (Fig. 5.29).”
P.: “The cardinality is considerably lower.”
5.2 “Global” Problems in the Execution Plan 55
A.: “Changing the table sequence in the join has been beneficial here. If the
cardinality of the filter ‘T2.B¼40’ had been high, the low cardinality of
the join could have been explained by a very high selectivity of the join
predicate ‘T1.A¼T2.A.’ In this case, changing the table sequence would
not have achieved anything.”
P.: “OK, nested loop join of two tables is clear now.”
A.: “Now let’s consider an inner join of several tables. Let’s look for the
table access in the execution plan with the highest cardinality. We can
call this one table A. If the cardinality of the join in which table A is
involved is also high and only falls in the next step or over a number of
steps, one can assume that the table order in this join is suboptimal. The
first table in the subsequent course of the execution plan in which the
cardinality falls when this table is joined can then be called table B. If
one inserts table B before table A in the join order, one can often reduce
the cardinalities of the relevant execution plan steps.”
P.: “You’re assuming that the cardinality of the joins will fall at some point.
Why?”
A.: “It must fall at some point because the number of hits for the whole join
is low.”
P.: “I don’t understand why the change in the table order described above
should reduce the cardinalities of the execution plan steps.”
A.: “Inner join’s hits remain unaffected by any change to the table order.
Let’s look at a part of the join from the beginning until the join to table
B. If we insert table B before table A, we don’t change the cardinality of
this join because we are remaining within the same join. If the cardinal-
ity of table B (and the cardinality of the subsequent tables in the join) is
low after this rearrangement, we have achieved the desired result. This is
very similar to the case of nested loop join of two tables, which we have
already discussed.”
P.: “In an inner join of two tables, one can change the table order without
causing a Cartesian product. In a join of several tables, this is not always
the case.”
A.: “Yes, we must therefore check whether this rearrangement is possible
without producing a Cartesian product.”
P.: “Could it be that we do not only have to move a table B, but several
tables, in order to avoid a Cartesian product?”
A.: “That’s possible. In practice, however, table B often follows immedi-
ately after table A in the join, so that one only has to move table B.”
P.: “I hope I understand how to handle an inner join now. What about outer
joins?”
A.: “When outer joins occur in the join of several tables, we regard each
outer join as an entity and take this into consideration when changing the
table order as described in the section ‘Formal Rules for Changing the
Table Order in a Join.’ I have prepared three typical examples which
clearly demonstrate the heuristic method. Let’s start with the example
56 5 Bottlenecks in the Execution Plan
we have already used in the section ‘Nested Loop Join Instead of Hash
Join and Vice Versa’ (see Fig. 5.15). Below is an extract from the
execution plan, with a list of the corresponding predicates:”
SQL Plan Monitoring Details (Plan Hash Value=2535171835)
Id Operation Name Execs Rows Activity
(Actual) (%)
0 SELECT STATEMENT 1 25
1 VIEW GC_METRIC_LATEST 1 25
2 UNION-ALL 1 25
3 FILTER 1 23
4 NESTED LOOPS 1 23
5 NESTED LOOPS 1 23
6 NESTED LOOPS 1 8M
7 NESTED LOOPS 1 966
8 HASH JOIN 1 966
9 HASH JOIN 1 966
10 TABLE ACCESS FULL EM_METRIC_COLUMNS 1 1625
11 NESTED LOOPS 1 3136
12 TABLE ACCESS BY EM_METRIC_GROUPS 1 12
INDEX ROWID
13 INDEX RANGE SCAN EM_METRIC_GROUPS_PK 1 12
14 INDEX RANGE SCAN EM_METRIC_COLUMN_VER_PK 12 3136
15 TABLE ACCESS FULL EM_METRIC_GROUP_VER 1 61661
16 TABLE ACCESS BY INDEX EM_METRIC_KEYS 966 966
ROWID
17 INDEX RANGE SCAN EM_METRIC_KEYS_PK 966 1932
18 TABLE ACCESS BY INDEX EM_METRIC_ITEMS 966 8M 41.36
ROWID
19 INDEX RANGE SCAN EM_METRIC_ITEMS_KEY_IDX 966 584M 23.73
20 INLIST ITERATOR 8M 23 0.52
21 TABLE ACCESS BY INDEX EM_MANAGEABLE_ENTITIES 38M 23 1.40
ROWID
22 INDEX RANGE SCAN EM_MANAGEABLE_ENTITIES_PK 38M 3M 24.78
…
Fig. 5.30 Example 1. The first improvement after changing the table order in the join
5.2 “Global” Problems in the Execution Plan 59
Fig. 5.31 Example 1. Improvement in execution plan after replacement of hash joins by nested
loop joins
5.2 “Global” Problems in the Execution Plan 61
Fig. 5.32 The second example of an inappropriate table order in a join with a small number
of hits
Fig. 5.35 The third example of an inappropriate table order in a join with a low number of hits
Fig. 5.36 Example 3. Improvement after moving the two tables in the join
P.: “The high cardinality of 780M occurs in step 8 with an index access. In the
next step, 7, it falls to 1M with the table access by rowid for the table
BCA_CN_LINK (alias T_02). The nested loop join in step 5 has the same
cardinality. This table is table A in this plan. In step 13, the cardinality falls
to 15504 with the join to table BCA_CONTRACT (alias T_00). That is
table B. However, I cannot place table BCA_CONTRACT before table
BCA_CN_LINK in the table order, without causing a Cartesian product,
because they don’t have any join conditions.”
A.: “That’s no problem. The tables in this join are joined together as follows:
T_00 ) T_01 ) T_02 ) T_03. This is a table chain. The table order in
the join is T_02, T_01, T_00, T_03. Previously, we saw that it may not
only be necessary to move just one table B, but several tables. We have
precisely such a case here. Which table do we also have to move?”
P.: “Table T_01, which is joined to T_02.”
A.: “Exactly. With the hint LEADING(T_00 T_01 T_02 T_03), I defined the
desired table order and executed the SQL statement again (Fig. 5.36).”
P.: “Why did you mark three lines in red in the execution plan in Fig. 5.36?”
A.: “I wanted to point out that this execution plan has further potential for
improvement. In step 7, one could try using an index range scan instead of
an FTS. Steps 12 and 13 show that the selectivity of the index
BCA_CN_LINK~S01 can be improved if one extends this index with
the columns from the predicates to the table access by rowid. I didn’t do
any further tuning because the runtime of approximately 5 minutes was
perfectly acceptable.”
Author: “In this section we will discuss joins with a large hit quantity. The
relevant SQL statement has to process a large amount of data, which
requires a certain amount of work. One can, therefore, not expect that
5.2 “Global” Problems in the Execution Plan 65
such a SQL statement will run as fast as lightning after tuning. Let’s
begin again with an inappropriate table order. How would you recognize
this in the execution plan of a join with a large hit quantity, Peter?”
Peter: “Some joins with especially high cardinalities can indicate this.”
A.: “You have joins with high as well as relatively low cardinality in the
execution plan?”
P.: “Exactly.”
A.: “In the section ‘Joins with a Low Number of Hits,’ we’ve already seen
what an adverse effect a high cardinality of some joins can have on the
performance of a join of several tables, and this was mainly using nested
loop joins. Could you tell me how severely this can affect the hash
joins?”
P.: “As the build table of a hash join is built in the memory, if possible it has
to fit into the memory. If it is larger, it complicates processing severely
and increases the runtime of the hash join. In a hash join of several tables,
the hits of a hash join are, at the same time, the build table of the next hash
join. For this reason, I would smooth out the existing cardinality peaks.”
A.: “How would you do that?”
P.: “Can’t one use the same heuristic method that we discussed in the
section ‘Joins with a Low Number of Hits’?”
A.: “Yes, you can. We have already shown enough examples of how to work
with this method. Assuming we have used this method and have no more
serious peaks, can we now be sure that the table order in the join is more
or less correct?”
P.: “Difficult to say.”
A.: “I don’t have any general suggestions in connection with formal SQL
tuning either. In any case, it’s worth examining the execution plan again
in detail. Our first example shows how one can recognize an inappropri-
ate table order in a join without cardinality peaks.”
P.: “When we have eliminated all ‘brakes’ in the execution plan, what else can
we do to improve performance if it is necessary to improve it further?”
A.: “Oracle offers a range of methods for this purpose: Materialized views,
star schemas, etc. If we don’t want to intervene so deeply in the existing
database processes and SQL statements, we can very successfully use
parallelization (parallel query) for the problematical SQL statements.
One can leave the decision regarding parallelization completely to
Oracle (using the feature ‘Automatic Degree of Parallelism’), or one
can define the degree of parallelism for the entire SQL statement oneself.
In this case, Oracle decides which objects are to be parallelized in which
execution plan steps. If one sees from the runtime statistics in the
execution plan that some steps are particularly expensive, then it
makes sense to parallelize precisely these steps manually with the
relevant hints. One can use parallelization both after and before formal
SQL tuning (if the relevant performance problem is so acute that one
needs a solution immediately and the SQL tuning can be performed
66 5 Bottlenecks in the Execution Plan
later). One has to be careful with parallelization because this feature can
take up a lot of resources. However, as we do not wish to focus on
parallelization in this book, we won’t investigate this interesting topic
any further.”
P.: “Can you please show me how one can use parallelization in connection
with the runtime statistics in the execution plan.”
A.: “I’ll show you with the second example in this section. We’ll start with
the first example. An execution plan of a join with a large hit quantity is
shown in Fig. 5.37. One can see that the cardinality in step 19 rises to
4159K and increases the cardinality of the nested loop join in step
12 accordingly. This cardinality remains unchanged in outer joins in
steps 10 and 11.”
Peter: “Is that an example from the section ‘An Index with a Large Clustering
Factor’?”
A.: “What a mammoth memory you’ve got! Yes, I did actually use a part
from this execution plan.”
P.: “Doesn’t this example come from the section ‘Joins with a Low Hit
Quantity’? The number of hits of the join is small, only 28 rows.”
A.: “Your question proves that it wasn’t a compliment after all when I said you
had a mammoth memory. Mammoths didn’t become extinct without
reason, did they? Do you remember that cardinality mustn’t fall as a result
of an aggregation? But in step 9 it does fall after an aggregation, so we have
to consider the cardinality before this operation. That is 4159K. In this
execution plan, we don’t see any cardinality peaks with individual joins.”
P.: “The cardinality of 4159K initially occurs with access to the table
PICKAUF. This table isn’t the last in the join. It is followed by the
tables QUANTEN and PRUEFGRUENDE. These two tables do not
have any join predicates for the table PICKAUF, however.”
A.: “That’s true. They are joined to the table PICKRUND (alias R) with an
outer join. As they come after the table PICKAUF in the join, the
cardinality of the relevant joins must be at least as high as the cardinality
of the table PICKAUF, i.e., 4159K.”
P.: “We can, however, place the tables QUANTEN and PRUEFGRUENDE
before the table PICKAUF in the join.”
A.: “Correct. This shows us that the table order in this join is not quite
optimal after all. One could have changed this table order as you have
just suggested. This change in the table order would have improved the
runtime by approximately 45 seconds at best (30.05 + 15.40 ~ 45). I
noticed that the table access by rowid for the table PICKAUF took
approx. 8 minutes. This table access was so expensive because the
index PI_PR_FK_I had a large clustering factor. Initially, I wanted to
try to do without table access by rowid. To achieve this, I had to extend
the index PI_PR_FK_I by a few columns. Peter, can you remember how
to find these columns?”
5.2 “Global” Problems in the Execution Plan 67
Fig. 5.37 A join with a large hit quantity and an index with a large clustering factor
68 5 Bottlenecks in the Execution Plan
Fig. 5.38 “Index-Only” access optimizes the table order in the join
P.: “I would look for these columns in predicates and in projections belong-
ing to step 18. As there are no predicates to this step, we only have
projections. There we can find two columns, STAT and MNG_SOLL.”
A.: “As the index PI_PR_FK_I only had two columns, it was possible to add
two columns without the index becoming too wide. For test purposes, a
new index, PI_PR_FK_LR_I, was created with these four columns parallel
to index PI_PR_FK_I and the SQL statement executed again (Fig. 5.38).”
P.: “The runtime is now 15.32 seconds. That really is an improvement! I notice,
however, that the table order has been changed by the optimizer: The table
PICKAUF is now in the last position, where it belongs in my opinion. Do
you have an explanation for this optimization of the table order?”
A.: “Clustering factor has a strong weighting as far as optimizer costs are
concerned. The appropriate formulae can be found in [2]. This seriously
affects the table order. We have eliminated the clustering factor in the
optimizer costs for an index because we have dispensed with table access
by rowid (precisely in this operation, the optimizer takes the clustering
factor into account as far as costs are concerned). Consequently, the
optimizer has changed the table order.”
P.: “Was it really worthwhile analyzing whether the table order in the join
was optimal? The change in this order only reduced the runtime by
45 seconds.”
A.: “Yes, that really isn’t a very large proportion of the runtime, which was
originally over 9 minutes. However, it could have been different if the
table order had remained unchanged after the index extension. Now let’s
5.2 “Global” Problems in the Execution Plan 69
Fig. 5.39 Parallelization based on the runtime statistics in the execution plan
70 5 Bottlenecks in the Execution Plan
discuss the second example in Fig. 5.39. The runtime of the relevant
SQL statement was 3837 seconds and had to be improved urgently. What
can you recognize in this execution plan, Peter?”
P.: “Step 50 is problematical. 351M rows are found there. This causes an
expensive hash join with a cardinality of 3G in step 31. Initially, this
cardinality falls to 350M in step 29 and then to 559K with the next join in
step 14. I think the table order in this join is suboptimal.”
A.: “Correct. It was possible to tune this plan. However, as a quick solution
was required and there wasn’t a heavy load on the system, I decided to
parallelize the problematical step 50. For this purpose, I used the hint
PARALLEL(@SEL$C8360722 FACT@SEL$4 8), which reduced the
runtime to 315 seconds.”
5.3 Summary
In this chapter we will discuss what is, in our view, a sensible formal SQL tuning
process. Some steps of this process can facilitate or even eliminate the need for
tuning. We also feel it is important to clarify which problems have a higher priority
during tuning if several problems occur in an execution plan at the same time.
0 INSERT STATEMENT 1
1 LOAD TABLE CONVENTIONAL 1
2 HASH UNIQUE 1514 1 0
3 HASH JOIN 1528 1 42296
4 TABLE ACCESS FULL CO_PRM 1 1 4727
5 HASH JOIN 1528 1 79305
6 INDEX FULL SCAN TE_PRM_PRM_PRDV_RULE_PK 1 1 4727
7 HASH JOIN 1614 1 79305
8 INDEX RANGE SCAN TE_001_PROM_INTERVAL_PK001 1 1 25225
9 HASH JOIN 1614 1 12M
10 TABLE ACCESS FULL TE_ITEM_CO_MRHRC_GP 15 1 145K
11 HASH JOIN 456 1 7M
12 TABLE ACCESS FULL CO_EL_MRST_PRDV 1 1 4448
13 HASH JOIN 456 1 18M
14 TABLE ACCESS FULL TE_PROMOTION_PRDV_RULE 1 1 4718
15 HASH JOIN 456 1 18M
16 TABLE ACCESS RU_PRDV 1 1 4244
FULL
17 MERGE JOIN 456 1 19M
CARTESIAN
18 TABLE ACCESS CO_EL_PRDV 456 1 5287
FULL
19 BUFFER SORT 456 5287 19M
20 TABLE ACCESS RU_PRDV_ITM 1 1 3671
FULL
P.: “The bottlenecks are very easy to find. The execution plan step with the
longest runtime is also a bottleneck, isn’t it?”
A.: “Not always. For example, if a step with an FTS instead of an index access
has the longest runtime, then this FTS is a bottleneck in the execution plan.
Often a problem in a long runtime only becomes visible in the subsequent
execution plan steps. We have already discussed this in the section “A
Non-selective Index.”
P.: “How should one proceed, then? Look for the highest cardinality in the
execution plan?”
A.: “One can look for the step with the longest runtime, as you suggested earlier.
But then you must try to find out what kind of problem it is. If the problem
belongs to one of the following categories:
Then the step with the longest runtime is a bottleneck in the execution
plan. A long runtime is a fairly precise indication of a problematical join from
the category ‘nested loop instead of hash join and vice-versa.’ If a problem
occurs regarding a high cardinality, this problem is not normally caused in the
step with the longest runtime, but in one of the preceding steps where a large
volume of data has to be processed (i.e., in a step with a high cardinality).
These are problems from the following categories:
One can also proceed differently: First of all, find the steps with a high
cardinality and then identify and solve the associated problems. According to
our statistics on the problem cases in the section ‘Statistics on Problem
Categories,’ one can solve most problems in this way. After that, in any
case, one must check the steps with long runtimes. This is the approach I
prefer. ‘Cardinality’ is a magic word in SQL tuning (Fig. 6.2). It plays a
decisive role when analyzing problems from the second group of categories.
In conjunction with the other runtime statistics, it is also extremely important
for the first group.”
P.: “What should I do if I have several problems in an execution plan?”
A.: “If these problems occur at different points in the execution plan, you can
solve them one after another. It is far more interesting when these problems
occur at practically the same point. Then one must decide how to start.”
P.: “I would start with the problem which poses the greatest threat to
performance.”
6 Procedure of Formal SQL Tuning 77
A.: “So you’re acting on the basis of runtime again: The problem which causes
the longest runtime is solved first. In many cases, this is the best way. In
Fig. 5.37, for example, we had two problems: an inappropriate table order in
the join and an index scan with an index with a large clustering factor and
subsequent table access by rowid. The first caused a runtime of approx.
45 seconds and the second approx. 8 minutes. For this reason, I solved the
second problem first, and this improved the table order at the same time.
However, there are cases in which the decision is not so easy to make because
it depends on a number of factors.”
P.: “Have we already had an example of this?”
A.: “Of course. That’s the example in Fig. 5.15. There you can see several
problems, for example, an inefficient nested loop join. It was, however,
difficult to say in advance what this nested loop join would cost exactly.
Only after using hash join instead of nested loop join did it become clear that
the nested loop join was fairly expensive (the runtime was reduced from
573 to approx. 30–50 seconds). In the second problem which is recognizable,
there is a nonselective index in step 19. We have already discussed this
problem in the section ‘Nested Loop Join Instead of Hash Join and Vice-
Versa.’ Could you analyze this problem again please, Peter?”
P.: “The cardinality in the case of index scan with the index
EM_METRIC_ITEMS_KEY_IDX is 584M in step 19. It falls to 8M in the
next step, step 18, with table access by rowid. This points to a selective filter
in step 18. If one extends the index EM_METRIC_ITEMS_KEY_IDX by the
78 6 Procedure of Formal SQL Tuning
The “formal method” developed from an idea of how one can successfully help
someone tune a very complex execution plan in a blog using pure text ping-pong.
“Formal method”—what does that actually mean? That’s what I asked myself
when I first heard of it. “Formal”—referring to form not content! If we draw
parallels between the meaning of the word and the approach, then I would describe
it as follows: The form defines the rules. Regardless of the data model or data
content, we apply these rules and achieve our goal in a very efficient manner.
In order to be able to carry out tuning effectively, one must understand execution
plans—to some extent at least. Chapter 3 is relatively detailed, as it should be.
As someone with personal practical experience, I can say that the use of runtime
statistics opens up totally new possibilities. They show you what is really happen-
ing. One can also compare them with optimizer estimations. In the past, to find the
causes of problems, we often carried out analyses by counting value distributions or
uniqueness and comparing them with optimizer statistics. To some extent, we had
to put ourselves in the position of a developer, i.e., to acquire knowledge of data
models. This takes quite a long time, however, and this book is specifically
designed to provide assistance here. Runtime statistics are what I always missed,
and, in retrospect, I’m not surprised that analysis was often so difficult and based on
“trial and error.”
I’ll just give you a brief outline of my personal approach, Peter.
Hanno: “Let’s assume that we have found a problematical cursor. This is
described in detail in [1]. Now the problem has to be made reproducible,
without changing the productive data, of course.”
Peter: “You find the SQL text and, if present, the bind variables. Then you
execute the SQL statement in order to obtain the important runtime
statistics.”
H.: “That’s right. I prepare all the basic conditions for execution. The SQL
statement is then executed and the plan with the function
DBMS_STATS.DISPLAY_CURSOR, including further sections, is
displayed.”
P.: “Then we have everything we need for the analysis?!”
H.: “Normally, yes. But there are a few exceptions. For example, with ‘bind
peeking,’ if the binds are not representative because they were originally
used for parsing the cursor, and Oracle is using the cursor again.”
P.: “That means it is the same plan but with completely different
cardinalities in each step?”
H.: “Exactly. It’s quite possible that that can happen. With the data I have
obtained, I then begin the analysis as discussed in the chapters
‘Bottlenecks in the Execution Plan’ and ‘Approaches to Formal SQL
Tuning’. I identify the bottleneck and think about a solution. As licenses
for EM packs are seldom available in my working environment, I am
often unable to access the AWR or use SQL monitoring. Nor am I able to
create any SQL profiles. If possible, therefore, I try to make structural
changes to schema objects. For example, extending an index can solve
some of our problem categories.”
During the preparation phase for this book, for statistical purposes, the authors
recorded real cases involving acute performance problems. This data from practical
applications resulted in several problem categories and their percentage
distributions (Fig. 7.1).
other problems 7%
These statistics were collected from both OLTP and DWH systems. They
confirm that formal SQL tuning covers most practical problems. Only 7 % belong
to the “other problems” category, and even these were successfully analyzed and
eliminated using the same formal principle.
There are usually enough indexes in the schemata of the applications. There is,
therefore, only a small percentage of “missing indexes” in the statistics.
There are often a lot of single columns indexed, and, therefore, most cases
belong to the problem category “nonselective index.” This means that two columns
are singly indexed, are not very selective, but are often queried together in SQL. A
combined index of these columns can increase selectivity and solve the problem. It
is therefore essential for a DBA to be able to analyze and remedy these problems.
Below is a simple test case, which anyone can try out for themselves.
The second biggest problem category is “an inappropriate table order in a join.” I
would just briefly like to mention my own experience here. As this is a global
problem, the solution is not quite so simple. One reason for the incorrect order is the
fact that the optimizer wrongly estimates the cardinality of a table join (or of a table
access). In my experience, one also often finds a nonselective index in such a case.
If one improves (extends) the index, the order of the join also often changes for the
better.
Sparse objects only occur when the application is in production. The objects are
fragmented due to inserts, updates, and deletes, possibly resulting in large gaps
which still have to be read. Indexes are very susceptible to this, for example, in the
case of columns with sequential numbers, status change, or time fields.
The two largest categories in our table account for 67 % of all cases. A DBA can
be fairly successful with SQL tuning if he can cope with these problems.
This case occurs very often in practice and is very easy to recognize. The solution is
easy to implement following the rules of formal SQL tuning and without changing
the SQL statement.
Peter: “There’s nothing to say here. The table without an index is read with an
FTS.”
H.: “Then let’s create an index. What do you say now?”
P.: “The predicates show ‘access’ by the index. After that, the 1000 rows in the
table are filtered. In the process, 1000 table blocks are accessed (103131).
Here, however, one can also see that the clustering factor is very high. 1000
rows—1000 buffers. . ..”
H.: “Yes, that’s true. A poor clustering factor + a poor optimizer estimation of
cardinality or a low selectivity of the index ¼ a big performance problem.”
P.: “Now the FILTER on ‘b’ has disappeared. All predicates are used in access
with the index. That means that the table is now only accessed due to the
projection of column ‘c.’ As you said, the index extension resulted in a
reduction of the cardinality (nonselective index) in step 2 and in a reduction
of the buffers in step 1.”
H.: “Now let’s add column ‘c’ to the index.”
P.: “That’s great! Now the 390 blocks (42030) are no longer necessary for
table access either, and only 30 remain. The improvements are quite consid-
erable. Is that only possible in the example or is it the same in practice?”
H.: “In my personal experience, it often works in practice. I use it very often, at
least up to the first index extension in the test (tab1_ii). In order to squeeze the
last drop out of it and use an index-only access, the conditions must be right.”
P.: “And what are they. . .?”
H.: “(1) Only a few or very narrow columns, so that the index doesn’t grow very
much. (2) If the index increases into the double-digit percent range due to the
change, there have to be good reasons, for example, if the SQL statements
which undergo the improvements shown above are executed thousands of
times per minute.”
This example shows the evaluation by a technical administrator with two potential
improvements and one special feature that he hadn’t expected. As the data is mostly
not in the cache at the moment of execution, I also simulate that accordingly with
the command “alter system flush buffer_cache” before each execution.
The SQL statement:
84 7 Practical Experience with Formal SQL Tuning
select
tp.produktionsplan_typname,
tp.tai_typname,
to_char(t.letzteausfuehrung,'YYYY-MM-DD HH24') std ,
count(*)
from
tai t, tai_produktionsplan tp
where
t.letzteausfuehrung > to_timestamp('2014.10.27 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
and t.letzteausfuehrung <= to_timestamp('2014.11.02 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
and t.kennung = tp.tai_kennung
group by tp.produktionsplan_typname,tp.tai_typname,to_char(t.letzteausfuehrung,'YYYY-MM-DD HH24')
order by tp.produktionsplan_typname,tp.tai_typname,to_char(t.letzteausfuehrung,'YYYY-MM-DD
HH24');
COLUMN_NAME COLUMN_POSITION
--------------- ---------------
SYS_NC00037$ 1
KENNUNG 2
Now we come to a special feature. Let’s look at the details for the plan above. As
expected, we have an asterisk in step 5. We don’t have an asterisk in step 4. Conse-
quently, step 4 can only have a projection (no predicates).
Predicate Information:
2 - filter(SYS_EXTRACT_UTC(TIMESTAMP' 2014-11-02
23:59:59.000000000')>SYS_EXTRACT_UTC(TIMESTAMP' 2014-10-27 00:00:00.000000000'))
3 - access("T"."KENNUNG"="TP"."TAI_KENNUNG")
5 - access("T"."SYS_NC00037$">SYS_EXTRACT_UTC(TIMESTAMP' 2014-10-27 00:00:00.000000000') AND
"T"."SYS_NC00037$"<=SYS_EXTRACT_UTC(TIMESTAMP' 2014-11-02 23:59:59.000000000'))
Fig. 7.3 Test case: index on a column of the type “TIMESTAMP WITH TIME ZONE”
86 7 Practical Experience with Formal SQL Tuning
This index would have become too large, and, as it was not a case of a high-
frequency SQL query but of a kind of ad hoc report, I decided on parallelizing the
FTS by means of the hint PARALLEL(TP 8) (Fig. 7.5).
My colleague was amazed to see the result of approximately 1-minute runtime
instead of the 1 hour he had expected.
I had been working with Oracle database operations at a large company for a long
time when Leonid came to give a talk on formal SQL tuning at the beginning of
2013. As my special area was precisely the field of optimization and
troubleshooting, I was all ears. I knew full well how much effort was involved in
optimization by the “trial and error” method. I often spent many hours unsuccess-
fully trying to optimize complex queries because my assumptions were incorrect.
Even in relatively simple execution plans, I was not always sure if an index access
was really a good idea at that point. I was only able to improve execution after a
detailed, time-consuming data distribution analysis. The method Leonid presented
promised to save a lot of time and I was quite excited about it. I have developed
some auxiliary programs for use with this method, and I always employ them for
SQL tuning.
Whenever an acute database incident has to be dealt with, or a repeated or
constant performance issue occurs, formal SQL evaluation is employed sooner or
later. It’s amazing how much time this saves! Sometimes optimum plans are found
for problematical queries, and sometimes index structures can be improved. If no
improvement is possible without changing the relevant SQL statement, concrete
optimization recommendations are prepared for development. Sometimes it is
evident that the problem cannot be solved either by SQL optimization or by
redesigning the application (data model design error). Thanks to the formal method,
the analysis can be done very quickly.
Inefficient plans occur suddenly and have various causes, such as massive data
changes, increase in data volume, introduction of new software, outdated or missing
optimizer statistics, etc. In case of strong fluctuations in data volume, the optimizer
generates a large number of different plans for the same SQL statement. That’s why
I first of all check whether it is possible to find another, better execution plan for the
problematical SQL statement in the AWR. If so, the better plan can be activated.
The optimization process is often completed after this step.
If it is not possible to find a suitable plan in the AWR, I prepare test versions of
the SQL statement for the purpose of SQL tuning. With my auxiliary programs, I
find the relevant SQL text in the database; I draw up a summary of the segments and
index structures involved. (This can be useful for the analysis.) If necessary, I
correct the SQL text so that my tests do not create or delete any database objects or
change or lock their data. For this purpose, I extract the relevant selects from the
DDL and DML commands and remove any existing “FOR UPDATE” clauses from
the commands “select for update.” It is worth formatting the SQL text. (There are a
lot of tools available for this free of charge.)
I then check the parameter settings which are relevant for the optimizer (primar-
ily those not documented). An inappropriately set parameter of this sort can hinder
the optimizer in the search for an optimum plan. When testing, I reset some of these
parameters to their default values and check the execution plan. The optimization
process is often successfully completed after this step.
www.allitebooks.com
88 7 Practical Experience with Formal SQL Tuning
In practice, I use the SQL Tuning Advisor (SQLT advisor) from Oracle. If the
SQLT advisor finds an alternative plan, then it is a good reason to test this plan.
However, I would not advise activating an automatically generated plan without
testing it. Even if the SQLT advisor promises a substantial improvement, this does
not always materialize. If the SQLT advisor does a good job and saves me a lot of
work, I am very pleased.
If manual SQL tuning is necessary, I follow the formal method as described in
this book. I find this method very effective and hope that it will also help our
readers. In order to change the order of accesses and access methods, I almost
exclusively use hints from outlines as a template. Compared to “classical” hints,
they take some getting used to at first. They refer directly to query blocks and can
therefore be placed in any query block (e.g., at the beginning of the SQL statement).
It is no longer necessary to distribute hints throughout the SQL text. This makes
hints easier to use. In the case of views in SQL statements, this is the only
possibility to address the relevant query blocks without changing these views.
I use mostly hidden hints for SQL tuning because it is not usually possible to
change the SQL statement. As a rule, I employ the OSP method for this (Outlines in
SQL Profiles) as described in [1].
Once the new plan has been activated, I monitor its effect over a lengthy period.
If this plan does not prove to be as effective as expected, I have a “drop profile”
command ready. This happens from time to time when certain nonrelevant bind
values have been used in tests, for example, when they refer to yesterday’s data
(instead of up-to-date data). One has to be careful with bind values when tuning.
A small example of formal SQL tuning. A SQL statement was running on a number
of instances and was using the lion’s share of CPU resources, mostly without
returning a single row. Below is the formatted text of this SQL statement.
SELECT business_activity_id, type_rd, status_rd, NAME, priority_rd, root_business_activity_id
FROM business_activity t
WHERE workflow_template_id = '.bF6exCIlPAi079H'
AND NAME = 'Deaktivieren'
AND status_rd = 'InProg'
AND external_system_indicator_rd = 'ERROR'
AND service_order_stp_id IN (
SELECT so.service_order_stp_id
FROM service_order so
, service_property sp
, property_value pv
WHERE so.service_order_stp_id = sp.service_order_stp_id
AND sp.service_property_id = pv.service_property_id
AND pv.value_string = 'ffmaems2')
ORDER BY business_activity_id
If we change the order with the hint LEADING (in this case, exceptionally, I
didn’t use any outlines), we get a completely different execution schema (Fig. 7.7).
3 - filter(("WORKFLOW_TEMPLATE_ID"='.bF6exCIlPAi079H' AND
"EXTERNAL_SYSTEM_INDICATOR_RD"='ERROR' AND "STATUS_RD"='InProg'))
4 - access("NAME"='Deaktivieren')
8 - access("PV"."VALUE_STRING"='ffmaems2')
9 - access("SP"."SERVICE_PROPERTY_ID"="PV"."SERVICE_PROPERTY_ID" AND
"SP"."SERVICE_ORDER_STP_ID"="SERVICE_ORDER_STP_ID")
3 - filter(("WORKFLOW_TEMPLATE_ID"='.bF6exCIlPAi079H' AND
"EXTERNAL_SYSTEM_INDICATOR_RD"='ERROR' AND "STATUS_RD"='InProg'))
4 - access("NAME"='Deaktivieren')
9 - access("SP"."SERVICE_ORDER_STP_ID"="SERVICE_ORDER_STP_ID")
10 - access("SP"."SERVICE_PROPERTY_ID"="PV"."SERVICE_PROPERTY_ID")
11 - filter("PV"."VALUE_STRING"='ffmaems2')
An interesting aspect of this example is the fact that the table SERVICE_ORDER
does not appear once in the plan. This enables the foreign key
SERVICE_PROPERTY. SERVICE_ORDER_STP_ID -> SERVICE_ORDER.
SERVICE_ORDER_STP_ID. At Oracle, this type of optimization is called join
elimination and is available from version 10.2 onward. One indication that Oracle
uses this optimization is the hint ELIMINATE_JOIN in the outlines.
The small test case which is based on our practical example reproduces this
behavior.
create table parent (
a number, b number,
constraint parent_pk primary key (a));
The query
select * from child where a in (select a from parent);
Outline Data
-------------
/*+
...
ELIMINATE_JOIN(@"SEL$5DA710D3" "PARENT"@"SEL$2")
...
*/
In this section I would like to present another interesting example. I would be very
grateful if Peter Smith would help me.
Peter: “Of course I’ll help you. I welcome any opportunity to practice SQL
tuning.”
Victor: “Fig. 7.8 shows an execution plan. This plan has caused a serious
performance problem in a system. How would you improve this plan,
Peter?”
7.2 Victor’s Experience 91
P.: “Both the highest cardinality of 8016K and the longest runtime of
1½ minutes occur in step 17. After the join with the view VW_NSO_1,
the cardinality falls to 55. I would change the table order in the join in
such a way that the table SENSORDATA follows the view VW_NSO_1.
But. . ..”
V.: “What?”
P.: “I’ve just noticed that that isn’t possible because this view and the table
SENSORDATA are joined with a right outer join.”
V.: “You have fallen into the same trap as me. Where do you see an outer
join?”
P.: “In step 1.”
V.: “But that isn’t a right outer join. It’s a hash join right semi! The word
‘semi’ tells us that it is a join with a subquery as an inline view. The word
‘right’ indicates that this inline view plays the role of the build table in the
hash join. As this is not an outer join, I have used the hint LEADING
(@"SEL$CC7EC59E" "VW_NSO_1"@"SEL$CC7EC59E"
"SENSORDATA"@"SEL$1") and obtained a runtime of approx.
5 seconds. As it was not possible to change the SQL statement. . ..”
P.: “You probably used the OSP method and created a SQL profile?”
92 7 Practical Experience with Formal SQL Tuning
Fig. 7.9 Example 2: The execution plan after generation of histograms for the column PARENT
V.: “Yes, and I was immediately asked whether there was another solution
without hidden hints.”
P.: “Why?”
V.: “SQL profiles (like SQL plan baselines) have to be maintained. If the
database is transferred to another computer, for example, one mustn’t
forget to transfer all created SQL profiles. Although I personally do not
regard this as any great effort, I have tried to find another solution.”
P.: “What’s that?”
V.: “I noticed that the optimizer does not correctly estimate the cardinality
when the table HIERARCHY is accessed in step 9. It assumes that this
cardinality is 1. In fact, it was 7. As the predicate ‘HIERARCHY.
PARENT¼52’ was used for access to the table HIERARCHY, I checked
if the column PARENT had histograms. I checked this in the view
DBA_TAB_COL_STATISTICS.”
P.: “I presume you didn’t find any histograms.”
V.: “You’re right. After creating histograms for the column PARENT, the
execution plan improved immediately (see Fig. 7.9). The optimizer
selected the same plan as I forced with the hint LEADING.”
P.: “In this case, the creation of histograms was a good alternative to the
formal method. Is that always the case? Can one always generate new or
additional optimizer statistics instead of using formal SQL tuning?”
V.: “In some situations that is possible, but certainly not in all. For example,
if the optimizer estimates the cardinality of a join badly with a skewed
distribution of data, as a rule, no statistics will help.”
Closing Remarks
8
That is the end of the book. We would be pleased if you decide to add formal SQL
tuning to your arsenal and use it in everyday practice. Do that and you will be amazed
how easy and effective this method is. With this figure, we take our leave (Fig. 8.1).
In the appendix we use an example to describe how one can use the formal
principle for the analysis of performance problems after an Oracle migration. Such
problems are considerably more complicated than the SQL tuning of individual
SQL statements because several SQL statements are usually affected. When
analyzing these problems, one has to determine what has caused the degradation
in performance (e.g., new optimizer features). The formal principle can be very
helpful here. If you would like to study the formal method in more detail, the
following material will be of interest to you.
Before you close the book and put it down, we would like to take a final
opportunity to clarify any outstanding questions. Peter may be able to help us
again here. He is full of questions and doesn’t give up until he receives a satisfac-
tory answer. We hope that his questions are also of interest to you.
Peter: “I must admit that I do have some questions. These don’t refer directly to
the formal method itself, which I have understood (at least I hope so).
They are of a more peripheral nature. First I would like to ask what
formal SQL tuning is. Is it a method at all or is it simply a collection of
empirical rules, which one can use when tuning?”
Author: “If we define a method as a systematic procedure for achieving an
objective, then formal SQL tuning is a method. In this book, we have
tried to systematize formal SQL tuning. The objective is also clear.
Where do you see a problem?”
P.: “Formal SQL tuning doesn’t cover all problems that can occur during
tuning.”
A.: “That’s true. Most practical cases of acute performance problems are
dealt with, however. If necessary, one can develop the method further
using the same principle.”
P.: “Are there cases where the formal method doesn’t work?”
A.: “Every method has its limits, Peter. I must say, however, that formal
SQL tuning is very reliable, at least for the categories of problem
described in this book.”
P.: “I am particularly interested in problems with an inappropriate table
order in the join. Can it ever happen that no table order change made
according to the rules described is capable of bringing about an appre-
ciable improvement in performance?”
A.: “There are cases in which it is generally not possible to achieve any
improvement by changing the table order in the join. This is normally
due to the data model, which is unsuitable for the particular query.”
P.: “Do you mean that formal SQL tuning has absolutely no
disadvantages?”
A.: “Not at all. For example, formal SQL tuning doesn’t consider any Oracle
transformations and optimizations, because it is very problematical to
formalize them in an easy manner and to incorporate them into the
formal method. An experienced specialist may be able to take into
account such Oracle features when tuning. An inexperienced person
will generally not succeed in doing this. I must say, however, that
transformations and optimizations seldom play a decisive role in SQL
tuning (as, e.g., in the case in the appendix). For this reason, I do not see
this as any major disadvantage.”
P.: “I have no further questions. Thank you very much.”
A.: “Then I wish you every success using the formal method.”
Appendix: Application of the Formal Principle
for the Analysis of Performance Problems
After an Oracle Migration
The method described in this book is helpful in most cases which a database
specialist is likely to encounter in everyday practice. It is also relatively simple.
With this method, we have tried to achieve a compromise between comprehensi-
bility and usefulness. We will leave it to you, our readers, to judge how successful
we have been.
Here we would like to present an example of how one can use the formal
principle for analyzing performance problems after an Oracle migration.
Peter: “Why are you describing this in the appendix and not in a chapter?”
Author: “This is a special case because one relatively seldom encounters such
problems (one doesn’t migrate Oracle databases every day!). So, for
practical reasons, we are putting this example in the appendix.”
P.: “Why do you find this example so interesting?”
A.: “First of all, this example demonstrates that the formal method described
here has its limitations; secondly, it shows how one can analyze rela-
tively complex problems using the same principle. Shall we get started?”
P.: “Yes, all right. I hope I can follow you.”
A.: “After an Oracle migration from 10.2.0.5 to 11.2.0.4, several SQL
statements became suboptimal. They had a similar structure. Below is
an example:
Fig. A.1 The suboptimal execution plan after the Oracle migration
Can you see anything special about this execution plan, Peter?”
P.: “I assume that Oracle has transformed the two subqueries into inline views
and joined them to the main query.”
A.: “Quite right, Peter. Oracle has generated two names for these views: VW_SQ_1
and VW_SQ_2. The abbreviation ‘SQ’ means subquery. Notice that Oracle uses
the operation ‘VIEW PUSHED PREDICATE’ for these two views. This means
that the join predicates are pushed into the inline view (predicate push down). In
our case, this happens in a natural way because these predicates are already
contained in the subqueries. The view VW_SQ_2 corresponds to the second
subquery. The join predicate which is pushed into this view is ‘S.SOC¼SOC.
SOC.’ We find this predicate in the section ‘Predicate Information’:
17 - access("S"."SOC"="SOC"."SOC")
filter(TO_CHAR(INTERNAL_FUNCTION("S"."EFFECTIVE_DATE"),'YYYYMMDD')<='20150102')
influence other execution plans. For this reason, I requested an execution plan
from 10.2.0.5, which looked like this (Fig. A.2):
Do you see the difference between these two plans, Peter?”
P.: “In the second plan, subqueries are processed as subqueries. But I don’t
understand why the first subquery was only executed 23 times, at least
Appendix: Application of the Formal Principle for the Analysis of Performance. . . 101
according to execution plan step 12. It should have been executed as many
times as the cardinality in step 3, i.e., 1980 times.”
A.: “In step 5, 23 rows from table SERVICE_AGREEMENT (alias SA) were
found. In the next step, the cardinality increased to 1980 after the join with
the table SOC. The subsequent outer join did not change the number of hits.
What is important for us is the fact that this number of hits only contains
23, or even fewer, rows with various values from SA.SUBSCRIBER_NO,
SA.BAN, SA.EXPIRATION_DATE, SA.SOC. Precisely, these values are
queried in the first subquery (see steps 14 and 15). I assume that Oracle is
using a kind of subquery caching here and has thereby optimized the number
of executions of this subquery or reduced them to 23. In [2], the author refers
to this as filter optimization. We will discuss this optimization later. I have
noticed another peculiarity in the second subquery.”
P.: “What’s that? I don’t notice anything.”
A.: “In the execution plan in Fig. A.1, the view VW_SQ_2, which corresponds to
the second subquery, was executed 1980 times (see step 13). Only 23 rows
were found there.”
P.: “Is this subquery so selective?”
A.: “I assumed that the reason would be different. The function DECODE, the
result of which is compared with the subquery in the SQL statement, returns a
null value if the value of the column TARGET_TARIFF differs from 0,1,2.”
P.: “I don’t quite understand that.”
A.: “An ‘ELSE’ condition is missing in this function. This simple example shows
that:
NVL(
----
NULL
I assumed (and a direct check confirmed this) that the result of the function
DECODE consisted mainly of null values. In the case of a null value, one can
spare the execution of the subquery because an equality condition for a null
value is always wrong. Oracle uses such optimization for subqueries. There is
also a similar optimization for joins. We will discuss these optimizations
(let’s call them filter ‘IS NOT NULL’) in detail later.”
P.: “Why didn’t Oracle use this optimization in the first execution plan, i.e., in
the case of nested loop join?”
A.: “That’s a fair question. At the moment we do not know enough to be able to
answer this question. First we have to examine the optimization in detail. We
will do that and then we’ll give you an answer.”
P.: “In what way did the fact that the DECODE function mostly returns null
values actually help you?”
102 Appendix: Application of the Formal Principle for the Analysis of Performance. . .
A.: “I extended the SQL statement by adding a condition, requesting that the
result of the DECODE function should be ‘not null’:
SELECT
FROM SERVICE_AGREEMENT SA,SOC ,PROMOTION_TERMS PRMT
WHERE SA.BAN = 116
AND SA.SUBSCRIBER_NO = ' XXXXXXXXXXXX'
AND SA.EXPIRATION_DATE <= TO_DATE('20150102', 'YYYYMMDD')
AND SA.SOC_SEQ_NO = (SELECT --+ index(SA2 SERVICE_AGREEMENT_PK)
MAX(SA2.SOC_SEQ_NO)
FROM SERVICE_AGREEMENT SA2
WHERE SA2.BAN = SA.BAN
AND SA2.SUBSCRIBER_NO = SA.SUBSCRIBER_NO
AND SA2.SOC = SA.SOC
AND SA2.EXPIRATION_DATE = SA.EXPIRATION_DATE)
AND SA.SOC = SOC.SOC
AND SA.TARIFF_OPTION IN ('VF_FUN_10', '000000000')
AND DECODE(RTRIM(SOC.TARGET_TARIFF),
RTRIM('VF_FUN_10'), 0,
RTRIM('VF_FUN'), 1,
'ALLTO', 2) = (SELECT --+ index(S SOC_PK)
MIN(DECODE(RTRIM(S.TARGET_TARIFF),
RTRIM('VF_FUN_10'), 0,
RTRIM('VF_FUN'), 1,
'ALLTO', 2))
FROM SOC S
WHERE S.SOC = SOC.SOC
AND '20150102' >= TO_CHAR(S.EFFECTIVE_DATE, 'YYYYMMDD')
AND TO_DATE('20150102', 'YYYYMMDD') < NVL(S.EXPIRATION_DATE,
TO_DATE('47001231', 'YYYYMMDD')))
AND '20150102' >= TO_CHAR(SOC.EFFECTIVE_DATE, 'YYYYMMDD')
AND TO_DATE('20150102', 'YYYYMMDD') < NVL(SOC.EXPIRATION_DATE, TO_DATE('47001231',
'YYYYMMDD'))
AND PRMT.SOC(+) = SOC.SOC
AND PRMT.SOC(+) = SOC.SOC
AND PRMT.TARGET_TARIFF(+) = SOC.TARGET_TARIFF
AND PRMT.EFFECTIVE_DATE(+) = SOC.EFFECTIVE_DATE
AND DECODE(RTRIM(SOC.TARGET_TARIFF),
RTRIM('VF_FUN_10'), 0,
RTRIM('VF_FUN'), 1,
'ALLTO', 2) is not null
ORDER BY DECODE(SA.SERVICE_TYPE,
'P', 1,
'M', 2, 3),
SA.EFFECTIVE_DATE;
The relevant execution plan then became almost exactly as fast as the
‘good’ plan in Fig. A.2 (Fig. A.3).”
Appendix: Application of the Formal Principle for the Analysis of Performance. . . 103
Fig. A.3 An additional condition “is not null” improved the execution plan
A.: “Formal tuning tries to improve the existing execution plan. It doesn’t
consider any transformations and optimizations which could lead to a
completely different execution plan. It should be noted that it is problematical
to extend formal tuning in this way here. But I also have to say that the cases
in which transformations and optimizations play a decisive role for tuning are
relatively rare in practice. I think we can now examine the two optimizations
subquery caching and filter ‘IS NOT NULL’ in greater depth. For this
purpose, we use the script test_case_subquery_caching_filter_is_not_null.
sql, which you can download from the website www.tutool.de/book, Peter.
There you can also find all the test cases relating to [1]. In this test case, three
tables, T1, T2, and T3, are created and filled with data. No column statistics
are generated. The first test demonstrates subquery caching (Fig. A.4).
select count(*) from t1 where decode(t1.b,1,-1,2,-2,-100) = (select /*+
no_unnest */ max(decode(t2.b,10,-1,15,-2)) from t2 where t2.a=t1.a) or
t1.c = (select /*+ no_unnest index(t3) */ max(t3.b) from t3 where
t3.a=t1.c)
P.: “Both subqueries correlate to the main query. I assume that table T1 has only
70 different values in column C and 20 different values in column A. Oracle
stores the results of these two subqueries for the different values of columns C
and A in the subquery cache. The subquery is only executed when the
relevant result is missing in the subquery cache.”
A.: “Your assumption is correct:
COUNT(DISTINCTC)
----------------
70
COUNT(DISTINCTA)
----------------
20
Appendix: Application of the Formal Principle for the Analysis of Performance. . . 105
I must say, however, that Oracle uses a hash algorithm for subquery
caching (see [2]). When hash collisions occur, the relevant subquery is
executed more often. The parameter ‘_query_execution_cache_max_size’
determines the size of the subquery cache. Let’s deactivate subquery caching
with the parameter setting “_query_execution_cache_max_size”¼0:
Session altered.
P.: “I don’t understand why there is a difference in the frequency with which
subqueries are executed.”
A.: “Unlike our practical example, the operator OR is between the subqueries
here. It therefore makes sense only to execute the second subquery for those
rows for which the first subquery does not provide any result.”
SQL> select count(*) from t1 where not exists (select /*+ no_unnest index(t3) */ * from t3 where
t3.a=t1.c);
COUNT(*)
----------
560
2 - filter(("T1"."C"= OR DECODE("T1"."B",1,(-1),2,(-2))=))
6 - access("T3"."A"=:B1)
9 - access("T2"."A"=:B1)
P.: “With the best will in the world, I don’t understand where the figure 24 comes
from.”
A.: “That’s very easy. Subquery caching is deactivated. Then Oracle only has to
use the optimization filter ‘IS NOT NULL.’ This means that the subquery
from the table T2 is executed for all rows for which the subquery from table
T3 does not return a result and for which the function decode(t1.b,1,-1,2,-2) is
‘not null’:”
SQL> select count(*) from t1 where not exists (select /*+ no_unnest index(t3) */ * from t3 where
t3.a=t1.c) and decode(t1.b,1,-1,2,-2) is not null;
COUNT(*)
----------
24
P.: “I should have guessed that myself. I notice that although the filter ‘IS NOT
NULL’ is used, it does not appear in the predicates. This makes the analysis
more difficult.”
A.: “That’s right. The important thing for us is that Oracle always uses the
optimization filter ‘IS NOT NULL’ for subqueries even if the relevant tables
have no optimizer statistics (as in our test case). Now let’s consider the
optimization filter ‘IS NOT NULL’ for joins. To do this, we force Oracle to
transform the subqueries into inline views. In the test case, we use outlines so
that Oracle always generates the same execution plan (in this way, it is easier
to compare the test results). As the SQL text with the outlines is very big, only
the relevant execution plan is presented here (Fig. A.7).”
Appendix: Application of the Formal Principle for the Analysis of Performance. . . 107
Fig. A.7 Optimization FILTER ‘IS NOT NULL’ for joins is not used
EXT
----------------------------------------------------------------
SYS_STU_GXO4ZZKOWJIU3MQ2G$$69D
12 - access("T2"."A"="T1"."A")
Fig. A.8 Optimization FILTER ‘IS NOT NULL’ for joins is used with extended optimizer
statistics