【问题标题】:MS Access: Is there a significant overhead when using CurrentDB as opposed to DBEngine(0)(0)?MS Access:与 DBEngine(0)(0) 相比,使用 CurrentDB 时是否有很大的开销?
【发布时间】:2009-12-02 15:46:01
【问题描述】:

从 David W Fenton 的 answer 到 SU 问题 Does MS Access 2003 contain a general purpose SQL console

使用 CurrentDB 作为执行对象的问题是每次调用它都会返回一个新的数据库对象

我的问题是:使用 CurrentDb 执行 SQL 或打开记录集时是否有开销,我应该避免吗?

【问题讨论】:

  • 您还没有回答自己的问题吗? -- 开销是重复创建和销毁 CurrentDB 对象
  • 可能张贴者应该插入重要的文字。

标签: ms-access


【解决方案1】:

不清楚你所说的“开销”是什么意思,所以我不知道任何人如何回答你的问题。

但 DBEngine(0)(0) 与 CurrentDB 的主题多年来已在 Access 新闻组中进行了相当广泛的讨论。我很久以前就对使用 CurrentDB 感到很平静,所以我将总结一下我所看到的情况。

DBEngine(0)(0) 在这段代码中比 CurrentDB 快得多:

  Dim db As DAO.Database
  Dim i As Integer

  Debug.Print "Start CurrentDB: " & Now()
  For i = 1 to 1000
    Set db = CurrentDB
    Set db = Nothing
  Next i
  Debug.Print "End CurrentDB: " & Now()

  Debug.Print "Start DBEngine(0)(0): " & Now()
  For i = 1 to 1000
    Set db = DBEngine(0)(0)
    Set db = Nothing
  Next i
  Debug.Print "End DBEngine(0)(0): " & Now()

如果我没记错的话,ADH97 说 DBEngine(0)(0) 大约快 17 倍。

但是看看那个代码——它没有测试任何有用的东西。请记住,CurrentDB 和 DBEngine(0)(0) 都返回指向当前在 Access UI 中打开的数据库的指针(下面有一些关于 DBEngine(0)(0) 的警告)。在 Access 应用程序中没有任何位置这些循环中的任何一个都将以任何方式有用。在实际代码中,您可以这样做:

  Dim db As DAO.Database
  Dim rs As DAO.Recordset

  Set db = CurrentDB
  Set rs = db.OpenRecordset("a SQL SELECT")
  [do something with the recordset]
  rs.Close
  Set rs = db.OpenRecordset("another SQL SELECT")
  [do something with this other recordset]
  rs.Close
  Set rs = Nothing
  db.Execute("A SQL DML statement")
  Debug.Print db.RecordsAffected
  Set db = Nothing

虽然 DBEngine(0)(0) 在循环中可能快 1700%,但这并不重要,因为您永远不会重复返回对当前在 Access UI 中打开的数据库的引用足够多次差异几乎可以忽略不计(我们在这里谈论的是毫秒,当然,对于具有更多对象的数据库,CurrentDB 将花费更长的时间)。

所以,首先,在我解释为什么存在差异之前,您必须首先认识到性能差异完全无关紧要,因为它可以超过最微不足道的差异的唯一情况是这种情况会是脑残的愚蠢代码。

现在,为什么会有不同?

嗯,有两个主要原因:

  1. DBEngine(0)(0) 返回当前在用户界面中打开的数据库首次打开时初始化的集合,除非您手动刷新集合。所以,如果你添加一个新保存的 QueryDef,为了让它在使用 DBEngine(0)(0) 的代码中可用,在添加新的 QueryDef 之后你必须调用

    DBEngine(0)(0).QueryDefs.Refresh
    在此之前,您的新查询不会在 QueryDefs 集合中,但在它之后,它会。而 CurrentDB 每次调用时都会刷新所有集合,因此您无需担心刷新任何集合。
  2. DBEngine(0)(0) 返回 Access Jet 工作区用来指向当前在 Access UI 中打开的数据库的内部指针。 CurrentDB 返回数据库结构的副本,每次调用 CurrentDB 都会创建一个新副本。因此,CurrentDB 将使用更多内存,因为它创建了指向当前在 Access UI 中打开的数据库的结构的副本,而 DBEngine(0)(0) 不使用额外的内存,因为它返回的不是副本,而是简单指向现有内存结构的指针。

集合的刷新可能是 CurrentDB 慢了“1700%”的原因(或者不管这个数字是多少),但可能一些额外的时间被设置数据库对象副本的过程占用了,以及。

同样,这对实际的编码实践没有任何影响,因为您不需要经常打开和关闭指向当前在 Access UI 中打开的数据库的指针,因为它不会一直打开和关闭。

那么,这是 potaeto/potahto 的事吗?

不,因为 DBEngine(0)(0) 中存在一个“错误”,可能导致它返回意外的数据库指针(尽管它实际上在技术上是正确的),并且在某些上下文中,这是在 Access 向导之后立即发生的已运行,DBEngine(0)(0) 将返回一个指向 wizard 数据库的指针,而不是当前在 Access UI 中打开的数据库。

那是因为有以下区别:

  1. 当前在 Access UI 中打开的数据库,并且

  2. DBEngine 对象的第一个工作区中的第一个数据库。

另一方面,CurrentDB 总是返回对#1 的引用,而从不返回#2。然而,DBEngine(0)(0) 可以返回其他内容,因为在很短的时间里,向导确实向导关闭后 DBEngine 对象的第一个工作区中的第一个数据库.

现在,生产代码是否有可能遇到此错误?可能不会,因为您不太可能在生产应用程序中使用向导。但这也适用于图书馆数据库,这种技术并不少见,尤其是对于高级 Access 程序员而言。

