Golang数据库操作笔记

golang连接数据库需要使用到系统标准库 database/sql 和 数据库驱动,database/sql 支持数据库连接池,是并发安全的。

下面以mysql 为例,展示下连接,查询,写入,更新,删除,事务等操作。

下载数据库连接驱动

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

数据库操作

连接数据库

连接数据库使用 sql.Open() 函数打开数据库连接(此时只会判断DSN是否错误,不会连接数据库),并使用 db.Ping() 进行数据库连接。

func Open(driverName, dataSourceName string) (*DB, error)

dataSourceName 的格式为 username: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 函数会返回一个 *Row 指针,通过该指针对象的 Scan() 方法可以获得结果,如果没有查询到结果,会返回一个 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)
}

注意

  1. Scan 方法会释放当前的数据库连接回到连接池中,如果调用了QueryRow不调用Scan方法,则会导致数据库连接无法释放;
  2. Scan的时候,需要保证查询的字段顺序及数量和变量保持一致,否则会返回错误。
  3. 当数据库中字段可以为空时,字段应使用指针类型或sql.NullString
  4. 如果未查询到结果,Scan 会返回一个sql.ErrNoRows ,需要通过 err == 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)
}

注意

  1. Rows 需要手动调用 Close() 方法释放数据库连接;
  2. 获取Rows 中的数据,需要使用循环来进行操作。

插入、修改和删除

插入、修改和删除统一通过 Exec 函数进行操作。

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

Exec 方法返回一个 Result 对象,该对象有两个方法:

LastInsertId() 用于获取插入的id,

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)

该方法返回一个 Stmt 的指针对象,通过调用StmtQuery(), QueryRow(), Exec() 等方法来执行sql语句,该对象需要调用 Close() 进行关闭

  1. 调用 db.Prepare() 方法进行sql预处理,得到Stmt 对象
  2. 通过调用StmtQuery(), QueryRow(), Exec() 等方法来执行sql语句,
  3. 关闭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 进行处理,开启事务后,事务内的sql需要使用返回的*Tx 进行处理。不能使用 db 进行操作,否则事务不生效。还可以通过sql.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")
}

我的博客 《Golang数据库操作》

本作品采用《CC 协议》,转载必须注明作者和本文链接
打酱油
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!