慎用gorm的preload加载大量关联数据

Prepared Statement contains too many placeholders

这是一个Mysql的Error。
当我们用类似 select * from users where id in (1,2,3,4,5)的时候,我们可能并没有在意这一串id到底能有多长。

实际上,这个in后面的list是有长度限制的,而且在Mysql中,这个限制无法更改,最大支持65535。

上面的query,当我们给的id太多超过这个限制的时候,就会出现
Prepared Statement contains too many placeholders
的报错了。

Why we should not abuse Gorm Preload?

1
2
3
4
5
6
7
8
9
10
11
type User Struct {
Id uint
Name string
Orders []Order `gorm:"many2many:user_orders"`
}

type Order Struct {
Id uint
Amount string
Users []User `gorm:"many2many:user_orders"`
}

上面的定义,如果我们需要找出用户id 1,2,3他们对应的所有order,可以简简单单用一个preload。

1
2
3
4
5
6
7
var users []User
var orders []Order
ids := []uint{1,2,3}
db.Preload("Orders").Find(&users, ids)
for _, user := range users {
orders = append(orders, user.orders)
}

这个乍看没啥问题呀。这个时候我们先看看gorm会去做哪几件事情。

1
2
3
// select * from users;
// select * from user_orders where user_id in (1,2,3);
// select * from orders where id in (xxx,xxx,xxx);

这个时候,如果1,2,3中间某个用户拥有大量的order,那最后的where语句就会抛出Prepared Statement contains too many placeholders的Error。

那咋办?

Work Around

直接用Raw写subquery。

1
2
3
var orders []Order
subquery := db.Raw("select order_id from user_orders where user_id in (1,3,4)")
db.Where("id in (?)", subquery).Find(&orders)
an>
2
3
4
5
6
f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello, world")
})

// func Handle(pattern string, handler Handler)
http.Handle("/", f)

更多参考

Golang