MySQL: la forma más rápida de ALTERAR TABLA para InnoDB

12

Tengo una tabla InnoDB que quiero modificar. La tabla tiene ~ 80 millones de filas y abandona algunos índices.

Quiero cambiar el nombre de una de las columnas y agregar algunos índices más.

  • ¿Cuál es la forma más rápida de hacerlo (suponiendo que pueda sufrir incluso el tiempo de inactividad, el servidor es un esclavo no utilizado)?
  • ¿Es un "simple" alter table, la solución más rápida?

En este momento, todo lo que me importa es la velocidad :)

Corrió
fuente
Muestre SHOW CREATE TABLE tblname\Gla columna que debe modificarse, el tipo de datos de la columna y el nuevo nombre de la columna.
RolandoMySQLDBA
aquí está: pastie.org/3078349 la columna que debe renombrarse es sent_aty para agregarle algunos índices más
Ran
¿Se debe cambiar el nombre de sent_at a qué?
RolandoMySQLDBA
digamos: new_sent_at
Ran

Respuestas:

14

Una forma segura de acelerar una ALTER TABLE es eliminar índices innecesarios

Estos son los pasos iniciales para cargar una nueva versión de la tabla.

CREATE TABLE s_relations_new LIKE s_relations;
#
# Drop Duplicate Indexes
#
ALTER TABLE s_relations_new
    DROP INDEX source_persona_index,
    DROP INDEX target_persona_index,
    DROP INDEX target_persona_relation_type_index
;

Tenga en cuenta lo siguiente:

  • Descarté source_persona_index porque es la primera columna en otros 4 índices

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • Descarté target_persona_index porque es la primera columna en otros 2 índices

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • Descarté target_persona_relation_type_index porque las primeras 2 columnas también están en target_persona_relation_type_message_id_index

OK Eso se encarga de índices innecesarios. ¿Hay algún índice que tenga baja cardinalidad? Aquí está la forma de determinar eso:

Ejecute las siguientes consultas:

SELECT COUNT(DISTINCT sent_at)               FROM s_relations;
SELECT COUNT(DISTINCT message_id)            FROM s_relations;
SELECT COUNT(DISTINCT target_object_id)      FROM s_relations;

Según su pregunta, hay alrededor de 80,000,000 de filas. Como regla general, MySQL Query Optimizer no usará un índice si la cardinalidad de las columnas seleccionadas es mayor que el 5% del recuento de filas de la tabla. En este caso, eso sería 4,000,000.

  • Si COUNT(DISTINCT sent_at)> 4,000,000
    • entonces ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • Si COUNT(DISTINCT message_id)> 4,000,000
    • entonces ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • Si COUNT(DISTINCT target_object_id)> 4,000,000
    • entonces ALTER TABLE s_relations_new DROP INDEX target_object_index;

Una vez que se ha determinado la utilidad o inutilidad de esos índices, puede volver a cargar los datos.

#
# Change the Column Name
# Load the Table
#
ALTER TABLE s_relations_new CHANGE sent_at sent_at_new int(11) DEFAULT NULL;
INSERT INTO s_relations_new SELECT * FROM s_relations;

Eso es todo, ¿verdad? NO !!!

Si su sitio web ha estado activo todo este tiempo, puede haber INSERTs ejecutándose contra s_relations durante la carga de s_relations_new. ¿Cómo puedes recuperar esas filas que faltan?

Busque la identificación máxima en s_relations_new y agregue todo después de esa ID de s_relations. Para garantizar que la tabla se congele y se use solo para esta actualización, debe tener un poco de tiempo de inactividad para obtener las últimas filas que se insertaron en s_relation_new. Aquí está lo que haces:

En el sistema operativo, reinicie mysql para que nadie más pueda iniciar sesión pero root @ localhost (deshabilita TCP / IP):

$ service mysql restart --skip-networking

A continuación, inicie sesión en mysql y cargue esas últimas filas:

mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

Luego, reinicie mysql normalmente

$ service mysql restart

Ahora, si no puede eliminar mysql, tendrá que hacer un cebo y activar s_relations. Simplemente inicie sesión en mysql y haga lo siguiente:

mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations_old WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

Darle una oportunidad !!!

PRECAUCIÓN: una vez que esté satisfecho con esta operación, puede abandonar la tabla anterior lo antes posible:

mysql> DROP TABLE s_relations_old;
RolandoMySQLDBA
fuente
12

La respuesta correcta depende de la versión del motor MySQL que esté utilizando.

