March 12, 2018 at 1:05 pm
I tried replacing the INNER JOIN by a CROSS APPLY and the statement runs fast again:
SELECT F.SomeValue, T.SomeValue
FROM fnGetData() F
CROSS APPLY (SELECT SomeValue FROM Table WHERE ID = F.TableID) T
March 12, 2018 at 4:02 pm
In general, it is NOT a good practice to add any kind of join or table hints to force the query to do things it normally wouldn't do. In SOME cases it may help, but there's no general rule telling you which cases those are. The query optimizer puts a lot of work into choosing the correct query plan, and if you can beat it just by adding a hint then the optimizer is probably being misled by something. Outdated statistics, missing indexes, parameter sniffing, bad cardinality estimates, unnecessary table scans? There are many possible causes. You need a deeper understanding of the query to figure out what is going on.
Look at the estimated query plan. If you have two versions of the query with different run times, put them in the same query pane and press CTRL-L. How does the LOOP JOIN plan differ from the CROSS APPLY plan? Does the cost estimate for both plans match the observed performance difference? Try comparing the actual plans. How do estimated rowcounts at each step compare to the actual rowcounts? Capture query statistics (SET STATISTICS TIME ON and SET STATISTICS IO ON) to get more detail. If you have SELECT queries with large rowcounts, try adding " INTO #junk " so you're not measuring the time required to send the results over the network to populate a grid in Management Studio.
The best way to figure out what is going wrong with your query is to examine the execution plan, and the best way to learn how to read execution plans is to see what plans are produced by variations in a given query and why the query optimizer is sometimes so wrong about how to handle the query.
March 13, 2018 at 8:00 am
Thanks Scott,
The problem is that I think there is nothing wrong with my statement. The base function returns keys and dates. It is quite complex but performs very well. The problem arises when I want to perform an extra join to get the name of a person by primary key.
Writing «INNER JOIN Person P ON P.ID = F.PersonID» seems the right thing to do but, as mentioned, the statement performs poorly (15 minutes instead of fraction of a second). Writing «INNER LOOP JOIN Person P ON P.ID = F.PersonID», «INNER MERGE JOIN Person P ON P.ID = F.PersonID», «INNER HASH JOIN Person P ON P.ID = F.PersonID» or even «CROSS APPLY (SELECT Name FROM Person WHERE ID = F.PersonID) P» restores the performance.
Execution plans are obviously different and they are quite complex, because of the base function and there is no index issue here since its all primary key based. It seems to me that there is a flaw in SQL Server plan builder (I did not test more recent versions of SQL Server but I suspect I might get different results) and I was wondering if anyone else ever had the similar issues.
I will fix the issue by using «CROSS APPLY» since I am not comfortable tweaking join types.
March 13, 2018 at 11:15 am
cmartel 20772 - Tuesday, March 13, 2018 8:00 AMThanks Scott,The problem is that I think there is nothing wrong with my statement. The base function returns keys and dates. It is quite complex but performs very well. The problem arises when I want to perform an extra join to get the name of a person by primary key.
Writing «INNER JOIN Person P ON P.ID = F.PersonID» seems the right thing to do but, as mentioned, the statement performs poorly (15 minutes instead of fraction of a second). Writing «INNER LOOP JOIN Person P ON P.ID = F.PersonID», «INNER MERGE JOIN Person P ON P.ID = F.PersonID», «INNER HASH JOIN Person P ON P.ID = F.PersonID» or even «CROSS APPLY (SELECT Name FROM Person WHERE ID = F.PersonID) P» restores the performance.
Execution plans are obviously different and they are quite complex, because of the base function and there is no index issue here since its all primary key based. It seems to me that there is a flaw in SQL Server plan builder (I did not test more recent versions of SQL Server but I suspect I might get different results) and I was wondering if anyone else ever had the similar issues.
I will fix the issue by using «CROSS APPLY» since I am not comfortable tweaking join types.
I'm pretty sure there is actually something wrong, or you wouldn't be having this problem in the first place. Clearly, it's not just the query that is the problem. If the optimizer is choosing a bad execution plan, then you need to fix the cause. My guess would be out of date statistics, but you'll need to determine when they were last updated. I'm all for being practical and going ahead and using CROSS APPLY, but it really should bother you that this occurred, until you have investigated all the possible causes.
Steve (aka sgmunson) 🙂 🙂 🙂
Rent Servers for Income (picks and shovels strategy)
March 13, 2018 at 11:38 am
Probably most of the people on this site have had a similar issue at least once. Sometimes a major restructuring of a query does not fool the query optimizer at all and you get an identical plan, and other times some trivial change makes it perform horribly. There is not one simple answer that explains all these cases, the only way I know of to figure out why two versions of the same query perform differently is to compare the execution plans.
Usually there is a reason the query optimizer seems to choose the wrong plan, such as outdated statistics or massive table fragmentation. It may reject a good plan because it overestimates the cost of some action, or choose a bad plan because it underestimates the cost. It can cache a query plan with one set of parameters, then reuse it with another set of parameters that make the plan a very bad choice. These are not flaws in the optimizer, but the result of design choices to let it usually pick a good enough plan in most cases in a reasonable amount of time. You can help it by making sure statistics are updated, tables and indexes are not fragmented, and queries are no more complex than they need to be.
If you want to know why the query optimizer is not getting the job done in this case, you have to do the hard work of going through the plans. Just going ahead with the CROSS APPLY version if it meets your requirements is also a reasonable choice.
March 13, 2018 at 12:38 pm
cmartel 20772 - Monday, March 12, 2018 1:05 PMI have a simple case where I retrieve data using an inline table-valued function, while joining a table using its primary key. Things were going as fast as expected until the database grew up. My function currently returns about 100,000 rows while the joined table contains about 10,000 rows and the following statement that was executing in less than a second now executes in minutes.SELECT F.SomeValue, T.SomeValue
FROM fnGetData() F
INNER JOIN Table T ON T.ID = F.TableIDI tried qualifying the INNER JOIN by specifying LOOP, MERGE and HASH and using ANY of the 3 types makes my statement run fast again.Is it a good practice to qualify the joins (if the seed of a statement is a function) ?
Which type is better ?
Is there something else I am missing ?I tried replacing the INNER JOIN by a CROSS APPLY and the statement runs fast again:
SELECT F.SomeValue, T.SomeValue
FROM fnGetData() F
CROSS APPLY (SELECT SomeValue FROM Table WHERE ID = F.TableID) T
Is it the fact that you added a join hint or the fact that adding the join hint caused a recompile that may have gotten you over the hump with a temporary bout with "bad parameter sniffing"? I suspect it was the resulting recompile. Updating stats would probably have cause the same effect.
--Jeff Moden
Change is inevitable... Change for the better is not.
March 14, 2018 at 7:51 am
Thanks!
Viewing 7 posts - 1 through 6 (of 6 total)
You must be logged in to reply to this topic. Login to reply