我想在我的 Go 服务器上使用 MySQL 的预准备语句,但我不确定如何让它与未知数量的参数一起工作。 一个端点允许用户发送一组 id,Go 将从数据库中选择与给定 id 匹配的对象。 该数组可以包含 1 到 20 个 id,那么我如何构造一个准备好的语句来处理这个问题呢? 我见过的所有示例都要求您确切地知道查询参数的数量。
我能想到的唯一(非常不可能)的选择是准备 20 个不同的 SELECT 语句,并使用与用户提交的 id 数量相匹配的语句 - 但这似乎是一个可怕的 hack。 那时我是否会看到准备好的语句的性能优势?
我被困在这里,所以任何帮助将不胜感激!
据我所知,没有任何 RDBMS 能够绑定未知数量的参数。永远不可能将数组与未知数量的参数占位符进行匹配。这意味着没有聪明的方法将数组绑定到查询,例如:
SELECT xxx FROM xxx WHERE xxx in (?,...,?)
这不是客户端驱动程序的限制,数据库服务器根本不支持。
有多种解决方法。
您可以使用 20 ? 创建查询,绑定您拥有的值,并通过 NULL 值完成绑定。由于涉及 NULL 值的比较操作的特定语义,它工作得很好。像“field = ?”这样的条件当参数绑定到 NULL 值时,即使某些行匹配,计算结果也始终为 false。假设数组中有 5 个值,数据库服务器将必须处理 5 个提供的值,加上 15 个 NULL 值。它通常足够聪明,可以忽略 NULL 值
另一种解决方案是准备所有查询(每个查询具有不同数量的参数)。仅当参数的最大数量受到限制时才有意义。它适用于准备语句真正重要的数据库(例如 Oracle)。
就MySQL而言,使用prepared statements的收益是相当有限的。请记住,准备好的语句仅在每个会话中维护,它们不会在会话之间共享。如果您有很多会话,它们会占用内存。另一方面,使用 MySQL 解析语句并不涉及太多开销(与其他一些数据库系统相反)。一般来说,生成大量准备好的语句来覆盖单个查询是不值得的。
请注意,某些 MySQL 驱动程序提供预准备语句接口,但它们并不在内部使用 MySQL 协议的预准备语句功能(同样,因为通常情况下,这是不值得的)。
还有一些其他解决方案(例如依赖临时表),但只有当参数数量很大时它们才有意义。
像下面的代码怎么样?对我来说唯一的缺点是使用
any
来满足 stmt.Exec(...)
的参数类型。
func (r *UserRepo) UsersByID(ids...any) ([]*User, error) {
if len(ids) == 0 {
return nil, ErrNoIDs
}
s := fmt.Sprintf(
"SELECT Name, Address, Active FROM Users WHERE ID IN (%s?)",
strings.Repeat("?,", len(ids)-1)
)
stmt, err := r.db.Prepare(s)
if err != nil {
return nil, err
}
defer stmt.Close()
rows, err := stmt.Exec(ids...)
if err != nil {
return nil, err
}
// Scan the rows
}