Problem: When I was design/performance reviewing an ISV ODBC application, I turned on SQL Server profiler and noticed that the application was using server cursors. This application simply sent a select query to the server and read all the results into its local cache. So I asked the application developer why he was asking for a server cursor instead of a default result set. It turned out that the developer did not explicitly ask for a server cursor. But when he did block fetches, as a side effect, the SQL Server ODBC driver asked for a server cursor...that is unexpected! So I went to my favorite search engine and queried on “SQL_ATTR_ROW_ARRAY_SIZE firehose” and I found out I was not alone asking the question: How can I do block fetches over a Default Result Set (fire hose cursor) instead of a server cursor?
Solution: It turns out that ODBC determines whether a server cursor or a Default Result Set should be used during prepare/execute time. If the multiple array binding happens before prepare/execute (row array size > 1), server cursor is used by ODBC. It could even re-prepare if the binding with array size > 1 is left before the next execute. So the solution is to temporarily set the row array size to 1 before the prepare/execute/re-execute and reset it back to the >1 value before fetching. That way, you get a fire hose cursor and the block-fetch behavior.
Results: When we made this change to the ISV application and executed a query returning 500K rows we noticed a 33% improvement (reduction) in elapsed time to process the result set - it cut down 1130 round-trip server cursor fetch calls to 0! ----------------------------------------------------------------- -- SQL Server Profiler Trace with Block Fetch over Server Cursors -- Notice multiple sp_cursorfetch calls ----------------------------------------------------------------- declare @p1 int set @p1=1073741825 declare @p2 int set @p2=180150003 declare @p5 int set @p5=4 declare @p6 int set @p6=1 declare @p7 int set @p7=-1 exec sp_cursorprepexec @p1 output,@p2 output,N'@P1 char(2)',N'SELECT au_lname FROM pubs.dbo.authors where state = @P1',@p5 output,@p6 output,@p7 output,'CA' select @p1, @p2, @p5, @p6, @p7 exec sp_cursorfetch 180150003,2,0,10 -- there could be 100's of these calls exec sp_cursorfetch 180150003,2,0,10 ... ... exec sp_cursorexecute 1073741825,@p2 output,@p3 output,@p4 output,@p5 output,'UT' select @p2, @p3, @p4, @p5 exec sp_cursorfetch 180150005,2,0,10 ... ... exec sp_cursorunprepare 1073741825 ---------------------------------------------------------------------- -- SQL Server Profiler Trace with Block Fetch over a Default Result Set -- Notice there are no sp_cursorfetch calls ---------------------------------------------------------------------- declare @p1 int set @p1=1 exec sp_prepexec @p1 output,N'@P1 char(2)',N'SELECT au_lname FROM pubs.dbo.authors where state = @P1','CA' exec sp_cursorexecute 1073741825,@p2 output,@p3 output,@p4 output,@p5 output,'UT' select @p2, @p3, @p4, @p5 exec sp_execute 1,'UT' exec sp_unprepare 1
Siva Raghupathy ISV Program Manager SQL Server Development Team