mysql如何连接数据库大白话

发布时间: 2023-11-21 12:39 阅读: 文章来源:1MUMB4112PS

go 原生提供了对数据库的支持,就是 database/sql 包,对关系型的数据库进行了通用的抽象,轻量、面向行的接口,所以使用这个包还需要下载对相应的数据库驱动,比如 mysql 的驱动包 github.com/go-sql-driver/mysql,执行:

go get -u github.com/go-sql-driver/mysql

由于不需要调用驱动包的含糊,只需要其执行一次 init 函数,imoprt 部分往往是:

"database/sql"_ "github.com/go-sql-driver/mysql"

不引入驱动,调用 sql 包的函数的时候会收到提示:

sql: unknown driver "mysql" (forgotten import?)

为什么引入 mysql 的包后就可以工作了呢? 这是因为 mysql 包里的 init 函数里执行了 sql.Register 函数:

func init() {sql.Register("mysql", &MySQLDriver{})}

查看 Register 函数的实现,会发现内部把驱动类型和驱动实例做了一个映射保存在 drivers 中,对 MySQLDriver 来说,就是 "mysql" => &MySQLDriver{},Register 函数的第二个参数是 driver.Driver 接口对象,它只要求实现 1 个方法:

type Driver interface { Open(name string) (Conn, error)}

Open 函数返回一个 driver.Conn 对象,根据注释得知该 Conn 对象在同一时间只会被一个 goroutine 所占用,通过查看 MySQLDriver 的 Open 函数发现它使用了内部的 connector 对象来实现,而 connector 对象又将功能更委托给了 mysqlConn 对象,该连接对象负责和 mysql 之间交互的协议。

sql 包使用 sql.Open 来获得一个数据库操作对象 sql.DB,而这个 DB 结构体中最重要的就是 driver.Connector,它要求实现 2 个方法:

1. Connect(context.Context) (Conn, error)

2. Driver() Driver

func getMysqlDB() *sql.DB { db, _ := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=true") //db, _ := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True") //db.Ping() return db}

问题是这个 sql.Open 里的 connector 是怎么和 mysql 驱动的 connector 连接上的呢? F12 进入 sql.Open 函数,发现它会尝试从 dirvers 获取 MySQLDriver 的实例,尝试转换成 driver.DriverContext 后调用其 OpenConnector 函数,如果转换失败了也会构造一个内部的 dsnconnector 对象,调用 driver 上的 Open 方法。

而 MySQLDriver 对 Open 和 OpenConnector 都提供了实现:

func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {cfg, err := ParseDSN(dsn)if err != nil {return nil, err}c := &connector{cfg: cfg,}return c.Connect(context.Background())}func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {cfg, err := ParseDSN(dsn)if err != nil {return nil, err}return &connector{cfg: cfg,}, nil}

sql 包依靠 Open、Register 2 个函数实现了驱动包的注入,获得 driver.Conn 对象后,主要工作就是解析 sql 查询和获得结果了(mysql 交互协议),因为查询通常分为 2 种类型:

1. query 查询带结果,比如 select 等

2. exec 只需执行,比如 update、delete 等

具体 sql 包在调用时会尝试把 driver.Conn 对象转换成 Queryer、QueryerContext 和 Execer、ExecerContext 执行其对应的方法,不过这一切使用 sql.DB 做为中间对象来操作,在 DB 内部它对 driver.Conn 对象做了进一层带锁的包装 driverConn,在任何查询前都会使用 db.conn 内部方法里获取 driverConn,它加锁以后从 freeConn 里取出一个并标记为 inUse 在使用,否则就检测是否达到最大连接数,没有就就调用 connector 对象的 Connect 方法获得 driver.Conn 后构造一个新的 driverConn,用完了后都会调用它的 relaseConn 把连接对象重新 put 回 freeConn 列表里。至此,go sql 驱动包的加载和执行的流程都清楚了。

mysql 的 go hello 版本如下:

func main() { var str string db := getMysqlDB() row := db.QueryRow("select ‘hello go-mysql‘") if row.Scan(&str) == nil {fmt.Println(str) }}

批量查询和使用预编译的查询:

