Python-PostgreSQL интерфейс psycopg2 -> выполнить - PullRequest
6 голосов
/ 28 декабря 2008

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

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

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

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

Поскольку psycopg2 так плохо документирован (как и многие замечательные модули ...), я не могу найти действенного и обходного пути. Я уменьшил число значений INSERTed для executemany с 1000 до 100, чтобы уменьшить вероятность неуникального INSERT на executemany, но я почти уверен, что это способ просто сказать psycopg2 игнорировать эти исключения или сообщить курсор, чтобы продолжить выполнение.

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

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

Ответы [ 4 ]

8 голосов
/ 15 февраля 2009

просто скопируйте все данные в чистую таблицу с помощью команды 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

5 голосов
/ 16 июня 2012

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

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

2) executemany лучше, чем execute, поскольку он делает для вас оператором prepare. Вы можете получить те же результаты самостоятельно с помощью команды, подобной следующей, чтобы набрать 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 снова и снова. Веселитесь.

0 голосов
/ 24 марта 2009

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

0 голосов
/ 29 декабря 2008

"Когда мой сценарий обнаруживает такой неуникальный INSERT, он вызывает connection.rollback () (который делает до 1000 строк каждый раз и, в некотором роде, делает executemany бесполезным), а затем вставляет все значения по одному."

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

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

Отказывает ли 1 блок из 1000 строк (из 5000 таких блоков)? Если это так, то программа execute many помогает 4,999 из 5000 и далеко не бесполезна.

Вас беспокоит эта неуникальная вставка? Или у вас есть фактическая статистика о том, сколько раз это произошло?

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

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

...