2012-06-18 19 views
7

Tengo el siguiente UPDATE consulta:Cómo acelerar una consulta UPDATE lenta

UPDATE Indexer.Pages SET LastError=NULL where LastError is not null; 

En este momento, esta consulta se lleva unos 93 minutos para completar. Me gustaría encontrar formas de hacer esto un poco más rápido.

La tabla Indexer.Pages tiene alrededor de 506,000 filas, y alrededor de 490,000 de ellas contienen un valor de LastError, por lo que dudo que pueda aprovechar los índices aquí.

La tabla (cuando se descomprime) tiene alrededor de 46 gigas de datos, sin embargo, la mayoría de esos datos se encuentra en un campo de texto llamado html. Creo que simplemente cargar y descargar tantas páginas está causando la desaceleración. Una idea sería crear una nueva tabla con solo el campo Id y html, y mantener Indexer.Pages lo más pequeño posible. Sin embargo, probar esta teoría sería una buena cantidad de trabajo ya que en realidad no tengo espacio en el disco duro para crear una copia de la tabla. Tendría que copiarlo en otra máquina, dejar caer la mesa y luego copiar los datos nuevamente, lo que probablemente demore toda la noche.

Ideas? Estoy usando Postgres 9.0.0.

ACTUALIZACIÓN:

He aquí el esquema:

CREATE TABLE indexer.pages 
(
    id uuid NOT NULL, 
    url character varying(1024) NOT NULL, 
    firstcrawled timestamp with time zone NOT NULL, 
    lastcrawled timestamp with time zone NOT NULL, 
    recipeid uuid, 
    html text NOT NULL, 
    lasterror character varying(1024), 
    missingings smallint, 
    CONSTRAINT pages_pkey PRIMARY KEY (id), 
    CONSTRAINT indexer_pages_uniqueurl UNIQUE (url) 
); 

También tengo dos índices:

CREATE INDEX idx_indexer_pages_missingings 
    ON indexer.pages 
    USING btree 
    (missingings) 
    WHERE missingings > 0; 

y

CREATE INDEX idx_indexer_pages_null 
    ON indexer.pages 
    USING btree 
    (recipeid) 
    WHERE NULL::boolean; 

No hay disparadores en esta tabla, y hay otra tabla que tiene una restricción FK en Pages.PageId.

+0

La actualización de 500,000 filas no debería tomar 93 minutos en primer lugar. Supongo que hay algo más involucrado. ¿Puedes mostrarnos la definición de la tabla? También se deben copiar 500,000 filas en un par de minutos (si no en segundos usando 'COPY') y no en" toda la tarde ". –

+3

A menos que el html sea normalmente muy pequeño, se almacenará automáticamente en una tabla TOSTADA separada detrás de las escenas y no será significativo en esta actualización. Cualquier desencadenante o definición de clave externa podría ser muy significativo, ¿hay alguno? ¿Hay algún índice que haga referencia a la columna LastError? Si es así, es probable que sean un problema. Si puede organizar la actualización en lotes más pequeños (utilizando rangos de teclas, por ejemplo) y VACÍO entre lotes, evitará la saturación de la tabla. Finalmente, actualice: http://www.postgresql.org/support/versioning/ Una versión X.Y.0 no es un buen lugar para estar. – kgrittn

+0

Voy a publicar más información de esquema más adelante hoy. Sin embargo, en este momento puedo decir que no hay factores desencadenantes, y este esquema fue diseñado para inserciones rápidas, por lo que no hay mucho en el camino de los índices. –

Respuesta

6

Lo que @kgrittn posted as comment es la mejor respuesta hasta el momento.Simplemente estoy completando detalles.

Antes de hacer nada otra cosa, usted debe upgrade PostgreSQL to a current version, al menos en la última versión de seguridad de la versión principal. See guidelines on the project.