如果存在实际性能差异,DBEngine(0)(0) 可能值得,但既然没有,CurrentDB 更可取,因为它在返回预期的数据库引用方面是 100% 可靠的。

说了这么多,我的应用中都没有使用。

相反,我使用一个函数来缓存一个用 CurrentDB 初始化的数据库变量。这意味着我不必初始化任何数据库变量,只需使用我的 dbLocal() 函数代替任何数据库变量。代码如下:

  Public Function dbLocal(Optional bolCleanup As Boolean = False) As DAO.Database
  ' This function started life based on a suggestion from 
  '   Michael Kaplan in comp.databases.ms-access back in the early 2000s
  ' 2003/02/08 DWF added comments to explain it to myself!
  ' 2005/03/18 DWF changed to use Static variable instead
  ' uses GoTos instead of If/Then because:
  '  error of dbCurrent not being Nothing but dbCurrent being closed (3420)
  '  would then be jumping back into the middle of an If/Then statement
  On Error GoTo errHandler
    Static dbCurrent As DAO.Database
    Dim strTest As String

  If bolCleanup Then GoTo closeDB

  retryDB:
    If dbCurrent Is Nothing Then
       Set dbCurrent = CurrentDb()
    End If
    ' now that we know the db variable is not Nothing, test if it's Open
    strTest = dbCurrent.Name

  exitRoutine:
    Set dbLocal = dbCurrent
    Exit Function

  closeDB:
    If Not (dbCurrent Is Nothing) Then
       'dbCurrent.close ' this never has any effect
       Set dbCurrent = Nothing
    End If
    GoTo exitRoutine

  errHandler:
    Select Case Err.Number
      Case 3420 ' Object invalid or no longer set.
        Set dbCurrent = Nothing
        If Not bolCleanup Then
           Resume retryDB
        Else
           Resume closeDB
        End If
      Case Else
        MsgBox Err.Number & ": " & Err.Description, vbExclamation, "Error in dbLocal()"
        Resume exitRoutine
    End Select
  End Function

在代码中,您可以这样使用:

  Dim rs As DAO.Recordset

  Set rs = dbLocal.OpenRecordset("SQL SELECT statement")
  [do whatver]
  rs.Close
  Set rs = Nothing
  dbLocal.Execute("SQL INSERT statement")
  Debug.Print dbLocal.OpenRecordset("SELECT @@IDENTITY")(0)
  dbLocal.Execute("SQL UPDATE statement")
  Debug.Print dbLocal.RecordsAffected

第一次调用时,它会用 CurrentDB 初始化自己并返回缓存的数据库对象。

当您关闭应用程序时,您调用它时将 bolCleanup 标志设置为 TRUE,以便它会清理缓存的变量。

如果你添加到集合中,它们不会刷新(因为你不是每次都调用 CurrentDB,只是使用一个用 CurrentDB 初始化的缓存数据库变量),所以你必须这样做:

  [add a new QueryDef]
  dbLocal.QueryDefs.Refresh

就是这样。没有全局变量,无需使用 CurrentDB(或 DBEngine(0)(0))不断地初始化数据库变量。您只需使用它并停止担心它。唯一的技术细节是确保您的应用程序的关闭例程调用 dbLocal(False)。

这就是我对 DBEngine(0)(0) 与 CurrentDB 的对比。

关于清理由这两种方法初始化的数据库变量的附带问题:

如果你用 CurrentDB 初始化一个 db 变量,你不要关闭它,只需将其设置为 Nothing:

  Dim db As DAO.Database

  Set db = CurrentDB
  ...
  'db.Close <= don't do this
  Set db = Nothing

如果你发出db.Close,什么都不会发生,不好也不好。

另一方面,在这种情况下:

  Dim db As DAO.Database

  Set db = DBEngine(0)(0)
  ...
  'db.Close <= don't do this
  Set db = Nothing

...发出 db.Close 可能会导致您的应用在某些版本的 Access 中崩溃。

它们都不能真正起作用,因为您无法通过数据库对象的 Close 方法关闭当前在 Access UI 中打开的数据库。

另一方面,如果你这样做:

  Dim db As DAO.Database

  Set db = DBEngine.OpenDatabase("path to external MDB file")
  ...
  db.Close ' <=you *must* do this
  Set db = Nothing

...你真的确实想关闭它,因为它是一个外部数据库。该代码不能用 CurrentDB 完成,因为这是打开对另一个数据库的引用的唯一方法。

【讨论】:

  • 好的 DWF,我期待您将其复制粘贴到您即将出版的 Access 书籍中 - 非常有用。如果你现在不起草一份,那你就是在损害社区。​​span>
  • 那里没有任何原创内容。它已经出版了无数次——每一本进入中级 VBA 编码的 Access 书籍都会讨论这个主题。不过,并非所有人都会得出正确的答案(在我看来)。多年来,它也在 Access 新闻组中被散列和重新散列。关于这个主题有很多错误信息和错误印象,但这并不能使我写的任何东西特别值得出版。真的,我站在巨人的肩膀上,Litwin & Getz & Kaplan 等等。
  • @David:在您的 dbLocal() 中,您使用带有注释“测试是否打开”的 strTest;由于您没有进一步引用strTest,我猜该分配实际上是为了导致要打开实例,而不是测试它是否打开......我是否正确,并且/或者你能详细说明一下吗?
  • 我认为真正的问题是显而易见的:在所有这些示例中,我们看到 Set db=CurrentDb。是什么原因?如果它具有在循环中插入行的性能优势,那么就这么说吧。 :)
  • 关于何时使用db.Close,我曾经听说过的经验法则是“如果你打开它,你应该关闭它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多