Enhancing Performance in ASP - Part II

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.

Objectives

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?

How the tests were set up

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.

Getting Started

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.

Should I use the ADOVBS.INC include file?

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.

Should I create a separate Connection object when using a Recordset?

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.

What are the most efficient Cursor and Lock Types?

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.

What is the best way to get a Recordset?

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.

Should I disconnect my recordsets?

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.

What is the best way to set recordset properties?

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.

What is the most efficient way to reference field values in the recordset?

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.

Obseravtions

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.

Is using a temporary string a good alternative to buffering?

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.

Summary of The Rules

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.

Conclusions

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.

Links

ADO

·         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

VBScript

·         Microsoft Windows Scripting Technologies - http://msdn.microsoft.com/scripting/

Test Tools

·         Microsoft’s Web Application Stress Tool - http://webtool.rte.microsoft.com/