Cardinality the heart of SQL tuning

I’m at the East Coast Oracle Users Conference and wanted to write a blog post about the first day, which is now done.  Rather than summarize all the sessions I thought I would focus on what struck me the most – namely the confirmation I received that cardinality is at the heart of SQL tuning.

I was reviewing my upcoming SQL tuning presentation and became afraid that I had focused too much on determining the number of rows, or cardinality, that queries or parts of queries returned.  But I attended two Tom Kyte sessions this morning that both reenforced the idea that I was on the right track.  One quote from a session was “A wrong plan is always caused by wrong cardinality estimates”.  He revised “always” to “almost certainly” which is still pretty strong.

My talk isn’t only about poorly performing queries where the optimizer has a bad cardinality estimate.  It is also about queries where the estimate is correct but no good plan exists.  I.e. You might need an index.  But, when I manually tune a query, regardless of why it is slow, I start with doing counts on various parts of the query to find out the true cardinality.

Evidently Oracle is putting research and development money into improving the optimizer’s cardinality estimates in future releases.  If Oracle and Tom Kyte see cardinality as central to proper query optimization and if my own experience confirms it then it makes a strong case to me that cardinality is at the heart of SQL tuning – whether automatically done by the optimizer or manually done by a human.

– Bobby

Posted in Uncategorized | 2 Comments

Now I’m doing two talks at ECO conference

I just found out that my alternate talk for the East Coast Oracle Users Conference is needed.  So, I’ll be doing my Exadata talk on Wednesday and my Intro to SQL Tuning talk Thursday.  This makes the conference even more fun for me since I’ll be busier making sure I do a good job on both talks.

Here is my schedule and links to the PowerPoint presentations:

Wednesday, October 17th, 1:15 pm-2:15 pm Exadata Distinctives

Thursday, October 18th, 11:30 am-12:30 pm Intro to SQL Tuning

If you are coming to the conference come check these out.  They are both focused primarily on tuning.  There is only a small overlap between the two.  A couple of my SQL tuning examples come from Exadata systems.

– Bobby

Posted in Uncategorized | 2 Comments

Subpartition stats quirks

I was working on a package that we use to gather statistics on the partition level after data is loaded and found some quirks that apply when the partition has subpartitions.

  1. You can’t use dbms_stats.gather_table_stats to gather stats on just one subpartition. (SEE THE COMMENTS BELOW FOR CLARIFICATION ON THIS)
  2. If you gather stats on a partition with granularity=>’PARTITION’ it doesn’t gather stats at the subpartition level.

So, if you update some data in subpartitions of a partition and want to update statistics on both the partition and its subpartitions you have to call dbms_stats.gather_table_stats twice.  Once with granularity=>’PARTITION’  and once with granularity=>’SUBPARTITION’.

i.e. If you have a subpartitioned table called SUBPARTAB and partition PART_20120101 and you want to gather stats at the partition and subpartition level you run the following commands.

execute dbms_stats.gather_table_stats(
NULL,'SUBPARTAB','PART_20120101',
granularity=>'PARTITION');

execute dbms_stats.gather_table_stats(
NULL,'SUBPARTAB','PART_20120101',
granularity=>'SUBPARTITION');

Note that there is no subpartition parameter to dbms_stats.gather_table_stats.  You can only specify the partition name.

Here an example script that demonstrates gathering stats at the global, partition, and subpartition levels and shows which update subpartition stats.

– Bobby

Posted in Uncategorized | 13 Comments

Promotional code for ECO conference

I got the following Facebook message about the East Coast Oracle Users Conference which starts in less than two weeks:

It’s Friday and ECO is just 12 days away. Looking forward to seeing everyone! Please spread this discount code to friends and colleagues 100512ECO, it will be good through tomorrow AM.

I’m doing my “Introduction to SQL Tuning” talk.  The PowerPoint is here.  The initial paper on the topic is here.  I’m speaking at 11:30 am on Thursday, October 18th.  The main days of the conference are October 17th and 18th.

