Golang 提供了database/sql包用于对SQL数据库的访问, 作为操作数据库的入口对象sql.DB, 主要为我们提供了两个重要的功能:
sql.DB 通过数据库驱动为我们提供管理底层数据库连接的打开和关闭操作.
sql.DB 为我们管理数据库连接池
需要注意的是, sql.DB表示操作数据库的抽象访问接口, 而非一个数据库连接对象; 它可以根据driver打开关闭数据库连接, 管理连接池。
正在使用的连接被标记为繁忙, 用完后回到连接池等待下次使用。所以, 如果你没有把连接释放回连接池, 会导致过多连接使系统资源耗尽。
go 第三方插件实现增删改查
go-sql-driver
1)下载
go get github.com/go-sql-driver/mysql
在$GOPATH项目地址内执行
2)导入依赖包
import "database/sql"import _ "github.com/go-sql-driver/mysql"
3) 连接数据库
db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/changwai?parseTime=true")
DNS格式: [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
完整的代码:
package mainimport ( "database/sql" "log" _ "github.com/go-sql-driver/mysql")func main() { db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/changwai?parseTime=true") if err != nil { log.Fatalln(err) } defer db.Close()}
sql.Open的第一个参数是driver名称, 第二个参数是driver连接数据库的信息, 各个driver可能不同。
DB不是连接, 并且只有当需要使用时才会创建连接, 如果想立即验证连接, 需要用Ping()方法, 如下
err = db.Ping()if err != nil {// do something here}
查询语句
CREATE TABLE `tw_user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `mobile` varchar(50) DEFAULT NULL, `mobiletime` int(11) unsigned NOT NULL DEFAULT '0', `password` varchar(32) NOT NULL,) ENGINE=InnoDB AUTO_INCREMENT=6297 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
读取DB
如果方法包含Query, 那么这个方法是用于查询并返回rows的。其他情况应该用Exec()。
package mainimport ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql")func main() { db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3307)/btcbing_com?parseTime=true") if err != nil { fmt.Println(err) } defer db.Close() var ( id int name string ) rows, err := db.Query("select id, username from tw_user where id = ?", 6273) //查询的是单行 //rows, err := db.Query("select id, username from tw_user where id > 6273") //查询的是多行 if err != nil { fmt.Println(err) } defer rows.Close() for rows.Next() { err := rows.Scan(&id, &name) if err != nil { fmt.Println(err) } fmt.Println(id, name) } err = rows.Err() if err != nil { fmt.Println(err) }}
上面代码的过程为:db.Query()表示向数据库发送一个query, defer rows.Close()非常重要, 遍历rows使用rows.Next(), 把遍历到的数据存入变量使用rows.Scan(), 遍历完成后检查error。
有几点需要注意:
检查遍历是否有error
结果集(rows)未关闭前, 底层的连接处于繁忙状态。当遍历读到最后一条记录时, 会发生一个内部EOF错误, 自动调用rows.Close(), 但是如果提前退出循环, rows不会关闭, 连接不会回到连接池中, 连接也不会关闭。
所以手动关闭非常重要。rows.Close()可以多次调用, 是无害操作。
QueryRow()方法
err在Scan后才产生, 所以可以如下写:
package mainimport ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql")func main() { db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3307)/btcbing_com?parseTime=true") if err != nil { fmt.Println(err) } defer db.Close() var username string err = db.QueryRow("select username from tw_user where id = ?", 6273).Scan(&username) if err != nil { fmt.Println(err) } fmt.Println(username)}
Query 和 QueryRow 两者都是查询的方法, 返回的结果比较
Query *Rows, errorQueryRow *Row
type Row struct { err error rows *Rows}
实例: 预准备语句(增删改查)
CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, `username` VARCHAR(64) NULL DEFAULT NULL, `departname` VARCHAR(64) NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL, PRIMARY KEY (`uid`))
CREATE TABLE `userdetail` ( `uid` INT(10) NOT NULL DEFAULT '0', `intro` TEXT NULL, `profile` TEXT NULL, PRIMARY KEY (`uid`))
package mainimport ( _ "github.com/go-sql-driver/mysql" "database/sql" "fmt" //"time")func main() { db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3307)/btcbing_com?parseTime=true") checkErr(err) //插入数据 stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") checkErr(err) id, err := res.LastInsertId() checkErr(err) fmt.Println(id) //更新数据 stmt, err = db.Prepare("update userinfo set username=? where uid=?") checkErr(err) res, err = stmt.Exec("astaxieupdate", id) checkErr(err) affect, err := res.RowsAffected() checkErr(err) fmt.Println(affect) //查询数据 rows, err := db.Query("SELECT * FROM userinfo") checkErr(err) for rows.Next() { var uid int var username string var department string var created string err = rows.Scan(&uid, &username, &department, &created) checkErr(err) fmt.Println(uid) fmt.Println(username) fmt.Println(department) fmt.Println(created) } //删除数据 stmt, err = db.Prepare("delete from userinfo where uid=?") checkErr(err) res, err = stmt.Exec(id) checkErr(err) affect, err = res.RowsAffected() checkErr(err) fmt.Println(affect) db.Close()}func checkErr(err error) { if err != nil { panic(err) }}
修改数据, 事务
一般用Prepare()和Exec()完成INSERT, UPDATE, DELETE操作。
package mainimport ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql")func main() { db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3307)/btcbing_com?parseTime=true") if err != nil { fmt.Println(err) } stmt, err := db.Prepare("INSERT INTO userinfo(username) VALUES(?)") if err != nil { fmt.Println(err) } res, err := stmt.Exec("Dolly") if err != nil { fmt.Println(err) } lastId, err := res.LastInsertId() //获取最后自动增长值 if err != nil { fmt.Println(err) } rowCnt, err := res.RowsAffected() //获取影响的行数 if err != nil { fmt.Println(err) } fmt.Printf("ID = %d, affected = %d\n", lastId, rowCnt)}
事务
db.Begin()开始事务, Commit() 或 Rollback()关闭事务。
如果你需要通过多条语句修改连接状态, 你必须使用Tx,例如:
package mainimport ( "database/sql" "fmt" "time" _ "github.com/go-sql-driver/mysql")func main() { db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3307)/btcbing_com?parseTime=true") if err != nil { fmt.Println(err) } tx, err := db.Begin() //开始事务 if err != nil { fmt.Println(err) } defer tx.Rollback() //回滚 stmt, err := tx.Prepare("INSERT INTO userinfo VALUES (?, ?, ?, ?)") if err != nil { fmt.Println(err) } defer stmt.Close() // danger! _, err = stmt.Exec(4, "xuchenkai", "财务部", time.Now().Format("2006-01-02")) //执行语句 if err != nil { fmt.Println(err) } err = tx.Commit() //提交事务 if err != nil { fmt.Println(err) }}// stmt.Close() runs here!