Pandas: Используйте многострочные вставки для значительного ускорения to_sql по соединениям с высокой задержкой.

Созданный на 1 дек. 2014  ·  48Комментарии  ·  Источник: pandas-dev/pandas

Я пытался вставить ~ 30 тыс. строк в базу данных mysql, используя pandas-0.15.1, oursql-0.9.3.1 и sqlalchemy-0.9.4. Поскольку машина находится как бы через Атлантику от меня, вызов data.to_sql занял > 1 часа, чтобы вставить данные. При проверке с помощью wireshark проблема заключается в том, что он отправляет вставку для каждой строки, затем ждет ACK перед отправкой следующей, и, короче говоря, время пинга меня убивает.

Однако, следуя инструкциям SQLAlchemy , я изменил

def _execute_insert(self, conn, keys, data_iter):
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement(), data)

к

def _execute_insert(self, conn, keys, data_iter):
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

и вся операция завершается менее чем за минуту. (Чтобы сэкономить вам клик, разница между несколькими вызовами insert into foo (columns) values (rowX) и одним массовым вызовом insert into foo (columns) VALUES (row1), (row2), row3) ). Учитывая, как часто люди используют pandas для вставки больших объемов данных, это похоже на огромную победу, которую было бы здорово включить более широко.

Некоторые проблемы:

  • Не каждая база данных поддерживает многострочные вставки (в SQLite и SQLServer этого не было раньше, хотя сейчас они поддерживают). Я не знаю, как это проверить с помощью SQLAlchemy.
  • Сервер MySQL, который я использовал, не позволял мне вставлять все данные за один раз, мне пришлось установить размер фрагмента (5 КБ работало нормально, но я думаю, что полных 30 КБ было слишком много). Если бы мы сделали это вставкой по умолчанию, большинству людей пришлось бы добавить размер фрагмента (который может быть трудно рассчитать, поскольку он может определяться максимальным размером пакета сервера).

Самый простой способ сделать это — добавить логический параметр multirow= (по умолчанию False ) в функцию to_sql , а затем оставить пользователя ответственным за установку размера фрагмента, но, возможно, есть лучший способ?

Мысли?

IO SQL Performance

Самый полезный комментарий

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

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

Все 48 Комментарий

Это кажется разумным. Спасибо за расследование!

