关于Golang中database/sql包的学习笔记
概述
使用DB导入driver这里使用的是MySQL drivers import("database/sql" _"github.com/go-sql-driver/mysql") 连接DBfuncmain(){db,err:=sql.Open("mysql","user:password@tcp(127.0.0.1:3306)/hello")iferr!=nil{log.Fatal(err) } deferdb.Close() } sql.Open的第一个参数是driver名称,第二个参数是driver连接数据库的信息,各个driver可能不同。DB不是连接,并且只有当需要使用时才会创建连接,如果想立即验证连接,需要用 err=db.Ping()iferr!=nil{//dosomethinghere} sql.DB的设计就是用来作为长连接使用的。不要频繁Open,Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open,Close。 读取DB如果方法包含 var( idint namestring) rows,err:=db.Query("selectid,namefromuserswhereid=?",1)iferr!=nil{log.Fatal(err) } deferrows.Close()forrows.Next(){err:=rows.Scan(&id,&name)iferr!=nil{log.Fatal(err) }log.Println(id,name) }err=rows.Err()iferr!=nil{log.Fatal(err) } 上面代码的过程为:
单行Queryerr在 varnamestringerr=db.QueryRow("selectnamefromuserswhereid=?",1).Scan(&name)iferr!=nil{log.Fatal(err) } fmt.Println(name) 修改数据,事务一般用Prepared Statements和 stmt,err:=db.Prepare("INSERTINTOusers(name)VALUES(?)")iferr!=nil{log.Fatal(err) } res,err:=stmt.Exec("Dolly")iferr!=nil{log.Fatal(err) } lastId,err:=res.LastInsertId()iferr!=nil{log.Fatal(err) } rowCnt,err:=res.RowsAffected()iferr!=nil{log.Fatal(err) }log.Printf("ID=%d,affected=%dn",lastId,rowCnt) 事务db.Begin()开始事务, 如果你需要通过多条语句修改连接状态,你必须使用Tx,例如:
Prepared StatementsPrepared Statements and Connection在数据库层面,Prepared Statements是和单个数据库连接绑定的。客户端发送一个有占位符的statement到服务端,服务器返回一个statement ID,然后客户端发送ID和参数来执行statement。 在GO中,连接不直接暴露,你不能为连接绑定statement,而是只能为DB或Tx绑定。
这就导致在高并发的场景,过度使用statement可能导致statement泄漏,statement持续重复prepare和re-prepare的过程,甚至会达到服务器端statement数量上限。 某些操作使用了PS,例如 有些场景不适合用statement:
在Transaction中使用PSPS在Tx中唯一绑定一个连接,不会re-prepare。 Tx和statement不能分离,在DB中创建的statement也不能在Tx中使用,因为他们必定不是使用同一个连接使用Tx必须十分小心,例如下面的代码: tx,err:=db.Begin()iferr!=nil{log.Fatal(err) } defertx.Rollback() stmt,err:=tx.Prepare("INSERTINTOfooVALUES(?)")iferr!=nil{log.Fatal(err) } deferstmt.Close()//danger!fori:=0;i<10;i++{ _,err=stmt.Exec(i)iferr!=nil{log.Fatal(err) } }err=tx.Commit()iferr!=nil{log.Fatal(err) }//stmt.Close()runshere! *sql.Tx一旦释放,连接就回到连接池中,这里stmt在关闭时就无法找到连接。所以必须在Tx commit或rollback之前关闭statement。 处理Error循环Rows的Error如果循环中发生错误会自动运行 forrows.Next(){//...}iferr=rows.Err();err!=nil{//handletheerrorhere} 关闭Resultsets时的error如果你在rows遍历结束之前退出循环,必须手动关闭Resultset,并且接收error。 forrows.Next(){//... break;//whoops,rowsisnotclosed!memoryleak...}//dotheusual"iferr=rows.Err()"[omittedhere]...//it'salwayssafeto[re?]closehere:iferr=rows.Close();err!=nil{//butwhatshouldwedoifthere'sanerror? log.Println(err) } QueryRow()的errorvarnamestringerr=db.QueryRow("selectnamefromuserswhereid=?",1).Scan(&name)iferr!=nil{log.Fatal(err) } fmt.Println(name) 如果id为1的不存在,err为sql.ErrNoRows,一般应用中不存在的情况都需要单独处理。此外,Query返回的错误都会延迟到Scan被调用,所以应该写成如下代码: 把空结果当做Error处理是为了强行让程序员处理结果为空的情况 |