In the second installment of this series, we will explore what is arguably the most popular use of ASP – interacting with database content through ADO or ActiveX Data Objects, Microsoft’s versatile and easy to use database interface.The greatest challenge in preparing this article was limiting the scope of the issues to test, since ADO has a fairly large feature set.Therefore, considering that reading large datasets can impose significant loads on a web server, I have decided to limit this study to finding optimal configurations for working with ADO Recordsets.This limitation still presents a challenge, however, since ADO provides many different ways to perform the same functions.For instance, Recordsets can be retrieved not only from the Recordset class, but also from the Connection and Command classes.In addition, once you have a Recordset, there are a multitude of options that can dramatically affect performance.So, as in the first article, I will try cover as many issues as possible.
If you are new to ADO, see some of the reference links at the end of this article for more details.
My objective for this study was to acquire enough information to be able to find answers to the following questions:
· Should I use the ADOVBS.INC include file?
· Should I create a separate Connection object when using a Recordset?
· What is the best way to retrieve a Recordset?
· What are the most efficient Cursor and Lock Types?
· Should I use Disconnected Recordsets?
· What is the best way to set Recordset Properties?
· What is the most efficient way to reference field values in the Recordset?
· Is using a temporary string a good alternative to buffering?
21 Active Server Pages were assembled to perform the tests for this study (click here to retrieve the files).Each page has been configured to run with three different queries returning recordsets of zero, 25, and 250 records.This will help us to isolate performance issues on both loading and iterating through recordsets.To accommodate these variations both the database connection string and the test SQL strings were stored as Application variables in the Global.asa.Since our test database is running on Microsoft SQL Server 7.0, our connection string specifies OLEDB as the connection provider and the Northwind sample database (included with SQL Server) as the current database.Our SQL Select statements are requesting 7 specific fields from the Northwind Orders table.
<SCRIPT LANGUAGE=VBScript RUNAT=Server>
Sub Application_OnStart
Application("Conn") = "Provider=SQLOLEDB; " & _
"Server=MyServer; " & _
"uid=sa; " & _
"pwd=;" & _
"DATABASE=northwind"
Application("SQL") ="SELECTTOP 0OrderID, " & _
"CustomerID, " & _
"EmployeeID, " & _
"OrderDate, " & _
"RequiredDate, " & _
"ShippedDate, " & _
"Freight " & _
"FROM[Orders] "
End Sub
</SCRIPT>
‘alternate sql – 25 records
Application("SQL") ="SELECTTOP 25 OrderID, " & _
"CustomerID, " & _
"EmployeeID, " & _
"OrderDate, " & _
"RequiredDate, " & _
"ShippedDate, " & _
"Freight " & _
"FROM[Orders] "
‘alternate sql – 250 records
Application("SQL") ="SELECTTOP 250 OrderID, " & _
"CustomerID, " & _
"EmployeeID, " & _
"OrderDate, " & _
"RequiredDate, " & _
"ShippedDate, " & _
"Freight " & _
"FROM[Orders] "
Code from global.asa
Our test web server is a dual 450 Mhz Pentium with 512 MB of RAM running NT Server 4.0 SP5, MDAC 2.1 (Data Access Components), and Version 5.0 of the Microsoft Scripting Engine.SQL Server is running on a separate machine with similar specifications.As in the first article, I have used the Microsoft Web Application Stress tool to record the time from the initial page request to the last byte transferred (TTLB –Time To Last Byte) in milliseconds from the server.This test script ran for 20 hours calling each page over 1300 times.The times shown are the average TTLB’s for the session.Keep in mind that, as in the first article, we are attempting to address only performance implications, not scalability or capacity issues.
Also, please note that we have enabled buffering on the server.In addition, I have made all the filenames the same length; therefore, filenames may have one or more underscores to pad the name.
For our first test, we will retrieve a simple recordset using the typical scenario found in a typical Microsoft ASP ADO sample file.In this example, we first create a connection object, then a recordset object.Of course, I have made modifications to the scripts to reflect the Best Practices covered in the Part I of this series.
<% Option Explicit %>
<!-- #Include file="ADOVBS.INC" -->
<%
Dim objConn
Dim objRS
Response.Write( _
"<HTML><HEAD>" & _
"<TITLE>ADO Test</TITLE>" & _
"</HEAD><BODY>" _
)
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = adOpenForwardOnly
objRS.LockType = adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
Response.Write( _
"<TABLE BORDER=1>" & _
"<TR>" & _
"<TH>OrderID</TH>" & _
"<TH>CustomerID</TH>" & _
"<TH>EmployeeID</TH>" & _
"<TH>OrderDate</TH>" & _
"<TH>RequiredDate</TH>" & _
"<TH>ShippedDate</TH>" & _
"<TH>Freight</TH>" & _
"</TR>" _
)
'write data
Do While Not objRS.EOF
Response.Write( _
"<TR>" & _
"<TD>" & objRS("OrderID") & "</TD>" & _
"<TD>" & objRS("CustomerID") & "</TD>" & _
"<TD>" & objRS("EmployeeID") & "</TD>" & _
"<TD>" & objRS("OrderDate") & "</TD>" & _
"<TD>" & objRS("RequiredDate") & "</TD>" & _
"<TD>" & objRS("ShippedDate") & "</TD>" & _
"<TD>" & objRS("Freight") & "</TD>" & _
"</TR> " _
)
objRS.MoveNext
Loop
Response.Write("</TABLE>")
End If
objRS.Close
objConn.Close
Set objRS = Nothing
Set objConn = Nothing
Response.Write("</BODY></HTML>")
%>
Code from ADO__01.asp
Here are the results:
![]()
Let’s review what the numbers in each column represent:
|
0 |
Represents the TTLB (Time To Last Byte) in milliseconds
when running the query that returns 0 records.In all our tests, this number is used as the overhead of the
page or the time it takes to load the page and create the objects but not
iterate through the data. |
|
25 |
TTLB in milliseconds to load and display 25 records. |
|
tot time/25 |
TTLB in milliseconds divided by 25 records.This represents the total average time per
record |
|
disp time/25 |
TTLB in milliseconds minus the TTLB for the “0” column
divided by the 25 records.This
represents the time to display each record within the recordset iteration. |
|
250 |
TTLB in milliseconds to load and display 250 records. |
|
tot time/250 |
TTLB in milliseconds divided by 250 records.This represents the total average time per
record |
|
disp time/250 |
TTLB in milliseconds minus the TTLB for the “0” column
divided by the 250 records.This
represents the time to display each record within the recordset iteration. |
We will compare our next test to these values.
This is an issue that I wish to get out of the way quickly.The ADOVBS.INC file provided by Microsoft contains 270 lines of code representing most of the constants that can be applied to ADO properties.In our example, there are only 2 constants referenced from this file.Therefore, for this test I have removed the include file reference and replaced the constants with the actual numbers from the property enumerations.
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
Code snippet from ADO__02.asp

