Indici

Gli indici sono la caratteristica più potente di Isar. Molti database incorporati offrono indici "normali" (se non del tutto), ma Isar ha anche indici compositi e multi-voce. Comprendere il funzionamento degli indici è essenziale per ottimizzare le prestazioni delle query. Isar ti permette di scegliere quale indice vuoi usare e come usarlo. Inizieremo con una rapida introduzione a cosa sono gli indici.

Cosa sono gli indici?

Quando una raccolta non è indicizzata, è probabile che l'ordine delle righe non sia distinguibile dalla query in quanto non ottimizzato e la query dovrà quindi cercare tra gli oggetti in maniera lineare. In altre parole, la query dovrà cercare in ogni oggetto per trovare quelli che soddisfano le condizioni. Come puoi immaginare, può volerci del tempo. Guardare attraverso ogni singolo oggetto non è molto efficiente.

Ad esempio, questa raccolta Product non è ordinata.

@collection
class Product {
  Id? id;

  late String name;

  late int price;
}

Dati:

idnomeprezzo
1Book15
2Table55
3Chair25
4Pencil3
5Lightbulb12
6Carpet60
7Pillow30
8Computer650
9Soap2

Una query che tenti di trovare tutti i prodotti che costano più di € 30 deve cercare in tutte e nove le righe. Questo non è un problema per nove righe, ma potrebbe diventare un problema per 100.000 righe.

final expensiveProducts = await isar.products.filter()
  .priceGreaterThan(30)
  .findAll();

Per migliorare le prestazioni di questa query, indicizziamo la proprietà price. Un indice è come una tabella di ricerca ordinata:

@collection
class Product {
  Id? id;

  late String name;

  @Index()
  late int price;
}

Indici generati:

priceid
29
34
125
151
253
307
552
606
6508

Ora, la query può essere eseguita molto più velocemente. L'esecutore può saltare direttamente alle ultime tre righe dell'indice e trovare gli oggetti corrispondenti in base al loro ID.

Ordinamento

Un'altra cosa interessante: gli indici possono eseguire un ordinamento super veloce. Le query ordinate sono costose perché il database deve caricare tutti i risultati in memoria prima di ordinarli. Anche se si specifica un offset o un limite, vengono applicati dopo l'ordinamento.

Immaginiamo di voler trovare i quattro prodotti più economici. Potremmo usare la seguente query:

final cheapest = await isar.products.filter()
  .sortByPrice()
  .limit(4)
  .findAll();

In questo esempio, il database dovrebbe caricare tutti (!) gli oggetti, ordinarli per prezzo e restituire i quattro prodotti con il prezzo più basso.

Come probabilmente puoi immaginare, questo può essere fatto in modo molto più efficiente con l'indice precedente. Il database prende le prime quattro righe dell'indice e restituisce gli oggetti corrispondenti poiché sono già nell'ordine corretto.

Per utilizzare l'indice per l'ordinamento, scriveremo la query in questo modo:

final cheapestFast = await isar.products.where()
  .anyPrice()
  .limit(4)
  .findAll();

La clausola .anyX() indica a Isar di usare un indice solo per l'ordinamento. Puoi anche usare una clausola where come .priceGreaterThan() e ottenere risultati ordinati.

Indici univoci

Un indice univoco garantisce che l'indice non contenga valori duplicati. Può essere costituito da una o più proprietà. Se un indice univoco ha una proprietà, i valori in questa proprietà saranno univoci. Se l'indice univoco ha più di una proprietà, la combinazione di valori in queste proprietà è univoca.

@collection
class User {
  Id? id;

  @Index(unique: true)
  late String username;

  late int age;
}

Qualsiasi tentativo di inserire o aggiornare i dati nell'indice univoco che causa un duplicato risulterà in un errore:

final user1 = User()
  ..id = 1
  ..username = 'user1'
  ..age = 25;

await isar.users.put(user1); // -> ok

final user2 = User()
  ..id = 2;
  ..username = 'user1'
  ..age = 30;

// try to insert user with same username
await isar.users.put(user2); // -> error: unique constraint violated
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]

Sostituisci gli indici

A volte non è preferibile generare un errore se viene violato un vincolo univoco. Invece, potresti voler sostituire l'oggetto esistente con quello nuovo. Questo può essere ottenuto impostando la proprietà replace dell'indice su true.

@collection
class User {
  Id? id;

  @Index(unique: true, replace: true)
  late String username;
}

Ora quando proviamo a inserire un utente con un nome utente esistente, Isar sostituirà l'utente esistente con quello nuovo.

final user1 = User()
  ..id = 1
  ..username = 'user1'
  ..age = 25;

await isar.users.put(user1);
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]

final user2 = User()
  ..id = 2;
  ..username = 'user1'
  ..age = 30;

await isar.users.put(user2);
print(await isar.user.where().findAll());
// > [{id: 2, username: 'user1' age: 30}]

Sostituire gli indici genera anche metodi putBy() che ti consentono di aggiornare gli oggetti invece di sostituirli. L'ID esistente viene riutilizzato e i collegamenti continuano a essere popolati.

final user1 = User()
  ..id = 1
  ..username = 'user1'
  ..age = 25;

// user does not exist so this is the same as put()
await isar.users.putByUsername(user1); 
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]

