Вопрос

Я использую ADO для сохранения данных в базе данных MS Access.Сохранение данных в файл заняло довольно много времени (около 7 секунд, что слишком долго для наших целей).Я посмотрел на количество выполняемых SQL-запросов, и оно составило около 4200;хотя там нет целой кучи данных.

Соединение с базой данных, по-видимому, является узким местом.Знаете ли вы какой-нибудь способ уменьшить количество времени, которое это занимает;либо каким-то образом объединив несколько операторов в один, чтобы уменьшить накладные расходы, либо каким-то трюком ADO / MS-Access?

Можете ли вы, например, вставить в таблицу несколько строк одновременно, и будет ли это заметно быстрее?

Дополнительная информация:

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

В ответ на несколько комментариев и откликов:Я оставляю соединение открытым все время и выполняю его как единую транзакцию с помощью beginTransaction() и CommitTransaciton()

Это было полезно?

Решение

Некоторые люди опубликовали сообщение о том, что @@ IDENTITY будет быстрым, так что здесь есть доказательство (с использованием VBA) того, как мои INSERT INTO две таблицы одновременно через трюк VIEW примерно в три раза быстрее, чем выполнение двух INSERTS и каждый раз захват значений @@ IDENTITY ... что неудивительно, так как последний включает три Выполнить операторы, и первый включает только один:)

На моей машине для 4200 итераций трюк VIEW занял 45 секунд, а подход @@ IDENTITY занял 127 секунд:

Sub InitInerts()
  On Error Resume Next
  Kill Environ$("temp") & "\DropMe.mdb"
  On Error GoTo 0
  Dim cat
  Set cat = CreateObject("ADOX.Catalog")
  With cat
    .Create _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    With .ActiveConnection

      Dim Sql As String

      Sql = _
      "CREATE TABLE TableA" & vbCr & "(" & vbCr & "   ID IDENTITY NOT" & _
      " NULL UNIQUE, " & vbCr & "   a_col INTEGER NOT NULL" & vbCr & ")"
      .Execute Sql

      Sql = _
      "CREATE TABLE TableB" & vbCr & "(" & vbCr & "   ID INTEGER NOT" & _
      " NULL UNIQUE" & vbCr & "      REFERENCES TableA (ID)," & _
      "  " & vbCr & "   b_col INTEGER NOT NULL" & vbCr & ")"
      .Execute Sql

      Sql = _
      "CREATE VIEW TestAB" & vbCr & "(" & vbCr & "   a_ID, a_col, " & vbCr & " " & _
      "  b_ID, b_col" & vbCr & ")" & vbCr & "AS " & vbCr & "SELECT A1.ID, A1.a_col," & _
      " " & vbCr & "       B1.ID, B1.b_col" & vbCr & "  FROM TableA AS" & _
      " A1" & vbCr & "       INNER JOIN TableB AS B1" & vbCr & "    " & _
      "      ON A1.ID = B1.ID"
      .Execute Sql

    End With
    Set .ActiveConnection = Nothing
  End With
End Sub

Sub TestInerts_VIEW()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  With con
    .Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    Dim timer As CPerformanceTimer
    Set timer = New CPerformanceTimer
    timer.StartTimer

    Dim counter As Long
    For counter = 1 To 4200
      .Execute "INSERT INTO TestAB (a_col, b_col) VALUES (" & _
                   CStr(counter) & ", " & _
                   CStr(counter) & ");"
    Next

    Debug.Print "VIEW = " & timer.GetTimeSeconds

  End With

End Sub

Sub TestInerts_IDENTITY()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  With con
    .Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    Dim timer As CPerformanceTimer
    Set timer = New CPerformanceTimer
    timer.StartTimer

    Dim counter As Long
    For counter = 1 To 4200
      .Execute "INSERT INTO TableA (a_col) VALUES (" & _
          CStr(counter) & ");"

      Dim identity As Long
      identity = .Execute("SELECT @@IDENTITY;")(0)

      .Execute "INSERT INTO TableB (ID, b_col) VALUES (" & _
                   CStr(identity) & ", " & _
                   CStr(counter) & ");"

    Next

    Debug.Print "@@IDENTITY = " & timer.GetTimeSeconds

  End With

