Wednesday, April 20, 2011

Yet Another Slow Query

by Enrique Aviles

As the database performance contact I get to troubleshoot slow queries. Typically when users experience a slow application screen an incident is submitted and the issue ends up on my plate. Recently, I had to investigate a report that was taking a little over 10 minutes to generate results. Although the application is an OLTP system some screens allow users to generate a report that can be sent to the printer. Normally these reports take a few seconds to appear on the screen but this one was way over the application’s SLA requirements.

Below is a sample query construct that captures the syntax of the actual query that was executed by the application. After examining execution plans and generating extended SQL trace I identified the parts highlighted in red as the ones contributing to the query’s poor response time.
SELECT T1.COLUMN_1,
T1.COLUMN_2,
V1.COLUMN_1,
V1.COLUMN_2,
T2.COLUMN_1,
T2.COLUMN_2,
V2.COLUMN_1,
V2.COLUMN_2,
CV.USAGE,
.
.
.
24 additional columns
.
.
.
FROM
TABLE_1 T1,
TABLE_2 T2,
TABLE_3 T3,
TABLE_4 T4,
VIEW_1 V1,
VIEW_2 V2,
COMPLEX_VIEW CV
.
.
An additional mix of 7 tables and views
.
.
WHERE T1.COLUMN_4 = T2.COLUMN_5
AND T2.COLUMN_7 = T3.COLUMN8
.
.
.
AND
CV.PARAMETER_ID = T3.ID
AND CV.PART_ID = T4.ID
AND CV.LOG_DATE = (
SELECT MAX(CV2.LOG_DATE)
FROM COMPLEX_VIEW CV2
WHERE CV2.PART_ID = CV.PART_ID
AND CV2.PARAMETER_ID = CV.PARAMETER_ID
)

AND T5.ID = 'ABC123'
The query’s runtime statistics are:
Elapsed: 00:13:13.01

Statistics
----------------------------------------------------------
4013 recursive calls
6372 db block gets
65682217 consistent gets
858907 physical reads
0 redo size
3328 bytes sent via SQL*Net to client
524 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
1 sorts (disk)
1 rows processed
The query took more than 13 minutes and over 65 million consistent gets to select one row. This is obviously unacceptable so it is clear why users were not happy with that particular report. What could be causing the query to generate so much work?

The complex view is composed of twelve tables and 40 lines in the WHERE clause, five of them OR conditions. I noticed that removing the subquery that gets the maximum LOG_DATE from the complex view helped the main query to complete in just a few seconds. Obviously, results were incorrect without the subquery so I had to figure out a way to preserve the logic that gets the maximum LOG_DATE while having the query complete in a matter of seconds.

Examining the complex view data showed there were no duplicate LOG_DATEs so the MAX aggregate function will always return the absolute maximum LOG_DATE for a given PART_ID/PARAMETER_ID combination. Finding that characteristic in the data led me to consider using a scalar subquery to get the USAGE value from the complex view. In the process I also wanted to select from the complex view in one pass so I decided to use the ROW_NUMBER analytic function to get the maximum LOG_DATE and eliminate the need for a self-join via a correlated subquery. Having devised that plan, I executed the following query to test what would become the scalar subquery:
SELECT USAGE
FROM (
SELECT CV.USAGE,
ROW_NUMBER() OVER (PARTITION BY CV.PART_ID,
CV.PARAMETER_ID
ORDER BY CV.LOG_DATE DESC) RN
FROM COMPLEX_VIEW CV
WHERE CV.PARAMETER_ID = 'ABC'
AND CV.PART_ID = 'XYZ'
)
WHERE RN = 1


Elapsed: 00:00:00.02

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
738 consistent gets
0 physical reads
0 redo size
532 bytes sent via SQL*Net to client
524 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
Only 0.02 second and 738 consistent gets to select the USAGE value from the complex view! It looks like the plan is coming together. I proceeded to replace the correlated subquery with the scalar subquery so the main query becomes:
SELECT  T1.COLUMN_1,
T1.COLUMN_2,
V1.COLUMN_1,
V1.COLUMN_2,
T2.COLUMN_1,
T2.COLUMN_2,
V2.COLUMN_1,
V2.COLUMN_2,
(
SELECT USAGE
FROM (
SELECT CV.USAGE,
ROW_NUMBER() OVER (PARTITION BY CV.PART_ID,
CV.PARAMETER_ID
ORDER BY CV.LOG_DATE DESC) RN
FROM COMPLEX_VIEW CV
WHERE CV.PARAMETER_ID = T3.ID
AND CV.PART_ID = T4.ID
)
WHERE RN = 1
) USAGE,

