Quando devo usar um índice composto?

133
  1. Quando devo usar um índice composto em um banco de dados?
  2. Quais são as ramificações de desempenho usando um índice composto)?
  3. Por que devo usar um índice composto?

Por exemplo, eu tenho uma homestabela:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  PRIMARY KEY  (`home_id`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
) ENGINE=InnoDB  ;

Faz sentido usar um índice composto para ambos geolate geolng, de modo que:

Eu substituo:

  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),

com:

KEY `geolat_geolng` (`geolat`, `geolng`)

Se então:

  • Por quê?
  • Qual é a ramificação do desempenho usando um índice composto)?

ATUALIZAR:

Como muitas pessoas afirmaram que é totalmente dependente das consultas que realizo, a seguir é apresentada a consulta mais comum:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

ATUALIZAÇÃO 2:

Com o seguinte esquema do banco de dados:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `primary_photo_group_id` int(10) unsigned NOT NULL default '0',
  `customer_id` bigint(20) unsigned NOT NULL,
  `account_type_id` int(11) NOT NULL,
  `address` varchar(128) collate utf8_unicode_ci NOT NULL,
  `city` varchar(64) collate utf8_unicode_ci NOT NULL,
  `state` varchar(2) collate utf8_unicode_ci NOT NULL,
  `zip` mediumint(8) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `num_of_beds` tinyint(3) unsigned NOT NULL,
  `num_of_baths` decimal(3,1) unsigned NOT NULL,
  `num_of_floors` tinyint(3) unsigned NOT NULL,
  `description` text collate utf8_unicode_ci,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  `display_status` tinyint(1) NOT NULL,
  `date_listed` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `contact_email` varchar(100) collate utf8_unicode_ci NOT NULL,
  `contact_phone_number` varchar(15) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`home_id`),
  KEY `customer_id` (`customer_id`),
  KEY `city` (`city`),
  KEY `num_of_beds` (`num_of_beds`),
  KEY `num_of_baths` (`num_of_baths`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
  KEY `account_type_id` (`account_type_id`),
  KEY `display_status` (`display_status`),
  KEY `sqft` (`sqft`),
  KEY `price` (`price`),
  KEY `primary_photo_group_id` (`primary_photo_group_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=8 ;

Usando o seguinte SQL:

EXPLAIN SELECT  homes.home_id,
                    address,
                    city,
                    state,
                    zip,
                    price,
                    sqft,
                    year_built,
                    account_type_id,
                    num_of_beds,
                    num_of_baths,
                    geolat,
                    geolng,
                    photo_id,
                    photo_url_dir
            FROM homes
            LEFT OUTER JOIN home_photos ON homes.home_id = home_photos.home_id
                AND homes.primary_photo_group_id = home_photos.home_photo_group_id
                AND home_photos.home_photo_type_id = 2
            WHERE homes.display_status = true
            AND homes.geolat BETWEEN -100 AND 100
            AND homes.geolng BETWEEN -100 AND 100

EXPLAIN retorna:

id  select_type  table        type  possible_keys                                    key                  key_len  ref     rows  Extra
----------------------------------------------------------------------------------------------------------
1   SIMPLE       homes        ref   geolat,geolng,display_status                     display_status       1        const   2     Using where
1  SIMPLE        home_photos  ref   home_id,home_photo_type_id,home_photo_group_id   home_photo_group_id  4        homes.primary_photo_group_id   4  

Não entendo muito bem como ler o comando EXPLAIN. Isso parece bom ou ruim. No momento, não estou usando um índice composto para geolat e geolng. Eu deveria ser?

Urso de pelúcia
fonte

Respostas:

111

Você deve usar um índice composto quando estiver usando consultas que se beneficiam dele. Um índice composto que se parece com isso:

index( column_A, column_B, column_C )

beneficiará uma consulta que use esses campos para ingressar, filtrar e às vezes selecionar. Também beneficiará consultas que usem os subconjuntos mais à esquerda de colunas nesse composto. Portanto, o índice acima também satisfará as consultas que precisam

index( column_A, column_B, column_C )
index( column_A, column_B )
index( column_A )

Mas isso não ajudará (pelo menos não diretamente, talvez possa ajudar parcialmente se não houver índices melhores) para consultas que precisam

index( column_A, column_C )

Observe como a coluna_B está ausente.

No seu exemplo original, um índice composto para duas dimensões beneficiará principalmente consultas que consultam as dimensões ou a dimensão mais à esquerda por si só, mas não a dimensão mais à direita por si só. Se você está sempre consultando duas dimensões, um índice composto é o caminho a seguir, não importa qual é a primeira (provavelmente).

Mark Canlas
fonte
1
Mark, atualizei minha postagem original (atualização 2). Esta é a minha consulta real. Meu esquema db real. E o que o comando EXPLAIN retorna. Então, com essas informações - devo usar um índice composto. Ainda não estou claro. Desde já, obrigado.
Teddy
Marque, o índice composto em sua resposta atende ao índice (coluna_C)?
Boris D. Teoharov
Não sei se entendi sua pergunta. Mas, se você estiver perguntando se o índice (A, B, C) ajudaria uma consulta que filtra na coluna C, a resposta normalmente seria não, ele não usaria o índice para filtragem. No entanto, ele poderia usar o índice para eliminar uma varredura de tabela se você estiver selecionando apenas um subconjunto do ABC. Então, isso é diferente, mas relacionado. Mas para usos típicos de índices para habilitar a filtragem, a resposta é não.
precisa saber é o seguinte
1
-1 porque um índice composto não ajuda WHERE geolat BETWEEN ??? AND ??? AND geolng BETWEEN ??? AND ???. Parará após o primeiro campo. A resposta de "Question Overflow" explica o porquê.
Rick James
1
@ no MySQL só pode usar um índice por cada uma das tabelas em uma consulta (existem exceções, por exemplo, mesclagem de índices). O que idealmente significa que uma tabela em uma consulta deve usar um único índice para todas as cláusulas where, junção de tabela, agrupar por e ordenar por. Portanto, um índice separado em cada coluna pode nem sempre funcionar, mas um índice composto pode fazer a mágica.
AKHIL MATHEW 25/07
56

Imagine que você tem as três consultas a seguir:

Consulta I:

SELECT * FROM homes WHERE `geolat`=42.9 AND `geolng`=36.4

Consulta II:

SELECT * FROM homes WHERE `geolat`=42.9

Consulta III:

SELECT * FROM homes WHERE `geolng`=36.4

Se você tiver um índice separado por coluna, todas as três consultas usarão índices. No MySQL, se você tiver um índice composto ( geolat, geolng), apenas as consultas I e II (que estão usando a primeira parte do índice composto) usam índices. Nesse caso, a consulta III requer pesquisa de tabela completa.

Na seção Índices de Colunas Múltiplas do manual, é explicado claramente como os índices de colunas funcionam, portanto, não quero redigitar o manual.

Na página Manual de Referência do MySQL :

Um índice de várias colunas pode ser considerado uma matriz classificada que contém valores criados concatenando os valores das colunas indexadas .

Se você usar um índice separado para as colunas geolat e geolng, você terá dois índices diferentes em sua tabela que poderão ser pesquisados ​​independentemente.

INDEX geolat
-----------
VALUE RRN
36.4  1
36.4  8
36.6  2
37.8  3
37.8  12
41.4  4

INDEX geolng
-----------
VALUE RRN
26.1  1
26.1  8
29.6  2
29.6  3
30.1  12
34.7  4

Se você usar o índice composto, terá apenas um índice para as duas colunas:

INDEX (geolat, geolng)
-----------
VALUE      RRN
36.4,26.1  1
36.4,26.1  8
36.6,29.6  2
37.8,29.6  3
37.8,30.1  12
41.4,34.7  4

RRN é o número de registro relativo (para simplificar, você pode dizer ID). Os dois primeiros índices gerados separadamente e o terceiro índice é composto. Como você pode ver, é possível pesquisar com base em geolng no composto, uma vez que é indexado por geolat, no entanto, é possível pesquisar por geolat ou "geolat AND geolng" (já que geolng é o índice de segundo nível).

Além disso, dê uma olhada na seção de manual Como o MySQL usa índices .

Emre Yazici
fonte
1
Na verdade, não tenho nenhuma dessas consultas. Minha consulta está listada na postagem original. Minha consulta é retornar casas dentro de uma grade quadrada. Eu sei espacial e não estou tentando calcular distâncias. Eu simplesmente quero saber se usando uma composição de índice faz sentido quando eu estou tentando exibir todas as casas dentro de uma grade especial geo (por exemplo bairro / cidade / condado)
Teddy
Eyazici, atualizei minha postagem original (atualização 2). Esta é a minha consulta real. Meu esquema db real. E o que o comando EXPLAIN retorna. Então, com essas informações - devo usar um índice composto. Ainda não estou claro. Obrigado antecipadamente
Teddy
@ "Na verdade, não tenho nenhuma dessas consultas.". Na verdade, usei a condição WHERE simples para explicar a lógica básica. Ao usar um condicional (ou seja, WHERE) em uma coluna, o MySQL tenta usar índices sempre que possível. "x ENTRE a AND b" é semelhante a "x> a AND x <b". Você usou as colunas geolng e geolat na sua consulta condicional. Se você usa o índice de composição "(geolat, geolng)" seu "E geolng entre ENTRE ??? E ???" condicional não obtém vantagens de índice (isto é para MySQL). Portanto, você deve usar um índice separado por coluna para o seu cenário.
Emre Yazici
Eu não entendo Por que devo usar índices separados para geolat e geolng quando eu vou sempre realizar uma consulta que incluem ambas as colunas
Teddy
1
Não. Quando um "intervalo" é encontrado (como em BETWEEN), nenhum outro campo do índice é considerado! Portanto, o índice composto não é melhor.
23615 Rick Rick
19

Pode haver um equívoco sobre o que o índice composto faz. Muitas pessoas pensam que o índice composto pode ser usado para otimizar uma consulta de pesquisa, desde que a wherecláusula cubra as colunas indexadas, no seu caso geolate geolng. Vamos nos aprofundar mais:

Acredito que seus dados sobre as coordenadas das casas seriam decimais aleatórios, assim:

home_id  geolat  geolng
   1    20.1243  50.4521
   2    22.6456  51.1564
   3    13.5464  45.4562
   4    55.5642 166.5756
   5    24.2624  27.4564
   6    62.1564  24.2542
...

Desde que geolate geolngvalores dificilmente se repetem. Um índice composto geolate geolngficaria assim:

index_id  geolat  geolng
   1     20.1243  50.4521
   2     20.1244  61.1564
   3     20.1251  55.4562
   4     20.1293  66.5756
   5     20.1302  57.4564
   6     20.1311  54.2542
...

Portanto, a segunda coluna do índice composto é basicamente inútil ! A velocidade da sua consulta com um índice composto provavelmente será semelhante a um índice apenas na geolatcoluna.

Como mencionado por Will, o MySQL fornece suporte a extensão espacial . Um ponto espacial é armazenado em uma única coluna em vez de duas lat lngcolunas separadas . O índice espacial pode ser aplicado a essa coluna. No entanto, a eficiência pode ser superestimada com base na minha experiência pessoal. Pode ser que o índice espacial não resolva o problema bidimensional, mas apenas acelere a pesquisa usando R-Trees com divisão quadrática .

A desvantagem é que um ponto espacial consome muito mais memória ao usar números de precisão dupla de oito bytes para armazenar coordenadas. Corrija-me se eu estiver errada.

Pergunta Overflow
fonte
5

Os índices compostos são muito poderosos, pois:

  • Aplicar a integridade da estrutura
  • Ativar a classificação em um ID FILTRADO

APLICAR A INTEGRIDADE DA ESTRUTURA

Os índices compostos não são apenas outro tipo de índice; eles podem fornecer estrutura NECESSÁRIA a uma tabela, reforçando a integridade como a Chave Primária.

O Innodb do Mysql suporta clustering e o exemplo a seguir ilustra por que um índice composto pode ser necessário.

Para criar um amigos tabelas (ou seja, para uma rede social) precisamos de 2 colunas: user_id, friend_id.

Strcture da tabela

user_id (medium_int)
friend_id (medium_int)

Primary Key -> (user_id, friend_id)

Por virtude, uma Chave Primária (PK) é única e, ao criar uma PK composta, o Innodb verifica automaticamente se não há duplicatas user_id, friend_idquando um novo registro é adicionado. Esse é o comportamento esperado, pois nenhum usuário deve ter mais de 1 registro (link de relacionamento), friend_id = 2por exemplo.

Sem uma PK composta, podemos criar esse esquema usando uma chave substituta:

user_friend_id
user_id
friend_id

Primary Key -> (user_friend_id)

Agora, sempre que um novo registro for adicionado, teremos que verificar se um registro anterior com a combinação user_id, friend_idainda não existe.

Como tal, um índice composto pode impor a integridade da estrutura.

ATIVAR A CLASSIFICAÇÃO EM UM ID FILTRADO

É muito comum classificar um conjunto de registros pela hora da postagem (carimbo de data / hora ou data / hora). Normalmente, isso significa postar em um determinado ID. Aqui está um exemplo

Tabela User_Wall_Posts (pense se as publicações no mural do Facebook)

user_id (medium_int)
timestamp (timestamp)
author_id (medium_int)
comment_post (text)

Primary Key -> (user_id, timestamp, author_id)

Queremos consultar e localizar todas as postagens user_id = 10e classificar as postagens dos comentários por timestamp(data).

SQL QUERY

SELECT * FROM User_Wall_Posts WHERE user_id = 10 ORDER BY timestamp DES

A PK composta permite ao Mysql filtrar e classificar os resultados usando o índice; O Mysql não precisará usar um arquivo temporário ou tipo de arquivo para buscar os resultados. Sem uma chave composta, isso não seria possível e causaria uma consulta muito ineficiente.

Como tal, as chaves compostas são muito poderosas e se adaptam mais ao simples problema de "Eu quero procurar, column_a, column_bentão usarei chaves compostas. Para o meu esquema de banco de dados atual, tenho tantas chaves compostas quanto chaves únicas. Não ignore uso de uma chave composta!

ProfileTwist
fonte
5

Índices compostos são úteis para

  • 0 ou mais cláusulas "=", mais
  • no máximo uma cláusula de intervalo.

Um índice composto não pode manipular dois intervalos. Discuto isso mais adiante no meu livro de receitas do índice .

Encontre o mais próximo - se a pergunta é realmente sobre como otimizar

WHERE geolat BETWEEN ??? AND ???
  AND geolng BETWEEN ??? AND ???

então, nenhum índice pode realmente lidar com ambas as dimensões.

Em vez disso, é preciso "pensar fora da caixa". Se uma dimensão for implementada por meio de particionamento e a outra for escolhida com cuidado PRIMARY KEY, é possível obter uma eficiência significativamente melhor para tabelas muito grandes de pesquisa de lat / lng. Meu blog latlng aborda os detalhes de como implementar "encontrar o mais próximo" no mundo. Inclui código.

O PARTITIONssão listras de gamas de latitude. O PRIMARY KEYdeliberadamente começa com longitude, de modo que as linhas úteis provavelmente estejam no mesmo bloco. Uma Rotina Armazenada orquestra o código confuso para fazer order by... limit...e aumentar o 'quadrado' ao redor do alvo até que você tenha cafés suficientes (ou o que seja). Ele também cuida dos cálculos do grande círculo e da manipulação da linha de dados e dos pólos.

Mais

Eu escrevi outro blog; ele compara 5 maneiras de fazer pesquisas lat / lng: http://mysql.rjweb.org/doc.php/latlng#representation_choices (faz referência ao link fornecido acima como um dos 5.) Uma das outras maneiras é essa, e ressalta que eles são ideais para o caso específico :

INDEX(geolat, geolng),
INDEX(geolng, geolat)

Ou seja, é importante ter as duas colunas em dois índices e não ter índices de coluna única no geolat e no geolng.

Rick James
fonte
1

Não há preto e branco, um tamanho serve para todas as respostas.

Você deve usar um índice composto, quando sua carga de trabalho de consulta se beneficiaria de um.

Você precisa perfilar sua carga de trabalho de consulta para determinar isso.

Um índice composto entra em jogo quando as consultas podem ser satisfeitas inteiramente a partir desse índice.

UPDATE (em resposta à edição da pergunta publicada): se você estiver selecionando * da tabela, o índice composto pode ser usado, mas não pode. Você precisará executar o EXPLAIN PLAN para ter certeza.

Mitch Wheat
fonte
Faz sentido usar um índice composto para dados de localização geográfica (latitude e longitude)?
Teddy
1
Depende inteiramente de quais consultas estão sendo feitas nessa tabela.
030 Mitch Wheat
Atualizei minha postagem original para incluir a consulta mais comum realizada. Veja acima.
Teddy
1

Para fazer pesquisas espaciais, você precisa de um algoritmo R-Tree , que permita pesquisar áreas geográficas muito rapidamente. Exatamente o que você precisa para este trabalho.

Alguns bancos de dados possuem índices espaciais embutidos. Uma rápida pesquisa no Google mostra que o MySQL 5 os possui (que, olhando seu SQL, acho que você está usando o MySQL).

Vai
fonte
1

O índice composto pode ser útil quando você deseja otimizar a group bycláusula (consulte este artigo http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html ). Por favor preste atenção:

As pré-condições mais importantes para o uso de índices para GROUP BY são que todas as colunas GROUP BY fazem referência a atributos do mesmo índice e armazenam suas chaves em ordem (por exemplo, este é um índice BTREE e não um índice HASH)

Alexander
fonte
GROUP BYnão foi mencionado.
Rick James
Não foi mencionado onde? :) É obviamente mencionado no artigo a que me referi. E responde às perguntas que foram feitas: Quando devo usar um índice composto em um banco de dados? Quais são as ramificações de desempenho usando um índice composto)? Por que devo usar um índice composto?
Alexander
Correção: GROUP BYnão foi mencionado pelo OP.
21715 Rick Rick #
Claro, essa foi a resposta - um dos casos em que usamos um índice composto em um banco de dados.
Alexander
0

Estou com @Mitch, depende inteiramente de suas perguntas. Felizmente, você pode criar e soltar índices a qualquer momento e acrescentar a palavra-chave EXPLAIN às suas consultas para ver se o analisador de consultas usa os índices.

Se você estiver procurando um par exato de lat / long, esse índice provavelmente faria sentido. Mas você provavelmente procurará casas a uma certa distância de um determinado local, para que suas consultas sejam mais ou menos assim (consulte a fonte ):

select *, sqrt(  pow(h2.geolat - h1.geolat,  2) 
               + pow(h2.geolng - h1.geolng, 2) ) as distance
from homes h1, homes h2
where h1.home_id = 12345 and h2.home_id != h1.home_id
order by distance

e o índice provavelmente não será útil. Para consultas geoespaciais, você precisa de algo como este .

Atualização: com esta consulta:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

O analisador de consultas pode usar um índice somente no geolat, ou um índice apenas no geolng, ou possivelmente os dois índices. Eu não acho que usaria um índice composto. Mas é fácil testar cada uma dessas permutações em um conjunto de dados real e (a) ver o que EXPLAIN diz a você e (b) medir o tempo que a consulta realmente leva.

Jim Ferrans
fonte
Estou simplesmente usando o desejo de retornar para casa dentro de uma grade quadrada. Eu sei espacial, então não estou tentando calcular a distância. Eu simplesmente quero retornar para casa dentro da grade quadrada e quero que ele seja executado rapidamente. Como tal, quero garantir que meus índices estejam configurados corretamente. Isso ajuda?
Teddy