Pregunta

Recientemente encontré y solucioné un error en un sitio en el que estaba trabajando que resultó en millones de filas de datos duplicadas en una tabla que será bastante grande incluso sin ellos (aún en millones). Puedo encontrar fácilmente estas filas duplicadas y puedo ejecutar una sola consulta de eliminación para matarlas a todas. El problema es que tratar de eliminar estas muchas filas de un disparo bloquea la mesa durante mucho tiempo, lo que me gustaría evitar si es posible. Las únicas formas en que puedo ver para deshacerme de estas filas, sin derribar el sitio (bloqueando la mesa) son:

  1. Escriba un script que ejecute miles de consultas de eliminación más pequeñas en un bucle. Teóricamente, esto se moverá por el problema de la mesa bloqueada porque otras consultas podrán ingresar a la cola y correr entre las eliminaciones. Pero todavía aumentará bastante la carga en la base de datos y tardará mucho en ejecutarse.
  2. Cambie el nombre de la tabla y recrea la tabla existente (ahora estará vacía). Luego haga mi limpieza en la mesa renombrada. Cambie el nombre de la nueva tabla, nombre la vieja y fusione las nuevas filas en la tabla renombrada. Esta es una forma considerablemente más pasos, pero debería hacer el trabajo con una interrupción mínima. La única parte difícil aquí es que la tabla en cuestión es una mesa de informes, por lo que una vez que se renombra y la vacía colocó en su lugar todos los informes históricos desaparecen hasta que lo volví a poner en su lugar. Además, el proceso de fusión podría ser un poco doloroso debido al tipo de datos que se almacenan. En general, esta es mi elección probable en este momento.

Me preguntaba si alguien más ha tenido este problema antes y, de ser así, ¿cómo lo trataste sin derribar el sitio y, con suerte, con una interrupción mínima para los usuarios? Si voy con el número 2, o un enfoque diferente y similar, puedo programar las cosas para correr tarde en la noche y hacer la fusión temprano a la mañana siguiente y simplemente dejarlo saber con anticipación, por lo que eso no es un gran problema. Solo estoy buscando ver si alguien tiene alguna idea para una forma mejor o más fácil de hacer la limpieza.

¿Fue útil?

Solución

DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000

Lave, enjuague, repita hasta que cero filas afectadas. Tal vez en un guión que duerme por un segundo o tres entre iteraciones.

Otros consejos

También recomiendo agregar algunas limitaciones a su mesa para asegurarse de que esto no le vuelva a suceder. Un millón de filas, a 1000 por disparo, tomarán 1000 repeticiones de un guión para completar. Si el script se ejecuta una vez cada 3.6 segundos, se realizará en una hora. Sin preocupaciones. Es poco probable que sus clientes se noten.

El siguiente elimina 1,000,000 de registros, uno a la vez.

 for i in `seq 1 1000`; do 
     mysql  -e "select id from table_name where (condition) order by id desc limit 1000 " | sed 's;/|;;g' | awk '{if(NR>1)print "delete from table_name where id = ",$1,";" }' | mysql; 
 done

Podrían agruparlos y eliminar table_name en el que en (id1, id2, .. id) estoy seguro de que también no

Tuve un caso de uso de eliminar 1 m+ filas en la tabla de filas 25m+ en el mysql. Probó diferentes enfoques como eliminar por lotes (descrito anteriormente).
He descubierto que la forma más rápida (copia de los registros requeridos a la nueva tabla):

  1. Crea una tabla temporal que contenga solo IDS.

Crear tabla id_temp_table (temp_id int);

  1. Insertar ID que deben eliminarse:

insertar en id_temp_table (temp_id) Seleccione .....

  1. Crear nueva tabla_new

  2. Inserte todos los registros de la tabla a la tabla_new sin filas innecesarias que están en id_temp_table

insertar en table_new .... donde table_id no en (seleccione distinto (temp_id) de id_temp_table);

  1. Cambiar las mesas

Todo el proceso tomó ~ 1 hora. En mi caso de uso, simple eliminación de lotes en 100 registros tomó 10 minutos.

