MS Access: Существуют ли значительные издержки при использовании CurrentDB в отличие от DBEngine (0) (0)? - PullRequest
7 голосов
/ 02 декабря 2009

из ответа Дэвида В. Фентона на вопрос SU Содержит ли MS Access 2003 консоль SQL общего назначения

Проблема с использованием CurrentDB в качестве объекта выполнения заключается в том, что он возвращает новый объект базы данных каждый раз, когда вы вызываете его

Мой вопрос: есть ли издержки при использовании CurrentDb для выполнения SQL или открытия набора записей, и мне следует избегать этого?

1 Ответ

20 голосов
/ 03 декабря 2009

Непонятно, что вы подразумеваете под термином «накладные расходы», поэтому я не знаю, как кто-нибудь мог бы ответить на ваш вопрос в качестве формулировки.

Но тема 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 (с некоторыми оговорками, ниже, для 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, достаточно для разница будет совсем незначительной (мы говорим здесь миллисекунды, хотя, конечно, CurrentDB займет больше времени для баз данных с большим количеством объектов).

Итак, прежде всего, прежде чем я объясню почему есть разница, вы должны сначала признать, что разница в производительности совершенно несущественна, поскольку единственное обстоятельство, при котором оно может превышать самое тривиальное различие, - это обстоятельство это был бы глупый код.

Теперь, почему разница?

Ну, есть две основные причины:

  1. DBEngine (0) (0) возвращает коллекции так, как они были инициализированы, когда база данных, открытая в данный момент в пользовательском интерфейсе, была впервые открыта, если только вы не обновите коллекции вручную. Таким образом, если вы добавляете новый сохраненный QueryDef, чтобы он был доступен в коде с использованием DBEngine (0) (0), после добавления нового QueryDef вы должны вызвать

    DBEngine(0)(0).QueryDefs.Refresh
    Перед этим ваш новый запрос не будет Коллекция QueryDefs, но после нее она будет. CurrentDB, с другой стороны, обновляет все коллекции при каждом вызове, поэтому вам не нужно беспокоиться об обновлении любой из ваших коллекций.
  2. DBEngine (0) (0) возвращает внутренний указатель, используемый рабочим пространством Access Jet для указания базы данных, открытой в данный момент в пользовательском интерфейсе Access. CurrentDB возвращает копию структуры базы данных, и каждый вызов CurrentDB создает новую копию. Таким образом, CurrentDB будет использовать больше памяти, потому что он создает копию структуры, которая указывает на базу данных, открытую в данный момент в пользовательском интерфейсе Access, в то время как DBEngine (0) (0) не использует дополнительную память, поскольку возвращает не копию, а просто указатель на существующую структуру памяти.

Вероятно, обновление коллекций является причиной того, что CurrentDB медленнее (на 1700%) (или каким-либо другим числом), но, вероятно, некоторое дополнительное время затрачивается на процесс настройки копии объекта базы данных. Тоже.

Опять же, ничего из этого не имеет никакого значения в реальной практике кодирования, поскольку вам просто не нужно постоянно открывать и закрывать указатели на базу данных, открытую в настоящее время в пользовательском интерфейсе Access, поскольку ЭТО НЕ ОТКРЫВАЕТСЯ И НЕ ЗАКРЫВАЕТСЯ ПОСТОЯННО.

Итак, это вещь потаэто / потахто?

Нет, потому что есть одна «ошибка» в DBEngine (0) (0), которая может привести к тому, что он возвратит неожиданный указатель базы данных (хотя это было бы технически правильно), и это происходит в определенных контекстах сразу после мастера доступа После запуска DBEngine (0) (0) вернет указатель на базу данных wizard , а не на базу данных, открытую в данный момент в интерфейсе доступа.

Это потому, что есть различие между:

  1. база данных, открытая в данный момент в пользовательском интерфейсе Access, И

  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.

Побочный вопрос об очистке переменных базы данных, инициализированных этими двумя методами:

Если вы инициализируете переменную db с помощью CurrentDB, вы не закрываете ее, просто установите для нее значение Nothing:

  Dim db As DAO.Database

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

Если вы делаете выдачу базы данных. Закройте, ничего не произойдет, ни плохого, ни хорошего.

С другой стороны, в этом случае:

  Dim db As DAO.Database

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

... при выдаче db.Close в некоторых версиях Access может произойти сбой приложения.

Ни один из них на самом деле не может работать, потому что вы не можете закрыть базу данных, открытую в данный момент в пользовательском интерфейсе Access, с помощью метода Close объекта базы данных.

С другой стороны, если вы сделаете это:

  Dim db As DAO.Database

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

... вы действительно делаете хотите закрыть его, так как это внешняя база данных. Этот код нельзя сделать с помощью CurrentDB, потому что это единственный способ открыть ссылку на другую базу данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...