tags:

views:

271

answers:

8

I have the following query:

SELECT COUNT(*) 
FROM Address adr INNER JOIN 
     Audit a on adr.UniqueId = a.UniqueId
  • on a database (1.3 million addresses, more than 4 million audits)
  • both UniqueId columns are clustered primary keys

The query is taking quite long to complete. I feel dumb, but is there any way to optimize it? I want to count all the address entries that have an underlying auditable.

EDIT: all your inputs are much appreciated, here are some more details:

  • The query will not be run often (it is only for validating), but thanks for the indexed view tip, I will add that to my knowledge for sure.
  • All Addresses have an associated 1-to-1 audit. Not all audits are addresses.
  • The query takes more than 1 minute to finish. I find this too long for a simple count.
A: 

Not sure if it would be faster but you could try the following

SELECT COUNT(adr.UniqueID) FROM Address adr INNER JOIN Auditable a on adr.UniqueId = a.UniqueId

It should give you the same count because unqieieid will never be null.

Ben Robinson
+1  A: 

Is Auditable.UniqueID a foreign key reference to Address.UniqueID, meaning there are no values in Auditable that don't also exist in Address?

If so, this may work and may be faster:

SELECT COUNT(DISTINCT Auditable.UniqueID)
FROM Auditable

Note: This also assumes that UniqueID is unique(/primary key) in the Address table but not unique in the Auditable table

Chris Shaffer
This does not work for me, as there are a lot more auditables than addresses.All addresses must have an auditable, but there are auditables for many other things also. I only want the count of auditables for Adresses.
ibiza
+6  A: 

If you run this query often and it needs to be super fast, create a materialized indexed view of it. There will be a slight overhead on INSERT/UPDATE/DELETEs, but this query will be just about instant. The aggregations can be precomputed and stored in the index to minimize expensive computations during query execution.

Improving Performance with SQL Server 2005 Indexed Views

KM
+1. Note that you should use `COUNT_BIG` instead of `COUNT` for the view to be indexable.
Quassnoi
thanks for the tip :)however that query will not be run often, but I will keep that knowledge for sure
ibiza
SQL Tuning should not rely on physically storing the answers as a first step. Materializing the answer should be a last resort.
Stephanie Page
@Stephanie Page, OP says that the query runs for 1+ minutes, and that the accepted answer of using a join hint makes it `twice as fast` which is no small feat on a query with over 1 million rows and no WHERE clause. However, it still takes 30+ seconds, which is quite slow. My solution would drastically improve the speed of the query to almost instant, so pick your poison.
KM
@KM, yes, let's materialize the answer to every query. It will always be faster. I didn't say that you'd never materialize, only not as a first step... no matter how fast it got with the proper query plan, materialized would always be faster, so your argument is moot. My point still holds, storing redundant copies of data to improve query performance is a last resort. If the first club you reach for is "Just store the results", you'd never know that the Hash wouldn't be fast enough. What if Has was 5 second and material was 4. You'd never know. Cuz you'd store the answers, get 4 sec and stop.
Stephanie Page
Also, I'm amazed that you know the speed of your query without running it.
Stephanie Page
@Stephanie Page, I've read many comments that you posted and you make a lot of assumptions. You assume that I recommend using this technique over standard tuning practices. You assume that this is my first step to tuning. You assume that I want to materialize everything. Chill out. I prefixed my answer with an "IF". I didn't say any of the things you are making assumptions about. I would never recommend this technique as a first step to tuning. I have never actually created an indexed materialized view in any production code of mine.
KM
@Stephanie Page, I have in the past deleted an answer because you were very argumentative and would not let things go and kept commenting without end. I find life too short, move on. I do not want to debate you endlessly on this issue or this answer. I will not respond to any more of your comments on this question.
KM
@KM, Since you didn't suggest standard tuning and did suggest this method, I'm not sure it's fair to say that you weren't recommending this method. What's an answer afterall if not a recommendation to do it *this* way. Yes, you start with IF, as in programming, there's a THEN which is executed upon a true condition... there's not a, BUT condition, like BUT try other normal tuning first. If you meant that, you should say so. It doesn't surprise me you recommend a option you've never used yourself.
Stephanie Page
@KM, I only come here to make sure that the naive are not being steered far off course. Stop the bad advice, I'll stop commenting. Glad to hear you're done responding.
Stephanie Page
A: 

Missing index on the foreign key, I would say.

  • 1.4 million and 4 million are not large tables, they are small. Say large when you go through 500 million entries, please.

  • For a real answer we need the execution plan / query plan, so we can see what happens.

  • And it would be nice to know what "Long" is in your world (given that you think 4 million rows are a lot). This question will not answer in 1 second ever - so what do you expect and what happens?

  • I bet, though, you have a missing index. SHort of that, I would start pointing at the hardware (because I have seen that, too, as the reason for crap performance).

TomTom
A: 

For large tables such as these, you may wish to partition your data to increase query performance. Also, if you haven't already, try running the Tuning Advisor to see if there are any additional indexes that may be advantageous. In addition, have you reorganized your clustered indexes lately -- is it a task that is part of a maintanence package? Many times, this will greatly improve your performance as well.

SideFX
+2  A: 

The real issue is the Nested Loop join. For each 1.4 Million rows in the Address table you're doing an index Seek into the Auditble table. That means 1.4M root block, branch block, and leaf block reads for a total of 4.2M block reads. The entire index is probably only 5K blocks or so... it should be doing a hash join so it reads both indexes once, and hashes through them.

