August 23, 2011 at 6:05 am
It's executed one per row. Hence 86K times.
The reason it "becomes" slow is that 86K times 0.01 sec is still 8.6 seconds.
August 23, 2011 at 6:07 am
Does the function query other tables? Then it doesn't matter how efficient the query inside it is when it has to execute 86000 times.
Look in the execution plan you posted and look for the "Compute Scalar" operator that executes the function. The "in-line" contains 86000 rows.
Edit: Ninja beat me
Edit 2: Here's a screen shot of the plan where the scalar function is executed
August 23, 2011 at 6:32 am
thank you both. I am trying to figure out the compute scalar operator among so many operators, so meanwhile I am attaching the schema for the procedure which I never posted.
Thanks
Chandan
August 23, 2011 at 6:58 am
I found the operator from the plan which you talked about. As per your remark, this function is getting executed 85k+ times. Not sure, how to avoid this.
Can you please guide me.
Chandan
August 23, 2011 at 7:01 am
Its probably in the view vw_D1OrderHeader.
August 23, 2011 at 7:05 am
Nils Gustav Stråbø (8/23/2011)
Its probably in the view vw_D1OrderHeader.
Yes. you were faster enough to leave me behind in editing my post. It is indeed the part of the view. What do you see as a quick cure to the problem. I think I should somehow get rid of the function in the view. the view is based on 5 costly tables each having million plus rows.
August 23, 2011 at 7:07 am
5M rows is not much. Should run pretty fast if you can avoid that function...
August 23, 2011 at 7:13 am
I don't know how the function looks like, but my suggestion is to create a new inline table variable function and then use CROSS APPLY/OUTER APPLY (depending on whether each passed parameter returns a row or not, similar to inner outer join) to call it in the view.
August 23, 2011 at 7:20 am
The view itself is nasty:
SELECT
b.CarrierCode,b.Descr as CarrierDescr, a.Comment, a.InternalCmt, RTRIM(e.CustName) as CustName, e.CustNum, a.DeliveryDate, Year(a.DeliveryDate) as DeliveryYear, a.InvDate,
a.InvoiceAmt, a.OrderID, a.OrderStatus, a.Po as PONumber, a.ReleaseNum,
c.ShipTo, c.ShipToName, d.Line1 as ShipToLine1, d.Line2 as ShipToLine2, d.AddrName as ShipToAddress, d.City as ShipToCity, d.Country as ShipToCountry, d.State as ShipToState,
d.ZipCode as ShipToZipCode,
dbo.fn_GetDispatchStatus(a.OrderID,a.Dispatched) as DispatchStatus,
a.Time1 as WindowStart, a.Time2 as WindowEnd, a.GUID as OrderGuid, c.CustomerID, b.VendorID,
a.DeliveryTicketNo,a.Dispatched, 0 as RecordCount
FROM dbo.Orders a (NOLOCK)
JOIN dbo.Carrier b (NOLOCK) ON b.CarrierID = a.CarrierID
JOIN dbo.Shipto c (NOLOCK) ON c.ShipToID = a.ShipToID
JOIN dbo.Address d (NOLOCK) ON d.AddressID = c.AddressID
JOIN dbo.Customer e (NOLOCK) ON e.CustomerID = c.CustomerID
It is using function as one of the select columns and it means it will get called so many times in the view itself.
Any clue how to avoid the function here?
Thanks for your help.
August 23, 2011 at 7:42 am
Here is an example on how inline functions outperforms a scalar function. the example is quite silly, but it illustrates the differences, and how scalar and inline functions are used.
If you look at the query plan for the inline example, you'll see that there is no call to the function itself. It is "merged" into the calling SQL.
In the example using the scalar function you will se that the function is called 100000 times, and if you run Profiler (inlcude SP:Starting) while the query is running you are in for a shock.
use tempdb
go
create table dbo.Orders(OrderId int identity primary key, OrderStatus tinyint not null)
go
-- Insert 100000 orders
Insert into Orders(OrderStatus)
select top 100000 t.c
from sys.columns a,sys.columns b
cross apply(select cast(newid() as binary(16)) & 1)t(c)
go
-- create a scalar function that reads order status
create function dbo.GetOrderStatus_scalar(@OrderId int)
returns tinyint
as
begin
declare @status tinyint
select @status=OrderStatus from dbo.Orders where OrderId=@OrderId
return @status
end
go
-- create an inline table valued function that reads order status
create function dbo.GetOrderStatus_inline(@OrderId int)
returns table
as
return(select OrderStatus from dbo.Orders where OrderId=@OrderId)
go
-- Execute a query using the scalar function. On my pc this takes 25 seconds, and performs 1.750.0000 reads and 27.000 writes!! YIKES!!
-- you have to use Profiler to see the actual reads and writes.
select OrderId,dbo.GetOrderStatus_scalar(OrderId) as OrderStatus
from dbo.Orders
-- And the execute a similar query using the inline function. This takes less than a second, with 698 logical reads, 0 writes!!!!
select o.OrderId,s.OrderStatus
from dbo.Orders o
cross apply dbo.GetOrderStatus_inline(o.OrderId) s
August 23, 2011 at 7:47 am
Same train of thoughts but with triggers. I wrote this 4 years ago but it still apply to this case => http://www.simple-talk.com/sql/t-sql-programming/rbar--row-by-agonizing-row/
August 23, 2011 at 8:31 am
Thanks to both of you. I will learn with the examples you provided and try to implement an inline function.
Meanwhi8le, the function was being used to display 'dispatchstatus'. To test the stuff, i got rid of this column so this function was never called in my procedure. I created a dummy procedure having everything as same as original but avoided using the column which uses function( did not use the function in view definition).
It gave me 20 seconds benefit but it still runs for 4 min 🙁
Table 'Address'. Scan count 0, logical reads 516864, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Customer'. Scan count 0, logical reads 12826152, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Shipto'. Scan count 0, logical reads 19323072, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Carrier'. Scan count 0, logical reads 14207632, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#7E8FF20D'. Scan count 2, logical reads 3551908, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 2, logical reads 10893249, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
I am posting the new execution plan here. I know some tables are being scanned because in where clause we have something like '%value% but still i think it is doing something which can be avoided.
August 23, 2011 at 10:15 am
I'm not certain this will help in your case, but I noticed that you have a variable named @ipOrderGuid which is declared as a varchar(max)
You might try changing the type of this variable to be the same type as the orderGuid column in Orders.
Another thing I'd recommend is refactoring the
(SELECT count(*) FROM @ipOrderStatusCodes WHERE...)=0
block in the header insert so that you pre-calculate the counts. I don't think this will improve your performance significantly, but it will simplify your plan a lot, making it much easier to zero in on your core problem...
You're already precalculating the count for all status codes, do the same for each of those.
After you make that change, please repost your plan.
Dan Guzman - Not the MVP (7/22/2010)
All questions have to be prefaced by Server version and 'according to MS Docs' or 'my own personal opinion based on how much detail I felt like digging into at the time.'
August 23, 2011 at 12:13 pm
weitzera (8/23/2011)
I'm not certain this will help in your case, but I noticed that you have a variable named @ipOrderGuid which is declared as a varchar(max)You might try changing the type of this variable to be the same type as the orderGuid column in Orders.
Another thing I'd recommend is refactoring the
(SELECT count(*) FROM @ipOrderStatusCodes WHERE...)=0
block in the header insert so that you pre-calculate the counts. I don't think this will improve your performance significantly, but it will simplify your plan a lot, making it much easier to zero in on your core problem...
You're already precalculating the count for all status codes, do the same for each of those.
After you make that change, please repost your plan.
I have changed the data type of variables on the same lines as the view(or base tables).It can surely help us. But i did not understand your point where you talked about 'refactoring' . Can you please explain it a bit.
Regards
Chandan
August 23, 2011 at 1:20 pm
On or around line 287 you have a bunch of subqueries selecting counts from the @ipOrderStatusCodes with no outer references (meaning the subquery does not reference anything in the main query, so it will be the same value for every row.)
I think you should change these lines like this:
((SELECT count(*) FROM @ipOrderStatusCodes WHERE StatusCode='O' AND SubCode='Declined')=0 OR (a.OrderStatus = 'O' AND DispatchStatus='Declined')) AND ...
becomes:
(@ipOrderStatusCodeCountODeclined=0 OR (a.OrderStatus = 'O' AND DispatchStatus='Declined')) AND ...
On line 193 where you calculate the total order status count, you can precalculate each of these values:
DECLARE @ipOrderStatusCodeCountODeclined INT;
SELECT @ipOrderStatusCodeCountODeclined=count(*) FROM @ipOrderStatusCodes WHERE StatusCode='O' AND SubCode='Declined';
and so on...
Again, I don't think this is affecting your performance, but it is making it harder to read the plan.
Dan Guzman - Not the MVP (7/22/2010)
All questions have to be prefaced by Server version and 'according to MS Docs' or 'my own personal opinion based on how much detail I felt like digging into at the time.'
Viewing 15 posts - 16 through 30 (of 39 total)
You must be logged in to reply to this topic. Login to reply