关于vb6:访问ADO记录集中的字段值的最有效方法是什么?

What is the most efficient way to access the value of a field in an ADO recordset?

我面前有一个VB6应用程序,该应用程序通过ADO访问Sql数据库。

检索记录集时,应用程序使用Bang(!)运算符访问记录集中的字段,例如RS!OrderId。

虽然我知道这种做法,但我从未真正使用过(除非我很懒),我也没有使用RS(" OrderId"),因为我一直(或通常)使用完全合格的方法(例如RS)。 fields(" OrderId")。value。甚至使用.Item属性将其进一步扩展。)

两者都返回完全相同的值,一个比另一个短。

我坚持使用此方法的原因是,在遥远的过去的某个时候,我相信有人告诉我,完全限定该字段的性能更高,因为代码必须翻译每次出现的!操作员为其完全合格的姐妹。然而 !运算符可减少打字,从而减少开发时间。

我似乎也想起了!因为ADO在将来的某个时候会被弃用。但是它似乎仍然存在于代码中,我只是想知道哪种方法被视为最佳实践,哪种方法比另一种方法表现更好。


我已经彻底测试了在我的应用程序中使用的VB6和ADO的性能。从记录集中获取数据的绝对最快的方法是使用FIELD对象。返回很多行时,您会注意到性能上的巨大差异。以下是我的应用程序中的代码块(减少以突出显示字段对象的正确使用)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Dim fMinLongitude As ADODB.Field
Dim fMinLatitude As ADODB.Field
Dim fMaxLongitude As ADODB.Field
Dim fMaxLatitude As ADODB.Field
Dim fStreetCount As ADODB.Field

If RS.RecordCount = 0 Then
    Exit Sub
End If

Set fMinLongitude = RS.Fields.Item("MinLongitude")
Set fMinLatitude = RS.Fields.Item("MinLatitude")
Set fMaxLongitude = RS.Fields.Item("MaxLongitude")
Set fMaxLatitude = RS.Fields.Item("MaxLatitude")
Set fStreetCount = RS.Fields.Item("StreetCount")

While Not RS.EOF
    LineGridCount = LineGridCount + 1
    With LineGrid(LineGridCount)
        .MinLongitude = fMinLongitude.Value
        .MaxLongitude = fMaxLongitude.Value
        .MinLatitude = fMinLatitude.Value
        .MaxLatitude = fMaxLatitude.Value
    End With
    RS.MoveNext

Wend

RS.Close
Set RS = Nothing

请注意,我为SQL Server存储过程返回的5列设置了字段对象。然后,我在循环中使用它们。当您执行RS.MoveNext时,它将影响字段对象。

使用上面显示的代码,我可以在不到1秒的时间内将26,000行加载到用户定义的类型中。实际上,运行代码花费了0.05秒。在已编译的应用程序中,它甚至更快。

如果不使用字段对象,则至少应使用WITH块。如另一篇文章中所述,使用序数位置比其他方法要快(字段方法除外)。如果计划使用顺序位置,则应使用WITH块。例如:

1
2
3
4
5
With RS.Fields
  ID = .Item(0).Value
  Name = .Item(1).Value
  EyeColor = .Item(2).Value
End With

使用with块是不错的选择,因为它减少了打字量,同时又加快了代码的执行速度。之所以会出现这种性能提高,是因为VB可以一次设置一个指向字段对象的指针,然后将该指针用于对字段对象的每次调用。

顺便说一下...我不喜欢"少打字"的说法。我经常发现性能更好的代码也是更复杂的代码。借助VB6的智能感知功能,多余的输入也不是很多。

RS(" FieldName")为15个字符。
我已经习惯于键入:rs(点)f(点)i(开括号)(quote)FieldName(quote)(闭括号)(dot)v。这是使用完整功能的6个额外按键合格的方法。

使用with块方法,它将是(点)i(右括号)(引用)FieldName(引用)(右括号)(点)v,这是17次按键。

在这种情况下,良好的习惯花很少的精力,却可以通过提高代码的执行率来获得丰厚的回报。

我只是做了一些性能测试。以下测试使用客户端游标,这意味着查询返回的所有数据都将复制到客户端计算机并存储在记录集对象中。

