Interface psycopg2 Python-PostgreSQL -> executemany

votos
6

Atualmente, estou analisando um arquivo de despejo wikipedia; Estou extrair um bando de dados a partir dele usando python e persiste-lo em um db PostgreSQL. Estou sempre tentando fazer as coisas mais rápido para este arquivo é enorme (18GB). A fim de interface com PostgreSQL, estou usando psycopg2, mas este módulo parece imitar muitos outros tais DBAPIs.

De qualquer forma, eu tenho uma pergunta sobre cursor.executemany (comando, valores); parece-me como executar uma executemany uma vez a cada 1000 valores mais ou menos é melhor do que cursor.execute telefone (valor comando%) para cada um desses 5 milhões de valores (por favor confirmar ou corrigir-me!).

Mas, você vê, eu estou usando um executemany para INSERT IGNORE 1000 linhas em uma tabela que tem uma restrição de integridade UNIQUE; essa restrição não é verificado em python de antemão, por isso, quer me obrigar a SELECIONE todo o tempo (isto parece contra produtivo) ou me obrigar a ficar mais de 3 GB de RAM. Tudo isto para dizer que conto com Postgres para me avisar quando meu script tentou inserir ignorar uma linha já existente através de pegar o psycopg2.DatabaseError.

Quando meu script detecta uma inserir tais non-UNIQUE IGNORE, ele connection.rollback () (que faz ups para 1000 linhas cada vez, e espécie de faz o inútil executemany) e insira IGNORE s todos os valores de um por um.

Desde psycopg2 é tão mal documentados (como tantos grandes módulos ...), eu não consigo encontrar uma solução eficiente e eficaz. Eu ter reduzido o número de valores INSERIR IGNORE ed por executemany 1000-100 a fim de reduzir a probabilidade de uma inserção não-UNIQUE IGNORE per executemany, mas estou bastante certo seu é apenas uma maneira de dizer psycopg2 ignorar esses execeptions ou para contar o cursor para continuar a executemany.

Basicamente, este parece ser o tipo de problema que tem uma solução tão fácil e popular, que tudo o que posso fazer é pedir, a fim de aprender sobre ele.

Obrigado novamente!

Publicado 28/12/2008 em 18:51
fonte usuário
Em outras línguas...                            


4 respostas

votos
0

"Quando o meu script detecta uma inserir tais non-UNIQUE IGNORE, ele connection.rollback () (que faz ups para 1000 linhas cada vez, e espécie de faz o inútil executemany) e insira IGNORE s todos os valores de um por um."

A questão realmente não faz muito sentido.

Será que cada bloco de 1.000 linhas falhar devido a linhas não-exclusivas?

Será que 1 bloco de 1.000 linhas falhar (a 5.000 tais blocos)? Se assim for, então a executar muitos ajuda para 4,999 fora de 5000 e está longe de ser "inútil".

Você está preocupado com esta inserção não-exclusiva? Ou você tem estatísticas reais sobre o número de vezes que isso acontece?

Se você mudou de 1.000 blocos de fila para 100 blocos de linhas, você pode - obviamente - determinar se há uma vantagem de desempenho para 1.000 blocos de linhas, 100 blocos de linha e 1 blocos de linha.

Por favor, realmente executar o programa real com banco de dados real e diferentes blocos de tamanho e postar os números.

Respondeu 29/12/2008 em 00:06
fonte usuário

votos
8

basta copiar todos os dados em uma tabela do zero com o comando \ copy do psql, ou use o método cursor.copy_in psycopg (). Então:

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

Isto irá dedup e corre muito mais rápido do que qualquer combinação de inserções.

-dg

Respondeu 15/02/2009 em 14:13
fonte usuário

votos
-1

usando uma instrução MERGE, em vez de um INSERT IGNORE seria resolver o seu problema.

Respondeu 24/03/2009 em 02:32
fonte usuário

votos
4

Eu tive o mesmo problema e procurou aqui por muitos dias para recolher um monte de dicas para formar uma solução completa. Mesmo se a questão ultrapassada, espero que este será útil para os outros.

1) Esquecer coisas sobre a remoção de índices / restrições e criá-los mais tarde, os benefícios são marginais ou pior.

2) executemany é melhor do que executar como faz para você a declaração preparar. Você pode obter os mesmos resultados se com um comando como o seguinte para ganhar velocidade de 300%:

# To run only once:
sqlCmd = """PREPARE myInsert (int, timestamp, real, text) AS
   INSERT IGNORE  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()

Este exemplo tem uma tabela fictícia restrição único em (IDNumber, date_obs, usuário).

3) A melhor solução é usar COPY_FROM e um gatilho para gerenciar a chave única ANTES DE INSERÇÃO IGNORE. Isso me deu mais 36x de velocidade. Comecei com inserções normais em 500 registros / seg. e com "cópia", eu tenho mais de 18.000 registros / seg. código de exemplo em Python com 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

Isso é tudo para a parte Python. Agora o gatilho PostgreSQL não ter exceção psycopg2.IntegrityError e, em seguida, todos os registros do comando COPY rejeitada:

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;

Agora ligar esta função para o gatilho de sua tabela:

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

Este parece ser um monte de trabalho, mas PostgreSQL é um animal muito rápido quando ele não tem de interpretar SQL mais e mais. Diverta-se.

Respondeu 16/06/2012 em 00:24
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more