End Sub

Это показывает, что узким местом в настоящее время являются накладные расходы, связанные с выполнением нескольких операторов. Что если бы мы могли сделать это всего за одно утверждение? Ну, угадайте, что, используя мой надуманный пример, мы можем. Сначала создайте таблицу последовательности уникальных целых чисел, являющуюся стандартным приемом SQL (в каждой базе данных должен быть один, IMO):

Sub InitSequence()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  With con
    .Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    Dim sql As String

    sql = _
        "CREATE TABLE [Sequence]" & vbCr & "(" & vbCr & "   seq INTEGER NOT NULL" & _
        " UNIQUE" & vbCr & ");"
    .Execute sql

    sql = _
        "INSERT INTO [Sequence] (seq) VALUES (-1);"
    .Execute sql

    sql = _
        "INSERT INTO [Sequence] (seq) SELECT Units.nbr + Tens.nbr" & _
        " + Hundreds.nbr + Thousands.nbr AS seq FROM ( SELECT" & _
        " nbr FROM ( SELECT 0 AS nbr FROM [Sequence] UNION" & _
        " ALL SELECT 1 FROM [Sequence] UNION ALL SELECT 2 FROM" & _
        " [Sequence] UNION ALL SELECT 3 FROM [Sequence] UNION" & _
        " ALL SELECT 4 FROM [Sequence] UNION ALL SELECT 5 FROM" & _
        " [Sequence] UNION ALL SELECT 6 FROM [Sequence] UNION" & _
        " ALL SELECT 7 FROM [Sequence] UNION ALL SELECT 8 FROM" & _
        " [Sequence] UNION ALL SELECT 9 FROM [Sequence] ) AS" & _
        " Digits ) AS Units, ( SELECT nbr * 10 AS nbr FROM" & _
        " ( SELECT 0 AS nbr FROM [Sequence] UNION ALL SELECT" & _
        " 1 FROM [Sequence] UNION ALL SELECT 2 FROM [Sequence]" & _
        " UNION ALL SELECT 3 FROM [Sequence] UNION ALL SELECT" & _
        " 4 FROM [Sequence] UNION ALL SELECT 5 FROM [Sequence]" & _
        " UNION ALL SELECT 6 FROM [Sequence] UNION ALL SELECT" & _
        " 7 FROM [Sequence] UNION ALL SELECT 8 FROM [Sequence]" & _
        " UNION ALL SELECT 9 FROM [Sequence] ) AS Digits )" & _
        " AS Tens, ( SELECT nbr * 100 AS nbr FROM ( SELECT" & _
        " 0 AS nbr FROM [Sequence] UNION ALL SELECT 1 FROM" & _
        " [Sequence] UNION ALL SELECT 2 FROM [Sequence] UNION"
    sql = sql & _
        " ALL SELECT 3 FROM [Sequence] UNION ALL SELECT 4 FROM" & _
        " [Sequence] UNION ALL SELECT 5 FROM [Sequence] UNION" & _
        " ALL SELECT 6 FROM [Sequence] UNION ALL SELECT 7 FROM" & _
        " [Sequence] UNION ALL SELECT 8 FROM [Sequence] UNION" & _
        " ALL SELECT 9 FROM [Sequence] ) AS Digits ) AS Hundreds," & _
        " ( SELECT nbr * 1000 AS nbr FROM ( SELECT 0 AS nbr" & _
        " FROM [Sequence] UNION ALL SELECT 1 FROM [Sequence]" & _
        " UNION ALL SELECT 2 FROM [Sequence] UNION ALL SELECT" & _
        " 3 FROM [Sequence] UNION ALL SELECT 4 FROM [Sequence]" & _
        " UNION ALL SELECT 5 FROM [Sequence] UNION ALL SELECT" & _
        " 6 FROM [Sequence] UNION ALL SELECT 7 FROM [Sequence]" & _
        " UNION ALL SELECT 8 FROM [Sequence] UNION ALL SELECT" & _
        " 9 FROM [Sequence] ) AS Digits ) AS Thousands;"
    .Execute sql

  End With

