1. Concetto e Applicazione degli Indici

1.1 Cos'è un Indice

Un indice è una struttura dati utilizzata in un sistema di gestione di database per velocizzare le operazioni di recupero dei dati. Può essere considerato come una "directory" nel database, che consente di individuare rapidamente i dati in una tabella, evitando scansioni complete della tabella e migliorando significativamente l'efficienza delle query. Nelle applicazioni pratiche, il corretto utilizzo degli indici può notevolmente migliorare le prestazioni di un database.

1.2 Tipi di Indici

Esistono vari tipi di indici, ognuno con il proprio design e ottimizzazione per differenti scenari di applicazione:

  • Indice a Singolo Campo: Un indice che include solo un campo, adatto per scenari che si basano su una singola condizione per query veloci.
  • Indice Composito: Un indice che include più campi, fornendo un'ottimizzazione per le query che contengono tali campi.
  • Indice Unico: Garantisce l'unicità dei campi dell'indice, impedendo l'esistenza di valori duplicati. Gli indici unici possono essere a singolo campo o composti.

2. Definizione degli Indici

2.1 Definizione dell'Indice di Campo

La creazione di un indice a singolo campo comporta l'istituzione di un indice su una specifica colonna di una tabella dati, che può essere realizzata utilizzando il metodo Fields. L'esempio seguente dimostra come creare un indice sul campo phone dell'entità User.

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("phone"),
    }
}
func (User) Indexes() []ent.Index {
    // Creare un indice a singolo campo.
    return []ent.Index{
        index.Fields("phone"),
    }
}

In questo frammento di codice, il campo phone è stato indicizzato. Questo consente al sistema di utilizzare l'indice per ricerche più rapide quando si effettuano query sul campo phone.

2.2 Definizione dell'Indice Unico

Un indice unico garantisce l'unicità dei dati sulle colonne indicizzate. Può essere creato aggiungendo il metodo Unique al campo. Gli esempi seguenti illustrano la creazione di indici unici per campi singoli e multipli.

Creazione di un indice unico per un campo singolo:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("email").Unique(),
    }
}

In questo esempio, il campo email è specificato come univoco, garantendo che l'email di ogni utente sia unica nel database.

Creazione di un indice unico per una combinazione di campi multipli:

func (User) Indexes() []ent.Index {
    return []ent.Index{
        // Creare un indice unico per campi multipli.
        index.Fields("nome", "cognome").Unique(),
    }
}

Questo codice definisce un indice unico composto per la combinazione dei campi nome e cognome, prevenendo l'occorrenza di record con gli stessi valori in entrambi i campi.

2.3 Definizione dell'Indice Composito

Quando le condizioni di query coinvolgono più campi, può risultare utile un indice composito. L'indice composito memorizza i dati nell'ordine definito nell'indice. L'ordine dell'indice ha un impatto significativo sulle prestazioni delle query, quindi nella definizione di un indice composito l'ordine dei campi deve essere stabilito in base al modello di query.

Ecco un esempio di creazione di un indice composito:

func (User) Indexes() []ent.Index {
    return []ent.Index{
        // Creare un indice composito con campi multipli.
        index.Fields("paese", "città"),
    }
}

In questo esempio, viene creato un indice composito sui campi paese e città. Ciò significa che, quando si eseguono operazioni di query che coinvolgono questi due campi, il database può localizzare rapidamente i dati che soddisfano le condizioni.

Un indice composito non solo velocizza le performance delle query, ma supporta anche operazioni di ordinamento basate sull'indice, offrendo prestazioni di recupero dati più efficienti. Nella progettazione di un indice composito, è comune posizionare i campi con maggiore selettività all'inizio dell'indice in modo che l'ottimizzatore del database possa sfruttare meglio l'indice.

3. Indici degli archi

Nel framework ent, gli indici degli archi sono un modo per definire gli indici attraverso gli archi (relazioni). Questo meccanismo viene tipicamente utilizzato per garantire l'unicità dei campi sotto relazioni specifiche. Ad esempio, se il modello del tuo database include città e strade, e ogni nome di strada sotto una città deve essere unico, gli indici degli archi possono essere utilizzati per ottenere questo.

