¿Cómo puedo acelerar "mostrar columnas" en MySQL?

7

Mi aplicación depende de ejecutar "mostrar columnas" para ciertas tablas. Tarda unos 60 ms en ejecutarse, mientras que todas nuestras otras consultas toman menos de un ms. Consultar information_schemadirectamente es aún más lento.

La base de datos contiene aproximadamente 250 bases de datos, con 100 a 200 tablas por base de datos (aproximadamente 20k tablas en total).

  • ¿Cómo puedo averiguar por qué estas operaciones son tan lentas?
  • ¿Quizás haya alguna configuración que pueda cambiar para que se ejecute más rápido o para almacenarla en caché en el lado de SQL?

(La aplicación realiza alrededor de 14 consultas de este tipo por carga de página; soy consciente de que este código heredado debe limpiarse, pero busco posibles opciones mientras trabajo en la solución a largo plazo).

mpen
fuente
1
fuera de interés, ¿en qué escenario serían 60 ms demasiado lentos para examinar las columnas de una tabla? No es algo que debe hacer cada petición
1
¿Qué quieres decir con mostrar columnas? ¿Garbing nombres de columnas de una tabla o imprimir una columna completa? Si son los nombres ... ¿por qué no lo coges una vez y lo almacenas en tu aplicación ... si eso no es posible, por qué no creas otra tabla que contenga todas las columnas basadas en la tabla?
@Jaitsu: No, no es algo que deberíamos estar haciendo, pero así es como es. Código heredado Hasta que tenga algo de tiempo para limpiarlo y hacerlo correctamente, quiero ver si puedo acelerarlo. Tengo alrededor de 14 de estos que ejecutan cada carga de página.
@FlorinStingaciu: Sí, nombres de columna. Ponerlos en otra mesa puede acelerar las cosas, pero se desincronizará, lo que anula el propósito de preguntar directamente a la mesa.
1
@ Mat: No es una mala idea. Votado para migrar a dba.

Respuestas:

12

MySQL recalcula las estadísticas de la tabla para ciertas operaciones que acceden a las INFORMATION_SCHEMAtablas ( SHOW COLUMNSes solo un alias conveniente para realizar consultas INFORMATION_SCHEMA.COLUMNS). Establezca innodb_stats_on_metadata en falso, lo que evitará que este recálculo ocurra cuando solicite metadatos de la tabla.

SET GLOBAL innodb_stats_on_metadata=0;

y agregue lo siguiente a my.cnf

[mysqld]
innodb_stats_on_metadata = 0
Aaron Brown
fuente
Debería haber mencionado que en realidad estoy usando MyISAM. Intenté configurar esto de todos modos, pero no produjo ningún beneficio.
mpen
¿Has considerado ALTER TABLE foo ENGINE = InnoDB? :) ¿Hay una buena razón para usar MyISAM?
Aaron Brown
Razones heredadas en su mayoría, creo. Me temo lo que podría pasar si lo intentara; No estoy seguro de que todos los FK se alinearán. Sin embargo, lo pensaré un poco más.
mpen
@AaronBrown +1 para esta respuesta porque quien enfrenta esta situación con una base de datos de todo InnoDB necesita esta información.
RolandoMySQLDBA
1
+1 por poner [mysqld]allí. Puede ser obvio para muchos que esta configuración iría bajo mysqld, pero puede no ser obvio para aquellos que harían esta pregunta. Por cierto, esto aceleró SELECT COUNT(*)en una de mis information_schemamesas a 6 segundos desde más de un minuto. Todavía lento, pero una gran mejora.
Buttle Butkus
3

Le sugiero que cree una base de datos que tenga las INFORMATION_SCHEMAtablas (o solo aquellas que necesita) como réplicas. Indícelos adecuadamente y obtendrá un aumento de rendimiento.

Sin INFORMATION_SCHEMAembargo, el problema de la sincronización entre esta base de datos es complicado.

Podría tener un procedimiento que sincronice estas tablas cada hora o cada 5 minutos (¿con qué frecuencia se cambia la estructura de las tablas?).

Otra idea sería utilizar MySQL Proxy para capturar cualquier ALTER TABLEdeclaraciones (y CREATE, y DROP, y CREATE INDEX, y cualquier otras declaraciones modificar la información que necesita) y luego sincronizar el esquema de información replicada después de estas declaraciones tienen éxito.