También quiero enfatizar lo que Kevin mencionó sobre índices que implican la columna LastError. Normalmente, HOT updates puede reciclar filas muertas en una página de datos y hacer que las ACTUALIZACIONES sean mucho más rápidas, eliminando de manera efectiva (la mayor parte) la necesidad de pasar la aspiradora. Relacionado:

Si se utiliza su columna en cualquier índice de ninguna manera, actualizaciones HOT están desactivadas, ya que rompería el índice (es). Si ese es el caso, debería poder acelerar la consulta mucho borrando todos estos índices antes de UPDATE y recíclelos más tarde.

En este contexto sería útil para ejecutar varias actualizaciones menores: Si ...
... la columna actualizada no está involucrado en ningún índice (que permite actualizaciones caliente). ... el UPDATE se divide fácilmente en varios parches en múltiples transacciones. ... las filas en esos parches se extienden sobre la tabla (físicamente, no lógicamente). ... no hay otras transacciones simultáneas que impidan la reutilización de tuplas muertas.

Entonces no tendría que VACCUUM entre múltiples parches, ya que las actualizaciones CALIENTES pueden reutilizar directamente tuplas muertas - sólo tuplas muertas de transacciones anteriores, no de los mismos o concurrentes queridos. Es posible que desee programar un VACUUM al final de la operación o simplemente dejar que la aspiración automática haga su trabajo.

Lo mismo podría hacerse con cualquier otro índice que no sea necesario para el UPDATE y, a juzgar por sus números, el UPDATE no utilizará un índice de todos modos. Si actualiza partes grandes de su tabla, crear nuevos índices desde cero es mucho más rápido que actualizar incrementalmente los índices con cada fila cambiada.

Además, no es probable que su actualización rompa ninguna restricción de clave externa . Puede intentar eliminar & recrear esos, también. Esto abre un intervalo de tiempo en el que la integridad referencial no se aplicaría. Si se infringe la integridad durante el UPDATE, se produce un error al intentar recrear el FK. Si lo hace todo dentro de uno transacción, las transacciones simultáneas nunca llegan a ver el FK caído, pero se toma un bloqueo de escritura en la mesa - lo mismo que con la caída/índices recreando o desencadenantes)

Por último, disable & enable triggers que son no es necesario para la actualización.

Asegúrese de hacer todo esto en una transacción. Tal vez hacerlo en una serie de parches más pequeños, por lo que no bloquea las operaciones simultáneas durante demasiado tiempo.

Así:

BEGIN; 
ALTER TABLE tbl DISABLE TRIGGER user; -- disable all self-made triggers 
-- DROP indexes (& fk constraints ?) 
-- UPDATE ... 
-- RECREATE indexes (& fk constraints ?) 
ALTER TABLE tbl ENABLE TRIGGER user; 
COMMIT; 

Puede no funcionar VACUUM dentro de un bloque de transacción. Per documentation:

VACUUM no se pueden ejecutar dentro de un bloque de transacción.

Se podría dividir su operación en unos grandes trozos y ejecutar en el medio:

VACUUM ANALYZE tbl; 

Si usted no tiene que hacer frente a las transacciones simultáneas que podría (aún más eficaz):

ALTER TABLE tbl DISABLE TRIGGER user; -- disable all self-made triggers 
-- DROP indexes (& fk constraints ?) 

-- Multiple UPDATEs with logical slices of the table 
-- each slice in its own transaction. 
-- VACUUM ANALYZE tbl; -- optionally in between, or autovacuum kicks in 

-- RECREATE indexes (& fk constraints ?) 
ALTER TABLE tbl ENABLE TRIGGER user; 
+1