I think the promotional code 100512ECO from the Facebook post will give you discounted registration.  This might be a nice lower cost conference for people who live near North Carolina and who didn’t attend Openworld in San Francisco this week (I didn’t).  It is two days instead of four, but has some top speakers and a good selection of content.

– Bobby

Posted in Uncategorized | Leave a comment

Removed ability to register on blog

I was getting some bad spam users registering on this blog so I disabled the ability to register.  You don’t really get any benefit from registering so I’m saving people time and blocking spam.  Anyone can leave a comment without registering.  I moderate all the comments so I don’t let spam through that way.  Also, anyone who wants updates by email can subscribe without registering so there’s no need for it.

If you find you can’t use the blog because of this change leave me a comment or email me at bobby@bobbydurrettdba.com.

– Bobby

Posted in Uncategorized | 2 Comments

Sum of temp used from ASH view

I had a situation where I wanted to compare the temp space used by two similar insert statements on a test system.  One was filling up the temporary tablespace and the other wasn’t.

select sql_id,sample_time,
sum(TEMP_SPACE_ALLOCATED)/(1024*1024*1024) gig
from DBA_HIST_ACTIVE_SESS_HISTORY
where sample_time > sysdate-2
and TEMP_SPACE_ALLOCATED > (1*1024*1024*1024)
and sql_id in ('99m08n4dv67h9','8av0z21tm15as')
group by sql_id,sample_time
order by sql_id,sample_time;

SQL_ID        SAMPLE_TIME                      GIG
------------- ------------------------- ----------
8av0z21tm15as 21-SEP-12 10.20.49.279 AM 35.8271484
8av0z21tm15as 21-SEP-12 10.20.59.309 AM 44.6728516
8av0z21tm15as 21-SEP-12 10.21.09.329 AM 142.089844
8av0z21tm15as 21-SEP-12 10.21.19.379 AM 133.241211
8av0z21tm15as 21-SEP-12 10.21.29.389 AM 142.089844
8av0z21tm15as 21-SEP-12 10.21.39.399 AM 142.089844
99m08n4dv67h9 21-SEP-12 12.32.50.790 PM 43.2587891
99m08n4dv67h9 21-SEP-12 12.33.00.810 PM 88.3574219
99m08n4dv67h9 21-SEP-12 12.33.10.820 PM 100.229492
99m08n4dv67h9 21-SEP-12 12.33.20.840 PM 111.904297
99m08n4dv67h9 21-SEP-12 12.33.30.860 PM 147.005859

I used a query that I had posted about earlier where you can see how much temp space a query is using by querying the ASH.  But, in this case the two queries were parallel inserts so I summed the temp space used across all the sessions running the given sql_id.  In this case I knew there was only one running at a time.  If multiple were running I would have summed the temp space used by all the inserts with the same sql_id.  The dev team reported that the first sql_id wasn’t using any temp and the second was filling it up, but this query showed that they both were using more than 140 gig of temp at their peak.

– Bobby

P.S.  In retrospect I should have left off the TEMP_SPACE_ALLOCATED condition but at the peak all the parallel query servers probably had more than 1 gig of temp.  This was built from the previous query that only showed sessions with temp usage over 1 gig.  In this example I really wanted all the sessions for the given sql_ids even if they had less than 1 gig temp.  But, I’m leaving the query as it is because it was what I really ran and it was helpful.

Posted in Uncategorized | Leave a comment

ASH query for sequential reads

Quick post of a query I just used to diagnose a problem with a SQL statement that had a lot of db file sequential read waits.  This uses ASH data to see which segment the waits were on the most.

select e.OWNER,e.SEGMENT_NAME,e.SEGMENT_TYPE,count(*) 
from
(select 
P1 FILE_ID,P2 BLOCK_ID
from DBA_HIST_ACTIVE_SESS_HISTORY
where 
sample_time 
between 
to_date('18-SEP-2012 08:00:00','DD-MON-YYYY HH24:MI:SS')
and 
to_date('18-SEP-2012 17:00:00','DD-MON-YYYY HH24:MI:SS')
and sql_id='88h5hqqu6g0xs' and 
event='db file sequential read') bw,dba_extents e
where bw.file_id=e.file_id and
e.BLOCK_ID <=bw.block_id and
e.BLOCK_ID+e.BLOCKS >bw.block_id
group by e.OWNER,e.SEGMENT_NAME,e.SEGMENT_TYPE
order by e.OWNER,e.SEGMENT_NAME,e.SEGMENT_TYPE;

