Quando a otimização de consulta do MySQL é mencionada, os índices são uma das primeiras coisas que são abordadas. Hoje, vamos tentar ver por que eles são tão importantes.
O que são índices?
Em geral, um índice é uma lista alfabética de registros com referências às páginas em que são mencionados. No MySQL, um índice é uma estrutura de dados usada para localizar linhas rapidamente. Os índices também são chamados de chaves e essas chaves são críticas para um bom desempenho - à medida que os dados crescem, a necessidade de usar os índices corretamente pode se tornar cada vez mais importante. O uso de índices é uma das maneiras mais poderosas de melhorar o desempenho da consulta - se os índices forem usados corretamente, o desempenho da consulta poderá aumentar dezenas ou até centenas de vezes.
Hoje, tentaremos explicar as vantagens e desvantagens básicas do uso de índices no MySQL. Tenha em mente que os índices MySQL por si só merecem um livro inteiro, então este post não cobrirá absolutamente tudo, mas será um bom ponto de partida. Para aqueles que estão interessados em como os índices funcionam em um nível mais profundo, a leitura do livro Relational Database Index Design and the Optimizers de Tapio Lahdenmäki e Michael Leach deve fornecer mais informações.
Os benefícios do uso de índices
Existem alguns benefícios principais de usar índices no MySQL e são os seguintes:
- Os índices permitem encontrar rapidamente as linhas que correspondem a uma cláusula WHERE;
- Os índices podem ajudar as consultas a evitar a busca em determinadas linhas, reduzindo assim a quantidade de dados que o servidor precisa examinar - se houver uma escolha entre vários índices, o MySQL geralmente usa o índice mais seletivo, que é um índice que encontra a menor quantidade de linhas;
- Os índices podem ser usados para recuperar linhas de outras tabelas em operações JOIN;
- Os índices podem ser usados para encontrar o valor mínimo ou máximo de uma coluna específica que usa um índice;
- Os índices podem ser usados para classificar ou agrupar uma tabela se as operações forem executadas em um prefixo mais à esquerda de um índice - da mesma forma, um prefixo mais à esquerda de um índice de várias colunas também pode ser usado pelo otimizador de consulta para procurar linhas;
- Os índices também podem ser usados para salvar E/S de disco - quando um índice de cobertura está em uso, uma consulta pode retornar valores diretamente da estrutura de índice, salvando E/S de disco.
Da mesma forma, existem vários tipos de índices:
- INDEX é um tipo de índice em que os valores não precisam ser exclusivos. Este tipo de índice aceita valores NULL;
- UNIQUE INDEX é frequentemente usado para remover linhas duplicadas de uma tabela - esse tipo de índice permite que os desenvolvedores imponham a exclusividade dos valores das linhas;
- FULLTEXT INDEX é um índice aplicado em campos que utilizam recursos de pesquisa de texto completo. Esse tipo de índice encontra palavras-chave no texto em vez de comparar diretamente os valores com os valores no índice;
- DESCENDING INDEX é um índice que armazena linhas em ordem decrescente - o otimizador de consulta escolherá esse tipo de índice quando uma ordem decrescente for solicitada pela consulta. Este tipo de índice foi introduzido no MySQL 8.0;
- PRIMARY KEY também é um índice. Em poucas palavras, a PRIMARY KEY é uma coluna ou um conjunto de colunas que identifica cada linha em uma tabela - frequentemente usada em conjunto com campos que possuem um atributo AUTO_INCREMENT. Esse tipo de índice não aceita valores NULL e, uma vez definido, os valores na PRIMARY KEY não podem ser alterados.
Agora, tentaremos analisar os benefícios e as desvantagens do uso de índices no MySQL. Começaremos com o lado positivo provavelmente mais discutido - acelerar as consultas que correspondem a uma cláusula WHERE.
Acelerando consultas que correspondem a uma cláusula WHERE
Os índices são frequentemente usados para acelerar as consultas de pesquisa que correspondem a uma cláusula WHERE. A razão pela qual um índice torna essas operações de pesquisa mais rápidas é bastante simples - as consultas que usam um índice evitam uma verificação completa da tabela.
Para acelerar as consultas que correspondem a uma cláusula WHERE, você pode usar a instrução EXPLAIN no MySQL. A instrução EXPLAIN SELECT deve fornecer algumas informações sobre como o otimizador de consulta MySQL executa a consulta - ela também pode mostrar se a consulta em questão usa um índice ou não e qual índice ela usa. Dê uma olhada na seguinte explicação da consulta:
mysql> EXPLAIN SELECT * FROM demo_table WHERE field_1 = “Demo” \G;
*************************** 1. row ***************************
<...>
possible_keys: NULL
key: NULL
key_len: NULL
<...>
A consulta acima não usa um índice. No entanto, se adicionarmos um índice em “field_1”, o índice será usado com sucesso:
mysql> EXPLAIN SELECT * FROM demo_table WHERE field_1 = “Demo” \G;
*************************** 1. row ***************************
<...>
possible_keys: field_1
key: field_1
key_len: 43
<...>
A coluna possible_keys descreve os índices possíveis que o MySQL pode escolher, a coluna key descreve o índice realmente escolhido e a coluna key_len descreve o comprimento da chave escolhida.
Neste caso, o MySQL realizaria uma pesquisa dos valores no índice e retornaria quaisquer linhas contendo o valor especificado - como resultado, a consulta seria mais rápida. Embora os índices ajudem certas consultas a serem mais rápidas, há algumas coisas que você precisa ter em mente se quiser que seus índices ajudem suas consultas:
- Isole suas colunas - o MySQL não pode usar índices se as colunas nas quais os índices são usados não estiverem isoladas. Por exemplo, uma consulta como essa não usaria um índice:
SELECT field_1 FROM demo_table WHERE field_1 + 5 = 10;
Para resolver isso, deixe a coluna que vem depois da cláusula WHERE sozinha - simplifique sua consulta o máximo possível e isole as colunas;
- Evite usar consultas LIKE com um curinga anterior - neste caso, o MySQL não usará um índice porque o curinga anterior significa que pode haver qualquer coisa antes do texto. Se você precisar usar consultas LIKE com curingas e quiser que as consultas usem índices, certifique-se de que o curinga esteja no final da instrução de pesquisa.
É claro que acelerar as consultas que correspondem a uma cláusula WHERE também pode ser feito de outras maneiras (por exemplo, particionamento), mas para simplificar, não vamos nos aprofundar nisso neste post.
No entanto, podemos estar interessados em diferentes tipos de tipos de índice, então vamos analisar isso agora.
Livrar-se de valores duplicados em uma coluna - índices únicos
O propósito de um índice UNIQUE no MySQL é reforçar a unicidade dos valores em uma coluna. Para usar um índice UNIQUE, execute uma consulta CREATE UNIQUE INDEX:
CREATE UNIQUE INDEX demo_index ON demo_table(demo_column);
You can also create a unique index when you create a table:
CREATE TABLE demo_table (
`demo_column` VARCHAR(100) NOT NULL,
UNIQUE KEY(demo_column)
);
Isso é tudo o que é preciso para adicionar um índice exclusivo a uma tabela. Agora, ao tentar adicionar um valor duplicado à tabela o MySQL retornará com o seguinte erro:
#1062 - Duplicate entry ‘Demo’ for key ‘demo_column’
Índices FULLTEXT
Um índice FULLTEXT é um índice aplicado às colunas que usam recursos de pesquisa de texto completo. Esse tipo de índice tem muitos recursos exclusivos, incluindo palavras irrelevantes e modos de pesquisa.
A lista de palavras irrelevantes do InnoDB possui 36 palavras enquanto a lista de palavras irrelevantes MyISAM possui 143. No InnoDB, as palavras irrelevantes são derivadas da tabela definida na variável innodb_ft_user_stopword_table, caso contrário, se esta variável não estiver definida, elas são derivadas da variável innodb_ft_server_stopword_table. Se nenhuma dessas duas variáveis estiver definida, o InnoDB usa a lista interna. Para ver a lista de palavras irrelevantes do InnoDB, consulte a tabela INNODB_FT_DEFAULT_STOPWORD.
No MyISAM, as palavras irrelevantes são derivadas do arquivo storage/myisam/ft_static.c. A variável ft_stopword_file permite que a lista de palavras irrelevantes padrão seja alterada. Stopwords serão desabilitados se esta variável for definida como uma string vazia, mas tenha em mente que se esta variável definir um arquivo, o arquivo definido não será analisado para comentários - MyISAM tratará todas as palavras encontradas no arquivo como stopwords.
Os índices FULLTEXT também são famosos por seus modos de pesquisa exclusivos:
- Se uma consulta de pesquisa FULLTEXT sem modificadores for executada, um modo de linguagem natural será ativado. O modo de linguagem natural também pode ser ativado usando o modificador IN NATURAL LANGUAGE MODE;
- O modificador WITH QUERY EXPANSION habilita um modo de pesquisa com expansão de consulta. Esse modo de pesquisa funciona realizando a pesquisa duas vezes e, quando a pesquisa for executada pela segunda vez, o conjunto de resultados incluirá alguns dos documentos mais relevantes da primeira pesquisa. Em geral, esse modificador é útil quando o usuário tem algum conhecimento implícito (por exemplo, o usuário pode pesquisar por “banco de dados” e esperar ver “InnoDB” e “MyISAM” no conjunto de resultados);
- O modificador IN BOOLEAN MODE permite pesquisar com operadores booleanos. Por exemplo, os operadores +, - ou * realizariam tarefas diferentes - o operador + definiria que o valor deve estar presente em uma linha, o operador - definiria que o valor não deve existir e o operador * atuaria como um curinga.
Uma consulta que usa um índice FULLTEXT tem a seguinte aparência:
SELECT * FROM demo_table WHERE MATCH(demo_field) AGAINST(‘value’ IN NATURAL LANGUAGE MODE);
Lembre-se de que os índices FULLTEXT geralmente são úteis para operações MATCH() AGAINST() - não para operações WHERE, o que significa que, se uma cláusula WHERE for usada, a utilidade de usar diferentes tipos de índice não seria eliminada.
Também vale a pena mencionar que os índices FULLTEXT têm um comprimento mínimo de caracteres. No InnoDB, uma pesquisa FULLTEXT só pode ser realizada quando a consulta de pesquisa consiste em um mínimo de três caracteres - esse limite é aumentado para quatro caracteres no mecanismo de armazenamento MyISAM.
ÍNDICES DESCENDENTES
Um índice DESCENDENTE é um índice onde o InnoDB armazena as entradas em ordem decrescente - o otimizador de consulta usará esse índice quando uma ordem decrescente for solicitada pela consulta. Esse índice pode ser adicionado a uma coluna executando uma consulta como abaixo:
CREATE INDEX descending_index ON demo_table(column_name DESC);
Um índice ascendente também pode ser adicionado a uma coluna - basta substituir DESC por ASC.
CHAVES PRIMÁRIAS
Uma PRIMARY KEY serve como um identificador exclusivo para cada linha em uma tabela. Uma coluna com uma PRIMARY KEY deve conter valores exclusivos - nenhum valor NULL também pode ser usado. Se um valor duplicado for adicionado a uma coluna que possui uma CHAVE PRIMÁRIA, o MySQL responderá com um erro #1062:
#1062 - Duplicate entry ‘Demo’ for key ‘PRIMARY’
Se um valor NULL for adicionado à coluna, o MySQL responderá com um erro #1048:
#1048 - Column ‘id’ cannot be null
Os índices primários também são chamados de índices clusterizados (discutiremos mais adiante).
Você também pode criar índices em várias colunas de uma só vez - esses índices são chamados de índices de várias colunas.
Índices de várias colunas
Os índices em várias colunas são muitas vezes mal interpretados - às vezes os desenvolvedores e DBAs indexam todas as colunas separadamente ou as indexam na ordem errada. Para tornar as consultas utilizando índices multicolunas o mais eficazes possível, lembre-se de que a ordem das colunas em índices que usam mais de uma coluna é uma das causas mais comuns de confusão neste espaço - já que não há ” soluções de ordem de índice, lembre-se de que a ordem correta dos índices de várias colunas depende das consultas que estão usando o índice. Embora isso possa parecer bastante óbvio, lembre-se de que a ordem das colunas é vital ao lidar com índices de várias colunas - escolha a ordem das colunas de forma que seja o mais seletiva possível para as consultas que serão executadas com mais frequência.
Para medir a seletividade para colunas específicas, obtenha a razão entre o número de valores indexados distintos e o número total de linhas na tabela - a coluna com maior seletividade deve ser a primeira .
Às vezes você também precisa indexar colunas de caracteres muito longas e, nesse caso, você pode economizar tempo e recursos indexando os primeiros caracteres - um prefixo - em vez do valor inteiro.
Índices de prefixo
Os índices de prefixo podem ser úteis quando as colunas contêm valores de string muito longos, o que significaria que adicionar um índice em toda a coluna consumiria muito espaço em disco. O MySQL ajuda a resolver esse problema, permitindo que você indexe apenas um prefixo do valor que, por sua vez, torna o tamanho do índice menor. Dê uma olhada:
CREATE TABLE `demo_table` (
`demo_column` VARCHAR(100) NOT NULL,
INDEX(demo_column(10))
);
A consulta acima criaria um índice de prefixo na coluna demo indexando apenas os 10 primeiros caracteres do valor. Você também pode adicionar um índice de prefixo a uma tabela existente:
CREATE INDEX index_name ON table_name(column_name(length));
Então, por exemplo, se você deseja indexar os primeiros 5 caracteres de uma coluna_demo em uma tabela_demo, você pode executar a seguinte consulta:
CREATE INDEX demo_index ON demo_table(demo_column(5));
Você deve escolher um prefixo que seja longo o suficiente para dar seletividade, mas também curto o suficiente para dar espaço. Isso pode ser mais fácil dizer do que fazer - você precisa experimentar e encontrar a solução que funciona para você.
Cobertura de índices
Um índice de cobertura “cobre” todos os campos obrigatórios para executar uma consulta. Em outras palavras, quando todos os campos em uma consulta são cobertos por um índice, um índice de cobertura está em uso. Por exemplo, para uma consulta como esta:
SELECT id, title FROM demo_table WHERE id = 1;
Um índice de cobertura pode ter esta aparência:
INDEX index_name(id, title);
Se você quiser ter certeza de que uma consulta usa um índice de cobertura, emita uma instrução EXPLAIN nela e dê uma olhada na coluna Extra. Por exemplo, se sua tabela tem um índice multicoluna em id e title e uma consulta que acessa apenas essas duas colunas é executada, o MySQL usará o índice:
mysql> EXPLAIN SELECT id, title FROM demo_table \G;
*************************** 1. row ***************************
<...>
type: index
key: index_name
key_len: 5
rows: 1000
Extra: Using index
<...>
Lembre-se de que um índice de cobertura deve armazenar os valores das colunas que cobre. Isso significa que o MySQL só pode usar índices B-Tree para cobrir consultas porque outros tipos de índices não armazenam esses valores.
Índices agrupados, secundários e cardinalidade do índice
Quando os índices são discutidos, você também pode ouvir os termos clusterizado, índices secundários e cardinalidade de índice. Simplificando, índices clusterizados são uma abordagem para armazenamento de dados e todos os índices que não sejam índices clusterizados são índices secundários. A cardinalidade do índice, por outro lado, é o número de valores únicos em um índice.
Um índice clusterizado acelera as consultas porque os valores próximos também são armazenados próximos uns dos outros no disco, mas essa também é a razão pela qual você só pode ter um índice clusterizado em uma tabela.
Um índice secundário é qualquer índice que não seja o índice primário. Esse índice pode ter duplicatas.
As desvantagens do uso de índices
O uso de índices certamente tem vantagens, mas não devemos esquecer que os índices também podem ser uma das principais causas de problemas no MySQL. Algumas das desvantagens de usar índices são as seguintes:
- Os índices podem degradar o desempenho de determinadas consultas - embora os índices tendam a acelerar o desempenho de consultas SELECT, eles diminuem o desempenho de consultas INSERT, UPDATE e DELETE porque, quando os dados são atualizados, o index também precisa ser atualizado junto com ele:qualquer operação que envolva a manipulação dos índices será mais lenta que o normal;
- Os índices consomem espaço em disco - um índice ocupa seu próprio espaço, portanto, os dados indexados também consumirão mais espaço em disco;
- Índices redundantes e duplicados podem ser um problema - o MySQL permite que você crie índices duplicados em uma coluna e não o “protege” de cometer tal erro. Dê uma olhada neste exemplo:
CREATE TABLE `demo_table` ( `id` INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, `column_2` VARCHAR(10) NOT NULL, `column_3` VARCHAR(10) NOT NULL, INDEX(id), UNIQUE(id) );
Um usuário inexperiente pode pensar que essa consulta faz com que a coluna id seja incrementada automaticamente, depois adiciona um índice na coluna e faz com que a coluna não aceite valores duplicados. No entanto, não é isso que está acontecendo aqui. Nesse caso, a mesma coluna tem três índices:um INDEX comum, e como o MySQL implementa restrições PRIMARY KEY e UNIQUE com índices, isso adiciona mais dois índices na mesma coluna!
Conclusão
Para concluir, os índices no MySQL têm seu próprio lugar - os índices podem ser usados em vários cenários, mas cada um desses cenários de uso tem suas próprias desvantagens que devem ser consideradas para aproveitar ao máximo índices que estão em uso.
Para usar bem os índices, crie o perfil de suas consultas, dê uma olhada nas opções que você tem quando se trata de índices, conheça seus benefícios e desvantagens, decida quais índices você precisa com base em seus requisitos e depois de indexar as colunas, certifique-se de que seus índices são realmente usado pelo MySQL. Se você indexou seu esquema corretamente, o desempenho de suas consultas deve melhorar, mas se o tempo de resposta não o satisfizer, veja se um índice melhor pode ser criado para melhorá-lo.