Here we see a decrease of 23% on load time.There is a nominal difference in the display time per record since this change should have no effect on iterating through the recordset.There are several resolutions to this problem.I recommend using the ADOVBS.INC file as a reference, and use comments when necessary to note settings.Remember, as stated in Part I, comments should not be feared since, when used in moderation, they have a very little impact on performance.Another approach is to copy just the constants you need from the file into you page.
A very cool way to resolve this problem is to make all of the ADO Constants available by adding by linking the ADO type library to your application.By adding the following to your Global.asa file you can get access to all the constants directly
<!--METADATA TYPE="typelib"
file="C:\Program Files\CommonFiles\SYSTEM\ADO\msado15.dll"
NAME="ADODB Type Library"
-->
or
<!--METADATA TYPE="typelib"
UUID="00000205-0000-0010-8000-00AA006D2EA4"
NAME="ADODB Type Library"
-->
So, here is our first rule:
·
Avoid including ADOVBS.INC file – use other methods for getting access
to constants.
To answer this question accurately, we will need to examine tests under two different conditions -- first, when one database transaction is executed per page and second, when many database transactions are executed per page.
In our previous examples, we have been creating a separate Connection object and passing it into the ActiveConnection property of the Recordset.However, it is also possible to simply pass the connection string into this property thus avoiding the extra step of instantiating and configuring a separate component in the script.
objRS.ActiveConnection = Application("Conn")
Code snippet from ADO__03.asp

