As I said in a comment, it sounds like parameter sniffing, but in the interest of being helpful I thought I'd expand on that. There are a number of articles on the web that
go into a lot more detail than I will, but the long and the short of parameter sniffing is that SQL-Server has cached an execution plan based on a value for the parameter that does not yield the best execution plan for the current value.
Supposing that Col1
has a nonclustered index on, but does not include col2
or col3
as non key columns then
SQL-Server has two options, it can either do a clustered index scan on My_Table
to get all the rows where Col1 LIKE @letter+'%'
, or it can search the index on Col1
then do a bookmark lookup on the clustered index to get the values
for each row returned by the index. I can't quite remember off the top of my head at what point SQL-Server switches between the two based on the estimated row count, it is at quite a low percentage, so I am fairly sure that if you are returning 150,000 records
out of 2,500,000 the optimiser will go for a clustered index scan. However, if you were only returning a few hundred rows then a bookmark lookup would be preferable.
When you don't use parameters SQL-Server will create a new execution plan each time it is executed, and produce the best execution plan for that parameter (assuming your statistics are up to date), when you do use a paramter the first time they query is run sql-server creates a plan
based on that particular parameter value, and stores that plan for later use. Each subsequent time the query is run sql-server recognises that the query is the same so doesn't recompile it. This means though that if the first time the query was run it was for
a parameter that returned a low number of rows then the bookmark lookup plan will be stored. Then if the next time the query is run it is passed for a value that returns a high number of rows where the optimal plan is a clustered index scan then the query is still executed using the suboptimal bookmark lookup and
will result in a longer execution time. This could of course also be true the other way round. There are a number of ways to get around parameter sniffing, but since your query is not very complex the compile time will not be significant, especially in comparison to the 30 seconds you say this query is taking
to run even at its best, so I would use the OPTION RECOMPILE
Query hint:
SELECT Col1, Col2, Col3
FROM ( SELECT ROW_NUMBER() OVER(ORDER BY Col1) AS rownumber,Col1,Col2,Col3
FROM my_table
WHERE Col1 LIKE @letter+'%'
) as somex
WHERE rownumber >= (@page-1)*(@pagesize)
AND rownumber <= (@page) * @pagesize
OPTION (RECOMPILE);
SELECT COUNT(*)
FROM my_table
WHERE Col1 LIKE @letter+'%'
OPTION (RECOMPILE);
The reason that when you tried this on a new server that it executed fine is that the first time it was run on the new server the parameterised query had to be compiled, and the plan generated was suitable to value of the parameter provided.
One final point, if you are using SQL_Server 2012 then you could use OFFSET/FETCH to do your paging:
SELECT Col1, Col2, Col3
FROM My_table
WHERE Col1 LIKE @letter+'%'
ORDER BY Col1 OFFSET (@page-1) * (@pagesize) ROWS FETCH NEXT @pagesize ROWS ONLY;