If you think these tables are large, I'm guessing this is on a small box without a lot of memory. You need to make sure that you have enough memory allocated to fit the entire index into memory to make the hash join efficient.

Stephanie Page
I'm assuming the blevel = 2.
Stephanie Page
hi and thanks for your comment. I am running this on a 4core cpu with 4gb ram...not the top but not bad
ibiza
laptops have 4gb ram these days, possibly even netbooks
KM
What kinda database server would you call bad then? 2GB? Quad core is fine, databases need memory, the more the better usually and your case is in the usual. Database servers usually have 16-32GB Minimum. Otherwise almost every read will be one from disk, nothing will be cached. Sorts will all use disk. Hashes will be avoided and Nested loops preferred because a hash needs memory a nested loop is all CPU and i/o.
Stephanie Page
crappy RAM for a the processor. Lots of parallelism (problem), and MOST likely a bad disc subsystem on top -> SQL Server tries to use the processor and kills the discs.
TomTom
Ok this is not on a prod DB server, this is on my dev machine.
ibiza
Ibiza, you can't TUNE queries on Dev boxes that aren't sized the same as prod. A pure waste of time. Any statistics you get, like query A is 100% faster than query B could be exactly the opposite on your full size Prod box.
Stephanie Page
Seriously... it doesn't just scale linearly. You can't draw an assumption.
Stephanie Page
+1  A: 

The clause EXISTS is less expensive to run than an INNER JOIN.

select COUNT(adr.UniqueId)
    from Addresses adr
    where EXISTS (
        select 1
            from Auditables aud
            where aud.UniqueId = adr.UniqueId
    )

Does this suits your need?

N.B. Guids are very expensive for the database engine.

Will Marcouiller
thanks for the pointers, EXISTS gives a performance boost of ~10%
ibiza
+11  A: 

Since you have two sets of data, ordered by the same value.. have you tried a merge join instead of the nested loop join?

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT COUNT(*)  
FROM Address adr INNER JOIN  
     Auditable a on adr.UniqueId = a.UniqueId 
OPTION (LOOP JOIN)

SELECT COUNT(*)  
FROM Address adr INNER JOIN  
     Auditable a on adr.UniqueId = a.UniqueId 
OPTION (MERGE JOIN)

SELECT COUNT(*)  
FROM Address adr INNER JOIN  
     Auditable a on adr.UniqueId = a.UniqueId 
OPTION (HASH JOIN)

Edit:

These explanations are conceptual. SQL Server may be doing more sophisticated operations than my examples show. This conceptual understanding, matched with the measuring of time and logical IO by the SET STATISTICS commands, and examination of query execution plans - form the basis of my query optimizing technique (grown over four years). May it serve you as well as it has me.

Setup

  • Get 5 decks of cards.
  • Take 1 deck and produce a parent data set.
  • Take the other 4 decks and produce the child data set.
  • Order each data set by card value.
  • Let m be the number of cards in the parent data set.
  • Let n be the number of cards in the child data set.

NestedLoop

  • Take a card off the top of the parent data set.
  • Search (using binary search) within the child data set for the first occurence of a match.
  • Seek forward in the child data set from the first match until a non-match is found. You've now found all the matches.
  • Repeat this for each card in the parent data set.

The nested loop algorithm iterates the parent data set, and then searches the child data set once for each parent, making it cost: m * log(n)

Merge

  • Take a card off the top of the parent data set.
  • Take a card off the top of the child data set.
  • If the cards match, pull cards from the top of each deck until a non-match is found from each. Produce each matching pair between the parent and child matches.
  • If the cards do not match, find the smaller between the parent and child cards, and take a card off the top of that data set.

The merge join algorithm iterates the parent data set once and the child data set once, making it cost: m + n. It relies on the data being ordered. If you ask for a merge join on un-ordered data, you will incur an ordering operation! This brings the cost to (m * log(m)) + (n * log(n)) + m + n. Even that might be better than nested loop in some cases.

Hash

  • Get a card table.
  • Take each card from the parent data set and place it on the card table where you can find it (does not have to be anything to do with card value, just has to be convenient for you).
  • Take each card from the child data set, locate its matching parent on the cardboard table and produce the matching pair.

The hash join algorithm iterates the parent data set once and the child data set once, making it cost: m + n. It relies on having a big enough card table to hold the entire contents of the parent data set.

David B
Amazing! Merge join is a 100% performance boost, twice as fast. Thank you very much, I was not aware that we can specify which type of join to use with a query, I will definitely read more on these. Thanks again, I feel this is an acceptable answer :) Do you mind explaining a bit more why the merge join is so much faster?
ibiza
+1 Well, I didn't know about this one! Thanks for providing this solution. I learned from it! @ibiza: Thanks for this comment which states the performance boost to give an order of idea.
Will Marcouiller
it must also be depending on the context...I am wondering how to determine for which case is each type of join the best choice
ibiza
Michael Buen
@Michael Buen: yes, the RDBMS should typically use table statistics to choose the best join algorithm. However, there are situations where the developer must override the optimizer... particularly in the absence of table statistics (in MSSql, table variables are an example. The optimizer always considers table variables to be empty).
David B
Also not mentioned in my answer are these - In typical querying, the filtering criteria yields such a small result set that the join algorithm does not matter. Parallelization changes everything, nested loop is very easy to parallelize. Merge join wins over hash join when both sets are large such that hash join can't afford a table large enough for the "small set".
David B
+1, great answer
KM