【问题标题】:What is the most efficient way to access the value of a field in an ADO recordset?访问 ADO 记录集中字段值的最有效方法是什么?
【发布时间】:2014-02-03 14:52:59
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 所有的 VB6 都或多或少地被弃用了,不是吗? .NET 不支持 Bang 运算符
  • 性能最好的是序数方法; rs(index) 或其(隐藏的)命名调用 rs.collect(index) 因为它们都避免了后期绑定开销!以及使用字段名称时所需的键查找
  • 如果您要检索整个记录集而不是回写它,我认为使用GetRows() 最快。

标签: vb6 ado


【解决方案1】:

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

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 块。例如:

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

使用 with 块很好,因为它减少了输入量,同时加快了代码的执行速度。之所以会出现这种性能提升,是因为 VB 可以设置一次指向字段对象的指针,然后在每次调用字段对象时重用该指针。

顺便说一句...我不喜欢“少打字”的说法。我经常发现性能更好的代码也是更复杂的代码。使用 VB6 的智能感知,额外的输入也不是那么多。

RS("FieldName") 为 15 个字符。
我已经养成了打字的习惯: rs (dot) f (dot) i (open parenthesis) (quote) FieldName (quote) (Close Parenthesis) (dot) v. 这是使用完全合格的方法。

使用with block方法,就是(点)i(开括号)(引号)FieldName(引号)(右括号)(点)v,也就是17次按键。

在这种情况下,一个好习惯只需付出很少的努力,就能通过更好的代码获得巨大的回报。

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

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

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

结果是:

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。另请注意,与字段方法相比,序数方法也具有不错的性能。

【讨论】:

  • 但是rs!fieldname(即使没有附加 .Value)只比rs.Fields.Item(index).Value 差 40% 左右,而且 CPU 的相当少量 多 40%时间。我不质疑最佳习惯的价值,但收益可能非常小,对于 100 万行来说大约为 1.5 秒或更短。
  • @Bob77 我试图表达的观点是 WITH 块会对性能产生很大影响。如果一个人不愿意通过使用 ADODB.Fields 的努力,那么他们至少应该使用 WITH 块和序数。此外,少量的 CPU 时间会给用户带来很大的延迟,并导致应用程序运行缓慢。
  • 我同意你的所有观点,这就是我投票赞成你的答案的原因。我只是不同意这种优化在大多数程序中产生了巨大的差异......尽管缓存大量使用的对象引用(显式或通过 With)可能会获得最大的努力。请注意,这也可以与 bang 语法一起使用,就像 .Value 一样。
  • 我喜欢“Fields”方法,但由于过去的经验,当我的一个团队使用 RS(0)、(1)、(2) 等时,我会避开 Ordinal 方法。它工作正常,并被现场推广。另一个团队中的另一个人决定使用完全相同的过程(他应该这样做),但他没有将字段附加到输出中,而是将它们插入并实时部署。突然间,我的伙计申请失败了。我立即强加了一条规则,即只在输出中附加字段。但是有人忽略了它,它又发生了,所以我禁止使用序数字段来进行显式命名方法。
  • 同样,Fields 方法的性能最高。如果您查看我的示例,您会根据字段名称(而不是序号)设置每个字段对象。您有效地获得了两全其美。您的代码不依赖于序数位置,甚至比序数方法执行得更好。基本上,它更快、更可靠。
【解决方案2】:

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

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

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

你可以在 VB 中使用这个枚举:

Enum enuCatsQuery
    CatName
    CatType
    CatSize
End Enum

以及访问该字段的代码:

StrMyName = Rs(enuCatsQuery.CatName)

【讨论】:

  • 我喜欢“Fields”方法,但由于过去的经验,当我的一个团队使用 RS(0)、(1)、(2) 等时,我会避开 Ordinal 方法。它工作正常,并被现场推广。另一个团队中的另一个人决定使用完全相同的过程(他应该这样做),但他没有将字段附加到输出中,而是将它们插入并实时部署。突然间,我的伙计申请失败了。我立即强加了一条规则,即只在输出中附加字段。但是有人忽略了它,它又发生了,所以我禁止使用序数字段来进行显式命名方法。
  • 是的,正如文章中提到的,这当然是一种权衡。性能和可维护性经常相互矛盾的另一个例子!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-25
  • 1970-01-01
  • 1970-01-01
  • 2014-07-12
  • 1970-01-01
相关资源
最近更新 更多