End Sub

Затем используйте таблицу Sequence для перечисления значений от 1 до 42000 и построения строк в одном операторе INSERT INTO..SELECT:

Sub TestInerts_Sequence()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  With con
    .Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    Dim timer As CPerformanceTimer
    Set timer = New CPerformanceTimer
    timer.StartTimer

    .Execute "INSERT INTO TestAB (a_col, b_col) " & _
             "SELECT seq, seq " & _
             "FROM Sequence " & _
             "WHERE seq BETWEEN 1 AND 4200;"

    Debug.Print "Sequence = " & timer.GetTimeSeconds



  End With

End Sub

Это выполняется на моей машине за 0,2 секунды!

Другие советы

Более поздние версии Access поддерживают переменную @@ IDENTITY. Это можно использовать для извлечения столбца идентификаторов после вставки без выполнения запроса.

INSERT INTO mytable (field1,field2) VALUES (val1,val2);
SELECT @@IDENTITY;

См. эту статью базы знаний .

  

мы вставляем строку, а затем еще один   запрос, чтобы получить его автоинкремент   Я БЫ; затем используйте этот идентификатор, чтобы вставить несколько   больше строк, связывающих их с первым

Это одна таблица, две таблицы или более двух таблиц?

Если одна таблица, вы можете рассмотреть другой дизайн, например. вы можете сгенерировать свои собственные случайные идентификаторы, использовать модель вложенных множеств, а не модель списка смежности и т. д. Трудно понять, не поделитесь ли вы своим дизайном; -)

Если в двух таблицах предполагается, что между таблицами в столбце AUTOINCREMENT / IDENTITY есть FOREIGN KEY , можно создать VIEW , который INNER JOIN две таблицы и INSERT INTO значение VIEW и значение AUTOINCREMENT / IDENTITY будут скопированы. к справочной таблице. Дополнительные сведения и рабочий пример в этом ответе о переполнении стека (ссылка ниже).

Если более одной таблицы, трюк VIEW не выходит за рамки двух таблиц AFAIK, поэтому вам, возможно, придется просто жить с низкой производительностью или менять технологию, например. О DAO сообщается быстрее, чем ADO, SQL Server может быть быстрее, чем ACE / Jet и т. Д. Опять же, не стесняйтесь делиться своим дизайном, может быть решение «нестандартно».

Как вставить данные?

В вашей ситуации может быть лучше использовать ADO для выполнения вставки вместо выполнения команды SQL.

for i = 1 to 100
   rs.AddNew
   rs("fieldOne") = "value1"
   rs("fieldOne") = "value2"
   rs.Update
   id = rs("uniqueIdColumn")
   'do stuff with id...
next

Я знаю, что использование ADO медленное, но во много раз быстрее, чем открытие 4200 подключений ...

Какое-нибудь предложение (которое даже можно объединить):

  • Почему бы вам не получить автоматически добавленный ИДЕНТИФИКАТОР перед вставкой строки, чтобы у вас уже было значение , доступное для вашего следующего запроса?Это должно сэкономить вам немного времени.
  • Закрытие / размыкание соединения также должно быть проверено.
  • Вы думали о том, чтобы манипулировать наборами записей (с помощью кода Visual Basic для обновления ваших данных) вместо таблиц (с помощью инструкций SQL, отправляемых в базу данных)?
  • Другим решением (если вы уверены, что соединение является узким местом) было бы создать таблицу локально, а затем экспортировать ее в файл access после выполнения задания.
  • Поскольку вы используете ADO, вы даже можете сохранить свои данные в виде XML-файла, загрузить набор записей из этого XML-файла, обработать его с помощью VBA и экспортировать в базу данных Access