The query represented by sql_id 88h5hqqu6g0xs is a delete and I knew it was doing a bunch of sequential read waits but I didn’t know on what segments.  The query showed me that it was on all the indexes of the table being deleted from as well as the table itself.

If you want to use the query yourself change the sql_id to the sql_id for your query and change the date and times to match the range during which your problem occurred.

– Bobby

Posted in Uncategorized | Leave a comment

In enemy territory – OK really just SQL Server class

I spent the past four days in SQL Server 2012 performance tuning class with four of my coworkers.  It was so surreal walking into the Microsoft offices on the first day.  I kind of felt guilty after all these years as an Oracle DBA.  But, it was interesting not only to learn about SQL Server but also to see how performance tuning would be taught.

One weird thing is how each concept in Oracle tuning seems to have its match in SQL Server but with slightly different names – I think one was “parameter sniffing” and Oracle uses “bind variable peeking”.  I guess Microsoft sniffs and Oracle peeks!

Like other classes we had a number of Powerpoints with copious notes on each slide.  I need to work on making the slide notes on my presentations better.  So far the notes have been mostly an afterthought with most of the work being put into a paper and then what I want to say with just bullet points on the slides.  Also, there were demonstrations and labs.  Most of what I have done has been a single one hour talk with no time for a lab.  But, I could see working in a live demonstration.  Maybe in the future I could gear up to do a longer class with labs but it would take time to prepare and it takes hours just to do a single one hour talk.

Jargon and clever details can get in the way of the bigger picture.  It is kind of cool to learn the jargon so I can fit in with the SQL Server crowd and you have to know what people are talking about.  But, maybe a long list of tuning features can miss the higher level picture – what do I do with all this pile of information?

Still, it was a good class and I hope to apply what I learned in my actual work.  Perhaps my Oracle tuning experience will bleed over into SQL Server more easily than I thought.  Still there is only so much you can do and you can’t be good at everything – jack of all trades and master of none or a mile wide and an inch deep if you aren’t careful.

– Bobby

 

Posted in Uncategorized | Leave a comment

Cheese in January and the limits of SQL optimization

Prepping for my Intro to SQL tuning talk on Monday I did a little further research on one of my examples.  It is a concept I’ve known about for a while and I feel like it is profound evidence for the limits of SQL optimization.

The sample query just wants a list of sales and products where the sales take place in January 2012 and the product is a type of cheese.

select 
sale_date, product_name, amount
from sales, products
where
sales.product_number=products.product_number and
sale_date between 
  to_date('01/01/2012','MM/DD/YYYY') and 
  to_date('01/31/2012','MM/DD/YYYY') and
product_type = 'Cheese';

The Oracle optimizer has no way of knowing how many sales rows are both in January and cheese.  None of the optimizer statistics relate data in one table that is joined to another.  I made some sample data so that half of the sales rows are in January and half are cheese but none are both.

create table sales
(
sale_date date,
product_number number,
customer_number number,
amount number
);

create table products
(
product_number number,
product_type varchar2(12),
product_name varchar2(12)
);

insert into sales values(to_date('01/02/2012','MM/DD/YYYY'),1,1,10);
insert into sales values(to_date('01/03/2012','MM/DD/YYYY'),1,1,20);
insert into sales values(to_date('02/04/2012','MM/DD/YYYY'),2,2,30);
insert into sales values(to_date('02/05/2012','MM/DD/YYYY'),2,2,40);

insert into products values (1,'Meat','Ground Beef');
insert into products values (2,'Cheese','Chedder');

Imagine that your company previously sold only meats and in February 2012 you just started selling cheese as well.  You know it, but the optimizer can’t.  Here is the plan showing that the optimizer thinks that half of the four sales rows meet the criteria:

-----------------------------------------------
| Id  | Operation          | Name     | Rows  |
-----------------------------------------------
|   0 | SELECT STATEMENT   |          |     2 |
|*  1 |  HASH JOIN         |          |     2 |
|*  2 |   TABLE ACCESS FULL| PRODUCTS |     1 |
|*  3 |   TABLE ACCESS FULL| SALES    |     3 |
-----------------------------------------------

This was run with histograms on all the columns, 100% sample size, and a multi-column histogram on sales_date and product number so the optimizer has about as much information as I can give it.  The example script for this and its log is in this zip.

This is a simple example, but my intuition tells me it has profound implications on how limited SQL optimization truly is.  Expand this example to hundreds of tables and columns with many complex real world relationships between the data.  What type of optimizer can quickly optimize an arbitrary query with all these complex interrelations?  I still have a lot to learn about all the things you can do to help the optimizer come up with the best plans without manually overriding it with hints or rewriting the code, but I think that ultimately manual intervention is required in many cases because of the fundamental limitations of SQL optimizer, not only in Oracle’s software, but in any possible SQL database implementation.  Of course there are many new features with each release of Oracle’s flagship RDBMS software and future research will probably come up with all kinds of clever ways to improve SQL optimization.  But my gut tells me that no optimizer that runs fast can ever be built that can always make the right decision when complex relationships exist between data in multiple tables.

– Bobby

 

Posted in Uncategorized | 4 Comments

use_nl and use_hash hints for inner tables of joins

Spent several hours practicing my Introduction to SQL Tuning talk yesterday and it has made me think through the things I’m saying.  I don’t want to lead anyone astray by my talk, or the things I’ve posted on this blog.  I’ve found something about the use_nl and use_hash hints that I’ve misunderstood for some time.  I thought that these hints specified both the outer and inner tables in the join.  Another way to say this is the left and right table in the join.  But, according to the documentation and my testing these hints really do work on the inner table of the join only.

In my example, which I used in the paper and the slides I wanted the join order to be sales, products, customers but sales and products to be a hash join and the join to customers to be a nested loops.  Here are the hints I used to force this join order and join methods:

/*+ leading(sales products) 
    use_hash(sales products)
    use_nl(products customers) */

Here is the plan:

-----------------------------------------
| Id  | Operation           | Name      |
-----------------------------------------
|   0 | SELECT STATEMENT    |           |
|   1 |  NESTED LOOPS       |           |
|*  2 |   HASH JOIN         |           |
|*  3 |    TABLE ACCESS FULL| SALES     |
|*  4 |    TABLE ACCESS FULL| PRODUCTS  |
|*  5 |   TABLE ACCESS FULL | CUSTOMERS |
-----------------------------------------

Now this is the right plan and I think that is why it is so confusing.  But you can accomplish the same thing by just listing the intended inner tables – products for the hash join and customers for the nested loops join.  Here is the simplified and correct hint – which produces the same plan:

/*+ leading(sales products) 
    use_hash(products)
    use_nl(customers) */

Just to prove these really are the inner tables I tried using sales in the hash join hint and products in the use_nl hint:

/*+ leading(sales products) 
    use_hash(sales)
    use_nl(products) */

-----------------------------------------
| Id  | Operation           | Name      |
-----------------------------------------
|   0 | SELECT STATEMENT    |           |
|*  1 |  HASH JOIN          |           |
|   2 |   NESTED LOOPS      |           |
|*  3 |    TABLE ACCESS FULL| SALES     |
|*  4 |    TABLE ACCESS FULL| PRODUCTS  |
|*  5 |   TABLE ACCESS FULL | CUSTOMERS |
-----------------------------------------

Notice that now we use a nested loops join with products as the inner table and sales as the outer table.  Sales can’t be the inner table of a join because of the leading hint so the use_hash hint is ignored and customers is the inner table of the hash join which is just something the optimizer chose on its own.

So, bottom line is when you are specifying use_hash or use_nl hints only list the inner or right table of your join.

– Bobby

Posted in Uncategorized | 11 Comments