Puntos menores: las aspiradoras incrementales liberarán punteros de línea de tupla, lo que la poda HOT durante el acceso normal no puede. Eso * puede * hacer que valga la pena entre los pasos de 'ACTUALIZACIÓN'. Recomiendo un 'VACUUM FREEZE ANALYZE' en la tabla al final del proceso si se espera que estas filas permanezcan (sin más' UPDATE' o 'DELETE') durante mucho tiempo; de lo contrario, los trabajos de autovacío que se activan para congelar tuplas para evitar el envolvente de ID de transacción pueden ser dolorosos. Es posible que pueda volver a escribir las tuplas con la información del bit de sugerencia y las ID de transacción congeladas en este punto, lo que le permite ahorrar un poco de sobrecarga. – kgrittn

+1

Por cierto, actualmente las personas abordan algunos de los problemas de rendimiento de larga data con claves externas, lo que incluye ser mucho más inteligente para evitar los gastos indirectos si los cambios específicos realizados no pueden causar un problema de integridad referencial. Si esta respuesta aún persiste después de que sale 9.3, la eliminación de claves externas puede ser mucho menos importante. (Y sí, me refiero a 9.3, que probablemente se lanzará en el verano de 2013). – kgrittn

0

Tu teoría es probablemente correcta. Leer la tabla completa (y luego hacer cualquier cosa) probablemente esté causando la desaceleración.

¿Por qué no creas otra tabla que tenga PageId y LastError? Inicialice esto con los datos en la tabla que tiene ahora (que debe tomar menos de 93 minutos). Luego, use LastError de la nueva tabla.

En su tiempo libre, puede eliminar LastError de su tabla existente.

Por cierto, normalmente no recomiendo mantener dos copias de una columna en dos tablas separadas. En este caso, sin embargo, parece que está atascado y necesita una forma de proceder.

+0

Excelente idea para crear una nueva tabla con la menor cantidad de datos, y luego descartar las otras columnas de la primera. Entonces podría renombrar las tablas cuando haya terminado. Además, la nueva tabla podría tener 'HtmlId' y' Html', y 'Indexer.Pages' podría tener una referencia a' HtmlId', que podría ser un poco más * normal *. –

1
UPDATE Indexer.Pages 
    SET LastError=NULL 
    ; 

La cláusula where no es necesario ya que los campos NULL NULL son ya, por lo que no dañará a ponerlos en null de nuevo (no creo que esto afectaría significativamente el rendimiento).

Dado su number_of_rows = 500K y su tamaño de tabla = 46G, llego a la conclusión de que su promedio rowsize es de 90KB. Eso es enorme ¿Tal vez podría mover las columnas {sin usar, dispersas} de su tabla a otras tablas?

+0

Tiendo a estar en la misma página que usted, sin embargo @kgrittn mencionó ya que estas filas están tostadas en la tabla principal, no debería afectar la actualización de actualización ya que no toco esas columnas. Tengo curiosidad si esto es verdad. Desafortunadamente, no podré probarlo hasta esta tarde cuando llegue a casa. –

+1

@MikeChristensen: ¡Oh, este consejo no es bueno! Llevaría a ACTUALIZACIONES vacías ralentizando su consulta. Definitivamente debe incluir la cláusula WHERE. Además, como PostgreSQL usa tablas TOAST para grandes valores, probablemente no ayude mucho a dividir su tabla. –

+0

Además del costo bruto de las actualizaciones de fila innecesarias mencionadas por @ErwinBrandstetter, hincharía innecesariamente la mesa, lo que provocaría que todo el acceso posterior fuera lento hasta que realice un mantenimiento agresivo.Tenga en cuenta que en PostgreSQL, una ACTUALIZACIÓN crea una nueva versión de la fila en una nueva posición, mientras deja la versión anterior de la fila para una limpieza posterior mediante un proceso de vacío. Una ACTUALIZACIÓN sin una cláusula WHERE va a duplicar el tamaño de su tabla base. Esta es la razón por la que estaba recomendando varias ACTUALIZACIONES más pequeñas con VACÍO en el medio, por lo que el espacio de las filas antiguas se puede reutilizar. – kgrittn

Cuestiones relacionadas