准备好的陈述和连接
在数据库级别,准备好的语句绑定到单个数据库连接。典型的流程是客户端向服务器发送带占位符的SQL语句以供准备,服务器用语句ID作出响应,然后客户端通过发送其ID和参数来执行语句。
然而,在Go中,连接并不直接暴露给database/sql
包的用户 。你不准备在连接上发表声明。你准备在一个DB
或一个Tx
。并database/sql
具有一些便利行为,例如自动重试。由于这些原因,准备好的语句和连接之间的基础关联(存在于驱动程序级别)对于您的代码而言是隐藏的。
以下是它的工作原理:
- 当您准备一份声明时,它将在池中的一个连接上准备好。
- 该
Stmt
对象记住使用了哪个连接。 - 当你执行时
Stmt
,它会尝试使用连接。如果因为关闭或忙于做其他事情而无法使用,则它会从池中获得另一个连接,并将语句与另一个连接上的数据库重新进行准备。
由于在原始连接繁忙时将根据需要重新编写语句,因此可能会导致数据库的高并发使用率(可能使大量连接繁忙)创建大量预准备语句。这可能会导致明显的语句泄露,正在准备和重新准备的语句比您想象的更频繁,甚至会遇到服务器端语句数量限制。
避免准备好的陈述
Go会在封面上为您创建准备好的语句。db.Query(sql, param1, param2)
例如,一个简单的 工作是准备sql,然后使用参数执行它,最后关闭语句。
但是,有时准备好的声明不是你想要的。这可能有几个原因:
- 数据库不支持预准备语句。例如,在使用MySQL驱动程序时,您可以连接到MemSQL和Sphinx,因为它们支持MySQL有线协议。但他们不支持包含预准备语句的“二进制”协议,因此它们可能会以混乱的方式失败。
- 这些语句没有足够的重用来使它们值得,并且以其他方式处理安全问题,所以性能开销是不希望的。
如果您不想使用预准备语句,则需要使用fmt.Sprint()
或类似的语法来组装SQL,并将其作为唯一参数传递给db.Query()
or db.QueryRow()
。您的驱动程序需要支持明文查询执行,这是通过Execer
和Queryer
接口 在Go 1.1中添加的,在这里记录。
准备交易报表
在a中创建的准备Tx
好的语句仅限于它,因此早期有关重新编制的警告不适用。当你在一个Tx
对象上进行操作时,你的动作直接映射到它的唯一连接。
这也意味着在一个内部创建的准备语句Tx
不能单独使用。同样,在a上创建的准备语句DB
也不能在事务中使用,因为它们将被绑定到不同的连接。
要使用在a中的事务外准备的准备好的语句Tx
,您可以使用 Tx.Stmt()
该语句,它将从事务外准备的语句创建新的特定于事务的语句。它通过采用现有的准备好的语句,设置与事务的连接并在每次执行时重新编写所有语句来完成此操作。这种行为及其实现是不可取的,甚至在database/sql
源代码中还有一个TODO 来改进它; 我们建议不要使用这个。
在处理交易中的预先准备的报表时,必须谨慎行事。考虑下面的例子:
tx, err := db.Begin() if err != nil { log.Fatal(err) } defer tx.Rollback() stmt, err := tx.Prepare("INSERT INTO foo VALUES (?)") if err != nil { log.Fatal(err) } defer stmt.Close() // danger! for i := 0; i < 10; i++ { _, err = stmt.Exec(i) if err != nil { log.Fatal(err) } } err = tx.Commit() if err != nil { log.Fatal(err) } // stmt.Close() runs here!
在Go 1.4关闭一个已*sql.Tx
释放的与它关联的连接回到池中之前,但在已准备好的语句中延迟调用Close 以便在发生后执行 ,这可能会导致并发访问基础连接,从而导致连接状态不一致。如果您使用Go 1.4或更早版本,则应确保在事务提交或回滚之前语句始终关闭。此问题已在CR 131650043的 Go 1.4中修复。
参数占位符语法
预准备语句中占位符参数的语法是特定于数据库的。例如,比较MySQL,PostgreSQL和Oracle:
MySQL PostgreSQL Oracle
===== ========== ======
WHERE col = ? WHERE col = $1 WHERE col = :col
VALUES(?, ?, ?) VALUES($1, $2, $3) VALUES(:val1, :val2, :val3)