在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
关于 Go 的标准库 database/sql 和 sqlxdatabase/sql 是 Go 操作数据库的标准库之一,它提供了一系列接口方法,用于访问数据库(mysql,sqllite,oralce,postgresql),它并不会提供数据库特有的方法,那些特有的方法交给数据库驱动去实现 而通常在工作中,我们更多的是用 https://github.com/jmoiron/sqlx 包来操作数据库,sqlx 是基于标准库 sql 的扩展,并且我们可以通过 sqlx 操作各种类型的数据,如将查询的数据转为结构体等 github 地址:
安装: go get "github.com/go-sql-driver/mysql" go get "github.com/jmoiron/sqlx"
sqlx 库提供了一些类型,掌握这些类型的用法非常的重要 1)DB(数据库对象) sql.DB 类型代表了数据库,其它语言操作数据库的时候,需要创建一个连接,对于 Go 而言则是需要创建一个数据库类型,它不是数据库连接,Go 中的连接来自内部实现的连接池,连接的建立是惰性的,连接将会在操作的时候,由连接池创建并维护 使用 sql.Open 函数创建数据库类型,第一个是数据库驱动名,第二个是连接信息的字符串 var Db *sqlx.DB db, err := sqlx.Open("mysql","username:password@tcp(ip:port)/database?charset=utf8") Db = db
2)Results 和 Result(结果集) 新增、更新、删除;和查询所用的方法不一样,所有返回的类型也不同
3)Statements(语句) sql.Stmt 类型表示 sql 语句,例如 DDL,DML 等类似的 sql 语句,可以当成 prepare 语句构造查询,也可以直接使用 sql.DB 的函数对其操作
实践部分(数据库CURD)数据库建表 以下所有 demo 都以下表结构作为基础 CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, `create_time` datetime DEFAULT NULL, `username` VARCHAR(64) DEFAULT NULL, `password` VARCHAR(32) DEFAULT NULL, `department` VARCHAR(64) DEFAULT NULL, `email` varchar(64) DEFAULT NULL, PRIMARY KEY (`uid`) )ENGINE=InnoDB DEFAULT CHARSET=utf8;
Exec() 方法使用(新增、修改、删除) func (db *DB) Exec(query string, args ...interface{}) (Result, error) Exec 和 MustExec 从连接池中获取一个连接然后指向对应的 query 操作,对于不支持 ad-hoc query execution 的驱动,在操作执行的背后会创建一个 prepared statement,在结果返回前,这个 connection 会返回到连接池中 需要注意的是,不同的数据库,使用的占位符不同,mysql 采用 ? 作为占位符
demo:定义了 4 个函数,分别是 连接数据库,插入数据,更新数据,删除数据 关于 下面数据库操作的几个小知识点
package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var ( userName string = "chenkai" password string = "chenkai" ipAddrees string = "192.168.0.115" port int = 3306 dbName string = "test" charset string = "utf8" ) func connectMysql() (*sqlx.DB) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset) Db, err := sqlx.Open("mysql", dsn) if err != nil { fmt.Printf("mysql connect failed, detail is [%v]", err.Error()) } return Db } func addRecord(Db *sqlx.DB) { for i:=0; i<2; i++ { result, err := Db.Exec("insert into userinfo values(?,?,?,?,?,?)",0, "2019-07-06 11:45:20", "johny", "123456", "技术部", "[email protected]") if err != nil { fmt.Printf("data insert faied, error:[%v]", err.Error()) return } id, _ := result.LastInsertId() fmt.Printf("insert success, last id:[%d]\n", id) } } func updateRecord(Db *sqlx.DB){ //更新uid=1的username result, err := Db.Exec("update userinfo set username = 'anson' where uid = 1") if err != nil { fmt.Printf("update faied, error:[%v]", err.Error()) return } num, _ := result.RowsAffected() fmt.Printf("update success, affected rows:[%d]\n", num) } func deleteRecord(Db *sqlx.DB){ //删除uid=2的数据 result, err := Db.Exec("delete from userinfo where uid = 2") if err != nil { fmt.Printf("delete faied, error:[%v]", err.Error()) return } num, _ := result.RowsAffected() fmt.Printf("delete success, affected rows:[%d]\n", num) } func main() { var Db *sqlx.DB = connectMysql() defer Db.Close() addRecord(Db) updateRecord(Db) deleteRecord(Db) } 运行结果: API server listening at: 127.0.0.1:59899 insert success, last id:[1] insert success, last id:[2] update success, affected rows:[1] delete success, affected rows:[1]
Query() 方法使用(查询单个字段数据) func (db *DB) Query(query string, args ...interface{}) (*Rows, error) Query() 方法返回的是一个 sql.Rows 类型的结果集 也可以用来查询多个字段的数据,不过需要定义多个字段的变量进行接收 迭代后者的 Next() 方法,然后使用 Scan() 方法给对应类型变量赋值,以便取出结果,最后再把结果集关闭(释放连接) package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var ( userName string = "chenkai" password string = "chenkai" ipAddrees string = "192.168.0.115" port int = 3306 dbName string = "test" charset string = "utf8" ) func connectMysql() (*sqlx.DB) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset) Db, err := sqlx.Open("mysql", dsn) if err != nil { fmt.Printf("mysql connect failed, detail is [%v]", err.Error()) } return Db } func queryData(Db *sqlx.DB) { rows, err := Db.Query("select * from userinfo") if err != nil { fmt.Printf("query faied, error:[%v]", err.Error()) return } for rows.Next() { //定义变量接收查询数据 var uid int var create_time, username, password, department, email string err := rows.Scan(&uid, &create_time, &username, &password, &department, &email) if err != nil { fmt.Println("get data failed, error:[%v]", err.Error()) } fmt.Println(uid, create_time, username, password, department, email) } //关闭结果集(释放连接) rows.Close() } func main() { var Db *sqlx.DB = connectMysql() defer Db.Close() queryData(Db) } 运行结果: 1 2019-07-06 11:45:20 anson 123456 技术部 [email protected] 3 2019-07-06 11:45:20 johny 123456 技术部 [email protected] 4 2019-07-06 11:45:20 johny 123456 技术部 [email protected]
Get() 方法使用 func (db *DB) Get(dest interface{}, query string, args ...interface{}) error 是将查询到的一条记录,保存到结构体 结构体的字段名首字母必须大写,不然无法寻址 package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var ( userName string = "chenkai" password string = "chenkai" ipAddrees string = "192.168.0.115" port int = 3306 dbName string = "test" charset string = "utf8" ) func connectMysql() (*sqlx.DB) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset) Db, err := sqlx.Open("mysql", dsn) if err != nil { fmt.Printf("mysql connect failed, detail is [%v]", err.Error()) } return Db } func getData(Db *sqlx.DB) { type userInfo struct { Uid int `db:"uid"` UserName string `db:"username"` CreateTime string `db:"create_time"` Password string `db:"password"` Department string `db:"department"` Email string `db:"email"` } //初始化定义结构体,用来存放查询数据 var userData *userInfo = new(userInfo) err := Db.Get(userData,"select *from userinfo where uid = 1") if err != nil { fmt.Printf("query faied, error:[%v]", err.Error()) return } //打印结构体内容 fmt.Println(userData.Uid, userData.CreateTime, userData.UserName, userData.Password, userData.Department, userData.Email) } func main() { var Db *sqlx.DB = connectMysql() defer Db.Close() getData(Db) } 运行结果: 1 2019-07-06 11:45:20 anson 123456 技术部 [email protected]
Select() 方法使用 func (db *DB) Select(dest interface{}, query string, args ...interface{}) error 将查询的多条记录,保存到结构体的切片中 结构体的字段名首字母必须大写,不然无法寻址 package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var ( userName string = "chenkai" password string = "chenkai" ipAddrees string = "192.168.0.115" port int = 3306 dbName string = "test" charset string = "utf8" ) func connectMysql() (*sqlx.DB) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset) Db, err := sqlx.Open("mysql", dsn) if err != nil { fmt.Printf("mysql connect failed, detail is [%v]", err.Error()) } return Db } func selectData(Db *sqlx.DB) { type userInfo struct { Uid int `db:"uid"` UserName string `db:"username"` CreateTime string `db:"create_time"` Password string `db:"password"` Department string `db:"department"` Email string `db:"email"` } //定义结构体切片,用来存放多条查询记录 var userInfoSlice []userInfo err := Db.Select(&userInfoSlice,"select * from userinfo") if err != nil { fmt.Printf("query faied, error:[%v]", err.Error()) return } //遍历结构体切片 for _, userData := range userInfoSlice { fmt.Println(userData.Uid, userData.CreateTime, userData.UserName, userData.Password, userData.Department, userData.Email) } } func main() { var Db *sqlx.DB = connectMysql() defer Db.Close() selectData(Db) } 运行结果: 1 2019-07-06 11:45:20 anson 123456 技术部 123456@163.com 3 2019-07-06 11:45:20 johny 123456 技术部 123456@163.com 4 2019-07-06 11:45:20 johny 123456 技术部 [email protected]
重点内容回顾sql.DB
连接池 只用 sqlx.Open() 函数创建连接池,此时只是初始化了连接池,并没有连接数据库,连接都是惰性的,只有调用 sqlx.DB 的方法时,此时才真正用到了连接,连接池才会去创建连接,连接池很重要,它直接影响着你的程序行为
连接池的工作原理也非常简单,当调用 sqlx.DB 的方法时,会首先去向连接池请求要一个数据库连接,如果连接池有空闲的连接,则返回给方法中使用,否则连接池将创建一个新的连接给到方法中使用;一旦将数据库连接给到了方法中,连接就属于方法了。方法执行完毕后,要不把连接所属权还给连接池,要不传递给下一个需要数据库连接的方法中,最后都使用完将连接释放回到连接池中 请求数据库连接的方法有几个,执行完毕处理连接的方式也不同:
每个连接都是惰性的,如果验证 sqlx.Open() 调用之后,sqlx.DB 类型对象可用呢?通过 DB.Ping() 方法来初始化 func (db *DB) Ping() error demo:需要知道,当调用了 Ping() 方法后,连接池一定会初始化一个数据库连接 package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var ( userName string = "chenkai" password string = "chenkai" ipAddrees string = "192.168.0.115" port int = 3306 dbName string = "test" charset string = "utf8" ) func connectMysql() (*sqlx.DB) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset) Db, err := sqlx.Open("mysql", dsn) if err != nil { fmt.Printf("mysql connect failed, detail is [%v]", err.Error()) } return Db } func ping(Db *sqlx.DB) { err := Db.Ping() if err != nil { fmt.Println("ping failed") } else { fmt.Println("ping success") } } func main() { var Db *sqlx.DB = connectMysql() defer Db.Close() ping(Db) } 运行结果: ping success
连接池配置 DB.SetMaxIdleConns(n int) 设置连接池中的保持连接的最大连接数。默认也是0,表示连接池不会保持数据库连接的状态:即当连接释放回到连接池的时候,连接将会被关闭。这会导致连接再连接池中频繁的关闭和创建,我们可以设置一个合理的值。
DB.SetMaxOpenConns(n int) 设置打开数据库的最大连接数。包含正在使用的连接和连接池的连接。如果你的方法调用 需要用到一个连接,并且连接池已经没有了连接或者连接数达到了最大连接数。此时的方法调用将会被 block,直到有可用的连接才会返回。设置这个值可以避免并发太高导致连接 mysql 出现 too many connections 的错误。该函数的默认设置是0,表示无限制
DB.SetConnMaxLifetime(d time.Duration) 设置连接可以被使用的最长有效时间,如果过期,连接将被拒绝
数据库连接重试次数 sqlx 中的方法帮我们做了很多事情,我们不用考虑连接失败的情况,当调用方法进行数据库操作的时候,如果连接失败,sqlx 中的方法会帮我们处理,它会自动连接2次,这个如果查看源码中我们可以看到如下的代码: 其它的方法中也有这种处理,代码中变量maxBadConnRetries小时如果连接失败尝试的次数,默认是 2 // ExecContext executes a query without returning any rows. // The args are for any placeholder parameters in the query. func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error) { var res Result var err error for i := 0; i < maxBadConnRetries; i++ { res, err = db.exec(ctx, query, args, cachedOrNewConn) if err != driver.ErrBadConn { break } } if err == driver.ErrBadConn { return db.exec(ctx, query, args, alwaysNewConn) } return res, err }
参考链接:https://www.cnblogs.com/zhaof/p/8509164.html ending ~
|
请发表评论