Consultas lentas, ineficientes ou longas são problemas que afetam regularmente os DBAs. Eles são sempre onipresentes, mas são uma parte inevitável da vida de qualquer pessoa responsável pelo gerenciamento de um banco de dados.
Um design de banco de dados ruim pode afetar a eficiência da consulta e seu desempenho. A falta de conhecimento ou o uso indevido de chamadas de função, procedimentos armazenados ou rotinas também podem causar degradação no desempenho do banco de dados e podem até prejudicar todo o cluster de banco de dados MySQL.
Para uma replicação mestre-escravo, uma causa muito comum desses problemas são tabelas que não possuem índices primários ou secundários. Isso causa um atraso de escravo que pode durar muito tempo (no pior cenário).
Neste blog de duas partes, daremos a você um curso de atualização sobre como lidar com a maximização de suas consultas de banco de dados no MySQL para aumentar a eficiência e o desempenho.
Sempre adicione um índice exclusivo à sua tabela
As tabelas que não têm chaves primárias ou exclusivas geralmente criam grandes problemas quando os dados aumentam. Quando isso acontece, uma simples modificação de dados pode travar o banco de dados. Falta de índices apropriados e uma instrução UPDATE ou DELETE foi aplicada à tabela em particular, uma varredura completa da tabela será escolhida como o plano de consulta pelo MySQL. Isso pode causar alta E/S de disco para leituras e gravações e degrada o desempenho de seu banco de dados. Veja um exemplo abaixo:
root[test]> show create table sbtest2\G
*************************** 1. row ***************************
Table: sbtest2
Create Table: CREATE TABLE `sbtest2` (
`id` int(10) unsigned NOT NULL,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
root[test]> explain extended update sbtest2 set k=52, pad="xx234xh1jdkHdj234" where id=57;
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | UPDATE | sbtest2 | NULL | ALL | NULL | NULL | NULL | NULL | 1923216 | 100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.06 sec)
Enquanto uma tabela com chave primária tem um plano de consulta muito bom,
root[test]> show create table sbtest3\G
*************************** 1. row ***************************
Table: sbtest3
Create Table: CREATE TABLE `sbtest3` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2097121 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
root[test]> explain extended update sbtest3 set k=52, pad="xx234xh1jdkHdj234" where id=57;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | UPDATE | sbtest3 | NULL | range | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
Chaves primárias ou exclusivas fornecem um componente vital para uma estrutura de tabela porque isso é muito importante, especialmente ao realizar manutenção em uma tabela. Por exemplo, usar ferramentas do Percona Toolkit (como pt-online-schema-change ou pt-table-sync) recomenda que você tenha chaves exclusivas. Lembre-se de que a PRIMARY KEY já é uma chave exclusiva e uma chave primária não pode conter valores NULL, mas uma chave exclusiva. Atribuir um valor NULL a uma chave primária pode causar um erro como,
ERROR 1171 (42000): All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead
Para nós escravos, também é comum que em certas ocasiões, a chave primária/única não esteja presente na tabela, o que, portanto, é uma discrepância da estrutura da tabela. Você pode usar mysqldiff para conseguir isso ou você pode mysqldump --no-data … params ee executar um diff para comparar sua estrutura de tabela e verificar se há alguma discrepância.
Digitalizar tabelas com índices duplicados e depois descartar
Índices duplicados também podem causar degradação de desempenho, especialmente quando a tabela contém um grande número de registros. O MySQL precisa realizar várias tentativas para otimizar a consulta e realizar mais planos de consulta para verificar. Ele inclui a varredura de estatísticas ou distribuição de índice grande e adiciona sobrecarga de desempenho, pois pode causar contenção de memória ou alta utilização de memória de E/S.
Degradação para consultas quando índices duplicados são observados em uma tabela também atribui a saturação do buffer pool. Isso também pode afetar o desempenho do MySQL quando o checkpoint libera os logs de transação no disco. Isso se deve ao processamento e armazenamento de um índice indesejado (que na verdade é um desperdício de espaço no tablespace específico dessa tabela). Observe que os índices duplicados também são armazenados no tablespace, que também deve ser armazenado no buffer pool.
Dê uma olhada na tabela abaixo que contém várias chaves duplicadas:
root[test]#> show create table sbtest3\G
*************************** 1. row ***************************
Table: sbtest3
Create Table: CREATE TABLE `sbtest3` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`,`pad`,`c`),
KEY `kcp2` (`id`,`k`,`c`,`pad`),
KEY `kcp` (`k`,`c`,`pad`),
KEY `pck` (`pad`,`c`,`id`,`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2048561 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
e tem um tamanho de 2,3GiB
root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd
2.3G /var/lib/mysql/test/sbtest3.ibd
Vamos eliminar os índices duplicados e reconstruir a tabela com um alter no-op,
root[test]#> drop index kcp2 on sbtest3; drop index kcp on sbtest3 drop index pck on sbtest3;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> alter table sbtest3 engine=innodb;
Query OK, 0 rows affected (28.23 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd
945M /var/lib/mysql/test/sbtest3.ibd
Ele conseguiu economizar até ~59% do tamanho antigo do espaço de tabela, que é realmente enorme.
Para determinar índices duplicados, você pode usar pt-duplicate-checker para lidar com o trabalho para você.
Ajuste seu pool de buffers
Para esta seção, estou me referindo apenas ao mecanismo de armazenamento InnoDB.
O buffer pool é um componente importante dentro do espaço do kernel do InnoDB. É aqui que o InnoDB armazena em cache os dados de tabela e índice quando acessado. Ele acelera o processamento porque os dados usados com frequência estão sendo armazenados na memória com eficiência usando BTREE. Por exemplo, se você tiver várias tabelas consistindo em>=100 GiB e forem acessadas intensamente, sugerimos que você delegue uma memória volátil rápida a partir de um tamanho de 128 GiB e comece a atribuir o pool de buffers com 80% da memória física. Os 80% devem ser monitorados de forma eficiente. Você pode usar SHOW ENGINE INNODB STATUS \G ou pode aproveitar o software de monitoramento, como o ClusterControl, que oferece um monitoramento refinado que inclui o pool de buffers e suas métricas de integridade relevantes. Defina também a variável innodb_buffer_pool_instances de acordo. Você pode definir isso maior que 8 (padrão se innodb_buffer_pool_size>=1GiB), como 16, 24, 32 ou 64 ou superior, se necessário.
Ao monitorar o pool de buffers, você precisa verificar a variável de status global Innodb_buffer_pool_pages_free, que fornece pensamentos se há necessidade de ajustar o pool de buffers, ou talvez considere se também existem índices indesejados ou duplicados que consomem o amortecedor. O SHOW ENGINE INNODB STATUS \G também oferece um aspecto mais detalhado das informações do buffer pool, incluindo seu buffer pool individual com base no número de innodb_buffer_pool_instances que você definiu.
Use índices FULLTEXT (mas somente se aplicável)
Usando consultas como,
SELECT bookid, page, context FROM books WHERE context like '%for dummies%';
em que context é uma coluna do tipo string (char, varchar, text), é um exemplo de uma consulta super ruim! Puxar um grande conteúdo de registros com um filtro que precisa ser ganancioso acaba com uma varredura completa da tabela, e isso é uma loucura. Considere usar o índice FULLTEXT. Os índices FULLTEXT têm um design de índice invertido. Índices invertidos armazenam uma lista de palavras e, para cada palavra, uma lista de documentos em que a palavra aparece. Para oferecer suporte à pesquisa de proximidade, as informações de posição de cada palavra também são armazenadas, como um deslocamento de byte.
Para usar FULLTEXT para pesquisar ou filtrar dados, você precisa usar a combinação da sintaxe MATCH() ...AGAINST e não como a consulta acima. Claro, você precisa especificar o campo para ser seu campo de índice FULLTEXT.
Para criar um índice FULLTEXT, basta especificar com FULLTEXT como seu índice. Veja o exemplo abaixo:
root[minime]#> CREATE FULLTEXT INDEX aboutme_fts ON users_info(aboutme);
Query OK, 0 rows affected, 1 warning (0.49 sec)
Records: 0 Duplicates: 0 Warnings: 1
root[jbmrcd_date]#> show warnings;
+---------+------+--------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------+
| Warning | 124 | InnoDB rebuilding table to add column FTS_DOC_ID |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)
Embora o uso de índices FULLTEXT possa oferecer benefícios ao pesquisar palavras em um contexto muito grande dentro de uma coluna, ele também cria problemas quando usado incorretamente.
Ao fazer uma pesquisa FULLTEXT para uma tabela grande que é acessada constantemente (onde várias solicitações de clientes estão pesquisando palavras-chave diferentes e exclusivas), isso pode exigir muito da CPU.
Há certas ocasiões em que FULLTEXT não é aplicável. Veja esta postagem de blog externa. Embora eu não tenha tentado isso com 8.0, não vejo nenhuma alteração relevante para isso. Sugerimos que não use FULLTEXT para pesquisar um ambiente de big data, especialmente para tabelas de alto tráfego. Caso contrário, tente aproveitar outras tecnologias, como Apache Lucene, Apache Solr, tsearch2 ou Sphinx.
Evite usar NULL em colunas
Colunas que contêm valores nulos são totalmente boas no MySQL. Mas se você estiver usando colunas com valores nulos em um índice, isso poderá afetar o desempenho da consulta, pois o otimizador não pode fornecer o plano de consulta correto devido à má distribuição do índice. No entanto, existem certas maneiras de otimizar consultas que envolvem valores nulos, mas é claro, se isso atender aos requisitos. Por favor, verifique a documentação do MySQL sobre Null Optimization. Você também pode verificar este post externo, que também é útil.
Projete sua topologia de esquema e estrutura de tabelas com eficiência
Até certo ponto, normalizar suas tabelas de banco de dados de 1NF (Primeira Forma Normal) para 3NF (Terceira Forma Normal) oferece algum benefício para a eficiência da consulta porque as tabelas normalizadas tendem a evitar registros redundantes. Um planejamento e design adequado para suas tabelas é muito importante porque é assim que você recupera ou puxa os dados e em cada uma dessas ações tem um custo. Com tabelas normalizadas, o objetivo do banco de dados é garantir que cada coluna não chave em cada tabela seja diretamente dependente da chave; a chave inteira e nada além da chave. Se esse objetivo for alcançado, os benefícios serão pagos na forma de redundâncias reduzidas, menos anomalias e eficiências aprimoradas.
Embora normalizar suas tabelas tenha muitos benefícios, isso não significa que você precise normalizar todas as suas tabelas dessa maneira. Você pode implementar um design para seu banco de dados usando o Star Schema. Projetar suas tabelas usando o Star Schema tem o benefício de consultas mais simples (evita junções cruzadas complexas), fácil de recuperar dados para relatórios, oferece ganhos de desempenho porque não há necessidade de usar uniões ou junções complexas ou agregações rápidas. Um Star Schema é simples de implementar, mas você precisa planejar com cuidado, pois pode criar grandes problemas e desvantagens quando sua mesa fica maior e requer manutenção. O Star Schema (e suas tabelas subjacentes) são propensos a problemas de integridade de dados, portanto, você pode ter uma alta probabilidade de que muitos de seus dados sejam redundantes. Se você acha que essa tabela deve ser constante (estrutura e design) e foi projetada para utilizar a eficiência da consulta, é o caso ideal para essa abordagem.
Misturar seus designs de banco de dados (desde que você seja capaz de determinar e identificar que tipo de dados deve ser puxado em suas tabelas) é muito importante, pois você pode se beneficiar com consultas mais eficientes e também ajudar o DBA com backups, manutenção e recuperação.
Livre-se de dados antigos e constantes
Recentemente, escrevemos algumas práticas recomendadas para arquivar seu banco de dados na nuvem. Ele aborda como você pode aproveitar o arquivamento de dados antes de ir para a nuvem. Então, como se livrar de dados antigos ou arquivar seus dados constantes e antigos ajuda na eficiência da consulta? Como dito no meu blog anterior, existem benefícios para tabelas maiores que são constantemente modificadas e inseridas com novos dados, o tablespace pode crescer rapidamente. MySQL e InnoDB funcionam de forma eficiente quando registros ou dados são contíguos entre si e têm significado para sua próxima linha na tabela. Ou seja, se você não possui registros antigos que não precisam mais ser usados, o otimizador não precisa incluir isso nas estatísticas, oferecendo um resultado muito mais eficiente. Faz sentido, certo? Além disso, a eficiência da consulta não está apenas no lado do aplicativo, ela também precisa considerar sua eficiência ao realizar um backup e quando estiver em manutenção ou failover. Por exemplo, se você tiver uma consulta ruim e longa que possa afetar seu período de manutenção ou um failover, isso pode ser um problema.
Ative o registro de consultas conforme necessário
Sempre defina o log de consultas lentas do MySQL de acordo com suas necessidades personalizadas. Se você estiver usando o Percona Server, poderá aproveitar o registro de consulta lenta estendido. Ele permite que você defina normalmente certas variáveis. Você pode filtrar tipos de consultas em combinação, como full_scan, full_join, tmp_table, etc. Você também pode ditar a taxa de log de consultas lentas por meio da variável log_slow_rate_type e muitas outras.
A importância de habilitar o registro de consultas no MySQL (como consultas lentas) é benéfica para inspecionar suas consultas para que você possa otimizar ou ajustar seu MySQL ajustando certas variáveis que atendam às suas necessidades. Para habilitar o log de consultas lentas, certifique-se de que estas variáveis estejam configuradas:
- long_query_time - atribua o valor correto de quanto tempo as consultas podem levar. Se as consultas demorarem mais de 10 segundos (padrão), elas cairão no arquivo de log de consulta lenta que você atribuiu.
- slow_query_log - para habilitá-lo, defina-o como 1.
- slow_query_log_file - este é o caminho de destino para seu arquivo de log de consulta lenta.
O log de consultas lentas é muito útil para análise de consultas e diagnóstico de consultas ruins que causam paralisações, atrasos de escravos, consultas de longa execução, uso intensivo de memória ou CPU ou até mesmo travamento do servidor. Se você usar pt-query-digest ou pt-index-usage, use o arquivo de log de consulta lenta como seu destino de origem para relatar essas consultas.
Conclusão
Discutimos algumas maneiras que você pode usar para maximizar a eficiência da consulta de banco de dados neste blog. Nesta próxima parte, discutiremos ainda mais fatores que podem ajudá-lo a maximizar o desempenho. Fique atento!