While there is still a connection created in the Recordset, it is created under the highly optimized conditions.Therefore, we see a 23% decrease in start up time from our previous test and, as expected, virtually no difference in the display time per record.
Therefore, our second rule is:
·
When working with a single Recordset, pass the connection string into
the
ActiveConnection
property.
Next, we will determine if this logic is still true when creating multiple recordsets in a page.To test this scenario, I have introduced a FOR loop to repeat the previous example 10 times.Within this test, we will study three variations:
First, we will create and destroy the Connection object in each loop:
Dim i
For i = 1 to 10
Set objConn =
Server.CreateObject("ADODB.Connection")
objConn.Open
Application("Conn")
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
objConn.Close
Set objConn = Nothing
Next
Code snippet from ADO__04.asp
Second, we will create a single Connection object outside the loop and share it with each Recordset:
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Dim i
For i = 1 to 10
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = objConn
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
Next
objConn.Close
Set objConn = Nothing
Code snippet from ADO__05.asp
Third, we will pass the connection string into the ActiveConnection Property in each loop:
Dim i
For i = 1 to 10
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.ActiveConnection = Application("Conn")
objRS.CursorType = 0 'adOpenForwardOnly
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
'write data
...
End If
objRS.Close
Set objRS = Nothing
Next
Code snippet from ADO__06.asp

As you might have guessed, creating and destroying the Connection object in each loop is an inefficient method.However, what is surprising is that simply passing the connection string in each loop is only slightly less efficient than sharing a single connection object.
Nonetheless, here is Rule #3:
·
When working with multiple Recordsets in a page, create one Connection
object and reuse it in the
ActiveConnection
property.
All of our tests so far have used a Forward Only cursor to iterate through the recordsets.However, ADO offers three other cursor types for recordsets -- Static, Dynamic, and Keyset.Each of these provides additional functionality such as the ability to move forward and backward and see changes to data as others are making them. Unfortunately, it is out of the scope of this article to discuss the implications of each of these cursor types.I will have to leave this up to you.However, below is a comparative analysis of each type.

Each of these additional cursors impose significantly more overhead than their Forward Only cousin (ADO__03).In addition, these cursors are slower during iteration.The caution that I would like to share is with you is to avoid the trap of thinking, “Well, every once in a while I need a Dynamic cursor, so I will just use that all the time.”
Essentially, the same issues apply to lock types.Our previous tests have been exclusively using the Read Only lock type, however, there are three others -- Lock Pessimistic, Lock Optimistic, and Lock Batch Optimistic.As with the Cursor options, these Locks provide additional functionality and control over the manipulation of the data in the recordset.Again, I will leave it up to you to learn about the proper use of each Lock setting.

So simple logic leads us to consider rule #4:
·
Use the simplest Cursor and Lock Type that is most appropriate to your
task.
So far we have been retrieving Recordsets only through the Recordset object.However, ADO provides some indirect ways of getting at recordsets.Our next test will compare the test values from ADO__03.asp with creating a recordset object directly from a Connection object.
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open Application("Conn")
Set objRS = objConn.Execute(Application("SQL"))
Code snippet from CONN_01.asp

Here we see a slight increase in overhead and no difference in the display time per record.
Next, lets look at creating a Recordset object directly from a Command object.
Set objCmd = Server.CreateObject("ADODB.Command")
objCmd.ActiveConnection = Application("Conn")
objCmd.CommandText = Application("SQL")
Set objRS = objCmd.Execute
Code snippet from CMD__01.asp

