Otimização de consultas no MySQL

Artigo da Revista SQL Magazine - Edição 25.

O mundo da teoria de banco de dados relacionais é dominado por tabelas e conjuntos, e operações em tabelas e conjuntos. Um banco de dados é um conjunto de tabelas, e uma tabela é um conjunto de linhas e colunas. Quando emitimos uma declaração SELECT para recuperar linhas de uma tabela, recebemos outro conjunto de linhas e colunas – isto é, outra tabela. Estas são noções abstratas que não fazem nenhuma referência quanto à representação subjacente que um de sistema de banco de dados usa para operar nos dados e nas tabelas. Outra abstração consiste em que operações em tabelas acontecem todas de uma vez. As consultas são conceituadas como operações de conjuntos e não existe nenhum conceito de tempo na teoria de conjuntos.

O mundo real, é claro, é bastante diferente. Sistemas de gerenciamento de banco de dados implementam conceitos abstratos, mas o fazem em sistemas de hardware reais limitados por restrições físicas reais. Como resultado, as consultas tomam tempo - às vezes um longo e irritante período de tempo. Como somos criaturas impacientes, não gostamos de esperar, deixamos o mundo abstrato de operações matemáticas instantâneas em conjuntos e procuramos por modos de acelerar nossas consultas. Felizmente, existem várias técnicas para atingir este objetivo:

Estes são os tipos de assuntos que iremos focalizar neste artigo. O objetivo é auxiliá-lo no aperfeiçoamento do desempenho do sistema de banco de dados, de forma que venha a processar as consultas o mais rápido possível.O SGBD que utilizaremos será o MySQL.

Indexação

Indexação é a ferramenta mais importante que temos para acelerar consultas. Temos outras técnicas a nossa disposição, mas geralmente o que faz realmente a diferença é o uso de índices. Na lista de e-mail do MySQL, as pessoas pedem freqüentemente ajuda para fazer com que uma consulta rode mais rapidamente. Em um número surpreendentemente de casos, não existe nenhum índice para as tabelas em consulta, e geralmente, a adição de índices resolve o problema imediatamente. Não é sempre que trabalho assim, porque nem sempre a otimização é tão simples de se obter. Entretanto, se não usarmos índices, em muitos casos estaremos desperdiçando nosso tempo na tentativa de melhorar o desempenho através de outros meios. Usamos a indexação para melhorar o desempenho e então vemos que outras técnicas poderão ser úteis neste sentido.

Este artigo descreve o que é um índice e como a indexação melhora o desempenho das consultas. Também discutiremos as circunstâncias sob as quais os índices poderiam degradar o desempenho e como fornecer diretrizes para a mais sabia escolha de índices para a tabela em consulta.

Benefícios da indexação

Veremos como os índices trabalham começando com uma tabela que não tem nenhum índice. Uma tabela não indexada é simplesmente uma coleção desordenada de linhas. Por exemplo, a Figura 1 mostra a tabela “ad”. Não há nenhum índice nesta tabela, portanto, para pesquisar as linhas para uma companhia em particular, é necessário examinar cada linha da tabela e ver se casa com o valor desejado. Isto envolve a varredura completa de uma tabela, o que é bem lento além de tremendamente ineficiente, caso a tabela for grande e contiver apenas alguns registros que casam com os critérios de pesquisa.

A Figura 2 mostra a mesma tabela acrescida de um índice na coluna company_num. O índice contém uma entrada para cada linha da tabela, mas as entradas de índice são ordenadas pelo valor de company_num. Agora, em vez de procurar linha a linha por itens casados, podemos usar o índice. Vamos supor que estamos procurando todas as linhas para a companhia 13. Começamos a varrer o índice e achamos três valores para esta companhia. Então atingimos o valor de índice para a companhia 14, que é maior do que estamos procurando. Os valores de índice estão ordenados, assim, quando lemos o registro de índice que contém 14, sabemos que não acharemos nenhum outro casamento e podemos deixar de procurar. Assim, o ganho de eficiência obtido com o uso do índice consiste em poder determinar precisamente onde as linhas casadas acabam e ignorarmos o resto das linhas. Outro ganho de eficiência é obtido pelo uso de algoritmos de posicionamento para achar a primeira entrada casada, sem ter que fazer uma varredura linear desde o início do índice (por exemplo, uma procura binária é muito mais rápido que uma varredura). Deste modo, podemos posicionar rapidamente no primeiro valor casado e eliminar muito tempo de procura. Bancos de dados usam várias técnicas para o posicionamento rápido por valores de índices, mas neste artigo não nos aprofundaremos nessas técnicas. O importante é que elas funcionam e que a indexação é uma coisa boa.

Figura 1. Tabela ad não indexada.

Figura 2. Tabela ad indexada.

Poderíamos querer saber por que não ordenamos as linhas de dados e dispensamos o índice. Não iremos produzir o mesmo tipo de melhoria em velocidade de pesquisa? Sim, isto seria verdade caso a tabela tivesse um único índice. Mas poderíamos querer acrescentar um segundo índice e, neste caso, não conseguiríamos ordenar os dados das linhas de dois modos diferentes ao mesmo tempo (por exemplo, poderíamos querer um índice por nome do cliente e outro por numero de ID do cliente ou pelo número de telefone). Usar índices como entidades separadas das linhas de dados resolve o problema e permite criar índices múltiplos. Além disso, as linhas de índice são geralmente mais curtas do que as linhas de dados. Quando inserimos ou apagamos novos valores, é mais fácil movermos valores mais curtos de índice para manter a ordenação do que mudar as linhas de dados, que são mais longas.