Si solo necesita los nombres de columna y ninguna otra información, como el tipo de datos, la longitud o los índices disponibles, tal vez podría reemplazar el uso de SHOW COLUMNSconsultas (rápidas) que devuelven solo 1 fila, con LIMIT 1o ninguna, con LIMIT 0o:

SELECT * FROM TableName WHERE FALSE ;

A pesar de los consejos generales contra el uso de SELECT *, este puede ser un caso legítimo donde nada más es útil. (¡todo lo demás *, pero puede provocar un error!)

ypercubeᵀᴹ
fuente
2

En este caso particular, creo que INFORMATION_SCHEMAes un arenque rojo. Según mis propias pruebas de SHOW COLUMNSrendimiento, la innodb_stats_on_metadatavariable no parece hacer ninguna diferencia en las tablas MyISAM o InnoDB.

Sin embargo, del manual de MySQL 5.0 ...

Algunas condiciones impiden el uso de una tabla temporal en memoria, en cuyo caso el servidor usa una tabla en disco:

[...]

  • Las declaraciones SHOW COLUMNSy The se DESCRIBEusan BLOBcomo tipo para algunas columnas, por lo tanto, la tabla temporal utilizada para los resultados es una tabla en el disco.

Esto parece haberse eliminado del manual a partir de MySQL 5.5, pero aún parece aplicarse en esa versión ...

mysql> SHOW VARIABLES LIKE 'version';
+---------------+-------------------------+
| Variable_name | Value                   |
+---------------+-------------------------+
| version       | 5.5.41-0ubuntu0.14.04.1 |
+---------------+-------------------------+
1 row in set (0.00 sec)

mysql> SHOW STATUS LIKE 'Created_tmp_disk_tables';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 0     |
+-------------------------+-------+
1 row in set (0.00 sec)

mysql> SHOW COLUMNS FROM mysql.user;
[...snip...]
42 rows in set (0.00 sec)

mysql> SHOW STATUS LIKE 'Created_tmp_disk_tables';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 1     |
+-------------------------+-------+
1 row in set (0.00 sec)

La información de campo devuelta con un conjunto de resultados de la consulta contiene la misma información que la devuelta SHOW COLUMNS, por lo que se SELECT * FROM my_table LIMIT 0debe lograr lo mismo sin crear una tabla temporal en disco por consulta.

Un ejemplo rápido para tomar los nombres de campo en PHP ...

$mysql = new mysqli('localhost', 'root', '', 'my_database');
$field_names = array();
$result = $mysql->query("SELECT * FROM my_table LIMIT 0");
$fields = $result->fetch_fields();
foreach ($fields as $fields)
{
    $field_names[] = $field->name;
}
var_dump($field_names);

Recuperar información de campo de esta manera es un poco más incómodo de decodificar. Tendrá que consultar la descripción de la MYSQL_FIELDestructura subyacente para extraer los tipos de datos y las marcas, pero se ejecuta aproximadamente 7 veces más rápido en mi sistema.

Aya
fuente
1

Me gusta la primera sugerencia en la respuesta de @ yerpcube (+1), pero me gustaría proponer algo

  • crear otra instancia de base de datos en el puerto 3307
  • mysqldump la base de datos de producción al archivo de texto SQL utilizando las siguientes opciones:
    • --no-data
    • --routines
    • --triggers
    • --all-databaseso --databasesseguido de una lista de bases de datos que desea
  • Cargue el archivo de texto SQL en el puerto 3307 Instancia MySQL

Por lo tanto, mysqldump debería tener el siguiente aspecto:

mysqldump --no-data --routines --triggers --all-databases > ImportFile.sql

Eso es. En el futuro, todo lo que necesita hacer es conectarse a esta instancia de base de datos del puerto 3307 y ejecutar cualquier consulta relacionada con el esquema al contenido de su corazón. Si conoce alguna tabla en la base de datos de producción que cambie, solo mysqldump el esquema de producción y vuelva a cargarlo en la instancia del puerto 3307.

ADVERTENCIA: Si instala una instancia de mysql en la misma máquina que producción, asegúrese absolutamente de conectarse a esa instancia usando

mysql -u... -p... -h127.0.0.1 -P3307 < ImportFile.sql

Si ejecutas

mysql -u... -p... -P3307 < ImportFile.sql

Manguera de producción. Así que ten cuidado !!!!

Una alternativa sería usar un servidor de base de datos separado.

RolandoMySQLDBA
fuente