.
.
.
24 additional columns
.
.
.
FROM TABLE_1 T1,
TABLE_2 T2,
TABLE_3 T3,
TABLE_4 T4,
VIEW_1 V1,
VIEW_2 V2,
.
.
An additional mix of 7 tables and views
.
.
WHERE T1.COLUMN_4 = T2.COLUMN_5
AND T2.COLUMN_7 = T3.COLUMN_8
.
.
.
AND T5.ID = 'ABC123'
Notice the complex view is not part of the FROM section and there are no joins to the complex view in the WHERE clause. I executed the new and improved query and got the following error:
ERROR at line 17:
ORA-00904: "T4"."ID": invalid identifier
The excitement was extinguished for a brief moment while I realized my mistake. How come T4.ID is an invalid identifier when I know ID is a valid column on TABLE_4? The problem is that TABLE_4 is not visible inside the inline view of the scalar subquery. The complex view is two levels deep so I can’t join CV and T4. How can I hide the logic of the scalar subquery in a way that allows me to join the complex view and TABLE_4? A view that implements the core logic of the scalar subquery achieves the desired result so I created the following view:
CREATE OR REPLACE VIEW CURRENT_USAGE
AS
SELECT USAGE,
PART_ID,
PARAMETER_ID
FROM
(
SELECT CV.USAGE,
CV.PART_ID,
CV.PARAMETER_ID,
ROW_NUMBER() OVER (PARTITION BY CV.PART_ID,
CV.PARAMETER_ID
ORDER BY CV.LOG_DATE DESC) RN
FROM COMPLEX_VIEW CV
)
WHERE RN = 1;
The main query needs to be modified again to reference the newly created view CURRENT_USAGE so it becomes:
SELECT  T1.COLUMN_1,
T1.COLUMN_2,
V1.COLUMN_1,
V1.COLUMN_2,
T2.COLUMN_1,
T2.COLUMN_2,
V2.COLUMN_1,
V2.COLUMN_2,
(
SELECT USAGE
FROM CURRENT_USAGE CU
WHERE CU.PARAMETER_ID = T3.ID
AND CU.PART_ID = T4.ID
)
USAGE,

.
.
.
24 additional columns
.
.
.
FROM TABLE_1 T1,
TABLE_2 T2,
TABLE_3 T3,
TABLE_4 T4,
VIEW_1 V1,
VIEW_2 V2,
.
.
An additional mix of 7 tables and views
.
.
WHERE T1.COLUMN_4 = T2.COLUMN_5
AND T2.COLUMN_7 = T3.COLUMN_8
.
.
.
AND T5.ID = 'ABC123'
This time around the only surprise was the following runtime statistics:
Statistics
----------------------------------------------------------
2 recursive calls
0 db block gets
28316 consistent gets
0 physical reads
0 redo size
3328 bytes sent via SQL*Net to client
524 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
1 rows processed
The new query returned the same data as the original query while performing a fraction of the work. Although 28,316 consistent gets is a bit high to return one row, this value is quite handsome when compared to the original value of 65 million consistent gets. This translates into a 99.96% improvement. Regarding response time, the original query took 13 minutes to complete while the new query only requires 0.3 second to generate results, also a 99.96% improvement.

Combining the right mix of database objects and SQL features helped me achieve such dramatic improvement. I hope my experience will help you consider creative solutions when faced with challenging SQL performance issues.

Thursday, March 31, 2011

Expert Oracle APEX

I'm a bit of a fan of APEX; haven't used it consistently in a while but I think it's a great tool. I've annoyed many managers/DBAs about getting it installed and configured for use...most of that to no avail.

Many years ago, almost 6 to be exact, I decided to port my business' J2EE app over to APEX and I needed hosting. Still relatively new, the market was small. Through the forums, I found John Scott and ShellPrompt. It took a few months to write the J2EE application from scratch, it took less than 2 weeks of off-hours work to port. I hosted my site there for a year, maybe a little longer. During that time, John was pure awesome. I blogged about John's customer service once, but that was another life and the blog no longer exists. We have met IRL, but have yet to have a beer together, I'm pretty sure he owes me at least 4 now.

Anyway, John has put together a new book, Expert Oracle Application Express which is a joint effort by some 14 different APEX authors.



While the content must be outstanding, the best part is that all funds will be donated to the families of 2 men, Carl Backstrom and Scott Spadafore. Both men worked for Oracle on the APEX team. Both men were incredibly involved in the community. Both were highly regarded in that community.


John blog's about it here.

If you use APEX, new to APEX, or just want to know WTF it is, go out and (pre)order this book now. Not only do you get a great resource, but you get to help out the families of Carl and Scott.

Wednesday, March 30, 2011

The Nerd Defense

Really couldn't pass this one up. Friend and co-worker, Erica Baer [@skibaer] who has a Ph.D. in some sort of crazy thing (and she's not even 30!) sent me a link to Eyeglasses and Mock Juror Decisions...I'm pretty sure her Ph.D. is in something related to psychology (which is good for me, free counseling) and the selection of jurors. Wait, it's Forensic Psychology, LinkedIn told me so. Sadly, much of my charm is wasted on her, she sees right through it. Oh well.

