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.