Yo usaría Archiver MK de lo excelente Maatkit El paquete de utilidades (un montón de scripts de Perl para MySQL Management) Maatkit es de Baron Schwartz, autor del libro O'Reilly "High Performance MySQL".

El objetivo es un trabajo de bajo impacto y solo para manchar datos antiguos de la tabla sin afectar mucho las consultas OLTP. Puede insertar los datos en otra tabla, que no necesita estar en el mismo servidor. También puede escribirlo en un archivo en un formato adecuado para el infile de datos de carga. O no puede hacer ninguno de los dos, en cuyo caso es solo una eliminación incremental.

Ya está construido para archivar sus filas no deseadas en pequeños lotes y, como bonificación, puede guardar las filas eliminadas en un archivo en caso de que arruine la consulta que selecciona las filas para eliminar.

No se requiere instalación, solo agarre http://www.maatkit.org/get/mk-archiver y ejecute Perldoc en él (o lea el sitio web) para la documentación.

Me enfrenté a un problema similar. Teníamos una tabla realmente grande, de aproximadamente 500 GB de tamaño sin partición y un solo índice en la columna Primary_Key. Nuestro maestro era un casco de una máquina, 128 núcleos y 512 conciertos de RAM y también teníamos múltiples esclavos. Intentamos algunas técnicas para abordar la eliminación de filas a gran escala. Los enumeraré a todos aquí de peor a lo mejor que encontramos

  1. Buscar y eliminar una fila a la vez. Este es lo peor que podrías hacer. Entonces, ni siquiera probamos esto.
  2. Obtener las primeras filas 'X' de la base de datos utilizando una consulta de límite en la columna Primary_Key, luego verificando las ID de fila para eliminar en la aplicación y disparar una sola consulta de eliminación con una lista de ID de Primary_Key. Entonces, 2 consultas por filas 'x'. Ahora, este enfoque estaba bien, pero hacer esto usando un trabajo por lotes eliminó alrededor de 5 millones de filas en 10 minutos más o menos, debido a que los esclavos de nuestro MySQL DB se retrasaron en 105 segundos. Retraso de 105 segundos en actividad de 10 minutos. Entonces, tuvimos que parar.
  3. En esta técnica, introdujimos un retraso de 50 ms entre nuestra búsqueda de lotes posterior y deleciones de tamaño 'x' cada uno. Esto resolvió el problema de retraso, pero ahora estábamos eliminando 1.2-1.3 millones de filas por 10 minutos en comparación con 5 millones en la técnica #2.
  4. Dividiendo la tabla de la base de datos y luego eliminar las particiones completas cuando no sea necesario. Esta es la mejor solución que tenemos, pero requiere una tabla prepartitada. Seguimos el Paso 3 porque teníamos una tabla muy antigua no partidada con solo indexación en la columna Primary_Key. Crear una partición habría llevado demasiado tiempo y estábamos en modo de crisis. Aquí hay algunos enlaces relacionados con la partición que encontré útil Referencia oficial de MySQL, Oracle DB Partición diaria.

Entonces, en mi opinión, si puede permitirse el lujo de crear una partición en su mesa, elija la opción #4, de lo contrario, está atascado con la opción #3.

Hazlo en lotes de vamos a decir 2000 filas a la vez. Comprometerse en el medio. Un millón de filas no es tanto y esto será rápido, a menos que tenga muchos índices en la tabla.

De acuerdo con la documentación de MySQL, TRUNCATE TABLE es una alternativa rápida a DELETE FROM. Prueba esto:

TRUNCATE TABLE table_name

Probé esto en 50 m filas y se hizo en dos minutos.

Nota: Las operaciones truncadas no son seguras de transacción; Se produce un error al intentar uno en el curso de una transacción activa o bloqueo de tabla activa

Para nosotros, el DELETE WHERE %s ORDER BY %s LIMIT %d La respuesta no era una opción, porque el criterio de dónde era lento (una columna no indexada) y golpearía el maestro.

Seleccione de una lectura-replica una lista de claves primarias que desea eliminar. Exportar con este tipo de formato:

00669163-4514-4B50-B6E9-50BA232CA5EB
00679DE5-7659-4CD4-A919-6426A2831F35

Use el siguiente script bash para obtener esta entrada y fragmentarla en declaraciones de eliminación Requiere Bash ≥ 4 debido a mapfile incorporado]:

sql-chunker.sh (Recuerda chmod +x yo, y cambia el shebang para señalar a tu ejecutable de bash 4):

#!/usr/local/Cellar/bash/4.4.12/bin/bash

# Expected input format:
: <<!
00669163-4514-4B50-B6E9-50BA232CA5EB
00669DE5-7659-4CD4-A919-6426A2831F35
!

if [ -z "$1" ]
  then
    echo "No chunk size supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

if [ -z "$2" ]
  then
    echo "No file supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

function join_by {
    local d=$1
    shift
    echo -n "$1"
    shift
    printf "%s" "${@/#/$d}"
}

while mapfile -t -n "$1" ary && ((${#ary[@]})); do
    printf "DELETE FROM my_cool_table WHERE id IN ('%s');\n" `join_by "','" "${ary[@]}"`
done < "$2"

Invocar así:

./sql-chunker.sh 1000 ids.txt > batch_1000.sql

Esto le dará un archivo con salida formateada como So (he usado un tamaño por lotes de 2):

DELETE FROM my_cool_table WHERE id IN ('006CC671-655A-432E-9164-D3C64191EDCE','006CD163-794A-4C3E-8206-D05D1A5EE01E');
DELETE FROM my_cool_table WHERE id IN ('006CD837-F1AD-4CCA-82A4-74356580CEBC','006CDA35-F132-4F2C-8054-0F1D6709388A');

Luego ejecute las declaraciones como así:

mysql --login-path=master billing < batch_1000.sql

Para aquellos que no están familiarizados con login-path, es solo un atajo para iniciar sesión sin escribir contraseña en la línea de comando.

Creo que la lentitud se debe al "índice agrupado" de MySQL, donde los registros reales se almacenan dentro del índice de clave principal, en el orden del índice de clave primaria. Esto significa que el acceso a un registro a través de la clave primaria es extremadamente rápido porque solo requiere una búsqueda de disco porque el registro en el disco allí mismo encontró la clave primaria correcta en el índice.

En otras bases de datos sin índices agrupados, el índice en sí no contiene el registro, sino solo una "compensación" o "ubicación" que indica dónde se encuentra el registro en el archivo de la tabla y luego se debe hacer una segunda recuperación en ese archivo para recuperar los datos reales .

Se puede imaginar al eliminar un registro en un índice agrupado que todos los registros anteriores de ese registro en la tabla deben moverse hacia abajo para evitar que se creen agujeros masivos en el índice (bueno, eso es lo que recuerdo hace unos años al menos, versiones posteriores. puede haber cambiado esto).

Conocer lo anterior lo que encontramos que realmente se elimina en MySQL era realizar las eliminaciones en orden inverso. Esto produce la menor cantidad de movimiento récord porque usted está eliminando los registros desde el final, lo que significa que las eliminaciones posteriores tienen menos objetos para reubicarse.

No he escrito nada para hacer esto, y hacerlo correctamente requeriría absolutamente un script, pero otra opción es crear una nueva tabla duplicada y seleccionar todas las filas que desee mantener. Use un desencadenante para mantenerlo actualizado mientras se completa este proceso. Cuando esté sincronizado (menos las filas que desea soltar), cambie el nombre de ambas tablas en una transacción, de modo que la nueva tome el lugar de lo anterior. ¡Deja la vieja mesa, y listo!

Esto (obviamente) requiere mucho espacio de disco adicional, y puede gravar sus recursos de E/S, pero de lo contrario, puede ser mucho más rápido.

Dependiendo de la naturaleza de los datos o en una emergencia, puede cambiar el nombre de la tabla antigua y crear una mesa nueva y vacía en su lugar, y seleccionar las filas de "Mantenga" en la nueva tabla a su tiempo libre ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top