So the link...had a great picture:



There are like 4 elements that apply to me there:
1. Nerd
2. Justice
3. Bald
4. Rollie Fingers-esque mustache
5. Glasses

Ok, 5, I can count, but you knew that.

Tuesday, March 29, 2011

OEL 6 + VirtualBox Guest Additions

I'm taking my first second spin at Oracle Enterprise Linux 6. The first time, I did not install a Desktop, so I was completely lost. I suppose I could do it now...but, I'm not going there just yet.

One of the very first things I do when creating a VM is install the Guest Additions. The main reason is screen size. Out-of-the-box, it's 800x600 or some such non-sense. I'm sure I could cope, if I had to, I'd rather not, it's just annoying. I guess a little perspective is in order:



That's against the backdrop of a 23" monitor. So yeah, annoying.

After having done this about 20 times, I know you need to install some extra packages. Usually the kernel* and make packages. I'll just skip straight to installing the Guest Additions though, let's see what happens.
Nothing to see here.
Well, Guest Additions includes the bi-directional clipboard support, and I'm not in the mood to type up the entire thing. Here's the specific error I received however (typed by hand, you are welcome):
Building the VirtualBox Guest Additions kernel modules
The headers for the current running kernel were not found. If the following
module compilation fails then this could be the reason.
The missing package can be probably [sic] installed with
yum install kernel-devel-2.6.32-100.28.5.el6.x86_64
The output of the log file shows this:
[root@oel6test VBOXADDITIONS_4.0.4_70112]# tail /var/log/vboxadd-install.log -n 100
Makefile:23: *** Error: unable to find the sources of your current Linux kernel.
Specify KERN_DIR= and run Make again.. Stop.
OK, let's try that. The Google Machine told me the kernel lives in /usr/src/kernels/
[root@oel6test VBOXADDITIONS_4.0.4_70112]# export KERN_DIR=/usr/src/kernels/
Run the command again and it tells me that "KERN_DIR does not point to a directory. Stop." No you stop. Whatever.

I then go into the "additional" packages provided by the OEL6 DVD. Funny, now that I look at the kernel* packages, I wonder if they are outdated? uname -r shows me 2.6.32-100.28.5.el6.x86_64 which I believe is correct, but the kernel* packages on the disk look to be in the 2.6.32-71 range. Weird, since I seem to have the latest...

Now, it's time for some public yum, Oracle's public yum server to be exact. Awesome, instructions on the front page on how to set it up, makes it somewhat foolproof for someone like me:

Download and copy the appropriate yum configuration file in place, by running the following commands as root:

Oracle Linux 4, Update 6 or Newer

# cd /etc/yum.repos.d
# mv Oracle-Base.repo Oracle-Base.repo.disabled
# wget http://public-yum.oracle.com/public-yum-el4.repo
Oracle Linux 5

# cd /etc/yum.repos.d
# wget http://public-yum.oracle.com/public-yum-el5.repo
Oracle Linux 6

# cd /etc/yum.repos.d
# wget http://public-yum.oracle.com/public-yum-ol6.repo
Oracle VM 2

# cd /etc/yum.repos.d
# wget http://public-yum.oracle.com/public-yum-ovm2.repo
Enable the appropriate repository by editing the yum configuration file

Open the yum configuration file in a text editor
Locate the section in the file for the repository you plan to update from, e.g. [el4_u6_base]
Change enabled=0 to enabled=1
Begin using yum, for example:

yum list

yum install firefox

And here is where I cheat, sort of. Instead of just using yum, I go to the Add/Remove Software GUI.



Wait just a second...those "%100%" packages have "uek" in them, I thought that was for the Unbreakable Enterprise Kernel. Did I install that? I thought I opted out of that...

So back to the DVD and there are those packages sitting there, of the "uek*100*" variety. Let's install those.
kernel-uek-2.6.32-100.28.5.el6.x86_64.rpm
kernel-uek-debug-2.6.32-100.28.5.el6.x86_64.rpm
kernel-uek-debug-devel-2.6.32-100.28.5.el6.x86_64.rpm
kernel-uek-devel-2.6.32-100.28.5.el6.x86_64.rpm
kernel-uek-doc-2.6.32-100.28.5.el6.noarch.rpm
kernel-uek-firmware-2.6.32-100.28.5.el6.noarch.rpm
kernel-uek-headers-2.6.32-100.28.5.el6.x86_64.rpm
I install the debug, the debug-devel, devel and headers. Reboot.

