Интерфейс Python-PostgreSQL psycopg2 - ›executemany

В настоящее время я анализирую файл дампа Википедии; Я извлекаю из него кучу данных с помощью Python и сохраняю их в базе данных PostgreSQL. Я всегда стараюсь ускорить процесс, потому что этот файл огромен (18 ГБ). Для взаимодействия с PostgreSQL я использую psycopg2, но этот модуль, похоже, имитирует многие другие подобные DBAPI.

В любом случае, у меня есть вопрос относительно cursor.executemany (команда, значения); мне кажется, что выполнение executemany один раз каждые 1000 значений или около того лучше, чем вызов cursor.execute (значение команды%) для каждого из этих 5 миллионов значений (пожалуйста, подтвердите или исправьте меня!).

Но, как видите, я использую команду ВСТАВИТЬ 1000 строк в таблицу, которая имеет УНИКАЛЬНОЕ ограничение целостности; это ограничение не проверяется заранее в python, так как это либо потребует от меня постоянно выбирать SELECT (это кажется контрпродуктивным), либо потребует от меня более 3 ГБ ОЗУ. Все это говорит о том, что я рассчитываю на то, что Postgres предупредит меня, когда мой скрипт попытается ВСТАВИТЬ уже существующую строку, перехватив psycopg2.DatabaseError.

Когда мой скрипт обнаруживает такую ​​не УНИКАЛЬНУЮ ВСТАВКУ, он connection.rollback () (который составляет до 1000 строк каждый раз и как бы делает выполнение многих бесполезными), а затем ВСТАВЛЯЕТ все значения одно за другим.

Поскольку psycopg2 так плохо документирован (как и многие отличные модули ...), я не могу найти действенного и действенного обходного пути. Я уменьшил количество значений, ВСТАВЛЯЕМЫХ для каждого исполнителя, с 1000 до 100, чтобы снизить вероятность неуникального ВСТАВКИ для каждого выполнения, но я почти уверен, что это способ просто сказать psycopg2 игнорировать эти исключения или сообщить курсор для продолжения выполнения.

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

Еще раз спасибо!


person Community    schedule 28.12.2008    source источник
comment
Я не уверен, но думаю, что executemany просто перебирает ваш список словарей (строк) и вызывает вставку для каждого. Так что не имеет значения, вызываете ли вы execute в цикле или вызываете executemany. Просто фиксация не должна вызываться в цикле, а не раз в 100 или 1000, в зависимости от ситуации.   -  person JV.    schedule 28.12.2008
comment
так что это примерно так: externalloop- ›получает 1000 следующих строк из списка -› дает внутреннему циклу - ›выполняет для каждого-› выходы из внутреннего цикла - ›commit -› externalloop продолжается до тех пор, пока данные не сохранятся. Вы можете опробовать это на наборе данных из 100000 против отговорок и проверить, имеет ли это значение.   -  person JV.    schedule 28.12.2008
comment
JV, значит, вы говорите, что многие исполнители IPC все еще связываются с postgeSQL для каждого INSERT? Это накладные расходы, присущие IPC, которые я надеюсь устранить с помощью executemany; если это не устранит, у меня нет достаточных оснований для его использования. Спасибо, но мне все еще нужно больше убежденности! -Ник   -  person    schedule 28.12.2008
comment
executemany подготавливает оператор SQL, чтобы его можно было выполнить быстрее, если вам нужно часто его вызывать. И, пожалуйста, не используйте интерполяцию строк (%) в запросе к базе данных. Используйте cursor.execute (sql_stmt, (arg1, arg2, ...). Это правильно экранирует ваши данные.   -  person    schedule 28.12.2008
comment
Хорошо, спасибо за информацию об интерполяции строк в cursor.execute. Итак, вы говорите, что executemany подготавливает оператор SQL, чтобы следующие 5 миллионов обращений к нему были быстрее; мне кажется, что cursor.execute тоже делает то же самое. Мне все еще интересно, пошлют ли исполнительные органы только один вызов IPC?   -  person    schedule 28.12.2008
comment
Эй, мне просто интересно. Если меня действительно беспокоит несколько IPC, то только в том, что он блокирует (а?). Как вы думаете, мне следует подключить свой парсер к моему клиенту db через очередь и разделить их по разным потокам? Это бы уберегло основную прогу от блокировки, не так ли?   -  person    schedule 28.12.2008
comment
Хм, в PEP 249 говорится, что .executemany () также может быть реализовано путем многократного вызова .execute (). Думаю, это зависит от реализации psycopg2.   -  person    schedule 29.12.2008
comment
При разделении потребителя и производителя очередью: это может немного помочь, когда вашему основному потоку нужно выполнить некоторую работу, пока ваш поток db вставляет, чтобы вы могли распараллелить задачи. Следите за тем, чтобы объем данных в очереди не увеличивался слишком сильно.   -  person    schedule 29.12.2008
comment
psycopg2 может быть плохо документирован, но официальный список рассылки очень активен, очень осведомлен, и фактический сопровождающий часто бывает первым, кто отвечает.   -  person bortzmeyer    schedule 02.01.2009
comment
@bortzmeyer: да, но было бы неплохо иметь доступ к файлу. Я уверен, что это уменьшило бы количество глупых вопросов, которые отправляют этим парням!   -  person Nicholas Leonard    schedule 04.01.2009
comment
Postgres 9.5 добавляет апсерты, поэтому вы можете игнорировать дубликаты с помощью INSERT row ON CONFLICT DO NOTHING.   -  person Justin Anderson    schedule 06.05.2016


Ответы (4)


просто скопируйте все данные в рабочую таблицу с помощью команды psql \ copy или используйте метод psycopg cursor.copy_in (). Потом:

insert into mytable
select * from (
    select distinct * 
    from scratch
) uniq
where not exists (
    select 1 
    from mytable 
    where mytable.mykey = uniq.mykey
);

Это будет дедупликация и выполняется намного быстрее, чем любая комбинация вставок.

-dg

person Community    schedule 15.02.2009

У меня была та же проблема, и я искал здесь много дней, чтобы собрать много подсказок, чтобы сформировать полное решение. Даже если вопрос устарел, надеюсь, это будет полезно другим.

1) Забудьте об удалении индексов / ограничений и их повторном создании позже, преимущества незначительны или хуже.

