MS Access:使用CurrentDB而不是DBEngine(0)(0)时是否存在显着的开销?

Nic*_*ick 7 ms-access

来自David W Fenton 对SU问题的回答MS Access 2003是否包含通用SQL控制台

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

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

Dav*_*ton 20

目前尚不清楚你所说的"开销"是什么意思,所以我不知道有人会如何回答你的问题.

但是多年来在Access新闻组中已经广泛讨论了DBEngine(0)(0)与CurrentDB的主题.我很久以前就使用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()
Run Code Online (Sandbox Code Playgroud)

如果我没记错的话,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
Run Code Online (Sandbox Code Playgroud)

虽然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)中有一个"bug"可能导致它返回一个意外的数据库指针(虽然它实际上在技术上是正确的),并且在Access向导运行后立即在某些上下文中, DBEngine(0)(0)将返回指向向导数据库的指针,而不是返回Access UI中当前打开的数据库.

那是因为以下区别:

  1. 数据库当前在Access UI中打开,AND

  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
Run Code Online (Sandbox Code Playgroud)

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

  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
Run Code Online (Sandbox Code Playgroud)

第一次调用它时,它将使用CurrentDB初始化自身并返回缓存的数据库对象.

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

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

  [add a new QueryDef]
  dbLocal.QueryDefs.Refresh
Run Code Online (Sandbox Code Playgroud)

就是这样.没有全局变量,不需要使用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
Run Code Online (Sandbox Code Playgroud)

如果你确实发出了db.Close,那么一切都不会发生,既不好也不好.

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

  Dim db As DAO.Database

  Set db = DBEngine(0)(0)
  ...
  'db.Close <= don't do this
  Set db = Nothing
Run Code Online (Sandbox Code Playgroud)

...发出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
Run Code Online (Sandbox Code Playgroud)

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

  • 那里没有任何原始的东西.它已经发布了数十亿次 - 每个进入中级VBA编码的Access书都将讨论该主题.但是,并非所有人都会得到正确答案(在我看来).多年来,它也在Access新闻组中被删除并重新出现.关于这个问题有很多错误的信息和错误的印象,但这并没有使我写的任何东西特别值得发表.真的,我站在巨人,Litwin&Getz&Kaplan和其他人的肩膀上. (3认同)