final user2 = User()
  ..id = 2;
  ..username = 'user1'
  ..age = 30;

await isar.users.put(user2);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1' age: 30}]

Come puoi vedere, l'id del primo utente inserito viene riutilizzato.

Indici senza distinzione tra maiuscole e minuscole

Tutti gli indici sulle proprietà String e List<String> fanno distinzione tra maiuscole e minuscole per impostazione predefinita. Se desideri creare un indice senza distinzione tra maiuscole e minuscole, puoi utilizzare l'opzione caseSensitive:

@collection
class Person {
  Id? id;

  @Index(caseSensitive: false)
  late String name;

  @Index(caseSensitive: false)
  late List<String> tags;
}

Tipo di indice

Esistono diversi tipi di indici. La maggior parte delle volte, vorrai usare un indice IndexType.value, ma gli indici hash sono più efficienti.

Indice di valore

Gli indici di valore sono il tipo predefinito e l'unico consentito per tutte le proprietà che non contengono stringhe o elenchi. I valori delle proprietà vengono utilizzati per creare l'indice. Nel caso di elenchi, vengono utilizzati gli elementi dell'elenco. È il più flessibile ma anche dispendioso in termini di spazio dei tre tipi di indice.

Suggerimento

Usa IndexType.value per le primitive, String dove hai bisogno della clausole-where startsWith() e List se vuoi cercare singoli elementi.

Indice hash

È possibile eseguire l'hashing di stringhe ed elenchi per ridurre significativamente lo spazio di archiviazione richiesto dall'indice. Lo svantaggio degli indici hash è che non possono essere usati per scansioni di prefissi (clausole-where startsWith).

Suggerimento

Usa IndexType.hash per stringhe ed elenchi se non hai bisogno di clausole-where startsWith e elementEqualTo.

Indice HashElements

Gli elenchi di stringhe possono essere sottoposti a hash per intero (usando IndexType.hash), oppure gli elementi dell'elenco possono essere sottoposti a hash separatamente (usando IndexType.hashElements), creando in modo efficace un indice multi-voce con elementi hash.

Suggerimento

Usa IndexType.hashElements per List<String> dove hai bisogno di clausole-where elementEqualTo.

Indici compositi

Un indice composito è un indice su più proprietà. Isar consente di creare indici compositi fino a tre proprietà.

Gli indici compositi sono anche noti come indici a più colonne.

Probabilmente è meglio iniziare con un esempio. Creiamo una collezione di persone e definiamo un indice composito sulle proprietà di età e nome:

@collection
class Person {
  Id? id;

  late String name;

  @Index(composite: [CompositeIndex('name')])
  late int age;

  late String hometown;
}

Dati:

idnomeetàcittà natale
1Daniel20Berlin
2Anne20Paris
3Carl24San Diego
4Simon24Munich
5David20New York
6Carl24London
7Audrey30Prague
8Anne24Paris

Indici generati:

etànomeid
20Anne2
20Daniel1
20David5
24Anne8
24Carl3
24Carl6
24Simon4
30Audrey7

L'indice composito generato contiene tutte le persone ordinate per età e nome.

Gli indici compositi sono ottimi se desideri creare query efficienti ordinate in base a più proprietà. Consentono anche clausole dove avanzate con più proprietà:

final result = await isar.where()
  .ageNameEqualTo(24, 'Carl')
  .hometownProperty()
  .findAll() // -> ['San Diego', 'London']

L'ultima proprietà di un indice composito supporta anche condizioni come startsWith() o lessThan():

final result = await isar.where()
  .ageEqualToNameStartsWith(20, 'Da')
  .findAll() // -> [Daniel, David]

Indici a più voci

Se indicizzi una lista usando 'IndexType.value', Isar creerà automaticamente un indice multi-voce e ogni voce nella lista viene indicizzata verso l'oggetto. Funziona per tutti i tipi di liste.

Le applicazioni pratiche per gli indici a voci multiple includono l'indicizzazione di un elenco di tag o la creazione di un indice full-text.

@collection
class Product {
  Id? id;

  late String description;

  @Index(type: IndexType.value, caseSensitive: false)
  List<String> get descriptionWords => Isar.splitWords(description);
}

Isar.splitWords() divide una stringa in parole secondo la specifica Unicode Annex #29open in new window, quindi funziona correttamente per quasi tutte le lingue.

Dati:

iddescriptiondescriptionWords
1comfortable blue t-shirt[comfortable, blue, t-shirt]
2comfortable, red pullover!!![comfortable, red, pullover]
3plain red t-shirt[plain, red, t-shirt]
4red necktie (super red)[red, necktie, super, red]

Le voci con parole duplicate vengono visualizzate solo una volta nell'indice.

Indici generati:

descriptionWordsid
comfortable[1, 2]
blue1
necktie4
plain3
pullover2
red[2, 3, 4]
super4
t-shirt[1, 3]

Questo indice può ora essere usato per prefisso (o uguaglianza) dove clausole delle singole parole della descrizione.

Suggerimento

Invece di memorizzare direttamente le parole, considera anche l'utilizzo del risultato di un algoritmo foneticoopen in new window come [Soundex](https://en.wikipedia.org/wiki/ Soundex).