golang 数据库

走远了吗. 提交于 2020-01-14 05:07:06

database/sql包

  • golang封装了database/sql标准库,它提供了用于处理sql相关的操作的接口。而接口的实现则交给了数据库驱动

    • 写代码逻辑的时候,不用考虑后端的具体数据库,即使迁移数据库类型的时候,也只需要迁移相应的驱动即可,而不用修改代码
    • 使用数据库时,除了database/sql包本身,还需要引入想使用的特定数据库驱
  • sql.Open()不建立与数据库的任何连接,也不会验证驱动连接参数。相反,它只是准备数据库抽象以供以后使用

    • 首次真正的连接底层数据存储区将在第一次需要时懒惰地建立。
    • 如果你想立即检查数据库是否可用(例如,检查是否可以建立网络连接并登陆),请使用db.Ping()来执行此操作
  • sql.DB对象被设计为长连接

    • 不要经常Open()Close()数据库对象
      • 否则会遇到诸如重复使用和连接共享不足,耗尽可用的网络资源以及由于TIME_WAIT中剩余大量TCP连接而导致的零星故障的状态
    • 应该在需要是把sql.DB对象作为参数,或在全局范围内使其可用
  • rows.Close()是一种无害的操作,如果它已经关闭,所以你可以多次调用它

    • 请注意首先检查错误,如果没有错误再调用rows.Close(),否则会导致运行时的panic
    • 不要在循环中用defer推迟。延迟语句在函数退出之前不会执行,所以长时间运行的函数不应该使用它
  • Query将返回一个sql.Rows,它保留数据库连接,直到sql.Rows关闭

    • 如果rows.Next()循环不是由于查询完毕而自动退出的(此时会自动关闭连接),连接不会释放
      • 垃圾回收器最终会关闭底层的net.Conn,但这可能需要很长时间
    • 如果你只想执行一个语句并检查是否有错误,但忽略结果(例如DELETE等),应该使用db.Exec()而不是db.Query()
  • 来自rows.Err()的错误可能是rows.Next()循环中各种错误的结果

    • 例如循环可能会由于查询时间过长等原因提前退出,这种情况在Next内是不会有错误的,也不会自动回收连接
  • 对于QueryRow(),有一个特殊的错误常量,称为sql.ErrNoRows,当结果为空时,它将从QueryRow()返回

    • 这在大多数情况下需要作为特殊情况来处理。
    • 空的结果通常不被应用程序代码认为是错误的,如果不检查错误是不是这个特殊常量,那么会导致你意想不到的应用程序代码错误
var name string
err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
if err != nil {
    if err == sql.ErrNoRows {
        // there were no rows, but otherwise no error occurred
    } else {
        log.Fatal(err)
    }
}
  • 如果你不知道查询将返回多少列,则可以使用Columns()来查询列名称列表。你可以检查此列表的长度以查看有多少列,并且可以将切片传递给具有正确数值的Scan()
    • 对于不定字段查询,我们可以定义一个map的key和value用来表示数据库一条记录的row的值。通过rows.Columns得到的col作为map的key值
    • 如果你不知道这些列或者它们的类型,你应该使用sql.RawBytes
cols, err := rows.Columns()
if err != nil{
    log.Fatalln(err)
}

vals := make([][]byte, len(cols))
scans := make([]interface{}, len(cols))
for i := range vals{
    scans[i] = &vals[i]
}

var results []map[string]string
for rows.Next(){
    err = rows.Scan(scans...)
    if err != nil{
        log.Fatalln(err)
    }

    row := make(map[string]string)
    for k, v := range vals{
        key := cols[k]
        row[key] = string(v)
    }
    results = append(results, row)
}
  • 不必检查或者尝试处理连接失败的情况
    • 当你进行数据库操作的时候,如果连接失败了,database/sql会自动尝试重连10次。仍然无法重连的情况下会自动从连接池再获取一个或者新建另外一个
  • 连接池配置
    • db.SetMaxOpenConns(n int) 设置打开数据库的最大连接数
      • 包含正在使用的连接和连接池的连接
      • 如果你的函数调用需要申请一个连接,并且连接池已经没有了连接或者连接数达到了最大连接数。此时的函数调用将会被block,直到有可用的连接才会返回
      • 设置这个值可以避免并发太高导致连接mysql出现too many connections的错误
      • 该函数的默认设置是0,表示无限制
    • db.SetMaxIdleConns(n int)设置连接池中的保持连接的最大连接数。
      • 默认也是0,表示连接池不会保持释放会连接池中的连接的连接状态:即当连接释放回到连接池的时候,连接将会被关闭
        • 这会导致连接再连接池中频繁的关闭和创建
    • db.maxLifeTime(t time.Duration) 设置连接被复用的最大时间
      • 注意并不是连接空闲时间,而是从连接建立到这个时间点就会被回收,从而保证连接活性
      • MySQL 侧会强制 kill 掉长时间空闲的连接(8h)
  • Query方法原理
    • 步骤
      • 从连接池中请求一个连接
      • 执行查询的sql语句
      • 将数据库连接的所属权传递给Result结果集
    • Query返回的结果集是sql.Rows类型。它有一个Next方法,可以迭代数据库的游标,进而获取每一行的数据
    • rows.Next方法设计用来迭代
      • 当它迭代到最后一行数据之后,会触发一个io.EOF的信号,即引发一个错误,同时go会自动调用rows.Close方法释放连接,然后返回false。此时循环将会结束退出
    • Scan函数会帮我们自动推断除数据字段匹配目标变量
      • 比如有个数据库字段的类型是VARCHAR,而他的值是一个数字串,例如"1"
      • 如果我们定义目标变量是string,则scan赋值后目标变量是数字string
      • 如果声明的目标变量是一个数字类型,那么scan会自动调用strconv.ParseInt()或者strconv.ParseInt()方法将字段转换成和声明的目标变量一致的类型
      • 当然如果有些字段无法转换成功,则会返回错误。因此在调用scan后都需要检查错误

GORM

  • 支持的数据库
    • MySQL, PostgreSQL,Sqlite3
    • 为了处理time.Time,连接MySQL时需要包括parseTime作为参数
db, err := gorm.Open("mysql","user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
  • 默认映射
    • 表名是结构体名称的复数形式
      • 通过给结构体添加TableName() string方法自定义表名
    • 列名是字段名的蛇形小写
      • 可以通过结构体tag重设: gorm:"column:<new name>"
    • 字段ID为默认主键
      • 使用结构体tag重设:gorm:"primary_key"
  • gorm的使用方式是链式操作,很多函数都返回*DB这个结构

参考

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!