Again, we see a slight increase in overhead and a nominal difference in the display time per record.
While the effect on performance is small for these last two approaches, there is a larger issue to consider.
Creating a recordset through the Recordset class gives you the greatest flexibility in controlling how the recordset will be handled.While the other methods do not present an overwhelming performance issue, you are stuck with whatever cursor types and lock types are returned by default, which may not be optimal for your particular needs.
So unless you need these alternative approaches for a specific reason, consider rule #5:
· Instantiate recordsets through the ADODB.Recordset class for the greatest performance and flexibility.
ADO provide an option for disconnecting a recordset, which means the recordset will retrieve all the data in a query up front, close the connection and use a local (or client) cursor to move through the dataset.This also provides an opportunity to release the connection early.This scenario is essential for handling remote data services where the data must be disconnected from the server.However, is it also beneficial for general use?Below we have added the CursorLocation property and closed the connection after opening the recordset.
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.CursorLocation = 2 'adUseClient
objRS.ActiveConnection = Application("Conn")
objRS.LockType = 1 'adLockReadOnly
objRS.Open Application("SQL")
objRS.ActiveConnection = Nothing
Code snippet from CLIENT1.asp
Theoretically, this technique should result in faster performance for two reasons. First, it avoids repeated requests across the connection as one is moving through the records and second, it relieves resource requirements by getting rid of the connection early.However, there appears to be an apparent inefficiency in using client side cursors.This may be due to the fact that when one uses the client cursor location, the CursorType is changed to Static no matter what your settings are.
Rule #6 is as follows:
·
Avoid disconnected recordsets unless they are required in a disconnected
environment.
All of our previous tests have set the properties for the Recordsets directly through the individual properties settings.However, the Recordset.Open function can accept additional parameters for all of the properties we require.While the individual lines of code for each property are easier to read and maintain, they each perform a separate function call that must be marshaled across the COM interface.
Set objRS = Server.CreateObject("ADODB.Recordset")
objRS.Open Application("SQL"), Application("Conn"), 0, 1 'adForwardOnly, adLockReadOnly
Code snippet from ADO__07.asp

There is a surprisingly small difference in the overhead of these approaches, which leads us to Rule #7:
· Don’t worry about setting Recordset properties individually.
Up to this point, I have been referencing fields in the Recordset by name.This can be an inefficient method since it requires a lookup for the field each time it is called.To demonstrate this, the next test will reference the fields by their index in field collection of the Recordset.
'write data
Do While Not objRS.EOF
Response.Write( _
"<TR>" & _
"<TD>" & objRS(0) & "</TD>" & _
"<TD>" & objRS(1) & "</TD>" & _
"<TD>" & objRS(2) & "</TD>" & _
"<TD>" & objRS(3) & "</TD>" & _
"<TD>" & objRS(4) & "</TD>" & _
"<TD>" & objRS(5) & "</TD>" & _
"<TD>" & objRS(6) & "</TD>" & _
"</TR> " _
)
objRS.MoveNext
Loop
Code snippet from ADO__08.asp

As expected, there is little change on the load time (the difference is probably due to a slight reduction in code), however, this technique produces a significant decrease in the effective display time.
In our next example, I will bind each field to a separate variable.This approach avoids all lookups within our table loop.
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
...
Dim fld0
Dim fld1
Dim fld2
Dim fld3
Dim fld4
Dim fld5
Dim fld6
Set fld0 = objRS(0)
Set fld1 = objRS(1)
Set fld2 = objRS(2)
Set fld3 = objRS(3)
Set fld4 = objRS(4)
Set fld5 = objRS(5)
Set fld6 = objRS(6)
'write data
Do While Not objRS.EOF
Response.Write( _
"<TR>" & _
"<TD>" & fld0 & "</TD>" & _
"<TD>" & fld1 & "</TD>" & _
"<TD>" & fld2 & "</TD>" & _
"<TD>" & fld3 & "</TD>" & _
"<TD>" & fld4 & "</TD>" & _
"<TD>" & fld5 & "</TD>" & _
"<TD>" & fld6 & "</TD>" & _
"</TR>" _
)
objRS.MoveNext
Loop
Set fld0 = Nothing
Set fld1 = Nothing
Set fld2 = Nothing
Set fld3 = Nothing
Set fld4 = Nothing
Set fld5 = Nothing
Set fld6 = Nothing
Response.Write("</TABLE>")
End If
Code snippet from ADO__09.asp

