Classic ASP - When to close recordset

TheCarver picture TheCarver · Jul 7, 2012 · Viewed 9.9k times · Source

I would like to know, which of the following examples is the best for closing a recordset object in my situation?

1)

This one closes the object inside the loop but opens a new object when it moves next. If there were 1000 records, this opens an object 1000 times and closes it 1000 times. This is what I would normally do:

SQL = " ... "
Set rs1 = conn.Execute(SQL)
While NOT rs1.EOF

    SQL = " ... "
    Set rs2 = conn.Execute(SQL)
    If NOT rs2.EOF Then
        Response.Write ( ... )
    End If
    rs2.Close : set rs2 = Nothing

rs1.MoveNext
Wend
rs1.Close : Set rs1 = Nothing

2)

This example is what I want to know about. Does saving the object closure (rs2.close) until after the loop has finished, gains or reduces performance? If there were 1000 records, this would open 1000 objects but only closes it once:

SQL = " ... "
Set rs1 = conn.Execute(SQL)
While NOT rs1.EOF

    SQL = " ... "
    Set rs2 = conn.Execute(SQL)
    If NOT rs2.EOF Then
        Response.Write ( ... )
    End If

rs1.MoveNext
Wend
rs1.Close : Set rs1 = Nothing
rs2.Close : set rs2 = Nothing

I hope I've explained myself well enough and it's not too stupid.

UPDATE

To those who think my query can be modified to avoid the N+1 issues (2nd query), here it is:

This is for an online photo library. I have two tables; "photoSearch" and "photos". The first, "photoSearch", has just a few columns and contains all searchable data for the photos, such as "photoID", "headline", "caption", "people", "dateCaptured" and "keywords". It has a multi-column full-text index on (headline, caption, people, keywords). The second table, "photos", contains all of the photos data; heights, widths, copyrights, caption, ID's, dates and much more. Both have 500K+ rows and the headline and caption fields sometimes return 2000+ characters.

This is approximately how the query looks now: (things to note: I cannot use joins with fulltext searching, hence keywords being stored in one column - in a 'de-normalized' table. Also, this kind of pseudo code as my app code is elsewhere - but it's close )

SQL = "SELECT photoID FROM photoSearch
WHERE MATCH (headline, caption, people, keywords)
AGAINST ('"&booleanSearchStr&"' IN BOOLEAN MODE)
AND dateCaptured BETWEEN '"&fromDate&"' AND '"&toDate&"' LIMIT 0,50;"
Set rs1 = conn.Execute(SQL)
While NOT rs1.EOF

    SQL = "SELECT photoID, setID, eventID, locationID, headline, caption, instructions, dateCaptured, dateUploaded, status, uploaderID, thumbH, thumbW, previewH, previewW, + more FROM photos LEFT JOIN events AS e USING (eventID) LEFT JOIN location AS l USING (locationID) WHERE photoID = "&rs1.Fields("photoID")&";"
    Set rs2 = conn.Execute(SQL)
    If NOT rs2.EOF Then
        Response.Write ( .. photo data .. )
    End If
    rs2.Close

rs1.MoveNext
Wend
rs1.Close

When tested, having the full-text index on its own table, "photoSearch", instead of the large table, "photos", seemed to improve speed somewhat. I didn't add the "photoSearch" table, it was already there - this is not my app. If I try joining the two tables to lose the second query, I lose my indexing all together, resulting in very long times - so I can't use joins with full-text. This just seemed to be the quickest method. If it wasn't for the full-text and joining problems, I would have combined both of these queries already.

Answer

htbasaran picture htbasaran · Jul 8, 2012

Here is the thing. First, get your photo ids and make mysql thinks that is an actual table that hold the photo ids only, and then make your actual statement, no need any extra recordset connections...

And do not forget to start from the end to do this. Here is the sample code with explanations:

Step 1 Create photo ids lookup table and name it: This will our PhotoId Lookup Table so name it as "PhotoIds"

SELECT photoID FROM photoSearch
WHERE MATCH (headline, caption, people, keywords)
AGAINST ('"&booleanSearchStr&"' IN BOOLEAN MODE)
AND dateCaptured BETWEEN '"&fromDate&"' AND '"&toDate&"' LIMIT 0,50) AS PhotoIds

Step 2 Now we have photo ids, so get the informations from it. We will insert the above statement just before WHERE clause the same way as we do with real tables. Note that our "fake" table must be between parantheses.

SQL = "SELECT p.photoID, p.setID, p.eventID, p.locationID, p.headline, p.caption, + more FROM
    photos AS p,
    events AS e USING (p.eventID),
    location AS l USING (p.locationID),
    (SELECT photoID FROM photoSearch WHERE MATCH (headline, caption, people, keywords)
        AGAINST ('"&booleanSearchStr&"' IN BOOLEAN MODE) AND dateCaptured BETWEEN
        '"&fromDate&"' AND '"&toDate&"' LIMIT 0,50) AS PhotoIds
    WHERE p.photoID=PhotoIds.photoID;"

Note: I just write these codes here and never tested. There may be some spelling errors or smt. Please let me know if you have troubles.

Now getting your primary question

No need to close the executed queries, especially if you are using execute method. Execute method closes itself after the execution unless its not returning any recordset data (thats the purpose of execute command at the first place) like: "INSERT", "DELETE", "UPDATE". If you didnt open a recordset object, so why try to close something never opened? Instead you can use Set Rs=Nothing to unreference the object and send to the garbage collection to free up some system resources (and thats nothing to do with mysql itself). If you are using "SELECT" queries, (the queries that will return some data) you must Open a recordset object (ADODB.Recordset) and if you opened it, you need to close it as soon as it finishes its job.

The most important thing is to close the "main connection to mysql server" after each page load. So you may consider to put your connection close algorithm (not recordset close) to an include file and insert it at the end of everypage you make the connection to the database. The long talk short: You must use Close() if you used Open()