Как повысить производительность для массовых вставок в таблицы ODBC, связанные в Access?
У меня есть файлы CSV и TXT для импорта. Я импортирую файлы в Access, а затем вставляю записи в связанную таблицу Oracle. Каждый файл имеет около 3 миллионов строк, и процесс занимает много времени.
Импорт в Access очень быстрый, но вставка в связанную таблицу Oracle занимает очень много времени.
Вот процесс, который я сейчас использую:
DoCmd.TransferText acImportFixed, "BUSSEP2014 Link Specification", "tblTempSmartSSP", strFName, False
db.Execute "INSERT INTO METER_DATA ([MPO_REFERENCE]) SELECT MPO_REFERENCE FROM tblTempSmartSSP;"`
tblTempSmartSSP
таблица доступа и METER_DATA
является связанной таблицей Oracle
Я также пробовал прямой импорт в связанную таблицу, и это тоже было очень медленно.
Как я могу ускорить процесс?
4 ответа
Такая ситуация не редкость при работе с массивными INSERT для связанных таблиц ODBC в Access. В случае следующего запроса доступа
INSERT INTO METER_DATA (MPO_REFERENCE)
SELECT MPO_REFERENCE FROM tblTempSmartSSP
где [METER_DATA] является связанной таблицей ODBC, а [tblTempSmartSSP] является локальной (собственной) таблицей доступа, ODBC несколько ограничен в том, насколько умным он может быть, поскольку он должен быть в состоянии разместить широкий спектр целевых баз данных, чьи возможности могут отличаться сильно. К сожалению, это часто означает, что, несмотря на один оператор SQL Access, то, что фактически отправляется в удаленную (связанную) базу данных, является отдельной INSERT (или эквивалентной) для каждой строки в локальной таблице. Понятно, что это может оказаться очень медленным, если локальная таблица содержит большое количество строк.
Вариант 1. Собственные массовые вставки в удаленную базу данных.
Все базы данных имеют один или несколько встроенных механизмов для массовой загрузки данных: Microsoft SQL Server имеет "bcp" и BULK INSERT
и Oracle имеет "SQL*Loader". Эти механизмы оптимизированы для массовых операций и обычно дают значительные преимущества в скорости. Фактически, если данные необходимо импортировать в Access и "массировать" перед их передачей в удаленную базу данных, все равно может быть быстрее выгрузить измененные данные обратно в текстовый файл и затем массово импортировать их в удаленную базу данных.
Вариант 2. Использование сквозного запроса в Access
Если механизмы массового импорта не являются выполнимым вариантом, то другой возможностью является создание одного или нескольких сквозных запросов в Access для загрузки данных с помощью операторов INSERT, которые могут вставлять более одной строки за раз.
Например, если удаленной базой данных был SQL Server (2008 или более поздняя версия), то мы могли бы выполнить запрос сквозного доступа (T-SQL), например
INSERT INTO METER_DATA (MPO_REFERENCE) VALUES (1), (2), (3)
вставить три строки одним оператором INSERT.
Согласно ответу на другой более ранний вопрос здесь соответствующий синтаксис для Oracle будет
INSERT ALL
INTO METER_DATA (MPO_REFERENCE) VALUES (1)
INTO METER_DATA (MPO_REFERENCE) VALUES (2)
INTO METER_DATA (MPO_REFERENCE) VALUES (3)
SELECT * FROM DUAL;
Я протестировал этот подход с SQL Server (поскольку у меня нет доступа к базе данных Oracle), используя собственную таблицу [tblTempSmartSSP] с 10000 строк. Код...
Sub LinkedTableTest()
Dim cdb As DAO.Database
Dim t0 As Single
t0 = Timer
Set cdb = CurrentDb
cdb.Execute _
"INSERT INTO METER_DATA (MPO_REFERENCE) " & _
"SELECT MPO_REFERENCE FROM tblTempSmartSSP", _
dbFailOnError
Set cdb = Nothing
Debug.Print "Elapsed time " & Format(Timer - t0, "0.0") & " seconds."
End Sub
... потребовалось приблизительно 100 секунд для выполнения в моей тестовой среде.
В отличие от следующего кода, который создает многострочные INSERT, как описано выше (используя то, что Microsoft называет конструктором табличных значений)...
Sub PtqTest()
Dim cdb As DAO.Database, rst As DAO.Recordset
Dim t0 As Single, i As Long, valueList As String, separator As String
t0 = Timer
Set cdb = CurrentDb
Set rst = cdb.OpenRecordset("SELECT MPO_REFERENCE FROM tblTempSmartSSP", dbOpenSnapshot)
i = 0
valueList = ""
separator = ""
Do Until rst.EOF
i = i + 1
valueList = valueList & separator & "(" & rst!MPO_REFERENCE & ")"
If i = 1 Then
separator = ","
End If
If i = 1000 Then
SendInsert valueList
i = 0
valueList = ""
separator = ""
End If
rst.MoveNext
Loop
If i > 0 Then
SendInsert valueList
End If
rst.Close
Set rst = Nothing
Set cdb = Nothing
Debug.Print "Elapsed time " & Format(Timer - t0, "0.0") & " seconds."
End Sub
Sub SendInsert(valueList As String)
Dim cdb As DAO.Database, qdf As DAO.QueryDef
Set cdb = CurrentDb
Set qdf = cdb.CreateQueryDef("")
qdf.Connect = cdb.TableDefs("METER_DATA").Connect
qdf.ReturnsRecords = False
qdf.sql = "INSERT INTO METER_DATA (MPO_REFERENCE) VALUES " & valueList
qdf.Execute dbFailOnError
Set qdf = Nothing
Set cdb = Nothing
End Sub
... потребовалось от 1 до 2 секунд, чтобы получить те же результаты.
(Конструкторы табличных значений T-SQL ограничены вставкой 1000 строк за раз, поэтому приведенный выше код немного сложнее, чем он был бы в противном случае.)
Другая возможность для SQL Server, еслиbcp
илиBULK COPY
не вариант:
-
METER_DATA
представляет собой связанную таблицу ODBC в Access дляdbo.METER_DATA
на SQL-сервере -
tblTempSmartSSP
это локальная таблица Access, содержащая 10_000 строк
В моей (очень старой) тестовой сети этот код VBA выполняется примерно за 240 секунд (около 4 минут).
Sub upload()
Dim cdb As DAO.Database
Set cdb = CurrentDb
Dim t0 As Single
t0 = Timer
cdb.Execute "INSERT INTO METER_DATA (MPO_REFERENCE) SELECT MPO_REFERENCE FROM tblTempSmartSSP"
Debug.Print Timer - t0
End Sub
Используя Python и pandas, этот код загружает те же 10_000 строк примерно за 5 секунд.
from time import perf_counter
import pandas as pd
import sqlalchemy as sa
acc_engine = sa.create_engine(
"access+pyodbc://@meter_data"
)
sql_engine = sa.create_engine(
"mssql+pyodbc://scott:tiger^5HHH@mssql_199",
fast_executemany=True,
)
t0 = perf_counter()
df = pd.read_sql_query("SELECT MPO_REFERENCE FROM tblTempSmartSSP", acc_engine)
df.to_sql("METER_DATA", sql_engine, schema="dbo", if_exists="append", index=False)
print(perf_counter() - t0)
Требования:
- Питон
- панды
- sqlalchemy-access (Раскрытие информации: я являюсь сопровождающим этого проекта.)
Обратите внимание, что другие базы данных, поддерживаемые SQLAlchemy (например, PostgreSQL), также могут обеспечивать значительное повышение производительности по сравнению со связанной таблицей ODBC в Access.
Извините, я забыл включить код:
Option Compare Database
Option Explicit
Public Function Run_Safe_SQL(strSQL)
On Error GoTo Error_Handler
Dim db As DAO.Database
Set db = CurrentDb()
db.Execute strSQL, dbFailOnError
DBEngine.Idle dbRefreshCache
' DoEvents
Exit_Here:
'Cleanup
Set db = Nothing
strSQL = ""
Exit Function
Error_Handler:
MsgBox Err.Description & " " & Err.Number
End Function
Вы должны импортировать все столбцы? Может быть, вы хотите оставить пустые столбцы, если таковые имеются; а также колонки, которые не являются абсолютно необходимыми для деловых целей