Не хочу быть умником ... Но есть ли причина продолжать использовать Access? SQL Server Express бесплатен, быстрее и эффективнее ...

Похоже, вы импортируете данные, и в Access есть гораздо более удобные средства для импорта данных. Да, он вставляет много записей одновременно, и это будет быстрее.

Можете ли вы описать приложение немного подробнее?

Самый дешевый и (в основном каждый раз) лучший прирост скорости, который вы можете дать базе данных, - это сохранять неизменяющиеся данные в кэше.

Не знаю, применимо ли это к вашему случаю, но это очень просто.Вы можете просто подключить (бесплатную) библиотеку, которая это делает, или сохранить локальную коллекцию прочитанных элементов.Пример :

  • Кто-то хочет прочитать пункт X.
  • Есть ли элемент X в локальной коллекции ?
  • НЕТ :прочитайте элемент X из базы данных и поместите его в локальную коллекцию.
  • ДА :просто верните локальную копию элемента X.

Конечно, это может быть немного сложнее, когда вы запускаете веб-сайт с большим количеством серверов.В этом случае просто используйте Блоки приложений или Кэширование.

Позвольте мне опровергнуть следующие утверждения:

  

предложение SELECT @@ IDENTITY -   безусловно, гораздо более быстрый способ   вставить данные и получить   Значение Autonumber, чем открыть AddOnly   набор записей, обновление полей, сохранение   запись и хранение Автономера   значение. SQL INSERT всегда будет   быстрее, чем использование строки за строкой   записей.

Я подумал, что подход с двумя наборами записей может быть немного быстрее, чем подход @@ IDENTITY, потому что я подозревал, что он включает в себя меньшее количество обращений к базе данных. В моем тестировании это было намного быстрее за 1,2 секунды, по сравнению с 127 секундами для подхода @@ IDENTITY. Вот мой код (код для создания .mdb опубликован в другом ответе):

Sub TestInerts_rs()

  Dim con
  Set con = CreateObject("ADODB.Connection")
  con.Open _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

  Dim timer As CPerformanceTimer
  Set timer = New CPerformanceTimer
  timer.StartTimer

  Dim rs1
  Set rs1 = CreateObject("ADODB.Recordset")
  With rs1
    .ActiveConnection = con
    .CursorType = 1  ' keyset
    .LockType = 3  ' optimistic
    .Source = "SELECT a_col, ID FROM TableA;"
    .Open
  End With

  Dim rs2
  Set rs2 = CreateObject("ADODB.Recordset")
  With rs2
    .ActiveConnection = con
    .CursorType = 1  ' keyset
    .LockType = 3  ' optimistic
    .Source = "SELECT b_col, ID FROM TableB;"
    .Open
  End With

  Dim counter As Long
  For counter = 1 To 4200
    rs1.AddNew "a_col", counter

    Dim identity As Long
    identity = rs1.Fields("ID").value

    rs2.AddNew Array(0, 1), Array(counter, identity)

  Next

  Debug.Print "rs = " & timer.GetTimeSeconds

End Sub

Некоторые мысли, которые могут или не могут быть полезны:

<Ол>
  • Позвольте мне поддержать предложение SELECT @@ IDENTITY - безусловно, гораздо более быстрый способ вставки данных и извлечения значения Autonumber, чем открытие набора записей AddOnly, обновление полей, сохранение записи и сохранение значения Autonumber. SQL INSERT всегда будет быстрее, чем использование построчного набора записей.

  • используя транзакции DAO, вы можете преобразовать несколько таких вставок в пакет, который выполняется одновременно. Я не уверен в этом, так как я не знаю точно, что вы делаете и сколько таблиц вы используете. Дело в том, что вы выполняете серию SQL INSERT в транзакции, а затем выполняете .Commit в конце, так что фактическая запись в реальный файл базы данных будет выполняться как одна операция, а не как 4200 (или Однако много) отдельных операций.

  • Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top