Indexes
Optimize query performance with powerful indexing strategies
Indexes
Indexes are Isar's most powerful feature for query optimization. Learn how to use single, composite, and multi-entry indexes effectively.
Understanding indexes is essential to optimize query performance!
What are Indexes?
Without indexes, queries must scan through every object linearly. With indexes, queries can jump directly to the relevant data.
Example Without Index
@collection
class Product {
Id? id;
late String name;
late int price;
}Unindexed Data:
| id | name | price |
|---|---|---|
| 1 | Book | 15 |
| 2 | Table | 55 |
| 3 | Chair | 25 |
| 4 | Pencil | 3 |
| 5 | Lightbulb | 12 |
| 6 | Carpet | 60 |
| 7 | Pillow | 30 |
| 8 | Computer | 650 |
| 9 | Soap | 2 |
To find products > €30, Isar must check all 9 rows:
final expensive = await isar.products.where()
.priceGreaterThan(30)
.findAll();With Index
Add an index to the price field:
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}Generated Index (sorted):
| price | id |
|---|---|
| 2 | 9 |
| 3 | 4 |
| 12 | 5 |
| 15 | 1 |
| 25 | 3 |
| 30 | 7 |
| 55 | 2 |
| 60 | 6 |
| 650 | 8 |
Now the query jumps directly to the relevant rows! ⚡
Creating Indexes
Single Property Index
@collection
class User {
Id? id;
@Index()
late String email;
@Index(type: IndexType.value)
late String username;
}Default - Stores the actual value. Supports all where clauses.
@Index(type: IndexType.value)
late String email;
// Supports: equalTo, between, startsWith, etc.
await isar.users.where()
.emailStartsWith('john')
.findAll();Stores hash of value. Only supports equality checks. Uses less space.
@Index(type: IndexType.hash)
late String email;
// Only supports: equalTo
await isar.users.where()
.emailEqualTo('john@example.com')
.findAll();For Lists only. Hashes each element individually.
@Index(type: IndexType.hashElements)
late List<String> tags;
await isar.posts.where()
.tagsElementEqualTo('flutter')
.findAll();Composite Indexes
Index multiple properties together for complex queries:
@collection
class Person {
Id? id;
late String firstName;
@Index(composite: ['firstName'])
late String lastName;
late int age;
}// Uses composite index efficiently
final people = await isar.persons
.where()
.lastNameFirstNameEqualTo('Doe', 'John')
.findAll();Composite indexes can also use only the first property: .lastNameEqualTo('Doe')
Multi-Property Composite
@collection
@Index(composite: ['lastName', 'age'])
class Person {
Id? id;
late String firstName;
late String lastName;
late int age;
}// All of these use the composite index:
.firstNameEqualTo('John')
.firstNameLastNameEqualTo('John', 'Doe')
.firstNameLastNameAgeEqualTo('John', 'Doe', 25)Multi-Entry Indexes
Create indexes for list elements:
@collection
class Post {
Id? id;
late String title;
@Index(type: IndexType.value)
late List<String> tags;
}// Fast lookup by any tag
final flutterPosts = await isar.posts
.where()
.tagsElementEqualTo('flutter')
.findAll();Multi-entry indexes can significantly increase database size for lists with many elements.
Unique Indexes
Enforce uniqueness constraints:
@collection
class User {
Id? id;
@Index(unique: true)
late String username;
late int age;
}await isar.writeAsync((isar) async {
final user1 = User()
..id = 1
..username = 'john_doe'
..age = 25;
await isar.users.put(user1); // ✅ inserted
final user2 = User()
..id = 2
..username = 'john_doe'
..age = 30;
await isar.users.put(user2); // ✅ overwrites the previous row
final current = await isar.users
.where()
.usernameEqualTo('john_doe')
.findFirst();
print(current);
// => {id: 2, username: john_doe, age: 30}
});Unique indexes always replace
v4 keeps the most recent write for a unique key combination. If you need to reject duplicates instead of overwriting, query first and throw your own error:
await isar.writeAsync((isar) async {
final exists = await isar.users
.where()
.usernameEqualTo('john_doe')
.findFirst();
if (exists != null) {
throw StateError('username already taken');
}
final user = User()
..id = 42
..username = 'john_doe'
..age = 30;
await isar.users.put(user);
});Case Sensitivity
Control case sensitivity for string indexes:
@collection
class User {
Id? id;
@Index(caseSensitive: false)
late String email;
}// Both find the same user
await isar.users.where().emailEqualTo('JOHN@example.com').findAll();
await isar.users.where().emailEqualTo('john@example.com').findAll();Case-insensitive indexes take slightly more space but provide flexible queries.
Index for Sorting
Indexes provide super-fast sorting:
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}// ❌ Slow - loads all, then sorts
final cheapest = await isar.products
.where()
.sortByPrice()
.limit(4)
.findAll();// ✅ Fast - uses sorted index
final cheapest = await isar.products
.where()
.anyPrice()
.limit(4)
.findAll();Using indexed sorting avoids loading and sorting all results in memory!
Where Clauses
Use indexes with where clauses for maximum performance:
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}// Fast - uses index
final products = await isar.products
.where()
.priceBetween(10, 100)
.findAll();
// Fast - index + sort
final sorted = await isar.products
.where()
.anyPrice()
.limit(10)
.findAll();
// Fast - index + filter
final filtered = await isar.products
.where()
.priceGreaterThan(50)
.where()
.nameStartsWith('iPhone')
.findAll();Index Types Comparison
| Type | Size | Where Clauses | Use Case |
|---|---|---|---|
IndexType.value | Large | All | Full text search, ranges |
IndexType.hash | Small | Equality only | Unique constraints, lookups |
IndexType.hashElements | Medium | List elements | Tag systems, categories |
Best Practices
Choose the Right Properties
Index properties used frequently in where clauses:
@collection
class User {
Id? id;
@Index() // Frequently queried
late String email;
late String name; // Not indexed - rarely queried alone
}Don't Over-Index
Each index increases write time and storage:
// ❌ Too many indexes
@collection
class User {
@Index() Id? id; // Id is already indexed!
@Index() late String email;
@Index() late String name;
@Index() late String phone;
@Index() late int age;
}
// ✅ Index only what you query
@collection
class User {
Id? id;
@Index(unique: true) late String email;
late String name;
late String phone;
late int age;
}Use Composite Indexes Wisely
// ✅ Good - queries firstName + lastName together
@Index(composite: ['lastName'])
late String firstName;
// ❌ Bad - separate queries
@Index()
late String firstName;
@Index()
late String lastName;Profile Your Queries
Use Isar Inspector to analyze query performance:
// Enable inspector in debug mode
final isar = Isar.open(
schemas: [UserSchema],
inspector: true, // Open inspector
);Index Limitations
- Only the first 1024 bytes of strings are indexed
- Maximum of 3 properties in composite indexes (on web)
- Indexes increase write operation time
- Indexes consume additional storage
Next Steps
Last Update