2008-09-21 24 views
13

Tengo una tabla de base de datos muy grande en PostgresQL y una columna como "copiado". Cada nueva fila comienza sin copia y luego será replicada a otra cosa por un programa de fondo. Hay un índice parcial en esa tabla "btree (ID) WHERE replicated = 0". El programa de fondo selecciona entre 2000 entradas como máximo (LÍMITE 2000), trabaja en ellas y luego confirma los cambios en una transacción utilizando 2000 comandos sql preparados.Actualizar tabla de base de datos PostgreSQL MUY GRANDE eficientemente

Ahora el problema es que quiero darle al usuario la opción de restablecer este valor duplicado, hacer que vuelva a cero todo.

Un conjunto de tablas de actualización replicado = 0;

no es posible:

  • Se necesita mucho tiempo
  • Se duplica el tamaño de la tabel debido MVCC
  • Se realiza en una sola transacción: Se falla o pasa a través.

En realidad, no necesito características de transacción para este caso: si el sistema deja de funcionar, procesará solo partes de él.

Varios otros problemas: haciendo un

update set replicated=0 where id >10000 and id<20000 

también es malo: Se hace un recorrido secuencial por toda la mesa entera que es demasiado lento. Si no estuviese haciendo eso, aún sería lento porque sería demasiadas búsquedas.

Lo que realmente necesito es una forma de recorrer todas las filas, cambiarlas y no estar obligado a una transacción gigante.

Extrañamente, un

UPDATE table 
    SET replicated=0 
WHERE ID in (SELECT id from table WHERE replicated= LIMIT 10000) 

también es lento, aunque debe ser una buena cosa: Ir a través de la tabla de la DISK-fin ...

(Nótese que en este caso hubo también un índice que cubría este)

(un límite de actualización como MySQL no está disponible para PostgreSQL)

por cierto: El verdadero problema es más complicado y estamos hablando de una sistema embebido aquí que ya está implementado, por lo que los cambios de esquema remoto son difíciles, pero posible Desafortunadamente, es PostgresQL 7.4.

La cantidad de filas a las que me refiero es, por ejemplo, 90000000. El tamaño del databse puede ser varios dozend gigabytes.

La base de datos solo contiene 5 tablas, una es muy grande. Pero eso no es un mal diseño, porque estos cuadros integrados solo funcionan con un tipo de entidad, ¡no es un sistema ERP o algo así!

¿Alguna idea?

Respuesta

8

¿Qué le parece agregar una nueva tabla para almacenar este valor replicado (y una clave principal para vincular cada registro a la tabla principal). Luego, simplemente agrega un registro por cada elemento replicado y elimina los registros para eliminar el indicador replicado.(O tal vez al revés: un registro por cada registro no replicado, dependiendo de cuál sea el caso común).

Eso también simplificaría el caso cuando desee volver a ponerlos a 0, ya que puede truncar la tabla (lo que pone a cero el tamaño de la tabla en el disco, ni siquiera tiene que aspirar para liberar el espacio)

+0

Esta es una muy buena idea, aunque desafortunadamente requiere un cambio de esquema (un procedimiento de actualización). Lo que realmente me gusta de este enfoque es que, de hecho, el índice parcial actual es internamente bastante similar a esta idea. Solo más flexible y manejable. – Christian

3

Si está tratando de restablecer toda la tabla, no solo unas pocas filas, generalmente es más rápido (en conjuntos de datos extremadamente grandes, no en tablas regulares) simplemente CREATE TABLE bar AS SELECT everything, but, copied, 0 FROM foo, y luego intercambie las tablas y suelte el antiguo uno. Obviamente, deberá asegurarse de que no se inserta nada en la tabla original mientras lo hace. También deberá volver a crear ese índice.

Editar: Una simple mejora con el fin de evitar el bloqueo de la mesa, mientras que la copia 14 Gigabytes:

lock ; 
create a new table, bar; 
swap tables so that all writes go to bar; 
unlock; 
create table baz as select from foo; 
drop foo; 
create the index on baz; 
lock; 
insert into baz from bar; 
swap tables; 
unlock; 
drop bar; 

(permiten escrituras suceden mientras que está haciendo la copia, e insertarlos post-factum).

1

Esto es pseudocódigo. Necesitará un archivo temporal de 400MB (para ints) o 800MB (para bigints) (puede comprimirlo con zlib si es un problema). Necesitará aproximadamente 100 escaneos de una mesa para aspiradoras. Pero no inflará una mesa más de 1% (como máximo 1000000 filas muertas en cualquier momento). También puede intercambiar menos exploraciones para obtener más inflado de la tabla.

// write all ids to temporary file in disk order     
// no where clause will ensure disk order 
$file = tmpfile(); 
for $id, $replicated in query("select id, replicated from table") { 
     if ($replicated<>0) { 
       write($file,&$id,sizeof($id)); 
     } 
} 

// prepare an update query 
query("prepare set_replicated_0(bigint) as 
     update table set replicated=0 where id=?"); 

// reread this file, launch prepared query and every 1000000 updates commit 
// and vacuum a table 
rewind($file); 
$counter = 0; 
query("start transaction"); 
while read($file,&$id,sizeof($id)) { 
     query("execute set_replicated_0($id)"); 
     $counter++; 
     if ($counter % 1000000 == 0) { 
       query("commit"); 
       query("vacuum table"); 
       query("start transaction"); 
     } 
} 
query("commit"); 
query("vacuum table"); 
close($file); 
2

Aunque no se puede solucionar el problema probable de uso de espacio (es temporal, sólo hasta que el vacío) es probable que pueda realmente acelerar el proceso en términos de tiempo de reloj. El hecho de que PostgreSQL use MVCC significa que debe poder hacer esto sin ningún problema relacionado con las filas recién insertadas. La tabla de creación como selección sorteará algunos de los problemas de rendimiento, pero no permitirá el uso continuado de la tabla y ocupará el mismo espacio. Simplemente abandona el índice, y recíbelo, luego haz un vacío.

drop index replication_flag; 
update big_table set replicated=0; 
create index replication_flag on big_table btree(ID) WHERE replicated=0; 
vacuum full analyze big_table; 
1

Creo que es mejor cambiar su postgres a la versión 8.X. probablemente la causa sea la versión baja de Postgres. Pruebe también esta consulta a continuación. Espero que esto pueda ayudar.

UPDATE table1 SET name = table2.value 
FROM table2 
WHERE table1.id = table2.id; 
0

Creo que lo que hay que hacer es un . copie el valor PK de 2000 registros en una tabla temporal con el mismo límite estándar, etc. b. seleccione los mismos 2000 registros y realice las operaciones necesarias en el cursor tal como está. c. Si tiene éxito, ejecute una sola consulta de actualización contra los registros en la tabla temporal. Borre la tabla temporal y ejecute el paso a nuevamente. d. Si no tiene éxito, borre la tabla temporal sin ejecutar la consulta de actualización. Simple, eficiente y confiable. Saludos, KT

Cuestiones relacionadas