Почему Dataframe.to_sql() замедляется после определенного количества строк?

У меня очень большой Pandas Dataframe ~9 миллионов записей, 56 столбцов, который я пытаюсь загрузить в таблицу MSSQL, используя Dataframe.to_sql(). Импорт всего Dataframe в одном операторе часто приводит к ошибкам, связанным с памятью.

Чтобы справиться с этим, я просматриваю Dataframe пакетами по 100 тысяч строк и импортирую пакет за раз. Таким образом, я больше не получаю никаких ошибок, но код резко замедляется примерно после 5,8 миллиона записей. Код, который я использую:

maxrow = df.shape[0]
stepsize = 100000
for i in range(0, maxrow, stepsize):
    batchstart = datetime.datetime.now()
        
    if i == 0:
        if_exists = 'replace'
            
    else:
        if_exists = 'append'
        
    df_import = df.iloc[i:i+stepsize]
        
    df_import.to_sql('tablename', 
                    engine,
                    schema='tmp',
                    if_exists=if_exists,
                    index=False,
                    dtype=dtypes
                    )

Я рассчитал время выполнения партий и обнаружил явный переломный момент в скорости: Эти результаты в основном одинаковы для пакетов по 50 000, 100 000 и 200 000 строк. загрузка 6 миллионов записей занимает около 40 минут, а загрузка следующих 3 миллионов записей — еще 2 часа 20 минут.

Я думал, что это произошло либо из-за размера таблицы MSSQL, либо из-за того, что что-то кэшировалось/сохранялось после каждой загрузки. Из-за этого я попытался поместить Dataframe в две разные таблицы. Я также пробовал что-то вроде expunge_all() на SQLALchemy session после каждой загрузки. И то и другое безрезультатно.

Остановка импорта вручную после 5 миллионов записей и перезапуск с 5 миллионов с новым объектом движка также не помогли.

У меня нет идей, что может быть причиной столь резкого замедления процесса, и я был бы очень признателен за помощь.

ОБНОВЛЯТЬ

В крайнем случае я изменил цикл, загрузив части Dataframe, начиная с самого высокого индекса, и заканчивая циклом вниз.

Это фактически изменило время на партию. Кажется, что сами данные отличаются/больше в дальнейшем в Dataframe. Не перегрузка соединения или слишком большая таблица SQL.

Спасибо всем, кто пытается помочь, но, похоже, мне нужно просмотреть данные, чтобы понять, что является причиной этого.

Пробовали ли вы просто использовать аргумент chunksize= для .to_sql(), чтобы увидеть, имеет ли это какое-либо значение?

Gord Thompson 28.03.2024 17:52

можете ли вы добавить некоторые подробности о SQL Server? (выберите @@version, это локальная версия, база данных Azure sql и т. д.) Схема целевой таблицы, включая все определения индексов. при разных запусках вы удаляете/перезагружаете одну и ту же таблицу или разные таблицы?

StrayCatDBA 28.03.2024 17:59

Каковы типы данных в DataFrame и каковы типы данных столбцов, которые он создает в целевой таблице SQL Server? Худшим результатом будет создание столбцов varchar(max), nvarchar(max) или varbinary(max) в таблице из-за дополнительных накладных расходов на управление хранилищем LOB (Large OBject).

AlwaysLearning 29.03.2024 01:31

@GordThompson: Да, это было первое, что я попробовал, прежде чем создать свой собственный цикл. Однако в большинстве случаев это не удавалось из-за ошибок памяти или отключения моего VPN-соединения (которое я использую для подключения к серверу MSSQL). Один раз это сработало, но на это ушло почти семь часов.

Beek 29.03.2024 09:04

@AlwaysLearning Создает довольно много столбцов varchar(max). Я явно указал ему типы данных SQLAlchemy и не добавил ограничений на длину столбцов, максимальная длина которых мне неизвестна.

Beek 29.03.2024 09:14

@StrayCatDBA Я думаю, что это локальный сервер, к которому я могу подключиться после активации VPN-соединения. @@version дает мне Microsoft SQL Server 2019 (RTM-CU22-GDR) (KB5029378) - 15.0.4326.1 (X64) . В таблице нет определений индексов. Первая итерация цикла заменяет существующую таблицу и выполняется после этого добавления к вновь созданной таблице.

Beek 29.03.2024 09:18

@AlwaysLearning: Теперь я попробовал ограничить размер каждого столбца varchar, задав для него максимальный размер. Это не повлияло на время, необходимое для каждой партии.

Beek 29.03.2024 11:47
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
7
230
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я нашел решение, которое может быть полезно для тех, кто хочет ускорить медленную Dataframe.to_sql() операцию, и уже пробовал такие вещи, как chunksizes и настройку SQLALchemy соединения с fast_executemany=True.

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

Сортировка исходного Dataframe с помощью DataFrame.sort_values() сократила необходимое время примерно на 2/3.

Однако кажется очень важным, по каким столбцам сортируются значения. Скорость оптимальна, когда Dataframe сортируется по уникальному столбцу и действует как вложенная/кластеризованная сортировка. Сортировка по этому столбцу неявно сортирует примерно по 20 столбцам.

Это сортировка адресов, которая также сортирует улицу, почтовый индекс, город, регион и т. д. Это также означает, что многие строки в пакете имеют много общих значений внутри строк. Когда Dataframe сортируется по другому столбцу, который не имеет связи с другими столбцами, это на самом деле существенно замедляет работу Dataframe.to_sql().

Возможно, это связано: github.com/mkleehammer/pyodbc/issues/741

Gord Thompson 05.04.2024 16:50

@GordThompson Я еще не сталкивался с этой проблемой, но кажется похожей. Я использую pyodbc версию 5.0.1. Как вы думаете, обновление до версии 5.1 устранило бы проблему автоматически?

Beek 05.04.2024 17:10

AFAIK, нет, проблема остается в последней версии pyodbc.

Gord Thompson 05.04.2024 18:08

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