我用于性能测试的代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
Private Sub Command1_Click()

    Dim DB As ADODB.Connection
    Dim RS As ADODB.Recordset
    Dim Results() As String

    Set DB = New ADODB.Connection
    DB.ConnectionString ="my connection string here"
    DB.CursorLocation = adUseClient
    DB.Open

    Set RS = New ADODB.Recordset
    Call RS.Open("Select * From MapStreetsPoints", DB, adOpenForwardOnly, adLockReadOnly)

    Dim Start As Single
    Dim FeatureId As Long
    Dim PointNumber As Long
    Dim Longitude As Single
    Dim Latitude As Single
    Dim fFeatureId As ADODB.Field
    Dim fPointNumber As ADODB.Field
    Dim fLongitude As ADODB.Field
    Dim fLatitude As ADODB.Field

    ReDim Results(5)

    RS.MoveFirst
    Start = Timer
    Do While Not RS.EOF
        FeatureId = RS!FeatureId
        PointNumber = RS!PointNumber
        Longitude = RS!Longitude
        Latitude = RS!Latitude
        RS.MoveNext
    Loop
    Results(0) ="Bang Method:" & Format(Timer - Start,"0.000")

    RS.MoveFirst
    Start = Timer
    Do While Not RS.EOF
        FeatureId = RS.Fields.Item("FeatureId").Value
        PointNumber = RS.Fields.Item("PointNumber").Value
        Longitude = RS.Fields.Item("Longitude").Value
        Latitude = RS.Fields.Item("Latitude").Value
        RS.MoveNext
    Loop
    Results(1) ="Fully Qualified Name Method:" & Format(Timer - Start,"0.000")

    RS.MoveFirst
    Start = Timer
    Do While Not RS.EOF
        FeatureId = RS.Fields.Item(0).Value
        PointNumber = RS.Fields.Item(1).Value
        Longitude = RS.Fields.Item(2).Value
        Latitude = RS.Fields.Item(3).Value
        RS.MoveNext
    Loop
    Results(2) ="Fully Qualified Ordinal Method:" & Format(Timer - Start,"0.000")

    RS.MoveFirst
    Start = Timer
    With RS.Fields
        Do While Not RS.EOF
            FeatureId = .Item("FeatureId").Value
            PointNumber = .Item("PointNumber").Value
            Longitude = .Item("Longitude").Value
            Latitude = .Item("Latitude").Value
            RS.MoveNext
        Loop
    End With
    Results(3) ="With Block Method:" & Format(Timer - Start,"0.000")

    RS.MoveFirst
    Start = Timer
    With RS.Fields
        Do While Not RS.EOF
            FeatureId = .Item(0).Value
            PointNumber = .Item(1).Value
            Longitude = .Item(2).Value
            Latitude = .Item(3).Value
            RS.MoveNext
        Loop
    End With
    Results(4) ="With Block Ordinal Method:" & Format(Timer - Start,"0.000")

    RS.MoveFirst
    Start = Timer
    Set fFeatureId = RS.Fields.Item("FeatureId")
    Set fPointNumber = RS.Fields.Item("PointNumber")
    Set fLatitude = RS.Fields.Item("Latitude")
    Set fLongitude = RS.Fields.Item("Longitude")
    Do While Not RS.EOF
        FeatureId = fFeatureId.Value
        PointNumber = fPointNumber.Value
        Longitude = fLongitude.Value
        Latitude = fLatitude.Value
        RS.MoveNext
    Loop
    Results(5) ="Field Method:" & Format(Timer - Start,"0.000")

    Text1.Text ="Rows =" & RS.RecordCount & vbCrLf & Join(Results, vbCrLf)

End Sub

结果是:

1
2
3
4
5
6
7
8
Rows = 2,775,548

Bang Method: 9.441
Fully Qualified Name Method: 9.367
Fully Qualified Ordinal Method: 5.191
With Block Method: 8.527
With Block Ordinal Method: 5.117
Field Method: 4.316

显然,现场方法是胜利者。它花费的时间不到bang方法的1/2。还要注意,序数方法与现场方法相比也具有不错的性能。


Bill Vaughn在他的文章" ADO Performance Best Practices"中对此进行了很好的讨论。如评论中的Alex K所指出的,结论是使用诸如rs(0)rs(7)之类的序数。

Bill还讨论了使用枚举为索引位置提供可读名称的技术。例如,查询如下:

1
SELECT CatName, CatType, CatSize from Cats Where...

您可以在VB中使用以下枚举:

1
2
3
4
5
Enum enuCatsQuery
    CatName
    CatType
    CatSize
End Enum

并使用以下代码访问该字段:

1
StrMyName = Rs(enuCatsQuery.CatName)