索引 (Indexes)
多字段索引
可以在一个或多个字段上配置索引,以提高数据检索速度或定义唯一性。
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/index"
)
// User 持有 User 实体的模式定义。
type User struct {
ent.Schema
}
func (User) Indexes() []ent.Index {
return []ent.Index{
// 非唯一索引。
index.Fields("field1", "field2"),
// 唯一索引。
index.Fields("first_name", "last_name").
Unique(),
}
}
注意,若要将单个字段设置为唯一,请在字段构建器上使用 Unique 方法,如下所示:
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("phone").
Unique(),
}
}
关联边索引
可以在字段和关联边的组合上配置索引。主要用例是在特定关系下的字段上设置唯一性。举一个例子:

在上面的例子中,我们有一个包含多条 Street(街道)的 City(城市),并且我们希望在每个城市下将街道名称设置为唯一。
ent/schema/city.go
// City 持有 City 实体的模式定义。
type City struct {
ent.Schema
}
// City 的字段。
func (City) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
}
}
// City 的关联边。
func (City) Edges() []ent.Edge {
return []ent.Edge{
edge.To("streets", Street.Type),
}
}
ent/schema/street.go
// Street 持有 Street 实体的模式定义。
type Street struct {
ent.Schema
}
// Street 的字段。
func (Street) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
}
}
// Street 的关联边。
func (Street) Edges() []ent.Edge {
return []ent.Edge{
edge.From("city", City.Type).
Ref("streets").
Unique(),
}
}
// Street 的索引。
func (Street) Indexes() []ent.Index {
return []ent.Index{
index.Fields("name").
Edges("city").
Unique(),
}
}
example.go
func Do(ctx context.Context, client *ent.Client) error {
// 与 `Save` 不同,`SaveX` 在出错时会引发恐慌(panic)。
tlv := client.City.
Create().
SetName("TLV").
SaveX(ctx)
nyc := client.City.
Create().
SetName("NYC").
SaveX(ctx)
// 向 "TLV" 添加一条名为 "ST" 的街道。
client.Street.
Create().
SetName("ST").
SetCity(tlv).
SaveX(ctx)
// 此操作会失败,因为 "ST"
// 已经在 "TLV" 下创建过了。
if err := client.Street.
Create().
SetName("ST").
SetCity(tlv).
Exec(ctx); err == nil {
return fmt.Errorf("expecting creation to fail")
}
// 向 "NYC" 添加一条名为 "ST" 的街道。
client.Street.
Create().
SetName("ST").
SetCity(nyc).
SaveX(ctx)
return nil
}
完整示例可在 GitHub 找到。
关联边字段索引
目前 Edges(关联边)列总是添加在 Fields(字段)列之后。然而,某些索引需要这些列在前,以实现特定的优化。您可以通过使用 Edge Fields(关联边字段) 来解决这个问题。
// Card 持有 Card 实体的模式定义。
type Card struct {
ent.Schema
}
// Card 的字段。
func (Card) Fields() []ent.Field {
return []ent.Field{
field.String("number").
Optional(),
field.Int("owner_id").
Optional(),
}
}
// Card 的关联边。
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("card").
Field("owner_id").
Unique(),
}
}
// Card 的索引。
func (Card) Indexes() []ent.Index {
return []ent.Index{
index.Fields("owner_id", "number"),
}
}
方言支持
可以使用 annotations(注解) 来支持特定数据库方言的功能。例如,要在 MySQL 中使用 索引前缀,请使用以下配置:
// User 的索引。
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("description").
Annotations(entsql.Prefix(128)),
index.Fields("c1", "c2", "c3").
Annotations(
entsql.PrefixColumn("c1", 100),
entsql.PrefixColumn("c2", 200),
)
}
}
上面的代码生成以下 SQL 语句:
CREATE INDEX `users_description` ON `users`(`description`(128))
CREATE INDEX `users_c1_c2_c3` ON `users`(`c1`(100), `c2`(200), `c3`)
Atlas 支持
从 v0.10 开始,Ent 使用 Atlas 运行迁移。此选项提供了对索引的更多控制,例如配置其类型或定义反向顺序的索引。
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("c1").
Annotations(entsql.Desc()),
index.Fields("c1", "c2", "c3").
Annotations(entsql.DescColumns("c1", "c2")),
index.Fields("c4").
Annotations(entsql.IndexType("HASH")),
// 在 MySQL 上启用 FULLTEXT 搜索,
// 在 PostgreSQL 上启用 GIN。
index.Fields("c5").
Annotations(
entsql.IndexTypes(map[string]string{
dialect.MySQL: "FULLTEXT",
dialect.Postgres: "GIN",
}),
),
// 对于 PostgreSQL,我们可以在索引中包含
// 非键列。
index.Fields("workplace").
Annotations(
entsql.IncludeColumns("address"),
),
// 在 SQLite 和 PostgreSQL 上定义部分索引。
index.Fields("nickname").
Annotations(
entsql.IndexWhere("active"),
),
// 定义自定义运算符类。
index.Fields("phone").
Annotations(
entsql.OpClass("bpchar_pattern_ops"),
),
}
}
上面的代码生成以下 SQL 语句:
CREATE INDEX `users_c1` ON `users` (`c1` DESC)
CREATE INDEX `users_c1_c2_c3` ON `users` (`c1` DESC, `c2` DESC, `c3`)
CREATE INDEX `users_c4` ON `users` USING HASH (`c4`)
-- 仅限 MySQL。
CREATE FULLTEXT INDEX `users_c5` ON `users` (`c5`)
-- 仅限 PostgreSQL。
CREATE INDEX "users_c5" ON "users" USING GIN ("c5")
-- 在 PostgreSQL 上包含仅索引扫描。
CREATE INDEX "users_workplace" ON "users" ("workplace") INCLUDE ("address")
-- 在 SQLite 和 PostgreSQL 上定义部分索引。
CREATE INDEX "users_nickname" ON "users" ("nickname") WHERE "active"
-- 仅限 PostgreSQL。
CREATE INDEX "users_phone" ON "users" ("phone" bpchar_pattern_ops)
函数索引
Ent 模式支持在字段和关联边(外键)上定义索引,但没有用于将索引部分定义为表达式(例如函数调用)的 API。如果您使用 Atlas 来管理模式迁移,可以按照 本指南 中的描述定义函数索引。
存储键
与字段类似,可以使用 StorageKey 方法配置自定义索引名称。
在 SQL 方言中,它被映射到索引名称。
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("field1", "field2").
StorageKey("custom_index"),
}
}