Os detalhes particulares de implementações de índice variam no MySQL para diferentes storage engines. Por exemplo, para uma tabela MyISAM, as linhas de dados da tabela são mantidas em um único arquivo de dados, e valores de índice são mantidos em um único arquivo de índice. Podemos ter mais de um índice em uma tabela, mas serão todos armazenados no mesmo arquivo de índices. Cada índice no arquivo de índices consiste em um arranjo ordenado de chaves de registro que são usados para acesso rápido ao arquivo de dados.

Por outro lado, os storage engines do BDB e do InnoDB não separam linhas de dados e valores de índice da mesma maneira, embora ambos mantenham índices como conjuntos de valores ordenados. Por padrão, o motor de armazenamento do BDB usa um único arquivo por tabela para armazenar dados e valores de índice. O motor do InnoDB usa um único tablespace dentro do qual gerencia o armazenamento de dados e índices para todas as tabelas. O InnoDB pode ser configurado para criar cada tabela com seu próprio tablespace, mas mesmo assim, os dados e índices são armazenados em uma tabela no mesmo arquivo tablespace.

Esta análise descreve os benefícios conseguidos com um índice no contexto de consultas a tabela única, onde o uso de um índice acelera significativamente as procuras eliminando a necessidade de varredura completa da tabela. Índices são realmente mais valiosos quando há consultas envolvendo junções com múltiplas tabelas. Em uma consulta a uma tabela única, o número de valores que precisamos examinar por coluna corresponde ao número de linhas na tabela. Em uma consulta de múltiplas tabelas, o número de possíveis combinações cresce em grandes proporções (representado pelo produto do número de linhas de cada tabela).

Supondo que temos três tabelas não indexadas, t1, t2, e t3, cada uma contendo uma coluna, i1, i2 e i3 respectivamente, e cada uma contendo 1.000 linhas com os números 1 até 1000. Uma consulta para achar todas as combinações de linhas de tabela nas quais os valores sejam iguais, terá a seguinte forma:

SELECT t1.i1, t2.i2, t3.i3 FROM t1, t2, t3 WHERE t1.i1 = t2.i2 AND t2.i1 = t3.i3;

O resultado desta consulta deveria ser 1.000 linhas, cada uma contendo três valores iguais. Se processarmos a consulta na ausência de índices, não teremos nenhuma idéia sobre quais linhas contêm quais valores sem fazer uma varredura total. Como conseqüência, temos que tentar todas as combinações para achar aquelas que casam com a cláusula WHERE. O número de possíveis combinações é 1.000 x 1.000 x 1.000 (um bilhão!) que equivale a um milhão de vezes mais do que o número de casamentos retornados. Isto representa muito esforço perdido. O exemplo ilustra como à medida que as tabelas crescem, o tempo para processar junções com essas tabelas cresce ainda mais se nenhum índice for usado, conduzindo a um desempenho muito fraco. Podemos acelerar as coisas consideravelmente indexando as tabelas porque os índices nos permitem processar a consulta deste modo:

  1. selecionar a primeira linha da tabela t1 e ver o valor contido na linha.
  2. usar o índice da tabela t2 e ir diretamente para a linha que casa com valor da tabela t1. Da mesma forma, usar o índice da tabela t3 e ir diretamente para a linha que casa com o valor da tabela t2.
  3. continuar com a próxima linha da tabela t1 e repetir o procedimento anterior. Repetir estes procedimentos até que todas as linhas da tabela t1 sejam examinadas.

Neste caso, ainda executamos uma varredura completa na tabela t1, mas podemos fazer procuras indexadas nas tabelas t2 e t3 para extrair diretamente as linhas dessas tabelas. A consulta roda aproximadamente um milhão de vezes mais rápido deste modo, literalmente. Este exemplo foi construído com a finalidade de fazer uma observação, é claro. Entretanto, os problemas que o mesmo ilustra são reais, e acrescentar índices a tabelas que não têm nenhum, freqüentemente resulta em consideráveis ganhos de desempenho.

O MySQL usa índices de vários modos:

Custos da indexação

Podemos ver que estamos pintando um quadro “cor de rosa" quanto aos benefícios da indexação. Há desvantagens? Sim, há. Há custos envolvidos de tempo e de espaço. Na prática, estas desvantagens tendem a ser superadas pelas vantagens, mas devemos saber de que custos estamos falando.

Em primeiro lugar, índices aumentam a velocidade de recuperação, porém reduzem a velocidade de inserções e exclusões, assim como atualizações de valores em colunas indexadas. Isto quer dizer que índices tornam mais lentas a maioria das operações que envolvem escrita. Isto acontece porque a gravação de um registro não só requer escrita das linhas de dados, mas também implica em mudanças nos índices. Quanto mais índices uma tabela tem, mais alterações serão realizadas e maior será a degradação média de desempenho.

Em segundo lugar, um índice ocupa espaço em disco. Isto poderia fazer com que o limite de tamanho de tabela seja alcançado mais rapidamente do que se não houver nenhum índice:

Artigos relacionados