2008-12-28 15 views
6

Actualmente estoy analizando un archivo de volcado de wikipedia; Estoy extrayendo un montón de datos de él usando Python y persistiéndolo en un DB de PostgreSQL. Siempre trato de hacer que las cosas vayan más rápido porque este archivo es enorme (18GB). Para poder interactuar con PostgreSQL, estoy usando psycopg2, pero este módulo parece imitar a muchos otros DBAPI.Interfaz psycopg2 de Python-PostgreSQL -> executemany

De todos modos, tengo una pregunta sobre cursor.executemany (comando, valores); me parece como ejecutar un executemany una vez cada 1000 valores más o menos es mejor que llamar a cursor.execute (comando% value) para cada uno de estos 5 millones de valores (¡por favor, confírmame o corrígeme!).

Pero, verá, estoy usando una ejecución para INSERTAR 1000 filas en una tabla que tiene una restricción de integridad ÚNICA; esta restricción no se verifica en python de antemano, ya que esto requeriría SELECCIONARme todo el tiempo (esto parece contraproducente) o requerir que obtuviera más de 3 GB de RAM. Todo esto para decir que cuento con Postgres para advertirme cuando mi script intentó INSERTAR una fila ya existente mediante la captura de psycopg2.DatabaseError.

Cuando mi script detecta un INSERT no ÚNICO, connection.rollback() (que crea ups en 1000 filas cada vez, y hace que la ejecución many inútil) y luego INSERT todos los valores uno por uno.

Dado que psycopg2 está tan poco documentado (como lo son tantos módulos excelentes ...), no puedo encontrar una solución eficiente y efectiva. He reducido el número de valores INSERTed por execute many de 1000 a 100 para reducir la probabilidad de un INSERT no ÚNICO por ejecución, pero estoy bastante seguro de que es una forma de decirle a psycopg2 que ignore estas excepciones o que diga al cursor para continuar la ejecución many.

Básicamente, este parece ser el tipo de problema que tiene una solución tan fácil y popular, que todo lo que puedo hacer es preguntar para aprender sobre él.

¡Gracias nuevamente!

+0

No estoy seguro, pero creo que executemany simplemente itera sobre su lista de diccionarios (filas) y llama a "insertar" en cada uno. Por lo tanto, no importa si se ejecuta ejecutar en un bucle o ejecutar executemany. Solo que el "commit" no se debe invocar en bucle, sino una vez cada 100 o 1000, según corresponda. –

+0

así es como: outerloop-> obtiene 1000 filas siguientes de la lista -> da al bucle interno -> ejecuta para cada-> bucle interno sale -> confirmar -> bucle externo continúa hasta que duren los datos. Puedes probarlo en un conjunto de datos de 100,000 contra el ejecutable y comprobar si hace una diferencia. –

+0

JV, ¿entonces estás diciendo que una ejecución todavía IPC se comunica con postgeSQL para cada INSERT? Es la sobrecarga inherente al IPC que espero eliminar al utilizar executemany; si no elimina esto, no tengo suficiente razón para usarlo. ¡Gracias, pero aún necesito más convicción! -Nick –

Respuesta

0

"Cuando mi script detecta un INSERT no ÚNICO, connection.rollback() (que crea ups en 1000 filas cada vez, y hace que la ejecución many inútil) y luego INSERT todos los valores uno por uno."

La pregunta realmente no tiene mucho sentido.

¿CUALQUIER bloque de 1,000 filas falla debido a filas no únicas?

¿Falla 1 bloque de 1,000 filas (de 5,000 de dichos bloques)? Si es así, entonces ejecutar muchas ayuda a 4,999 de 5,000 y está lejos de ser "inútil".

¿Le preocupa este accesorio no exclusivo? ¿O tienes estadísticas reales sobre la cantidad de veces que esto sucede?

Si ha pasado de 1,000 bloques de filas a 100 bloques de filas, puede, obviamente, determinar si hay una ventaja de rendimiento para 1,000 bloques de filas, 100 bloques de filas y 1 bloque de filas.

Ejecute realmente el programa real con la base de datos real y bloques de diferentes tamaños y publique los números.

8

simplemente copie todos los datos en una tabla temporal con el comando psql \ copy, o use el método psycopg cursor.copy_in(). Luego:

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

Esto se desduplicará y se ejecutará mucho más rápido que cualquier combinación de insertos.

-dg

-1

utilizando una instrucción MERGE en lugar de un INSERT se podría resolver su problema.

+1

PostgreSQL no admite la instrucción MERGE en absoluto, a partir de la versión 8.5 beta 2. – intgr

4

que tenían el mismo problema y han buscado aquí por muchos días para recoger una gran cantidad de consejos para formar una solución completa. Incluso si la pregunta es obsoleta, espero que esto sea útil para otros.

1) olvido de las cosas acerca de la eliminación de las restricciones índices/& recrear más tarde, los beneficios son marginales o peor.

2) executemany es mejor que ejecutar, ya que hace que la declaración de prepararse. Usted puede obtener los mismos resultados usted mismo con un comando como el siguiente para ganar velocidad 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() 

Este ejemplo tabla ficticia tiene una restricción única en (IDNumber, date_obs, usuario).

3) La mejor solución es utilizar COPY_FROM y un disparador para gestionar la clave única antes de insertar. Esto me dio 36 veces más velocidad. Empecé con insertos normales a 500 registros/seg. y con "copia" obtuve más de 18,000 registros/seg. Código de muestra en Python con 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 

Eso es todo por la parte de Python. Ahora el gatillo que PostgreSQL no tiene excepción psycopg2.IntegrityError y luego todos los registros del comando de copia rechazada:

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; 

ahora enlazar esta función para el gatillo de su tabla:

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

Esto parece como mucho de trabajo, pero Postgresql es una bestia muy rápida cuando no tiene que interpretar SQL una y otra vez. Que te diviertas.

Cuestiones relacionadas