Si utiliza 5.6+, los cambios de nombre y la adición / eliminación de índices se realizan en línea , es decir, sin copiar todos los datos de la tabla.

Solo utilícelo ALTER TABLEcomo de costumbre, será casi instantáneo para los cambios de nombre e índices, y razonablemente rápido para la adición de índices (tan rápido como leer toda la tabla una vez).

Si usa 5.1+ y el complemento InnoDB está habilitado, también se agregarán / eliminarán índices en línea. No estoy seguro sobre los cambios de nombre.

Si usa una versión anterior, ALTER TABLEsigue siendo la más rápida, pero probablemente será terriblemente lenta porque todos sus datos se volverán a insertar en una tabla temporal debajo del capó.

Finalmente, es hora de desacreditar el mito. Desafortunadamente, no tengo suficiente karma aquí para comentar las respuestas, pero creo que es importante corregir la respuesta más votada. Esto esta mal :

Como regla general, MySQL Query Optimizer no usará un índice si la cardinalidad de las columnas seleccionadas es mayor que el 5% del recuento de filas de la tabla

En realidad es al revés .

Los índices son útiles para seleccionar pocas filas, por lo que es importante que tengan una alta cardinalidad, lo que significa muchos valores distintos y estadísticamente pocas filas con el mismo valor.

mezis
fuente
Enlace a la documentación del complemento InnoDB (no se pudo pegar debido a los límites de repeticiones).
mezis
2
En MySQL 5.5 encontré RENAME TABLEinstantáneo (como se esperaba), pero CHANGE COLUMNal cambiar el nombre de la clave primaria hice una copia completa ... ¡7 horas! ¿Posiblemente solo porque era la clave principal? No está bien.
KCD
2

Tuve el mismo problema con Maria DB 10.1.12, luego, después de leer la documentación, descubrí que hay una opción para realizar la operación "en el lugar" que elimina la copia de la tabla. Con esta opción, la tabla alter es muy rápida. En mi caso fue:

alter table user add column (resettoken varchar(256),
  resettoken_date date, resettoken_count int), algorithm=inplace;

Esto es muy rápido. Sin la opción del algoritmo, nunca terminaría.

https://mariadb.com/kb/en/mariadb/alter-table/

Saule
fuente
0

Para el cambio de nombre de la columna,

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

debe estar bien y no llevar ningún tiempo de inactividad.

Para los índices, la instrucción CREATE INDEX bloqueará la tabla. Si se trata de un esclavo no utilizado, como mencionaste, no es un problema.

Otra opción sería crear una tabla nueva que tenga los nombres e índices de columna adecuados. Luego podría copiar todos los datos en él, luego ejecutar una serie de

BEGIN TRAN;
ALTER TABLE RENAME tablename tablenameold;
ALTER TABLE RENAME newtablename tablename;
DROP TABLE tablenameold;
COMMIT TRAN;

Esto minimizaría el tiempo de inactividad a costa de usar temporalmente el doble de espacio.

TetonSig
fuente
1
DDL en MySQL no es transaccional. Cada instrucción DDL activa un COMPROMISO. Escribí sobre esto: dba.stackexchange.com/a/36799/877
RolandoMySQLDBA
0

También tengo este problema y usé este SQL:

/*on créé la table COPY SANS les nouveaux champs et SANS les FKs */
CREATE TABLE IF NOT EXISTS prestations_copy LIKE prestations;

/* on supprime les FKs de la table actuelle */
ALTER TABLE `prestations`
DROP FOREIGN KEY `fk_prestations_pres_promos`,
DROP FOREIGN KEY `fk_prestations_activites`;

/* on remet les FKs sur la table copy */
ALTER TABLE prestations_copy 
    ADD CONSTRAINT `fk_prestations_activites` FOREIGN KEY (`act_id`) REFERENCES `activites` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
    ADD CONSTRAINT `fk_prestations_pres_promos` FOREIGN KEY (`presp_id`) REFERENCES `pres_promos` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION;

/* On fait le transfert des données de la table actuelle vers la copy, ATTENTION: il faut le même nombre de colonnes */
INSERT INTO prestations_copy
SELECT * FROM prestations;

/* On modifie notre table copy de la façon que l'on souhaite */
ALTER TABLE `prestations_copy`
    ADD COLUMN `seo_mot_clef` VARCHAR(50) NULL;

/* on supprime la table actuelle et renome la copy avec le bon nom de table */
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE prestations;
RENAME TABLE prestations_copy TO prestations;
SET FOREIGN_KEY_CHECKS=1;   

Espero que pueda ayudar a alguien

Saludos,

Será

William Rossier
fuente