2010-12-03 14 views
6

Tengo un script que genera decenas de miles de inserciones en un db postgres a través de un ORM personalizado. Como puedes imaginar, es bastante lento. Esto se usa con fines de desarrollo para crear datos ficticios. ¿Hay una optimización simple que puedo hacer en el nivel de Postgres para hacer esto más rápido? Es el único script que se ejecuta secuencialmente y no requiere seguridad de subprocesos.Optimización de inserción de Postgres

¿Puedo desactivar todas las cerraduras, controles de seguridad, disparadores, etc.? Solo estoy buscando una solución rápida y sucia que acelere en gran medida este proceso.

Gracias.

Respuesta

8

Si no necesita ese tipo de funcionalidad en el entorno de producción, le sugiero que desactive fsync de su configuración de PostgreSQL. Esto acelerará las inserciones dramáticamente.

Nunca apague fsync en una base de datos de producción.

+1

Acepto: fsync nunca se debe apagar en producción (a menos que tenga un controlador respaldado por batería muy confiable). Pero synchronous_commit = false en realidad podría mejorar las cosas y no impone un gran riesgo –

+0

En mi entorno de prueba 'synchronous_commit' no mejoró la velocidad lo suficiente como para hacer la diferencia. IIRC esto cortó a la mitad un proceso de creación y población de DB de 2 minutos, pero apagar fsync lo hizo funcionar en 10 segundos. No tengo decenas de miles de registros, así que mi base de datos de prueba nunca golpea el disco con 'fsync = off'. – jmz

+3

¡Un caché respaldado por batería no puede evitar que se desactive fsync! Si su sistema operativo se bloquea o pierde potencia después de tener una sincronización ficticia, antes de que los datos se escriban en un disco, PERDERá los datos. También hay preguntas sobre las escrituras de página completa que ahora son 100% seguras incluso en los controladores RAU de caché de BBU. –

3

Una cosa que puede hacer es eliminar todos los índices, hacer sus inserciones y luego volver a crear los índices.

2

¿Está enviando un lote de decenas de miles de INSERTs O ¿está enviando decenas de miles de INSERT?

Sé con Hibernate que puede agrupar todas sus declaraciones SQL y enviarlas al final en una gran porción en lugar de tomar el impuesto de la red y la sobrecarga de la base de datos para hacer miles de sentencias SQL individualmente.

8

La forma más rápida de insertar datos sería el comando COPY. Pero eso requiere un archivo plano como su entrada. Supongo que generar un archivo plano no es una opción.

No cometer con demasiada frecuencia, especialmente no ejecutar esto con el autocommit habilitado. "Decenas de miles" parece que una única confirmación al final sería la correcta.

Si puede convice su ORM para hacer uso de inserción de varias filas de Postgres' que acelerar las cosas, así

Este es un ejemplo de una inserción de múltiples filas:

 
insert into my_table (col1, col2) 
values 
(row_1_col_value1, row_1_col_value_2), 
(row_2_col_value1, row_2_col_value_2), 
(row_3_col_value1, row_3_col_value_2) 

Si no se puede generar la sintaxis anterior y está utilizando Java asegurarse de que está utilizando instrucciones por lotes en lugar de plantillas de los estados individuales (tal vez otras capas de base de datos permiten que algo similar)

Editar:

jmz 'publicación me inspiró a agregar algo:

También puede ver una mejora cuando aumenta wal_buffers a un valor mayor (p. Ej. 8MB) y checkpoint_segments (por ejemplo, 16)

+0

+1 para COPY. El mejor enfoque para la velocidad. – karlgrz

+1

El comando de copia NO requiere un archivo plano, ya que puede recibir datos de entrada estándar. Realice una copia de seguridad de texto plano de su base de datos y lo verá lleno de comandos de copia que incluyen stdin. –

+2

@Scott: tienes razón. Pero el formato sigue siendo un formato de "texto sin formato". Entonces, para aprovechar el rápido mecanismo COPY, el esfuerzo por reescribir el programa existente es esencialmente el mismo ya sea que COPY lo tome desde un archivo o desde stdin –

6

Para insertos ese número en los cientos de miles, por lotes:

begin; 
insert1 ... 
insert2 ... 
... 
insert10k ... 
commit; 

Para insertos en los millones utilizar la copia:

COPY test (ts) FROM stdin; 
2010-11-29 22:32:01.383741-07 
2010-11-29 22:32:01.737722-07 
... 1Million rows 
\. 

asegurarse de que cualquier col utilizado como FK en otra tabla está indexado si es más que un tamaño trivial en la otra tabla.

2

Si se acaba de inicializar los datos de pruebas constantes, también se puede poner los datos de prueba en una mesa (s) de puesta en escena, a continuación, sólo copiar los contenidos de la tabla, usando

INSERT INTO... SELECT... 

que debería ser casi tan rápido como usando COPY (aunque no lo comparé), con la ventaja de que puede copiar usando solo comandos SQL, sin la molestia de configurar un archivo externo como COPY.

2

¡Intente hacer tanto como sea posible en una sola solicitud!

insert into my_table (col1, col2) 
values (
    unnest(array[row_1_col_value_1, row_2_col_value_1, row3_col_value_1]), 
    unnest(array[row_1_col_value_2, row_2_col_value_2, row_3_col_value_2)); 

Esto se asemeja a la sugerencia de @a_horse_with_no_name. La ventaja de usar unnest es: ¡puede usar parámetros de consulta que contienen matrices!

insert into my_table (col1, col2) 
values (unnest(:col_values_1), unnest(:col_values_2)); 

por el colapso de tres insert declaraciones en uno, se ahorra más del 50% del tiempo de ejecución. Y mediante el uso de parámetros de consulta con 2000 valores en un solo Insert, obtengo un factor de velocidad de 150 en mi aplicación.