2) Executemany лучше, чем execute, поскольку оно делает за вас инструкцию подготовки. Вы можете получить те же результаты самостоятельно, выполнив следующую команду, чтобы увеличить скорость на 300%:

# To run only once:
sqlCmd = """PREPARE myInsert (int, timestamp, real, text) AS
   INSERT INTO myBigTable (idNumber, date_obs, result, user)
     SELECT $1, $2, $3, $4 WHERE NOT EXISTS
     (SELECT 1 FROM myBigTable WHERE (idNumber, date_obs, user)=($1, $2, $4));"""
curPG.execute(sqlCmd)
cptInsert = 0   # To let you commit from time to time

#... inside the big loop:
curPG.execute("EXECUTE myInsert(%s,%s,%s,%s);", myNewRecord)
allreadyExists = (curPG.rowcount < 1)
if not allreadyExists:
   cptInsert += 1
   if cptInsert % 10000 == 0:
      conPG.commit()

Этот пример фиктивной таблицы имеет уникальное ограничение на (idNumber, date_obs, user).

3) Лучшее решение - использовать COPY_FROM и TRIGGER для управления уникальным ключом ПЕРЕД ВСТАВКОЙ. Это дало мне в 36 раз больше скорости. Я начал с обычных вставок со скоростью 500 записей / сек. а с «копией» я получил более 18 000 записей в секунду. Пример кода на Python с Psycopg2:

ioResult = StringIO.StringIO() #To use a virtual file as a buffer
cptInsert = 0 # To let you commit from time to time - Memory has limitations
#... inside the big loop:
   print >> ioResult, "\t".join(map(str, myNewRecord))
   cptInsert += 1
   if cptInsert % 10000 == 0:
      ioResult = flushCopyBuffer(ioResult, curPG)
#... after the loop:
ioResult = flushCopyBuffer(ioResult, curPG)

def flushCopyBuffer(bufferFile, cursorObj):
   bufferFile.seek(0)   # Little detail where lures the deamon...
   cursorObj.copy_from(bufferFile, 'myBigTable',
      columns=('idNumber', 'date_obs', 'value', 'user'))
   cursorObj.connection.commit()
   bufferFile.close()
   bufferFile = StringIO.StringIO()
   return bufferFile

Вот и все, что касается Python. Теперь триггер Postgresql не должен иметь исключения psycopg2.IntegrityError, а затем все записи команды COPY отклонены:

CREATE OR REPLACE FUNCTION chk_exists()
  RETURNS trigger AS $BODY$
DECLARE
    curRec RECORD;
BEGIN
   -- Check if record's key already exists or is empty (file's last line is)
   IF NEW.idNumber IS NULL THEN
      RETURN NULL;
   END IF;
   SELECT INTO curRec * FROM myBigTable
      WHERE (idNumber, date_obs, user) = (NEW.idNumber, NEW.date_obs, NEW.user);
   IF NOT FOUND THEN -- OK keep it
      RETURN NEW;
   ELSE    
      RETURN NULL; -- Oups throw it or update the current record
   END IF;
END;
$BODY$ LANGUAGE plpgsql;

Теперь свяжите эту функцию с триггером вашей таблицы:

CREATE TRIGGER chk_exists_before_insert
   BEFORE INSERT ON myBigTable FOR EACH ROW EXECUTE PROCEDURE chk_exists();

Это кажется большим трудом, но Postgresql - очень быстрый зверь, когда ему не нужно интерпретировать SQL снова и снова. Развлекайся.

person Le Droid    schedule 15.06.2012

«Когда мой скрипт обнаруживает такую ​​не УНИКАЛЬНУЮ ВСТАВКУ, он connection.rollback () (который составляет до 1000 строк каждый раз и как бы делает бессмысленным выполнение всех операций), а затем ВСТАВЛЯЕТ все значения одно за другим».

Вопрос действительно не имеет большого смысла.

КАЖДЫЙ блок из 1000 строк терпит неудачу из-за неуникальных строк?

Выходит ли из строя 1 блок из 1000 строк (из 5000 таких блоков)? Если это так, то выполнение многих помогает для 4 999 из 5 000 и далеко не «бесполезно».

Вас беспокоит эта неуникальная вставка? Или у вас есть актуальная статистика того, сколько раз это происходило?

Если вы переключились с 1000 блоков строк на 100 блоков строк, вы можете - очевидно - определить, есть ли преимущество в производительности для блоков 1000 строк, 100 блоков строк и 1 блока строк.

Пожалуйста, запустите фактическую программу с реальной базой данных и блоками разного размера и опубликуйте числа.

person S.Lott    schedule 28.12.2008

использование оператора MERGE вместо INSERT решит вашу проблему.

person Community    schedule 24.03.2009
comment
PostgreSQL вообще не поддерживает оператор MERGE, начиная с версии 8.5 beta 2. - person intgr; 18.11.2009