连接数据库是典型的CS编程,服务器端被动等待客户端建立TCP连接,并在此连接上进行特定的应用层 协议。但一般用户并不需要了解这些细节,这些都被打包到了驱动库当中,只需要简单的调用打开就可以指定协议连接到指定的数据库。
数据库的种类和产品太多,协议太多,Go官方很难提供针对不同数据库的驱动程序,往往由各数据库官 方或第三方给出不同开发语言的驱动库。但是,为了Go语言可以提前定义操作一个数据库的所有行为 (接口)和数据(结构体)的规范,这些定义在database/sql下。
MySQL驱动
- https://github.com/go-sql-driver/mysql 支持 database/sql,使用广泛
- https://github.com/ziutek/mymysql 支持 database/sql,支持自定义接口
- https://github.com/Philio/GoMySQL 不支持 database/sql,支持自定义接口
注册驱动:
// github.com/go-sql-driver/mysql/mysql/driver.go 代码中有注册驱动 func init() { // 83行 sql.Register("mysql", &MySQLDriver{}) }
连接
DSN例子 https://github.com/go-sql-driver/mysql#examples
使用示例:
package logg import ( "io" "github.com/rs/zerolog" ) func InfoLog(out io.Writer) zerolog.Logger { zerolog.TimeFieldFormat = "2006-01-02 15:04:05.000 +0800" logger := zerolog.New(out).With().Timestamp().Logger().Level(1) return logger }logg/logg.go
package main import ( "cabel/logg" "database/sql" // 接口定义库 "fmt" "os" "time" _ "github.com/go-sql-driver/mysql" // 具体实现,可以不调用,加载一下就行,在init中注册mysql这个名字 "github.com/rs/zerolog/log" ) var db0 *sql.DB func init() { connstr := "gopher:123456@tcp(172.0.0.1:3306)/gopher" var err error // 接口库的方法,指定使用的数据库种类,名称就要找到该名称对应的数据库的驱动 db0, err = sql.Open("mysql", connstr) if err != nil { log.Err(err).Send() } db0.SetConnMaxIdleTime(time.Second * 30) // 连接超时时间 // 数据库连接最大生命周期,用于指定数据库连接在被重用之前的最大存活时间 db0.SetConnMaxLifetime(time.Second * 60) db0.SetMaxOpenConns(100) // 最大连接数,默认0为不限制 db0.SetMaxIdleConns(10) // 空闲连接数 } // 定义结构体,结构体中字段与数据库中字段顺序最好一一对应 type Emp struct { emp_no int birth_date, first_name, last_name string gender int hire_date string } func main() { f0, err := os.OpenFile("logs/info.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Err(err).Send() } defer f0.Close() f1, err := os.OpenFile("logs/error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Err(err).Caller().Send() } defer f1.Close() l0 := logg.InfoLog(f0) l1 := logg.InfoLog(f1) err = db0.Ping() if err == nil { l0.Info().Msg("数据库初始化成功") } else { l1.Err(err).Msg("数据库连接失败") } // 单行查询 row := db0.QueryRow("select * from employees where emp_no > ? limit 1", 10007) if row.Err() != nil { l1.Err(row.Err()).Caller().Msg("数据库查询失败") } var r Emp row.Scan(&r.emp_no, &r.birth_date, &r.first_name, &r.last_name, &r.gender, &r.hire_date) fmt.Println(r) // 预编译查询,多行查询 stmt, err := db0.Prepare("select * from employees where emp_no > ? and emp_no < ? order by emp_no limit 3") if err != nil { l1.Err(err).Caller().Msg("预编译查询语句失败") } rows, err := stmt.Query("10003", 10008) if err != nil { l1.Err(err).Caller().Msg("查询失败") } // Next来遍历,每一次,rows都指向当前行 for rows.Next() { err = rows.Scan(&r.emp_no, &r.birth_date, &r.first_name, &r.last_name, &r.gender, &r.hire_date) if err != nil { l1.Err(err).Msg("填充失败") } fmt.Println(r) t0, err := time.Parse("2006-01-02", r.birth_date) // 时间解析 if err != nil { l1.Err(err).Send() } fmt.Printf("%T %[1]v\n", t0) } }main.go
返回结果:
使用db.Prepare预编译并使用参数化查询
- 对预编译的SQL语句进行缓存,省去了每次解析优化该SQL语句的过程
- 防止注入攻击
- 使用返回的sql.Stmt操作数据库