1. Conceito e Aplicação de Índices
1.1 O que é um Índice
Um índice é uma estrutura de dados usada em um sistema de gerenciamento de banco de dados para acelerar operações de recuperação de dados. Pode ser visto como um "diretório" no banco de dados, permitindo a rápida localização de dados em uma tabela de dados, evitando varreduras completas da tabela e melhorando significativamente a eficiência das consultas. Na prática, o uso correto de índices pode melhorar bastante o desempenho de um banco de dados.
1.2 Tipos de Índices
Existem vários tipos de índices, cada um com seu próprio design e otimização para diferentes cenários de aplicação:
- Índice de Campo Único: Um índice que inclui apenas um campo, adequado para cenários que dependem de uma única condição para consultas rápidas.
- Índice Composto: Um índice que inclui vários campos, proporcionando otimização para consultas que contêm esses campos.
- Índice Único: Garante a unicidade dos campos do índice, impedindo a existência de valores duplicados. Os índices únicos podem ser de campo único ou compostos.
2. Definição de Índice
2.1 Definição de Índice de Campo
A criação de um índice de campo único envolve o estabelecimento de um índice em uma coluna específica de uma tabela de dados, o que pode ser alcançado usando o método Fields
. O exemplo a seguir demonstra como criar um índice no campo telefone
da entidade Usuário
.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("telefone"),
}
}
func (User) Indexes() []ent.Index {
// Criar um índice de campo único.
return []ent.Index{
index.Fields("telefone"),
}
}
Neste trecho de código, o campo telefone
é indexado. Isso permite que o sistema utilize o índice para pesquisas mais rápidas ao consultar o campo telefone
.
2.2 Definição de Índice Único
Um índice único garante a unicidade dos dados nas colunas indexadas. Pode ser criado adicionando o método Unique
ao campo. Os exemplos a seguir ilustram a criação de índices únicos para campos únicos e múltiplos.
Criar um índice único para um campo único:
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("email").Unique(),
}
}
Neste exemplo, o campo email
é especificado como único, garantindo que o e-mail
de cada usuário seja único no banco de dados.
Criar um índice único para uma combinação de vários campos:
func (User) Indexes() []ent.Index {
return []ent.Index{
// Criar um índice único para vários campos.
index.Fields("primeiro_nome", "sobrenome").Unique(),
}
}
Este código define um índice único composto para a combinação dos campos primeiro_nome
e sobrenome
, impedindo a ocorrência de registros com os mesmos valores em ambos os campos.
2.3 Definição de Índice Composto
Quando as condições da consulta envolvem vários campos, um índice composto pode entrar em ação. O índice composto armazena os dados na ordem definida no índice. A ordem do índice tem um impacto significativo no desempenho da consulta, portanto, ao definir um índice composto, a ordem dos campos precisa ser determinada com base no padrão da consulta.
Aqui está um exemplo de criação de um índice composto:
func (User) Indexes() []ent.Index {
return []ent.Index{
// Criar um índice composto com vários campos.
index.Fields("país", "cidade"),
}
}
Neste exemplo, é criado um índice composto nos campos país
e cidade
. Isso significa que ao realizar operações de consulta envolvendo esses dois campos, o banco de dados pode localizar rapidamente os dados que atendem às condições.
Um índice composto não apenas acelera o desempenho da consulta, mas também suporta operações de ordenação com base em índices, proporcionando um desempenho de recuperação de dados mais eficiente. Ao projetar um índice composto, é comum colocar os campos com maior seletividade no início do índice, para que o otimizador do banco de dados possa utilizar melhor o índice.
3. Índices de Relacionamento
No framework ent, os índices de relacionamento são uma maneira de definir índices através de relacionamentos (edges). Esse mecanismo é tipicamente utilizado para garantir a unicidade de campos sob relacionamentos específicos. Por exemplo, se o modelo do seu banco de dados incluir cidades e ruas, e cada nome de rua sob uma cidade precisar ser único, os índices de relacionamento podem ser utilizados para alcançar isso.
// O arquivo ent/schema/street.go define o esquema da entidade Street.
type Street struct {
ent.Schema
}
func (Street) Fields() []ent.Field {
// Define campos
return []ent.Field{
field.String("name"),
}
}
func (Street) Edges() []ent.Edge {
// Define o relacionamento de edge com City, onde Street pertence a uma City.
return []ent.Edge{
edge.From("city", City.Type).
Ref("streets").
Unique(),
}
}
func (Street) Indexes() []ent.Index {
// Cria um índice único através do edge para garantir a unicidade dos nomes de ruas dentro da mesma cidade.
return []ent.Index{
index.Fields("name").
Edges("city").
Unique(),
}
}
Neste exemplo, criamos uma entidade Street
e a associamos com a entidade City
. Ao definir um índice de relacionamento no método Indexes
da entidade Street
, garantimos que o nome de cada rua sob uma cidade seja único.
Capítulo 5: Opções Avançadas de Índice
5.1 Índices de Texto Completo e Hash
Índice de texto completo e índice hash são dois tipos de índice exclusivos no MySQL e PostgreSQL, e são utilizados em diferentes cenários de otimização de consulta.
O índice de texto completo é comumente utilizado para buscar dados de texto, especialmente quando é necessário realizar buscas complexas, como buscas de correspondência de palavras. Ambos os bancos de dados MySQL e PostgreSQL suportam indexação de texto completo. Por exemplo, no MySQL, você pode definir um índice de texto completo assim:
// O arquivo ent/schema/user.go define o esquema da entidade User
func (User) Indexes() []ent.Index {
// Cria um índice de texto completo usando a categoria FULLTEXT no MySQL
return []ent.Index{
index.Fields("description").
Annotations(entsql.IndexTypes(map[string]string{
dialect.MySQL: "FULLTEXT",
})),
}
}
O índice hash é particularmente adequado para consultas de igualdade e não suporta ordenação e consultas de intervalo. No PostgreSQL, você pode usar um índice hash assim:
func (User) Indexes() []ent.Index {
// Define um índice do tipo HASH
return []ent.Index{
index.Fields("c4").
Annotations(entsql.IndexType("HASH")),
}
}
5.2 Índices Parciais e Prefixos de Índice
Índice parcial é um tipo de índice que indexa apenas as linhas em uma tabela que satisfazem condições específicas. No SQLite e PostgreSQL, você pode usar a cláusula WHERE para criar índices parciais.
Por exemplo, definindo um índice parcial no PostgreSQL:
func (User) Indexes() []ent.Index {
// Cria um índice parcial no campo "nickname", contendo apenas linhas onde "active" é verdadeiro
return []ent.Index{
index.Fields("nickname").
Annotations(
entsql.IndexWhere("active"),
),
}
}
O prefixo do índice é particularmente útil para campos de texto, especialmente no MySQL. Ele pode encurtar o tempo de criação do índice, reduzir o espaço ocupado pelo índice e também proporcionar bom desempenho. Como mostrado abaixo, para o MySQL, você pode definir um índice com um prefixo:
func (User) Indexes() []ent.Index {
// Cria um índice usando prefixo de índice
return []ent.Index{
index.Fields("description").
Annotations(entsql.Prefix(128)),
}
}
5.3 Anotações de Índice e Personalização
No ent, Anotações
é um recurso que permite aos desenvolvedores customizar índices. Você pode definir o tipo de índice, configurar regras de ordenação e mais.
Por exemplo, o código a seguir demonstra como especificar as regras de ordenação de coluna em um índice:
func (User) Indexes() []ent.Index {
return []ent.Index{
// Usa anotações para definir as regras de ordenação de coluna no índice
index.Fields("c1", "c2", "c3").
Annotations(entsql.DescColumns("c1", "c2")),
}
}
Com o recurso de anotações, os desenvolvedores podem customizar flexivelmente os índices para otimizar o desempenho e a estrutura do banco de dados.