At the start of this year I embarked on a new adventure. I’m now at Zione Solutions in a consulting role as a Senior Database Administrator and doing what I’ve been teaching for the past so many years. It’s been quite a change, for one I am now using that strange new join syntax “LEFT OUTER JOIN … ON…” which I had successfully avoided for all these years. It’s turned out to be harder than I expected to learn. Also the queries I’ve been tuning are rather huge. An average query is about 3-400 lines long, and the plans for these will easily be 1000 lines. These are reporting queries the classic Data Warehouse type queries. The smaller tables are millions of rows and 10’s of billions in a table is not uncommon. Also it’s on Exadata.
I’ve seen firsthand that what I’ve been teaching works.
One of the most important things you can do when tuning a query is; let the numbers lead you to the problem. Some folks can look at a query and, without running it, make a good guesses as to where the problem will likely be. And that can work, however why bother? Run the query and use a tool like SQL Monitor to see how the plan is executing. In the SQL Monitor report the activity column is a great way to see which step is taking the most percentage of time. Now you can see exactly where a query is spending all it’s time and then pound that nail down.
Also it’s about starting and staying small with the rows. Here is where getting predicates applied at the right time and use of things like the WITH clause can really help. With Exadata and working with huge volumes of data this concept is still critical to success; however the use of indexes is not a solution more often than not.
Why? Not because indexes are bad, it’s just that index use tends to serialize the access. This tends to cause nested loop joins which end up being very expensive many times because the plan will hit the inner table (or index) way too much. This can be millions of times, even if using a unique index to look up values, doing it over a million times isn’t efficient. A fast full index scan can work fine, since it works like a full table scan and can be in parallel.
Using subquery factors (also called CTE, Common Table Expression) are very powerful in these queries. These are those temporary views you define in the WITH clause, typically it’s best to use the MATERIALIZE hint. This hint forces the optimizer into making the sub query factor a global temp table like structor, without thie hint the optimizer might just merge it back into the query. This is of course exactly what you don’t want to happen most times. Taking a subquery that is executed even just a couple times and making a sub query factor can be huge. Also sometimes taking that subquery out of the main query and making it a “stand alone” query can help parallelization. This can also work well to get that “start small stay small” thing.
In the team that I’m working in now, it’s speculated that about 60% of the performance problems are solved with using sub query factors (CTEs).
Oh and did I mention LAT (Lateral) views? Yea these tend to work very poorly in these kinds of queries. Seeing one or more in these plans is a known killer to performance. Making them go away can be tricky sometimes. A typical cause of these is having an OR in the ON clause of an outer join. These LAT view might work well with smaller data sets, but with large ones these can cause a query to stall at that LAT view for very long periods of time, like hours.
For me it’s been quite a beginning to the year. As the saying goes chance is inevitable, expect from vending machines. And this year has had plenty, but still not from vending machines.