No joy. But I can see that it can't find the gcc command, so I just have to load that package. Of course it isn't that easy, it has dependencies.
[root@oel6test Packages]# rpm -ivh gcc-4.4.4-13.el6.x86_64.rpm 
warning: gcc-4.4.4-13.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
error: Failed dependencies:
cloog-ppl >= 0.15 is needed by gcc-4.4.4-13.el6.x86_64
cpp = 4.4.4-13.el6 is needed by gcc-4.4.4-13.el6.x86_64
First up:
[root@oel6test Packages]# rpm -ivh glibc-headers-2.12-1.7.el6.x86_64.rpm 
warning: glibc-headers-2.12-1.7.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
Preparing... ########################################### [100%]
1:glibc-headers ########################################### [100%]
Now I can load the devel files for glibc.
[root@oel6test Packages]# rpm -ivh glibc-devel-2.12-1.7.el6.x86_64.rpm 
warning: glibc-devel-2.12-1.7.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
Preparing... ########################################### [100%]
1:glibc-devel ########################################### [100%]
What else?
[root@oel6test Packages]# rpm -ivh cpp-4.4.4-13.el6.x86_64.rpm 
warning: cpp-4.4.4-13.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
error: Failed dependencies:
libmpfr.so.1()(64bit) is needed by cpp-4.4.4-13.el6.x86_64
Seriously. Since this isn't the first time today, I know what package that so file is from.
[root@oel6test Packages]# rpm -ivh mpfr-2.4.1-6.el6.x86_64.rpm 
warning: mpfr-2.4.1-6.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
Preparing... ########################################### [100%]
1:mpfr ########################################### [100%]
Now cloog-ppl.
[root@oel6test Packages]# rpm -ivh cloog-ppl-0.15.7-1.2.el6.x86_64.rpm 
warning: cloog-ppl-0.15.7-1.2.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
error: Failed dependencies:
libppl.so.7()(64bit) is needed by cloog-ppl-0.15.7-1.2.el6.x86_64
libppl_c.so.2()(64bit) is needed by cloog-ppl-0.15.7-1.2.el6.x86_64
What about a ppl* package?
[root@oel6test Packages]# rpm -ivh ppl-0.10.2-11.el6.x86_64.rpm 
warning: ppl-0.10.2-11.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
Preparing... ########################################### [100%]
1:ppl ########################################### [100%]
Almost there. Back to cloog.
[root@oel6test Packages]# rpm -ivh cloog-ppl-0.15.7-1.2.el6.x86_64.rpm 
warning: cloog-ppl-0.15.7-1.2.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
Preparing... ########################################### [100%]
1:cloog-ppl ########################################### [100%]
cpp
[root@oel6test Packages]# rpm -ivh cpp-4.4.4-13.el6.x86_64.rpm 
warning: cpp-4.4.4-13.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
Preparing... ########################################### [100%]
1:cpp ########################################### [100%]
gcc?
[root@oel6test Packages]# rpm -ivh gcc-4.4.4-13.el6.x86_64.rpm 
warning: gcc-4.4.4-13.el6.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
Preparing... ########################################### [100%]
1:gcc ########################################### [100%]
Yes!

It's working...

Sweet!
[root@oel6test VBOXADDITIONS_4.0.4_70112]# /bin/bash ./VBoxLinuxAdditions.run install
Verifying archive integrity... All good.
Uncompressing VirtualBox 4.0.4 Guest Additions for Linux.........
VirtualBox Guest Additions installer
Removing installed version 4.0.4 of VirtualBox Guest Additions...
Removing existing VirtualBox DKMS kernel modules [ OK ]
Removing existing VirtualBox non-DKMS kernel modules [ OK ]
Building the VirtualBox Guest Additions kernel modules
Your guest system does not seem to have sufficient OpenGL support to enable
accelerated 3D effects (this requires Linux 2.6.27 or later in the guest
system). This Guest Additions feature will be disabled.


Building the main Guest Additions module [ OK ]
Building the shared folder support module [ OK ]
Doing non-kernel setup of the Guest Additions [ OK ]
Starting the VirtualBox Guest Additions [ OK ]
Installing the Window System drivers
Installing X.Org Server 1.7 modules [ OK ]
Setting up the Window System to use the Guest Additions [ OK ]
You may need to restart the hal service and the Window System (or just restart
the guest system) to enable the Guest Additions.

Installing graphics libraries and desktop services componen[ OK ]
Reboot (or just restart your X-windows.



Voila!

Update: 03/30/2011 20:50:00 EST
Thanks to Robin Moffat [blog|@rnm1978] for providing a much simpler solution:
yum install kernel* dkms gcc
Reboot after that and install your Guest Additions. No more having to figure out the dependencies on your own (I'll leave it to you to decide whether that is a good exercise or not).

Update: 02/14/2013 12:00:00 EST
One more addition from Mr. Closson (comments, below).
# yum install kernel-uek-devel