I would like to know if the following is an error in our setup or a bug in SQL Server: if we run a certain stored procedure with three parameters, it takes about 3 minutes.
CREATE PROCEDURE [dbo].[ourProcedure]
#param1 INT,
#param2 INT,
#param3 DATETIME
AS
BEGIN...
If we run the same procedure, but in the creation we have created local copies of the parameters, it takes only 11 seconds!
CREATE PROCEDURE [dbo].[ourProcedure]
#param1_x INT,
#param2_x INT,
#param3_x DATETIME
AS
BEGIN
DECLARE #param1 INT
DECLARE #param2 INT
DECLARE #param3 DATETIME
#param1 = #param1_x
#param2 = #param2_x
#param3 = #param3_x
...
Can someone tell my WHY? Why doesn't SQL Server handle parameters like C#?
This is what is usually called "parameter sniffing". The thing is that SQL Server optimizer uses values of the parameters along with internal statistical information on distribution of values to estimate cardinalities for the values passed into the procedure, and produce an execution plan. However, this can go wrong for many reasons. Other point to make is that the execution plans are cached so the procedure is being optimized just the first time it's executed. If parameter used for optimizing the procedure are different enough from the current ones it could lead to bad performance.
Optimizer cannot use values of variables to do the same, because the variable assignement is part of the very same batch that's being optimized and not known beforehand. In this case it uses heuristics and averages which results probably in different execution plan.
In short, you have two different execution plans here, the first one also probably optimized for different set of parameter values.
Related
I have a simple stored procedure that has parameter
CREATE Procedure GetSupplierForTesting
(#SupplierId INT)
AS
SELECT SuppLabel
FROM Supplier
WHERE Supplier.SupplierId = #SupplierId
I am able to call it with the exec command inside another stored procedure like this
exec GetSupplierForTesting #SupplierId = 10
I came across an article that explains how sp_executesql is faster than exec. My problem is that I don't know how to call a stored procedure that has parameters with sp_executesql. I have tried this code
DECLARE #SupplierId INT = 10;
EXEC sp_executesql N'GetSupplierForTesting', N'#SupplierId INT', #SupplierId
but I am getting an error:
Procedure or function 'GetSupplierForTesting' expects parameter '#SupplierId', which was not supplied
The syntax you would need would be
DECLARE #SupplierId INT = 10;
EXEC sys.sp_executesql N'GetSupplierForTesting #SupplierId=#SupplierId',
N'#SupplierId INT',
#SupplierId=#SupplierId
But don't do this. It is utterly pointless. There is no magic performance increase to be expected from using sp_executesql to basically wrap the same exec statement and execute it at a different scope.
Just use
exec dbo.GetSupplierForTesting #SupplierId = 10
1
Performance issue is based on assumption that when you are using non-parametrized ad-hoc query, it (the string representing this query) will be different every time - because specific argument values are parts of query text.
Whilst parametrized query keeps it's body unchanged because in place of where ... and title="asdf" you have where ... and title = #title. Only contents of variable #title change. But query text persists and sql server realizes that there is no need to recompile it.
Non-parametrized query will be recompiled every time you change values used in it.
2
You are getting exception because your script does not pass any arguments to the stored proc.
Your script is: 'GetSupplierForTesting' - that's it.
By passing arguments to sp_executesql you are passing them to the scipt. Not to the sp used in script, but to the script itself. E.g.:
exec sp_executesql N'print #val', N'#val int', #val = 1
this script does utilize variable #val. Your - does not. It just contains name of the proc. So your script corrected should look like
exec sp_executesql
N'exec GetSupplierForTesting #Supplier = #Supplier_id_value',
N'#Supplier_id_value int',
#Supplier_id_value = 10
script contains code calling your sp and this code passes argument to sp with value taken from #Supplier_id_value variable
#Supplier_id_value is declared as int for internals of this script
value of 10 is passed to the argument of the script
3
Performance issue you are talking about is about ad-hocs, not SPs.
Another face of this issue is parameter sniffing problem. Sometimes with specific param values your script or SP should use another execution plan, different from the plan it used for previously passed param values.
Every time recompiled ("slowly executed") ad-hoc would be (re)compiled for sure and probably would get better execution plan while SP or parametrized query would probably not be recompiled and would use worse, less optimal execution plan and would finally perform much slower than "slow because of recompilation" ad-hoc query.
There are no "write this - and it will work slowly", "write that - and it will hurtle like a rocket" rules in sql. It all depends on many factors. Sometimes one would probably need specifically ad-hocs, sometimes - should avoid them totally.
I am trying to run an inline TVF as a raw parameterized SQL query.
When I run the following query in SSMS, it takes 2-3 seconds
select * from dbo.history('2/1/15','1/1/15','1/31/15',2,2021,default)
I was able to capture the following query through SQL profiler (parameterized, as generated by Entity framework) and run it in SSMS.
exec sp_executesql N'select * from dbo.history(#First,#DatedStart,#DatedEnd,#Number,#Year,default)',N'#First date,#DatedStart date,#DatedEnd date,#Maturity int,#Number decimal(10,5)',#First='2015-02-01',#DatedStart='2015-01-01',#DatedEnd='2015-01-31',#Year=2021,#Number=2
Running the above query in SSMS takes 1:08 which is around 30x longer than the non parameterized version.
I have tried adding option(recompile) to the end of the parameterized query, but it did absolutely nothing as far as performance. This is clearly an indexing issue to me, but I have no idea how to resolve it.
When looking at the execution plan, it appears that the parameterized version mostly gets mostly hung up on an Eager Spool (46%) and then a Clustered Index scan (30%) which are not present in the execution plan without parameters.
Perhaps there is something I am missing, can someone please point me in the right direction as to how I can get this parameterized query to work properly?
EDIT: Parameterized query execution plan, non-parameterized plan
Maybe it's a parameter sniffing problem.
Try modifying your function so that the parameters are set to local variables, and use the local vars in your SQL instead of the parameters.
So your function would have this structure
CREATE FUNCTION history(
#First Date,
#DatedStart Date,
#DatedEnd Date,
#Maturity int,
#Number decimal(10,5))
RETURNS #table TABLE (
--tabledef
)
AS
BEGIN
Declare #FirstVar Date = #First
Declare #DatedStartVar Date = #DatedStart
Declare #DatedEndVar Date = #DatedEnd
Declare #MaturityVar int = #Maturity
Declare #NumberVar decimal(10,5) = #Number
--SQL Statement which uses the local 'Var' variables and not the parameters
RETURN;
END
;
I've had similar probs in the past where this has been the culprit, and mapping to local variables stops SQL Server from coming up with a dud execution plan.
I have strange issue in my win-form application where I am calling a stored procedure and it takes about 6 seconds to execute.
(This stored procedure accepts several parameters including one output parameter)
From application level I used :
Dim dt1 = DateTime.Now
cmd.ExecuteNonQuery()
MessageBox.Show(DateTime.Now.Subtract(dt1).Seconds)
This is about 5-6 seconds
I have tried running the same stored procedure with same parameters on sql-server and it takes no time to execute:
declare #val decimal
exec mysp 'value1','value2','value3','value4',#val out
select #val
I am not sure what the issue is or where to start.
The issue with difference between calling SP directly and from .NET code, maybe due to parameter sniffing. SQL Server maybe caching execution plan that is not optimal for the parameters you're passing from code.
To avoid this try adding WITH RECOMPILE to your SP definition, e.g.
CREATE PROCEDURE MySP (
... parameters...
) WITH RECOMPILE
AS
BEGIN
...
It could be an issue if your stored procedure expects say nvarchar and you use a varchar. SQL server will accept the parameters but will be forced to do a conversion. Do you have more specifics?
Why does sp_executesql REQUIRE ntext/nchar/nvarchar parameters and statements as opposed to text/char/varchar?
I understand how to work around this issue (by having all statements/parameters be declared as nvarchar), but why does Microsoft impose this? What are the benefits?
I'm not sure that this is enforced for any particular reason specific to that procedure. Another example of a system stored procedure that is fussy about datatypes is sp_trace_create.
Not only does this demand an nvarchar for the #tracefile parameter but also the parameter #maxfilesize must be passed as 'bigint' and it doesn't accept a literal integer which would always be able to cast fine.
This Fails
DECLARE #TraceID int
EXEC sp_trace_create #TraceID OUTPUT, 0, N'X:\Foo', 1, NULL
This Succeeds
DECLARE #TraceID int
DECLARE #x bigint = 1
EXEC sp_trace_create #TraceID OUTPUT, 0, N'X:\Foo', #x, NULL
Both of these show up as System Extended Stored Procedures in the master database. I assume that the calling mechanism for these extended stored procedures is different from that used for regular stored procedures such that SQL Server doesn't implicitly cast the parameters.
It isn't necessarily a bad thing that sp_executesql enforces nvarchar like that though. One potential problem that can occur with dynamic SQL is that if a string is supplied as nvarchar then sanitised then coerced to varchar the conversion can open up SQL injection opportunities. An example of that is in my answer here.
I need to use the same query twice but have a slightly different where clause. I was wondering if it would be efficient to simply call the same stored proc with a bit value, and have an IF... ELSE... statement, deciding on which fields to compare.
Or should I make two stored procs and call each one based on logic in my app?
I'd like to know this more in detail though to understand properly.
How is the execution plan compiled for this? Is there one for each code block in each IF... ELSE...?
Or is it compiled as one big execution plan?
You are right to be concerned about the execution plan being cached.
Martin gives a good example showing that the plan is cached and will be optimized for a certain branch of your logic the first time it is executed.
After the first execution that plan is reused even if you call the stored procedure (sproc) with a different parameter causing your executing flow to choose another branch.
This is very bad and will kill performance. I've seen this happen many times and it takes a while to find the root cause.
The reason behind this is called "Parameter Sniffing" and it is well worth researching.
A common proposed solution (one that I don't advice) is to split up your sproc into a few tiny ones.
If you call a sproc inside a sproc that inner sproc will get an execution plan optimized for the parameter being passed to it.
Splitting up a sproc into a few smaller ones when there is no good reason (a good reason would be modularity) is an ugly workaround. Martin shows that it's possible for a statement to be recompiled by introducing a change to the schema.
I would use OPTION (RECOMPILE) at the end of the statement. This instructs the optimizer to do a statement recompilation taking into account the current value of all variables: not only parameters but local variables are also taken into account which can makes the difference between a good and a bad plan.
To come back to your question of constructing a query with a different where clause according to a parameter. I would use the following pattern:
WHERE
(#parameter1 is null or col1 = #parameter1 )
AND
(#parameter2 is null or col2 = #parameter2 )
...
OPTION (RECOMPILE)
The down side is that the execution plan for this statement is never cached (it doesn't influence caching up to the point of the statement though) which can have an impact if the sproc is executed many time as the compilation time should now be taken into account. Performing a test with production quality data will give you the answer if it's a problem or not.
The upside is that you can code readable and elegant sprocs and not set the optimizer on the wrong foot.
Another option to keep in mind is that you can disable execution plan caching at the sproc level (as opposed to the statement level) level which is less granular and, more importantly, will not take into account the value of local variables when optimizing.
More information at
http://www.sommarskog.se/dyn-search-2005.html
http://sqlinthewild.co.za/index.php/2009/03/19/catch-all-queries/
It is compiled once using the initial value of the parameters passed into the procedure. Though some statements may be subject to deferred compile in which case they will be compiled with whatever the parameter values are when eventually compiled.
You can see this from running the below and looking at the actual execution plans.
CREATE TABLE T
(
C INT
)
INSERT INTO T
SELECT 1 AS C
UNION ALL
SELECT TOP (1000) 2
FROM master..spt_values
UNION ALL
SELECT TOP (1000) 3
FROM master..spt_values
GO
CREATE PROC P #C INT
AS
IF #C = 1
BEGIN
SELECT '1'
FROM T
WHERE C = #C
END
ELSE IF #C = 2
BEGIN
SELECT '2'
FROM T
WHERE C = #C
END
ELSE IF #C = 3
BEGIN
CREATE TABLE #T
(
X INT
)
INSERT INTO #T
VALUES (1)
SELECT '3'
FROM T,
#T
WHERE C = #C
END
GO
EXEC P 1
EXEC P 2
EXEC P 3
DROP PROC P
DROP TABLE T
Running the 2 case shows an estimated number of rows coming from T as 1 not 1000 because that statement was compiled according to the initial parameter value passed in of 1. Running the 3 case gives an accurate estimated count of 1000 as the reference to the (yet to be created) temporary table means that statement was subject to a deferred compile.