This approach has produced our best results so far.Note that the display time per record is down to .45 milliseconds.
All the test scripts so far have been configured to require some knowledge of the resulting recordset.For instance, we have been hardcoding the field names in our column headers and referencing fields individually for values.Our next example provides a very dynamic solution by iterating through the field collection not only for the data but for the field headings as well.
If objRS.EOF Then
Response.Write("No Records Found")
Else
'write headings
Response.Write("<TABLE BORDER=1><TR>")
For Each objFld in objRS.Fields
Response.Write("<TH>"
& objFld.name & "</TH>")
Next
Response.Write("</TR>")
'write data
Do While Not objRS.EOF
Response.Write("<TR>")
For Each objFld in objRS.Fields
Response.Write("<TD>"
& objFld.value & "</TD>")
Next
Response.Write("</TR>")
objRS.MoveNext
Loop
Response.Write("</TABLE>")
End If
Code snippet from ADO__10.asp

As you can see, we have taken a bit of a performance hit, however, this approach is still faster than the ADO__07.
Our next test is a compromise between the last two tests.We will maintain our dynamic flexibility while gaining some performance back by saving the field references in a dynamically allocated array.
If objRS.EOF Then
Response.Write("No Records Found")
Else
Dim fldCount
fldCount = objRS.Fields.Count
Dim fld()
ReDim fld(fldCount)
Dim i
For i = 0 to fldCount-1
Set fld(i) = objRS(i)
Next
'write headings
Response.Write("<TABLE BORDER=1><TR>")
For i = 0 to fldCount-1
Response.Write("<TH>"
& fld(i).name & "</TH>")
Next
Response.Write("</TR>")
'write data
Do While Not objRS.EOF
Response.Write("<TR>")
For i = 0 to fldCount-1
Response.Write("<TD>"
& fld(i) & "</TD>")
Next
Response.Write("</TR>")
objRS.MoveNext
Loop
For i = 0 to fldCount-1
Set fld(i) = Nothing
Next
Response.Write("</TABLE>")
End If
Code snippet from ADO__11.asp

While this is not faster than our previous best, it is considerably faster than our first few examples and has the advantage of being able to represent ANY recordset dynamically.
In our next test, we take a radical departure from our previous schemes and use the GetRows command of the Recordset to create an array for iteration rather than the iterating through the recordset itself.Note that the Recordset is set to Nothing immediately after calling GetRows,thus freeing system resources sooner.In addition, note that the first array dimension represents the fields; the second dimension represents the rows.
If objRS.EOF Then
Response.Write("No Records Found")
objRS.Close
Set objRS = Nothing
Else
'write headings
...
'set array
Dim arrRS
arrRS = objRS.GetRows
'close recordset early
objRS.Close
Set objRS = Nothing
'write data
Dim numRows
Dim numFlds
Dim row
Dim fld
numFlds = Ubound(arrRS, 1)
numRows = Ubound(arrRS, 2)
For row= 0 to numRows
Response.Write("<TR>")
For fld = 0 to numFlds
Response.Write("<TD>"
& arrRS(fld, row) & "</TD>")
Next
Response.Write("</TR>")
Next
Response.Write("</TABLE>")
End If
Code snippet from ADO__12.asp

By using the GetRows command, the entire recordset is fetched and loaded into an array.While this may impose resource issues when retrieving extremely large recordsets, the iteration of the data is much faster since function calls like MoveNext and testing for EOF are eliminated. The speed does come at a price however, since the metadata of the recordset no longer resides with the data.I worked around this by using the recordset to retrieve the heading names prior to calling GetRows.In addition, data types and other information could also be extracted in advance.Also note that is our test, the performance advantage was only seen when using the larger recordset.
In our last test of this section, we go one step further and use the GetString command of the recordset.This method extracts the entire recordset into a one large string, allowing you to specify your own delimiters.
If objRS.EOF Then
Response.Write("No Records Found")
objRS.Close
Set objRS = Nothing
Else
'write headings
...
'set array
Dim strTable
strTable = objRS.GetString (2,
, "</TD><TD>",
"</TD></TR><TR><TD>")
'close recordset early
objRS.Close
Set objRS = Nothing
Response.Write(strTable &
"</TD></TR></TABLE>")
End If
Code snippet from ADO__13.asp