// Il file ent/schema/street.go definisce lo schema dell'entità Street.
type Street struct {
    ent.Schema
}

func (Street) Fields() []ent.Field {
    // Definire i campi
    return []ent.Field{
        field.String("name"),
    }
}

func (Street) Edges() []ent.Edge {
    // Definisci la relazione degli archi con City, dove Street appartiene a una City.
    return []ent.Edge{
        edge.From("city", City.Type).
            Ref("streets").
            Unique(),
    }
}

func (Street) Indexes() []ent.Index {
    // Crea un indice univoco attraverso l'arco per garantire l'unicità dei nomi delle strade all'interno della stessa città.
    return []ent.Index{
        index.Fields("name").
            Edges("city").
            Unique(),
    }
}

In questo esempio, creiamo un'entità Street e la associamo all'entità City. Definendo un indice degli archi nel metodo Indexes dell'entità Street, garantiamo che il nome di ogni strada sotto una città sia unico.

Capitolo 5: Opzioni di Indice Avanzate

5.1 Indici Full-Text e Hash

L'indice full-text e l'indice hash sono due tipi di indici unici in MySQL e PostgreSQL e vengono utilizzati per differenti scenari di ottimizzazione delle query.

L'indice full-text è comunemente utilizzato per cercare dati testuali, specialmente quando è necessario effettuare ricerche complesse, come ricerche di corrispondenza di parole. Entrambi i database MySQL e PostgreSQL supportano l'indicizzazione full-text. Ad esempio, in MySQL, puoi definire un indice full-text in questo modo:

// Il file ent/schema/user.go definisce lo schema dell'entità Utente
func (User) Indexes() []ent.Index {
    // Crea un indice full-text utilizzando la categoria FULLTEXT in MySQL
    return []ent.Index{
        index.Fields("description").
            Annotations(entsql.IndexTypes(map[string]string{
                dialect.MySQL: "FULLTEXT",
            })),
    }
}

L'indice hash è particolarmente adatto per le query di uguaglianza e non supporta ordinamenti e query di intervallo. In PostgreSQL, puoi usare un indice hash in questo modo:

func (User) Indexes() []ent.Index {
    // Definisci un indice di tipo HASH
    return []ent.Index{
        index.Fields("c4").
            Annotations(entsql.IndexType("HASH")),
    }
}

5.2 Indici Parziali e Prefix degli Indici

L'indice parziale è un tipo di indice che indicizza solo le righe in una tabella che soddisfano condizioni specifiche. In SQLite e PostgreSQL, puoi utilizzare la clausola WHERE per creare indici parziali.

Ad esempio, definendo un indice parziale in PostgreSQL:

func (User) Indexes() []ent.Index {
    // Crea un indice parziale sul campo "nickname", contenente solo le righe in cui "active" è vero
    return []ent.Index{
        index.Fields("nickname").
            Annotations(
                entsql.IndexWhere("active"),
            ),
    }
}

Il prefisso dell'indice è particolarmente utile per i campi di testo, specialmente in MySQL. Può abbreviare il tempo di creazione dell'indice, ridurre lo spazio occupato dall'indice e fornire anche buone prestazioni. Come mostrato di seguito, per MySQL, puoi definire un indice con un prefisso:

func (User) Indexes() []ent.Index {
    // Crea un indice utilizzando un prefisso dell'indice
    return []ent.Index{
        index.Fields("description").
            Annotations(entsql.Prefix(128)),
    }
}

5.3 Annotation degli Indici e Personalizzazione

In ent, Annotations è una funzionalità che consente agli sviluppatori di personalizzare gli indici. Puoi definire il tipo di indice, impostare regole di ordinamento e altro ancora.

Ad esempio, il codice seguente dimostra come specificare le regole di ordinamento delle colonne in un indice:

func (User) Indexes() []ent.Index {
    return []ent.Index{
        // Utilizzare le annotazioni per definire le regole di ordinamento delle colonne dell'indice
        index.Fields("c1", "c2", "c3").
            Annotations(entsql.DescColumns("c1", "c2")),
    }
}

Con la funzionalità di annotazione, gli sviluppatori possono personalizzare gli indici in modo flessibile per ottimizzare le prestazioni e la struttura del database.