database/sqldatabase/sql
mysql

下载数据库连接驱动

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

数据库操作

连接数据库

sql.Open()db.Ping()
func Open(driverName, dataSourceName string) (*DB, error)
dataSourceNameusername:password@tcp(host:port)/database
package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql" // 导入mysql包,并不使用
)

// 连接mysql示例
func main() {
    dsn := "root:123123@tcp(127.0.0.1:3306)/golang"
    db, err := sql.Open("mysql", dsn) // 打开一个数据库连接,不会校验用户名和密码是否正确
    if err != nil {
        fmt.Printf("DSN[%s]格式错误: %v \n", dsn, err) // 如果出现错误,则DSN格式错误
        return
    }

    err = db.Ping() // 尝试连接mysql

    if err != nil {
        fmt.Printf("连接数据库失败: %v \n", err)
        return
    }

    fmt.Println("连接数库成功")
}

查询

单行查询

db.QueryRow()
func (db *DB) QueryRow(query string, args ...interface{}) *Row
func (r *Row) Scan(dest ...interface{}) error
QueryRow*RowScan()sql.ErrNoRows
package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "log"
)

var db *sql.DB

// 声明一个user结构体
type user struct {
    id       int
    nickname string
    avatar   *string // 数据库字段可以为空时,使用指针类型
}

func initDB() (err error) {
    dsn := "root:@tcp(127.0.0.1:3306)/golang"
    db, err = sql.Open("mysql", dsn) // 打开一个数据库连接,不会校验用户名和密码是否正确
    if err != nil {
        return err
    }
    err = db.Ping()

    if err != nil {
        return err
    }
    return
}

// 连接mysql示例
func main() {
    err := initDB() // 连接数据库

    if err != nil {
        log.Println("数据库连接失败,", err)
        return
    }
    sqlStr := "select id, nickname, avatar from users where id = ?" // 构建查询语句
    var u user
    err = db.QueryRow(sqlStr, 1).Scan(&u.id, &u.nickname, &u.avatar) // 查询单条数据并扫描结果

    if err != nil {
        if err == sql.ErrNoRows {
            log.Print("未查询到结果!")
      return
        }
        log.Printf("查询失败: %s", err)
    return
    }

    log.Printf("查询结果:%#v", u)
}
sql.NullStringScansql.ErrNoRowserr == sql.ErrNoRows

多行查询

db.Query()*Rowserror

示例:

// 多行查询
func queryMany() {
    sqlStr := "select id, nickname, avatar from users"

    rows, err := db.Query(sqlStr) // 查询sql语句
    if err != nil {
        fmt.Println("查询失败", err)
        return
    }
    users := make([]user, 0)

    defer rows.Close() // rows需要手动调用Close()释放连接
    for rows.Next() {  // 遍历调用Scan方法
        var u user
        err = rows.Scan(&u.id, &u.nickname, &u.avatar) // 对对象进行赋值
        if err != nil {
            fmt.Println("获取数据失败", err)
            continue
        }
        users = append(users, u)
    }
    fmt.Printf("%#v", users)
}
RowsClose()Rows

插入、修改和删除

Exec
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
ExecResult
LastInsertId()
RowsAffected()

插入数据

func insert() {
    sqlStr := "insert into users(name, age) values('王五', 10)" // 插入sql
    ret, err := db.Exec(sqlStr) // 执行插入语句

    if err != nil {
        fmt.Println("插入数据失败,", err)
        return
    }

    lastId, err := ret.LastInsertId() // 获取插入ID

    if err != nil {
        fmt.Println("插入数据失败,", err)
        return
    }

    fmt.Println("插入数据成功:", lastId)
}

更新数据

func update() {
    sqlStr := "update users set age = 12 where name = '王五'"

    ret, err := db.Exec(sqlStr)
    if err != nil {
        fmt.Println("更新数据失败", err)
        return
    }

    rows, err := ret.RowsAffected()

    if err != nil {
        fmt.Println("获取行数失败", err)
        return
    }

    fmt.Println("更新数据行数:", rows)
}

删除数据

func deleteRow() {
    sqlStr := "delete from users where age = 12"

    ret, err := db.Exec(sqlStr)

    if err != nil {
        fmt.Println("删除数据失败", err)
        return
    }

    rows, err := ret.RowsAffected()
    if err != nil {
        fmt.Println("获取行数失败", err)
        return
    }

    fmt.Println("删除行数:", rows)
}

Mysql 预处理

什么是预处理

普通Sql语句执行过程:

  1. 客户端对sql语句进行占位符替换得到完整的sql语句
  2. 客户端发送完整的sql语句到mysql服务端
  3. mysql服务端执行完整的sql语句,并将结果返回给客户端

预处理sql语句执行过程

  1. 将sql语句分成命令部分和数据部分
  2. 先把命令发送到mysql服务端,mysql服务端对sql语句进行预处理
  3. 然后将数据部分发送给mysql服务端,mysql服务端对sql语句进行占位符替换
  4. mysql服务端执行完整的sql语句,并将结果返回给客户端

为什么要预处理?

  1. 优化mysql服务器重复执行sql的方法,提升服务器性能。对于相同的sql,可以一次编译多次执行,节省编译的性能和时间开销
  2. 避免sql注入

使用预处理

db.Prepare()
func (db *DB) Prepare(query string) (*Stmt, error)
StmtStmtQuery()QueryRow()Exec()Close()
db.Prepare()StmtStmtQuery()QueryRow()Exec()Stmt
func prepareQueryDemo(id int) {
    sqlStr := "select id, name, age from users where id = ?"
    stmt, err := db.Prepare(sqlStr)

    if err != nil {
        fmt.Println("预处理失败", err)
        return
    }

    defer stmt.Close()
    row := stmt.QueryRow(id)

    var u user
    err = row.Scan(&u.id, &u.name, &u.age)

    if err != nil {
        fmt.Println("获取数据失败", err)
        return
    }

    fmt.Printf("获取数据成功%#v\n", u)

}

事务

db.BeginTx*Txdbsql.TxOption
func transaction() {
    ctx := context.Background()
    tx, err := db.BeginTx(ctx, &sql.TxOptions{}) //开启事务
    if err != nil {
        log.Fatal("begin tx error:", err)
    }
  // do something
    sqlStr1 := "insert into users(nickname, avatar) values('王五', null)" // 插入sql
    _, err = tx.Exec(sqlStr1)                                           // 执行插入语句
    sqlStr2 := "insert into users(nickname, avatar) values('李四', null)" // 插入sql
    _, err = tx.Exec(sqlStr2)                                           // 执行插入语句
    if err != nil {
        tx.Rollback() // 事务回滚
        log.Println("Rollback", err)
        return
    }

    tx.Commit() // 事务提交
    log.Println("committed")
}
本作品采用《CC 协议》,转载必须注明作者和本文链接