Для реализации это будет зависеть от того, как sqlalchemy работает с вариантами базы данных, которые не поддерживают это (в данный момент я не могу это проверить, но кажется, что sqlalchemy вызывает ошибку (например, http://stackoverflow.com/questions/ 23886764/multiple-insert-statements-in-mssql-with-sqlalchemy). Кроме того, если это приведет к тому, что многим людям придется устанавливать размер фрагмента, это действительно не очень хорошая идея делать по умолчанию (если только мы не установим chunksize на значение по умолчанию).
Так что добавление ключевого слова может быть лучше.

@artemyk @mangecoeur @hayd @danielballan

По-видимому, SQLAlchemy имеет флаг dialect.supports_multivalues_insert (см., например, http://pydoc.net/Python/SQLAlchemy/0.8.3/sqlalchemy.sql.compiler/, возможно, называемый supports_multirow_insert в других версиях, https ://www.mail-archive.com/[email protected]/msg202880.html).

Поскольку это может значительно ускорить вставку, и мы можем легко проверить наличие поддержки, я думаю, что, возможно, мы могли бы сделать это по умолчанию, а также установить для размера фрагмента значение по умолчанию (например, фрагменты по 16 КБ ... не уверен, что слишком большой в большинстве случаев). Если многострочная вставка не удалась, мы могли бы создать исключение, предлагающее уменьшить размер фрагмента?

Теперь мне просто нужно убедить людей SQLAlchemy установить для supports_multivalues_insert значение true на SQL Server> 2005 (я взломал его в коде, и он работает нормально, но по умолчанию он не включен).

Говоря более по теме, я думаю, что размер фрагмента может быть сложным. В моей настройке mysql (которую я, вероятно, настроил для разрешения больших пакетов) я могу установить chunksize = 5000, в моей настройке SQLServer 500 было слишком большим, но 100 работало нормально. Тем не менее, вероятно, большая часть преимуществ этого метода связана с переходом от вставки 1 строки за раз к 100, а не от 100 к 1000.

Что, если chunksize=None означает «Адаптивный выбор размера фрагмента»? Попробуйте что-то вроде 5000, 500, 50, 1. Пользователи могут отключить это, указав размер фрагмента. Если накладные расходы от этих попыток слишком велики, мне нравится предложение @maxgrenderjones : chunksize=10 лучше по умолчанию, чем chunksize=1 .

В последнем комментарии « chunksize=10 лучше по умолчанию, чем chunksize=1 » -> я думаю, что это не совсем так. Текущая ситуация состоит в том, чтобы выполнить _one_ оператор выполнения, который состоит из многострочных операторов вставки одной строки (что не является размером фрагмента, равным 1), в то время как chunksize=10 будет означать выполнение большого количества операторов выполнения с каждым разом, когда одна многострочная вставлять.
И я не знаю, обязательно ли это быстрее, но многое зависит от ситуации. Например, с текущим кодом и локальной базой данных sqlite:

In [4]: engine = create_engine('sqlite:///:memory:') #, echo='debug')

In [5]: df = pd.DataFrame(np.random.randn(50000, 10))

In [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 loops, best of 3: 956 ms per loop

In [7]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loops, best of 3: 2.23 s per loop

Но, конечно, это не использует многострочную функцию

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

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

Может быть, мы можем просто начать с добавления этой функции с помощью нового ключевого слова multirow=True (на данный момент со значением по умолчанию False), а затем мы всегда сможем позже увидеть, можем ли мы включить ее по умолчанию?

@maxgrenderjones @nhockham хочет сделать пиар, чтобы добавить это?

@jorisvandenbossche Я думаю, что рискованно начинать добавлять аргументы ключевого слова для адресации определенных профилей производительности. Если вы можете гарантировать, что он будет быстрее во всех случаях (при необходимости, определяя лучший метод на основе входных данных), тогда вам вообще не нужен флаг.

Разные настройки БД могут иметь разную оптимизацию производительности (разные профили производительности БД, локальные и сетевые, большая память и быстрые SSD и т. д. и т. д.), если вы начнете добавлять флаги ключевых слов для каждого из них, это станет беспорядком.

Я бы предложил создать подклассы SQLDatabase и SQLTable для конкретных реализаций производительности, они будут использоваться через объектно-ориентированный API. Возможно, можно было бы добавить метод «переключения бэкенда», но, честно говоря, использование OO API очень просто, поэтому это, вероятно, излишне для того, что уже является специализированным вариантом использования.

Я создал такой подкласс для загрузки больших наборов данных в Postgres (на самом деле гораздо быстрее сохранить данные в CSV, чем использовать встроенные нестандартные команды COPY FROM sql, чем использовать вставки, см. https://gist.github. com/mangecoeur/1fbd63d4758c2ba0c470#file-pandas_postgres-py). Чтобы использовать его, вы просто делаете PgSQLDatabase(engine, <args>).to_sql(frame, name,<kwargs>)

Просто для справки: я попытался запустить код @jorisvandenbossche (3-е декабрьское сообщение), используя многострочную функцию. Это немного медленнее. Таким образом, компромиссы по скорости здесь нетривиальны:

In [4]: engine = create_engine('sqlite:///:memory:') #, echo='debug')

In [5]: df = pd.DataFrame(np.random.randn(50000, 10))

In [6]: 

In [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 loops, best of 3: 1.05 s per loop

In [7]: 

In [7]: from pandas.io.sql import SQLTable

In [8]: 

In [8]: def _execute_insert(self, conn, keys, data_iter):
   ...:         data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
   ...:         conn.execute(self.insert_statement().values(data))
   ...:     

In [9]: SQLTable._execute_insert = _execute_insert

In [10]: 

In [10]: reload(pd)
Out[10]: <module 'pandas' from '/usr/local/lib/python2.7/site-packages/pandas/__init__.pyc'>

In [11]: 

In [11]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 loops, best of 3: 9.9 s per loop

Кроме того, я согласен с тем, что добавление параметров ключевых слов рискованно. Тем не менее, многострочная функция кажется довольно фундаментальной. Кроме того, «обезьянье исправление», вероятно, не более устойчиво к изменениям API, чем параметры ключевого слова.

Как я и подозревал. Исправление обезьяны - это не то решение, которое я предлагал, а скорее то, что мы отправляем ряд подклассов, ориентированных на производительность, которые информированный пользователь может использовать через интерфейс OO (чтобы избежать загрузки функционального API слишком большим количеством опций).

-----Оригинал сообщения-----
От: "Артемий Колчинский" [email protected]
Отправлено: 26.02.2015 17:13
Кому: "pydata/pandas" [email protected]
Копия: "mangecoeur" Джон. палаты[email protected]
Тема: Re: [pandas] Использование многострочных вставок для значительного ускорения соединений с высокой задержкой to_sqlover (#8953)

Просто для справки: я попытался запустить код @jorisvandenbossche (3-е декабрьское сообщение), используя многострочную функцию. Это немного медленнее. Таким образом, компромиссы по скорости здесь нетривиальны:
В [4]: ​​engine = create_engine('sqlite:///:memory:') #, echo='debug')

В [5]: df = pd.DataFrame(np.random.randn(50000, 10))

В [6]:

В [6]: %timeit df.to_sql('test_default', engine, if_exists='replace')
1 петля, лучшая из 3: 1,05 с на петлю

В [7]:

В [7]: из pandas.io.sql импортировать SQLTable

В [8]:

В [8]: def _execute_insert(self, conn, keys, data_iter):
...: data = [dict((k, v) для k, v в zip(keys, row)) для строки в data_iter]
...: conn.execute (self.insert_statement (). значения (данные))
...:

В [9]: SQLTable._execute_insert = _execute_insert

В [10]:

В [10]: перезагрузить (pd)
Выход[10]:

В [11]:

В [11]: %timeit df.to_sql('test_default', engine, if_exists='replace', chunksize=10)
1 петля, лучшая из 3: 9,9 с на петлю
Кроме того, я согласен с тем, что добавление параметров ключевых слов рискованно. Тем не менее, многострочная функция кажется довольно фундаментальной. Кроме того, «обезьянье исправление», вероятно, не более устойчиво к изменениям API, чем параметры ключевого слова.

Ответьте на это письмо напрямую или просмотрите его на GitHub.

Согласно первоначальному заголовку заявки, я не думаю, что этот подход будет предпочтительным во всех случаях, поэтому я бы не стал использовать его по умолчанию. Однако без этого панды to_sql для меня непригодны для использования, поэтому для меня достаточно важно продолжать запрашивать изменение. (Это также стало первым, что я меняю, когда обновляю версию для панд). Что касается разумных значений chunksize , я не думаю, что есть одно истинное значение n , так как размер пакета будет зависеть от того, сколько столбцов (и что в них) сложно предсказать. . К сожалению, SQLServer выходит из строя с сообщением об ошибке, которое выглядит совершенно не связанным (но таковым не является), если вы установите слишком высокое значение chunksize (вероятно, поэтому многострочные вставки не включаются, кроме как с исправлением в SQLAlchemy), но он отлично работает с mysql . Пользователям, возможно, придется поэкспериментировать, чтобы определить, какое значение n , скорее всего, приведет к приемлемо большому размеру пакета (для какой бы ни была их резервная база данных). То, что панды выбрали n , скорее всего, приведет нас к более глубокому описанию деталей реализации, чем мы хотим (т.е. в противоположном направлении от подхода максимально возможной абстракции SQLALchemy)

Короче говоря, я бы рекомендовал добавить его в качестве ключевого слова с некоторыми полезными комментариями о том, как его использовать. Это не первый раз, когда ключевое слово используется для выбора реализации (см.: http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.html), но, возможно, это не так. t лучший пример, так как я понятия не имею о том, что означает raw= , даже прочитав объяснение!

Я заметил, что он также потребляет огромное количество памяти. Например, DataFrame размером 1,6+ ГБ с примерно 700 000 строк и 301 столбцом требует почти 34 ГБ во время вставки! Это как сверх неэффективно. Любые идеи о том, почему это может быть так? Вот фрагмент экрана:

image

Привет, ребята,
есть прогресс в этом вопросе?

Я пытаюсь вставить около 200 тысяч строк с помощью to_sql, но это занимает вечность и потребляет огромное количество памяти! Использование размера фрагмента помогает с памятью, но скорость все равно очень низкая.

Судя по трассировке MSSQL DBase, у меня сложилось впечатление, что вставка фактически выполняется по одной строке за раз.

Единственный жизнеспособный подход в настоящее время — создать дамп в CSV-файл в общей папке и использовать BULK INSERT. Но это очень раздражает и неэлегантно!

@andreacassioli Вы можете использовать odo для вставки DataFrame в базу данных SQL через промежуточный файл CSV. См. раздел Загрузка файлов CSV в базы данных SQL .

Я не думаю, что вы можете приблизиться к производительности BULK INSERT , используя ODBC.

@ostrokach спасибо, действительно, сейчас я использую файлы csv. Если бы я мог подобраться поближе, я бы обменял немного времени на простоту!

Я подумал, что это может помочь кому-то:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#i -m-inserting-400-000-rows-with-the-orm-and-it-s-really-slow

@indera pandas не использует ORM, только sqlalchemy Core (именно то, что в записи документа предлагается использовать для больших вставок)

есть ли какой-либо консенсус о том, как обойти это в то же время? Я вставляю несколько миллионов строк в postgres, и это занимает вечность. Подходит ли CSV/odo?

@russlamb Практический способ решить эту проблему — просто выполнить массовую загрузку. Однако это кто-то специфичный для БД, поэтому odo имеет решения для postgresl (и может быть mysql ), я думаю. для чего-то вроде sqlserver вам нужно «сделать это самостоятельно» (IOW вы должны это написать).

Для sqlserver я использовал драйвер FreeTDS (http://www.freetds.org/software.html и https://github.com/mkleehammer/pyodbc) с сущностями SQLAlchemy, что привело к очень быстрой вставке (20 тыс. строк на фрейм данных) :

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()


class DemographicEntity(Base):
    __tablename__ = 'DEMOGRAPHIC'

    patid = db.Column("PATID", db.Text, primary_key=True)
    """
    patid = db.Column("PATID", db.Text, primary_key=True, autoincrement=False, nullable=True)
    birth_date = db.Column("BIRTH_DATE", db.Date)
    birth_time = db.Column("BIRTH_TIME", db.Text(5))
    sex = db.Column("SEX", db.Text(2))

def get_db_url(db_host, db_port, db_name, db_user, db_pass):
    params = parse.quote(
        "Driver={{FreeTDS}};Server={};Port={};"
        "Database={};UID={};PWD={};"
        .format(db_host, db_port, db_name, db_user, db_pass))
    return 'mssql+pyodbc:///?odbc_connect={}'.format(params)

def get_db_pool():
    """
    Create the database engine connection.
    <strong i="6">@see</strong> http://docs.sqlalchemy.org/en/latest/core/engines.html

    :return: Dialect object which can either be used directly
            to interact with the database, or can be passed to
            a Session object to work with the ORM.
    """
    global DB_POOL

    if DB_POOL is None:
        url = get_db_url(db_host=DB_HOST, db_port=DB_PORT, db_name=DB_NAME,
                         db_user=DB_USER, db_pass=DB_PASS)
        DB_POOL = db.create_engine(url,
                                   pool_size=10,
                                   max_overflow=5,
                                   pool_recycle=3600)

    try:
        DB_POOL.execute("USE {db}".format(db=DB_NAME))
    except db.exc.OperationalError:
        logger.error('Database {db} does not exist.'.format(db=DB_NAME))

    return DB_POOL


def save_frame():
    db_pool = get_db_pool()
    records = df.to_dict(orient='records')
    result = db_pool.execute(entity.__table__.insert(), records)

Подходит ли CSV/odo?

Я думаю, что это решение почти всегда будет быстрее, независимо от настроек многорядности/размера фрагмента.

Но, @russlamb , всегда интересно услышать, будет ли такое многострочное ключевое слово улучшением в вашем случае. См., например, https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975, чтобы легко проверить это.

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

@jorisvandenbossche В документе, на который я ссылался выше, упоминается: «В качестве альтернативы ORM SQLAlchemy предлагает набор методов Bulk Operations, которые обеспечивают перехваты в подразделах рабочего процесса для создания конструкций INSERT и UPDATE на уровне ядра с небольшой степенью ORM. на основе автоматизации».

Я предлагаю реализовать специальную версию sqlserver для to_sql , которая внутри использует ORM SQLAlchemy для ускорения, как в коде, который я разместил выше.

Это было предложено ранее. Путь, по которому вы идете, - это реализовать pandas sql
класс, оптимизированный для бэкенда. Я опубликовал суть в прошлом для использования
команду postgres COPY FROM, которая намного быстрее. Однако что-то похожее
теперь доступен в odo и построен более надежным способом. Там не так много
пункт ИМХО в дублировании работы от odo.

07.03.2017 00:53, "Андрей Сура" [email protected] написал:

@jorisvandenbossche https://github.com/jorisvandenbossche Документ
Я связал выше упоминается: «В качестве альтернативы ORM SQLAlchemy предлагает Bulk
Операционный набор методов, которые обеспечивают перехваты в подразделах
единица рабочего процесса для выдачи INSERT и UPDATE на уровне ядра
конструкции с небольшой степенью автоматизации на основе ORM».

Я предлагаю реализовать специальную версию sqlserver для
"to_sql", который под капотом использует ядро ​​SQLAlchemy для ускорения.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

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

07.03.2017 00:53, "Андрей Сура" [email protected] написал:

@jorisvandenbossche https://github.com/jorisvandenbossche Документ
Я связал выше упоминается: «В качестве альтернативы ORM SQLAlchemy предлагает Bulk
Операционный набор методов, которые обеспечивают перехваты в подразделах
единица рабочего процесса для выдачи INSERT и UPDATE на уровне ядра
конструкции с небольшой степенью автоматизации на основе ORM».

Я предлагаю реализовать специальную версию sqlserver для
"to_sql", который под капотом использует ядро ​​SQLAlchemy для ускорения.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/pandas-dev/pandas/issues/8953#issuecomment-284437587 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAtYVDXKLuTlsh9ycpMQvU5C0hs_RxuYks5rjCwBgaJpZM4DCjLh
.

Это исправляется/занимается? На данный момент вставка фреймов данных pandas в базу данных SQL выполняется очень медленно, если только это не игрушечный фрейм данных. Давайте примем решение и продвинем его вперед?

@dfernan Как упоминалось выше, вы можете посмотреть на odo . Использование промежуточного файла CSV всегда будет на порядки быстрее, чем использование sqlalchemy, независимо от того, какие улучшения здесь происходят...

@ostrokach , я не убежден, что поведение odo - это то, чего хочет типичный пользователь Pandas. Вставка нескольких строк через ODBC, вероятно, достаточно быстра для большинства аналитиков.

Говоря за себя, я только что потратил несколько часов, переключаясь с патча с обезьянами выше на odo. Время работы простых панд составляло более 10 часов, RBAR. Патч обезьяны работает в 2 на одном и том же наборе данных.
Маршрут odo/CSV оказался быстрее, как и ожидалось, но не настолько, чтобы оправдать затраченные усилия. Я возился с проблемами преобразования CSV, которые меня не особо волновали, и все во имя того, чтобы избежать патча для обезьян. Я импортирую 250 тыс. строк из примерно 10 баз данных mysql и PG в общую область в Postgres для анализа NLP.

Я хорошо знаком с методами массовой загрузки, которые используют партнеры Одо. Я использовал их в течение многих лет, когда я начинаю с данных CSV. У них есть ключевые ограничения:

  1. В случае df->CSV->Postgres требуется доступ к оболочке и шаг scp для получения CSV на хосте PG. Похоже, @mangecoeur обошел это с помощью потока на STDIN.
  2. Для моей цели (250 тысяч строк комментариев с множеством особых случаев в текстовом содержании) я изо всех сил пытался правильно настроить параметры CSV. Я не хотел прироста производительности настолько сильно, чтобы продолжать инвестировать в это.

Я снова переключаюсь на патч, чтобы приступить к анализу.

Я согласен с @jorisvandenbossche , @maxgrenderjones. Вариант (не по умолчанию) для выбора этого был бы чрезвычайно полезен. Точка зрения @artemyk о диалекте.supports_multivalues_insert может даже сделать это разумным значением по умолчанию.

Я рад представить PR, если это продвинет это вперед.

просто чтобы добавить мой опыт работы с odo, он не работал для массовых вставок MS Sql из-за известной проблемы с кодировкой. imho вставки m-row - хорошее практическое решение для большинства людей.

@markschwarz вариант, позволяющий этому работать быстрее, был бы очень кстати!

Отслеживая запросы с помощью sqlite, я, кажется, получаю мультивставки при использовании chunksize :

2017-09-28 00:21:39,007 INFO sqlalchemy.engine.base.Engine INSERT INTO country_hsproduct_year (location_id, product_id, year, export_rca, import_value, cog, export_value, distance, location_level, product_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2017-09-28 00:21:39,007 INFO sqlalchemy.engine.base.Engine ((75, 1237, 1996, 1.7283086776733398, 273487116.0, 0.0, 514320160.0, 0.5413745641708374, 'country', '4digit'), (75, 1237, 1997, 1.7167805433273315, 312047528.0, 0.0, 592372864.0, 0.5314807891845703, 'country', '4digit'), (75, 1237, 1998, 1.2120152711868286, 341676961.0, 0.0, 468860608.0, 0.5472233295440674, 'country', '4digit'), (75, 1237, 1999, 1.236651062965393, 334604240.0, 0.0, 440722336.0, 0.5695921182632446, 'country', '4digit'), (75, 1237, 2000, 1.189828872680664, 383555023.0, 0.0, 426384832.0, 0.5794379711151123, 'country', '4digit'), (75, 1237, 2001, 0.9920380115509033, 374157144.0, 0.3462945520877838, 327031392.0, 0.6234743595123291, 'country', '4digit'), (75, 1237, 2002, 1.0405025482177734, 471456583.0, 0.0, 377909376.0, 0.6023964285850525, 'country', '4digit'), (75, 1237, 2003, 1.147829532623291, 552441401.0, 0.0, 481313504.0, 0.5896202325820923, 'country', '4digit')  ... displaying 10 of 100000 total bound parameter sets ...  (79, 1024, 2015, 0.0, None, 0.8785018920898438, 0.0, 0.9823430776596069, 'country', '4digit'), (79, 1025, 1995, 0.0, None, 0.5624096989631653, 0.0, 0.9839603304862976, 'country', '4digit'))

(без патча обезьяны, то есть)

Интересно, что с патчем обезьяны он ломается, когда я задаю размер фрагмента 10 ^ 5, но не 10 ^ 3. Ошибка «слишком много переменных sql» на sqlite.

@makmanalp , я еще не отследил PG, чтобы проверить это поведение, но я почти всегда устанавливаю размер фрагмента при вставке. В моем примере выше я случайным образом установил 5 значений в диапазоне 200-5000. Я не видел резкой разницы во времени между этими вариантами без патча с обезьяной. С патчем прошедшее время уменьшилось примерно на 80%.

https://github.com/pandas-dev/pandas/issues/8953#issuecomment -76139975

Этот обезьяний патч еще работает? Я попробовал это на MS SQL Server, но не увидел улучшения. Кроме того, он выдает исключение:

(pyodbc.Error) ('07002', '[07002] [Microsoft][SQL Server Native Client 11.0]COUNT field incorrect or syntax error (0) (SQLExecDirectW)')

@hangyao Я думаю, что этот патч зависит от реализации, это одна из тех вещей, которые DBAPI python оставляет на усмотрение драйвера DBAPI. Так что может быть быстрее или нет. RE: синтаксическая ошибка, не уверен в этом.

Я думаю о добавлении функции ниже в файл /io/sql.py в строке 507 внутри функции _engine_builder в новом закрытии IF в строке 521, что делает «новый» _engine_builder ниже фрагмент. Я кратко протестировал его в своей среде, и он отлично работает с базами данных MSSQL, достигая более чем 100-кратного ускорения. Я еще не тестировал его на других базах данных.

Дело в том, что мне не нужно делать PR, потому что я думаю, что нужно больше усилий, чтобы сделать его аккуратным и безопасным, чем просто вставить его, как показано ниже, это может не всегда быть желаемой спецификацией и добавить логический переключатель, который включает / выключает этот параметр , (например, fast_executemany=True ) в to_sql казалось слишком большим усилием, чтобы просто делать это без запроса, я считаю.

Итак, мои вопросы:

  • Работает ли приведенная ниже функция, а также увеличивает скорость INSERT для PostgreSQL?

  • Событие pandas хочет, чтобы этот фрагмент был в их источнике? Если так:

  • Желательно ли добавить эту функцию к функциям по умолчанию sql.py или есть лучшее место для добавления?

Люблю слышать комментарии.

Источник ответа: https://stackoverflow.com/questions/48006551/speeding-up-pandas-dataframe-to-sql-with-fast-executemany-of-pyodbc/48861231#48861231

@event.listens_for(engine, 'before_cursor_execute')
def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
    if executemany:
        cursor.fast_executemany = True
def _engine_builder(con):
    """
    Returns a SQLAlchemy engine from a URI (if con is a string)
    else it just return con without modifying it.
    """
    global _SQLALCHEMY_INSTALLED
    if isinstance(con, string_types):
        try:
            import sqlalchemy
        except ImportError:
            _SQLALCHEMY_INSTALLED = False
        else:
            con = sqlalchemy.create_engine(con)

    @event.listens_for(engine, 'before_cursor_execute')
    def receive_before_cursor_execute(conn, cursor, statement, params, context, executemany):
        if executemany:
            cursor.fast_executemany = True
return con

@tsktsktsk123 Недавно по этому поводу был объединен PR: https://github.com/pandas-dev/pandas/pull/19664. Я еще не просмотрел подробно ваш пост, и он, конечно, не совсем такой же (он использует атрибут supports_multivalues_insert движка sqlalchemy), но просто чтобы убедиться, что вы знаете об этом, если это уже помогает также.

Это отличные новости! Я не смотрел в PR, но сравню его в эти выходные и вернусь с результатами. Спасибо за внимание.

Я просто пробовал 0.23.0 RC2 (на postgresql), и вместо повышения производительности мой скрипт стал значительно медленнее. Запрос к БД стал намного быстрее, но при измерении времени для to_sql() он фактически стал в 1,5 раза медленнее (например, с 7 до 11 секунд)...

Не уверен, что замедление происходит из-за этого PR, поскольку я только что протестировал RC.

Кто-нибудь еще сталкивался с такой же проблемой?

@schettino72 Сколько данных вы вставляли?

Около 30 тыс. строк с 10 столбцами. Но на самом деле почти все, что я пытаюсь сделать, работает медленнее (SQL работает быстрее, но в целом медленнее). Он создает огромный оператор SQL, в котором есть интерполяция значений для КАЖДОГО значения. Что-то типа

 %(user_id_m32639)s, %(event_id_m32639)s, %(colx_m32639)s,

Я обнаружил, что d6tstack намного проще в использовании, это однострочный d6tstack.utils.pd_to_psql(df, cfg_uri_psql, 'benchmark', if_exists='replace') , и он намного быстрее, чем df.to_sql() . Поддерживает postgres и mysql. См. https://github.com/d6t/d6tstack/blob/master/examples-sql.ipynb .

Я использовал решение Monkey Patch:

from pandas.io.sql import SQLTable

def _execute_insert(self, conn, keys, data_iter):
    print "Using monkey-patched _execute_insert"
    data = [dict((k, v) for k, v in zip(keys, row)) for row in data_iter]
    conn.execute(self.insert_statement().values(data))

SQLTable._execute_insert = _execute_insert

некоторое время, но теперь я получаю сообщение об ошибке:

TypeError: insert_statement() missing 2 required positional arguments: 'data' and 'conn'

Кто-нибудь еще получает это? Я на Python 3.6.5 (Anaconda) и pandas==0.23.0

это исправляется? В настоящее время df.to_sql работает очень медленно и вообще не может использоваться во многих практических случаях. Проект Odo, кажется, уже заброшен.
У меня есть следующие варианты использования в финансовых временных рядах, где df.to_sql практически не используется:
1) копирование исторических данных csv в базу данных postgres - не может использовать df.to_sql и должен был использовать собственный код вокруг функциональности psycopg2 copy_from
2) потоковые данные (поступают в пакете ~ 500-3000 строк в секунду) для сброса в базу данных postgres - опять же, производительность df.to_sql довольно разочаровывает, поскольку для вставки этих естественных пакетов данных в postgres требуется слишком много времени.
Единственное место, где я нахожу df.to_sql полезным сейчас, это автоматическое создание таблиц!!! - это не тот вариант использования, для которого он был разработан.
Я не уверен, что другие люди также разделяют ту же проблему, но эта проблема требует некоторого внимания, чтобы интерфейсы «dataframes-to-database» работали бесперебойно.
Ждать с нетерпением.

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

Это мой код:
df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

и я получаю эту ошибку:

Traceback (most recent call last):

  File "<ipython-input-11-cf095145b980>", line 1, in <module>
    handler.insert_financial_data_from_df(data, "GOOG")

  File "C:\Users\user01\Documents\Code\FinancialHandler.py", line 110, in insert_financial_data_from_df
    df.to_sql("financial_data", con=conn, if_exists="append", index=False, method="multi")

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\generic.py", line 2531, in to_sql
    dtype=dtype, method=method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 460, in to_sql
    chunksize=chunksize, dtype=dtype, method=method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 1547, in to_sql
    table.insert(chunksize, method)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 686, in insert
    exec_insert(conn, keys, chunk_iter)

  File "C:\Users\user01\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\io\sql.py", line 609, in _execute_insert_multi
    conn.execute(self.table.insert(data))

TypeError: insert() takes exactly 2 arguments (1 given)

Почему это происходит? Я использую Python 3.7.3 (Anaconda), pandas 0.24.2 и sqlite3 2.6.0.

Заранее большое спасибо!

@jconstanzo , вы можете открыть это как новую проблему?
И если возможно, можете ли вы попытаться привести воспроизводимый пример? (например, небольшой пример фрейма данных, который может показать проблему)

@jconstanzo У меня такая же проблема. Использование method='multi' (в моем случае в сочетании с chunksize ) вызывает эту ошибку при попытке вставки в базу данных SQLite.

К сожалению, я не могу предоставить пример фрейма данных, потому что мой набор данных огромен, поэтому я в первую очередь использую method и chunksize .

Прошу прощения за задержку. Я только что открыл проблему для этой проблемы: https://github.com/pandas-dev/pandas/issues/29921

Была ли эта страница полезной?
0 / 5 - 0 рейтинги