2009-05-07 1 views
0

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

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

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

Дополнительно:

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

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

+2

Возможно, вы открываете и закрываете соединение между каждым запросом? Если это так, не забудьте оставить его открытым для всего набора инструкций. – schooner

+0

Сказано так, я чувствую, что вы пытаетесь получить ответ на неправильный вопрос. Вы должны сначала убедиться, что выполнение 4200 запросов в базе данных является правильным решением. –

+0

Возможно, что сам доступ является узким местом ...Вы можете попробовать обновить базу данных до Sql Server Express или Sql Server Compact Edition. –

ответ

5

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

на моей машине за 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 

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

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 

Затем с помощью таблицы последовательности перечислить значения от 1 до 42000 и построить строки в одного ВСТАВИТЬ 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 секунды!

2

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

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

См. Это knowledge base article.

0

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

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

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

Если две таблицы, при условии, что существует FOREIGN KEY между таблицы в столбце AUTOINCREMENT/IDENTITY, вы можете создать VIEW, что две таблицы и INSERT INTOVIEW, а значение AUTOINCREMENT/IDENTITY будет скопировано в ссылочную таблицу. Более подробная информация и рабочий пример в этом ответе на переполнение стека (ссылка ниже).

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

How to Insert Data?

0

Для вашей ситуации, это может быть лучше использовать 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 соединений ...

+0

Я не открываю 4200 соединений, хотя - я оставляю связь открытой все время. Будет ли это иметь значение, чтобы сделать это? – Smashery

+0

Это должно быть намного быстрее, чем выполнение запросов отдельно. – CB01

0

Некоторые предложения (которые могут быть даже сочетаема):

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

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

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

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

+0

См. Мои ответы, в которых предлагается, что метод набора строк по строкам намного быстрее, чем предложение @@ IDENTITY. – onedaywhen

0

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

+0

Инерция, я думаю. Я знаю, что это не самое приятное решение. – Smashery

+0

К сожалению, я думаю, что все мы сражались с инерцией. Удачи тебе. – Adrien

0

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

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

+0

У нас есть структура данных, которую мы должны сохранить в файл MDB. Он состоит из C-структур (так что все очень статические). Однако, как начало перехода к более гибкой структуре данных, мы отходим от сериализации. – Smashery

0

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

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

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

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

0

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

ВЫБОР @@ IDENTITY предложение - определенно намного более быстрый способ данных вставки и извлечения значение Autonumber, чем открыть AddOnly записей, обновление поля, сохраняя запись и сохранение значения Autonumber . 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 

 Смежные вопросы

  • Нет связанных вопросов^_^