¿Cómo mejorar el rendimiento de InnoDB DELETE?

9

Entonces tengo esta tabla de auditoría (rastrea acciones en cualquier tabla de mi base de datos):

CREATE TABLE `track_table` (
  `id` int(16) unsigned NOT NULL,
  `userID` smallint(16) unsigned NOT NULL,
  `tableName` varchar(255) NOT NULL DEFAULT '',
  `tupleID` int(16) unsigned NOT NULL,
  `date_insert` datetime NOT NULL,
  `action` char(12) NOT NULL DEFAULT '',
  `className` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userID` (`userID`),
  KEY `tableID` (`tableName`,`tupleID`,`date_insert`),
  KEY `actionDate` (`action`,`date_insert`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

y necesito comenzar a archivar elementos obsoletos. La tabla ha crecido a aproximadamente 50 millones de filas, por lo que la forma más rápida en que podía eliminar las filas era eliminarla de una tabla a la vez (según tableName).

Esto funciona bastante bien, pero en algunas de las tablas que requieren mucha escritura, no se completará. Mi consulta elimina todos los elementos que tienen una deleteacción asociada en una combinación tupleID / tableName:

DELETE FROM track_table WHERE tableName='someTable' AND tupleID IN (
  SELECT DISTINCT tupleID FROM track_table
  WHERE tableName='someTable' AND action='DELETE' AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
)

Dejé que esto se ejecute en mi servidor durante 3 días y nunca se completó para la tabla más grande. La salida de explicación (si cambio la eliminación para seleccionar:

| id | select_type        | table       | type | possible_keys      | key     | key_len | ref        | rows    | Extra                        |
|  1 | PRIMARY            | track_table | ref  | tableID            | tableID | 257     | const      | 3941832 | Using where                  |
|  2 | DEPENDENT SUBQUERY | track_table | ref  | tableID,actionDate | tableID | 261     | const,func |       1 | Using where; Using temporary |

Entonces, 4 millones de filas no deberían tomar 3 días para eliminar, creo. Tengo mi innodb_buffer_pool_size establecido en 3GB, y el servidor no está configurado para usar one_file_per_table. ¿De qué otras formas puedo mejorar el rendimiento de eliminación de InnoDB? (Ejecutando MySQL 5.1.43 en Mac OSX)

Derek Downey
fuente

Respuestas:

11

Puede eliminar datos en lotes.

En SQL Server, la sintaxis es delete top Xfilas de una tabla. Luego lo hace en un ciclo, con una transacción para cada lote (si tiene más de un extracto, por supuesto), para mantener las transacciones cortas y mantener bloqueos solo por períodos cortos.

En la sintaxis de MySQL: DELETE FROM userTable LIMIT 1000

Hay restricciones al respecto (no se puede usar LIMITen eliminaciones con combinaciones, por ejemplo), pero en este caso es posible que pueda hacerlo de esa manera.

Existe un peligro adicional de usar LIMITcon DELETErespecto a la replicación; las filas eliminadas a veces no se eliminan en el mismo orden en el esclavo que en el maestro.

Mariana
fuente
6

Intente utilizar un enfoque de tabla temporal. Intenta algo como esto:

Paso 1) CREATE TABLE track_table_new LIKE track_table;

Paso 2) INSERT INTO track_table_new SELECT * FROM track_table WHERE action='DELETE' AND date_insert >= DATE_SUB(CURDATE(), INTERVAL 30 day);

Paso 3) ALTER TABLE track_table RENAME track_table_old;

Paso 4) ALTER TABLE track_table_new RENAME track_table;

Paso 5) DROP TABLE track_table_old;

No incluí el campo de tupla en el Paso 2. Por favor, vea si esto produce el efecto deseado. Si esto es lo que desea, es posible que desee deshacerse del campo de tuplas por completo a menos que use el campo de tuplas por otros motivos.

RolandoMySQLDBA
fuente
Esa es una solución interesante. Necesito el campo de tupla en la tabla. tableName / tupleID es una clave externa indefinida de la tabla que se está registrando. Indefinido porque hasta hace poco, esta tabla era MyISAM, que no admite claves foráneas.
Derek Downey
1

La eliminación de filas no deseadas en el lote debería permitir que otras operaciones funcionen. Pero su eliminación de la operación tiene condiciones, así que asegúrese de que haya un índice apropiado en las columnas sobre las condiciones.

Debido a que MySQL no soporta la función completa del recorrido de índice suelta, se puede tratar de ajustar la secuencia de KEY actionDate (action, date_insert)a KEY actionDate (date_insert, action). Con el prefijo 'date_insert', MySQL debería usar este índice para escanear las filas que son anteriores a su condición de fecha y hora.

Con dicho índice, puede escribir SQL como:

DELETE
FROM track_table
WHERE tableName='someTable'
    AND action='DELETE'
    AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
LIMIT 1000 -- Your size of batch
Mike Lue
fuente
1
| id | select_type        | table       | type | possible_keys      | key     | key_len | ref        | rows    | Extra                        |
|  1 | PRIMARY            | track_table | ref  | tableID            | tableID | 257     | const      | 3941832 | Using where                  |
|  2 | DEPENDENT SUBQUERY | track_table | ref  | tableID,actionDate | tableID | 261     | const,func |       1 | Using where; Using temporary |

-Puño, desde su explicación key_len tan grande => necesita degradar el tamaño lo más pequeño posible. Para su consulta, creo que la mejor manera es cambiar el tipo de datos del campo de acción de char (12) a tinyint, para que el mapeo de datos se vea así:

1: -> DELETE
2: -> UPDATE
3: -> INSERT
...

y también puedes cambiar table_id en lugar de tablename. El DDL para el mejor rendimiento puede:

CREATE TABLE `track_table` (
  `id` int(11) unsigned NOT NULL,
  `userID` smallint(6) unsigned NOT NULL,
  `tableid` smallint(6) UNSIGNED NOT NULL DEFAULT 0,
  `tupleID` int(11) unsigned NOT NULL,
  `date_insert` datetime NOT NULL,
  `actionid` tinyin(4) UNSIGNED NOT NULL DEFAULT 0,
  `className` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userID` (`userID`),
  KEY `tableID` (`tableid`,`tupleID`,`date_insert`),
  KEY `actionDate` (`actionid`,`date_insert`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `actions` (
  `id` tinyint(4) unsigned NOT NULL 
  `actionname` varchar(255) NOT NULL,
  PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `table_name` (
  `id` tinyint(4) unsigned NOT NULL 
  `tablename` varchar(255) NOT NULL,
  PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

para que la consulta pueda ejecutarse así:

DELETE FROM track_table WHERE tableid=@tblid AND tupleID IN (
  SELECT DISTINCT tupleID FROM track_table
  WHERE tableid=@tblid AND actionid=@actionid AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
).

Pero la forma más rápida fue usando la partición. para que puedas soltar la partición. Actualmente, mi mesa tiene más de 40mil filas. y actualizar cada hora (400k filas de actualización para cada vez), y puedo soltar la partición curr_date y volver a cargar los datos en la tabla. el comando soltar muy rápido (<100 ms). Espero que esto ayude.

Thanh Nguyen
fuente