func scan() {db := session.GetMysqlDB()rows, _ := db.Query("select * FROM user")defer rows.Close()// 遍历 rows}func single() {var user Userdb := session.GetMysqlDB()db.QueryRow("select * FROM user WHERE id = 1").Scan(&user.id, &user.passport, &user.password, &user.nickname, &user.createdAt)fmt.Println(user)}func prepare() {db := session.GetMysqlDB()stmt, _ := db.Prepare("select * FROM user WHERE id = ?")defer stmt.Close()rows, _ := stmt.Query(1)defer rows.Close()//}

怎么遍历 rows 呢? 需要使用 Next() 来判断,当遇到 io.EOF 或者 rows 被 close 会结束遍历。

for rows.Next() { var user User rows.Scan(&user.id, &user.passport, &user.password, &user.nickname, &user.createdAt) fmt.Println(user)}// 使用 rows.Error 来得到错误

插入时使用 LastInsertId 来获取最后一行的 id:

func main() {db := session.GetMysqlDB()stmt, _ := db.Prepare("insert INTO user(`passport`, `password`,`nickname`) VALUES(?, ?, ?)")result, _ := stmt.exec("lisi", "abc123", "李四")fmt.Println(result.LastInsertId())}

使用 begin 和 beginTx 开启一个事务,begin 使用默认的 context.Background() 来调用 beginTx,事务会独占数据库连接,但是 Tx 对象上的方法和 sql.DB 都是一一对应,调用 commit 或者 rollback 后,Tx 对象才被 Close 掉。

func main() {db := session.GetMysqlDB()stmt, _ := db.Prepare("insert INTO user(`passport`, `password`,`nickname`) VALUES(?, ?, ?)")result, _ := stmt.exec("lisi", "abc123", "李四")fmt.Println(result.LastInsertId())}

NULL 字段有时候会很麻烦,处理 NULL 有两个办法:

1. 使用数据库函数 COALESCE 让可能 NULL 的字段不可能返回 NULL

2. 使用 sql.NullString、sql.NullInt32 等类型

type NullString struct { String string Validbool // Valid is true if String is not NULL}

对于第二种办法,Scan 操作后需要先用 Valid 判断一下,为 true 则调用 String 或者 Value 方法,值得注意的并没有 sql.NullUint32 等类型,可以自定义 NULL 类型,实现 Scan 和 Value 方法。比如 NullInt32 的实现如下:

// NullInt32 represents an int32 that may be null.// NullInt32 implements the Scanner interface so// it can be used as a scan destination, similar to NullString.type NullInt32 struct {    Int32 int32    Valid bool // Valid is true if Int32 is not NULL}// Scan implements the Scanner interface.func (n *NullInt32) Scan(value interface{}) error {    if value == nil {        n.Int32, n.Valid = 0, false        return nil    }    n.Valid = true    return convertAssign(&n.Int32, value)}// Value implements the driver Valuer interface.func (n NullInt32) Value() (driver.Value, error) {    if !n.Valid {        return nil, nil    }    return int64(n.Int32), nil}

如你所见,上面的 Scan 的用法其实非常的不方便,实际项目里用 struct tag + orm 来做比较多,下面是使用 sqlx (go get https://github.com/jmoiron/sqlx) 的例子:

type User struct {IDint`db:"id"`Passportstring`db:"passport"`Passwordstring`db:"password"`Nicknamestring`db:"nickname"`CreatedAt time.Time `db:"create_time"`}func main() {var total intvar user Uservar users []Uservar names []stringdb := session.GetSqlxDB()db.Get(&total, "select COUNT(*) FROM user")fmt.Println(total)db.Get(&user, "select * FROM user LIMIT 1")fmt.Println(user)db.select(&users, "select * FROM user")fmt.Println(users)db.select(&names, "select nickname FROM user")fmt.Println(names)db.QueryRowx("select * FROM user LIMIT 1").StructScan(&user)fmt.Println(user)rows, _ := db.Queryx("select * FROM user")defer rows.Close()for rows.Next() {var u Userrows.StructScan(&u)fmt.Println(u)}}

除了好用的 Get、select、StructScan,还有 MapScan、SliceScan 分别对应 map[string]interface{} 和 []interface{},如果需要数据库 ORM 可以关注下 https://github.com/go-gorm/gorm。

本章节的代码 https://github.com/developdeveloper/go-demo/tree/master/22-access-rdb-sql

•••展开全文