Como posso acelerar "mostrar colunas" no MySQL?

7

Meu aplicativo depende da execução de "mostrar colunas" para determinadas tabelas. Demora cerca de 60ms para ser executado, enquanto todas as outras consultas levam menos de um ms. Consultar information_schemadiretamente é ainda mais lento.

O banco de dados contém cerca de 250 bancos de dados, com 100 a 200 tabelas por banco de dados (cerca de 20k tabelas no total).

  • Como posso descobrir por que essas operações são tão lentas?
  • Talvez exista alguma configuração que eu possa alterar para torná-lo mais rápido ou armazená-lo em cache no lado do SQL?

(O aplicativo faz cerca de 14 consultas desse tipo por carregamento de página - estou ciente de que esse código herdado precisa ser limpo, mas procurando por opções possíveis enquanto trabalho na correção de longo prazo.)

mpen
fonte
11
por interesse, em que cenário 60ms seria muito lento para examinar as colunas de uma tabela? Não é algo que você deveria estar fazendo a cada pedido
11
O que você quer dizer com mostrar colunas? Vestir nomes de colunas de uma tabela ou imprimir uma coluna inteira? Se é o nome .. por que você não o pega apenas uma vez e o armazena no seu aplicativo ... se isso não é possível, por que você não cria outra tabela que contém todas as colunas com base na tabela?
@Jaitsu: Não, não é algo que deveríamos estar fazendo, mas é assim que é. Código legado. Até que eu tenha algum tempo para limpá-lo e fazê-lo corretamente, quero ver se consigo acelerar. Eu tenho cerca de 14 deles que executam cada carregamento de página.
@FlorinStingaciu: Sim, nomes de colunas. Colocá-los em outra tabela pode acelerar as coisas, mas ficará fora de sincronia, o que anula todo o propósito de perguntar diretamente à tabela.
11
@ Mat: Não é uma má idéia. Votou para migrar para o dba.

Respostas:

12

O MySQL recalcula as estatísticas da tabela para certas operações que acessam INFORMATION_SCHEMAtabelas ( SHOW COLUMNSé apenas um alias conveniente para consulta INFORMATION_SCHEMA.COLUMNS). Defina innodb_stats_on_metadata como false, o que impedirá que esse recálculo ocorra quando você solicitar metadados da tabela.

SET GLOBAL innodb_stats_on_metadata=0;

e adicione o seguinte a my.cnf

[mysqld]
innodb_stats_on_metadata = 0
Aaron Brown
fonte
Eu deveria ter mencionado que estou realmente usando o MyISAM. Tentei definir isso de qualquer maneira, mas não produziu nenhum benefício.
MPEN
Você considerou ALTER TABLE foo ENGINE = InnoDB? :) Existe uma boa razão para usar o MyISAM?
Aaron Brown
Razões legadas principalmente, eu acho. Receio o que pode acontecer se eu tentar isso; não tenho certeza de que todos os FKs se alinharão. Vou pensar um pouco mais.
MPEN
@AaronBrown +1 para esta resposta, porque quem enfrentar essa situação com um banco de dados all-InnoDB precisa dessas informações.
RolandoMySQLDBA
11
+1 para colocar [mysqld]lá. Pode ser óbvio para muitos que essa configuração se enquadra no mysqld, mas pode não ser óbvio para aqueles que fazem essa pergunta. By the way, isso acelerou SELECT COUNT(*)em uma das minhas information_schemamesas para 6 segundos a partir de mais de um minuto. Ainda lento, mas uma enorme melhoria.
Buttle Butkus
3

Eu sugiro que você crie um banco de dados que tenha as INFORMATION_SCHEMAtabelas (ou apenas as que você precisa) como réplicas. Indexá-los adequadamente e você terá ganho de desempenho.

O problema de sincronizar entre esse banco de dados e INFORMATION_SCHEMAé complicado.

Você pode ter um procedimento que sincronize essas tabelas a cada hora ou a cada 5 minutos (com que frequência a estrutura das tabelas é alterada?).

Outra idéia seria usar o MySQL Proxy para capturar quaisquer ALTER TABLEinstruções (e CREATE/ DROPe CREATE INDEXquaisquer outras instruções que modifiquem as informações necessárias) e sincronizar o esquema de informações replicadas após o êxito dessas instruções.


Se você precisar apenas dos nomes das colunas e de nenhuma outra informação, como tipo de dados, comprimento ou índices disponíveis, talvez possa substituir o uso de SHOW COLUMNSconsultas (rápidas) que retornam apenas 1 linha, com LIMIT 1ou nenhuma, com LIMIT 0ou:

SELECT * FROM TableName WHERE FALSE ;

Apesar dos conselhos gerais contra o uso de SELECT *, este pode ser um caso legítimo em que nada mais é útil. (tudo o resto *, pode resultar em erro!)

ypercubeᵀᴹ
fonte
2

Nesse caso em particular, acho que INFORMATION_SCHEMAé um arenque vermelho. Nos meus próprios testes de SHOW COLUMNSdesempenho, a innodb_stats_on_metadatavariável parece não fazer diferença nas tabelas MyISAM ou InnoDB.

No entanto, no manual do MySQL 5.0 ...

Algumas condições impedem o uso de uma tabela temporária na memória; nesse caso, o servidor usa uma tabela em disco:

[...]

  • As instruções SHOW COLUMNSe The são DESCRIBEusadas BLOBcomo o tipo para algumas colunas; portanto, a tabela temporária usada para os resultados é uma tabela em disco.

Isso parece ter sido removido do manual a partir do MySQL 5.5, mas ainda parece se aplicar nessa versão ...

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)

As informações de campo retornadas com um conjunto de resultados da consulta contêm as mesmas informações retornadas por SHOW COLUMNS, portanto, SELECT * FROM my_table LIMIT 0deve-se obter a mesma coisa sem criar uma tabela temporária em disco por consulta.

Um exemplo rápido para apenas pegar os nomes dos campos em 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 informações de campo dessa maneira é um pouco mais difícil de decodificar. Você precisará consultar a descrição da MYSQL_FIELDestrutura subjacente para obter os tipos e sinalizadores de dados, mas ela é executada cerca de 7 vezes mais rápido no meu sistema.

Aya
fonte
1

Gosto da primeira sugestão na resposta de @ yerpcube (+1), mas gostaria de propor algo

  • crie outra instância de banco de dados na porta 3307
  • mysqldump o banco de dados de produção para o SQL Text File usando as seguintes opções:
    • --no-data
    • --routines
    • --triggers
    • --all-databasesou --databasesseguido por uma lista de bancos de dados que você deseja
  • Carregue o arquivo SQL Text na instância do MySQL da porta 3307

Assim, o mysqldump deve ter a seguinte aparência:

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

É isso aí. A partir de agora, tudo o que você precisa fazer é conectar-se a esta instância do banco de dados da porta 3307 e executar qualquer consulta relacionada ao esquema ao conteúdo do seu coração. Se você souber de alguma tabela no banco de dados de produção que seja alterada, apenas mysqldump o esquema da produção e recarregue-o novamente na instância da porta 3307.

AVISO: Se você instalar uma instância do mysql na mesma máquina que a produção, certifique-se de se conectar a essa instância usando

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

Se você executar

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

Mangueira produção. Por isso tem cuidado !!!!

Uma alternativa seria usar apenas um servidor de banco de dados separado.

RolandoMySQLDBA
fonte