While this approach nears hypersonic levels, it is really only suitable for the simplest of layouts since it can’t adapt to special conditions in the data at all.
Before we started this set of tests, the time for executing each record hovered between .83 milliseconds.Most of the tests in this set have halved that amount.While some of the approaches clearly offer superior speed, that performance comes at the price of flexibility.
So, here are a few more Rules in order of importance:
·
When values in a recordset do not need to be treated in a special way
and can be formatted in a consistent format, use the
GetString
method to extract the data.
·
When you need more flexibility in layout, but you do not need to work
with the metadata of the recordset, use the
GetRows
method to extract the data to an array.
·
When you need flexibility in layout and metadata, bind your fields to
local variables before entering a data retrieval loop
·
Always avoid referencing fields by name.
This is a little side excursion inspired by a number of comments submitted for my last article.The issue in question was around the use of buffering and the alternate use of temporary strings to collect output allowing Response.Write to be called only once.To test this, I started with the code from ADO_11, however, instead of calling Response.Write on every iteration, I append the results to a string, then called Response.Write on the string when the entire operation was done.
Dim strTable
strTable = ""
'write headings
strTable = strTable & "<TABLE
BORDER=1><TR>"
For i = 0 to fldCount-1
strTable = strTable &
"<TH>" & fld(i).name & "</TH>"
Next
strTable = strTable & "</TR>"
'write data
Do While Not objRS.EOF
strTable = strTable &
"<TR>"
For i = 0 to fldCount-1
strTable = strTable &
"<TD>" & fld(i) & "</TD>"
Next
strTable = strTable &
"</TR>"
objRS.MoveNext
Loop
For i = 0 to fldCount-1
Set fld(i) = Nothing
Next
strTable = strTable & "</TABLE>"
Response.Write(strTable)
Code snippet from STR__01.asp

Hmmm, this doesn’t appear to be performing too well.Perhaps, as someone suggested, we should allocate some space for this string with the Space command so that it does not need to keep reallocating space for itself during the iterations.
Dim strTable
strTable = Space(10000)
Code snippet from STR__02.asp

Oh well, perhaps the Space command does not work as was suggested.
Our Final Rule:
· Don’t use temporary strings to collect output.
Well, we have covered a lot, so lets recap the rules:
·
Avoid including the ADOVBS.INC file -- use other methods for accessing
ADO constants.
·
When working with a single Recordset, pass the connection string into
the
ActiveConnection
property.
·
When working with multiple recordsets in a page, create one connection
object and reuse it in the
ActiveConnection
property.
·
Use the Cursor Type that is most appropriate to your task.
· Instantiate recordsets through the ADODB.Recordset class for the greatest performance and flexibility.
· Don’t worry about setting Recordset properties individually.
·
Avoid disconnected recordsets unless they are required in a disconnected
environment.
·
When values in a Recordset do not need to be treated in a special way
and can be formatted in a consistent format, use the
GetString
method to extract the data.
·
When you need more flexibility in layout, but you do not need to work
with the metadata of the recordset, use the
GetRows
method to extract the data into an array.
·
When you need flexibility in layout and metadata, bind your fields to
local variables before entering a data retrieval loop
· Always avoid referencing fields by name.
· Don’t use temporary strings to collect output.
Once again, the overriding point to be learned in these tests is that small changes can make huge differences in performance.If we compare our first test to ADO__09, our fastest results when iterating throught the Recordset, we see at least a 50% decrease in response time across the board.

If we compare our first test with our fastest overall implementation using the GetString method, we see the response time go down to a fraction of the original time.

So remember, don’t take anything for granted.If you are unsure, run some tests.
· ADO documentation on Microsoft’s MSDN Online Library-http://msdn.microsoft.com/library/psdk/dasdk/ados4piv.htm
· Microsoft Platform SDK - http://www.microsoft.com/msdownload/platformsdk/setuplauncher.htm
· Microsoft Data Access Components (MDAC) 2.1.2.4202.3 (GA) Download Page - http://www.microsoft.com/data/download_21242023.htm
· MDAC component checker at http://www.microsoft.com/data/download.htm
· Microsoft Windows Scripting Technologies - http://msdn.microsoft.com/scripting/
· Microsoft’s Web Application Stress Tool - http://webtool.rte.microsoft.com/