设为首页 收藏本站
查看: 851|回复: 0

[经验分享] 关于Golang中database/sql包的学习笔记

[复制链接]

尚未签到

发表于 2018-10-20 09:12:17 | 显示全部楼层 |阅读模式
  https://segmentfault.com/a/1190000003036452
  因为最近在学习Go,所以找了revel这个框架来学习,感觉和php的面向对象有很大不同。revel没有提供db mapping的组件,所以在github上搜了很多ORM来学习,在jmoiron/sqlx中发现了一篇比较详细介绍database/sql这个包的文章,拿来和大家分享。本文并不是按字句的翻译,如果哪里表述不清楚建议阅读原文 原文地址
概述
  sql.DB不是一个连接,它是数据库的抽象接口。它可以根据driver打开关闭数据库连接,管理连接池。正在使用的连接被标记为繁忙,用完后回到连接池等待下次使用。所以,如果你没有把连接释放回连接池,会导致过多连接使系统资源耗尽。
使用DB
导入driver
  这里使用的是MySQL drivers
import (    "database/sql"  
    _ "github.com/go-sql-driver/mysql")
连接DB
func main() {    db, err := sql.Open("mysql",        "user:password@tcp(127.0.0.1:3306)/hello")    if err != nil {        log.Fatal(err)  
    }
  
    defer db.Close()
  
}
  sql.Open的第一个参数是driver名称,第二个参数是driver连接数据库的信息,各个driver可能不同。DB不是连接,并且只有当需要使用时才会创建连接,如果想立即验证连接,需要用Ping()方法,如下:
err = db.Ping()if err != nil {    // do something here}  sql.DB的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open, Close。
读取DB
  如果方法包含Query,那么这个方法是用于查询并返回rows的。其他情况应该用Exec()。
var (  
    id int
  
    name string)
  
rows, err := db.Query("select id, name from users where id = ?", 1)if err != nil {    log.Fatal(err)
  
}
  
defer rows.Close()for rows.Next() {    err := rows.Scan(&id, &name)    if err != nil {        log.Fatal(err)
  
    }    log.Println(id, name)
  
}err = rows.Err()if err != nil {    log.Fatal(err)
  
}
  上面代码的过程为:db.Query()表示向数据库发送一个query,defer rows.Close()非常重要,遍历rows使用rows.Next(), 把遍历到的数据存入变量使用rows.Scan(), 遍历完成后检查error。有几点需要注意:

  •   检查遍历是否有error
  •   结果集(rows)未关闭前,底层的连接处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close(),但是如果提前退出循环,rows不会关闭,连接不会回到连接池中,连接也不会关闭。所以手动关闭非常重要。rows.Close()可以多次调用,是无害操作。
单行Query
  err在Scan后才产生,所以可以如下写:
var name stringerr = db.QueryRow("select name from users where id = ?", 1).Scan(&name)if err != nil {    log.Fatal(err)  
}
  
fmt.Println(name)
修改数据,事务
  一般用Prepared Statements和Exec()完成INSERT, UPDATE, DELETE操作。
stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)")if err != nil {    log.Fatal(err)  
}
  
res, err := stmt.Exec("Dolly")if err != nil {    log.Fatal(err)
  
}
  
lastId, err := res.LastInsertId()if err != nil {    log.Fatal(err)
  
}
  
rowCnt, err := res.RowsAffected()if err != nil {    log.Fatal(err)
  
}log.Printf("ID = %d, affected = %d\n", lastId, rowCnt)
事务
  db.Begin()开始事务,Commit() 或 Rollback()关闭事务。Tx从连接池中取出一个连接,在关闭之前都是使用这个连接。Tx不能和DB层的BEGIN, COMMIT混合使用。
  如果你需要通过多条语句修改连接状态,你必须使用Tx,例如:

  •   创建仅对单个连接可见的临时表
  •   设置变量,例如SET @var := somevalue
  •   改变连接选项,例如字符集,超时
Prepared Statements
Prepared Statements and Connection

  在数据库层面,Prepared Statements是和单个数据库连接绑定的。客户端发送一个有占位符的statement到服务端,服务器返回一个statement>  在GO中,连接不直接暴露,你不能为连接绑定statement,而是只能为DB或Tx绑定。database/sql包有自动重试等功能。当你生成一个Prepared Statement

  •   自动在连接池中绑定到一个空闲连接
  •   Stmt对象记住绑定了哪个连接
  •   执行Stmt时,尝试使用该连接。如果不可用,例如连接被关闭或繁忙中,会自动re-prepare,绑定到另一个连接。
  这就导致在高并发的场景,过度使用statement可能导致statement泄漏,statement持续重复prepare和re-prepare的过程,甚至会达到服务器端statement数量上限。
  某些操作使用了PS,例如db.Query(sql, param1, param2), 并在最后自动关闭statement。
  有些场景不适合用statement:

  •   数据库不支持。例如Sphinx,MemSQL。他们支持MySQL wire protocol, 但不支持"binary" protocol。
  •   statement不需要重用很多次,并且有其他方法保证安全。例子
在Transaction中使用PS
  PS在Tx中唯一绑定一个连接,不会re-prepare。
  Tx和statement不能分离,在DB中创建的statement也不能在Tx中使用,因为他们必定不是使用同一个连接使用Tx必须十分小心,例如下面的代码:
tx, err := db.Begin()if err != nil {    log.Fatal(err)  
}
  
defer tx.Rollback()
  
stmt, err := tx.Prepare("INSERT INTO foo VALUES (?)")if err != nil {    log.Fatal(err)
  
}
  
defer stmt.Close() // danger!for i := 0; i < 10; i++ {
  
    _, err = stmt.Exec(i)    if err != nil {        log.Fatal(err)
  
    }
  
}err = tx.Commit()if err != nil {    log.Fatal(err)
  
}// stmt.Close() runs here!
  *sql.Tx一旦释放,连接就回到连接池中,这里stmt在关闭时就无法找到连接。所以必须在Tx commit或rollback之前关闭statement。
处理Error
循环Rows的Error
  如果循环中发生错误会自动运行rows.Close(),用rows.Err()接收这个错误,Close方法可以多次调用。循环之后判断error是非常必要的。
for rows.Next() {    // ...}if err = rows.Err(); err != nil {    // handle the error here}关闭Resultsets时的error
  如果你在rows遍历结束之前退出循环,必须手动关闭Resultset,并且接收error。
for rows.Next() {    // ...  
    break; // whoops, rows is not closed! memory leak...}// do the usual "if err = rows.Err()" [omitted here]...// it's always safe to [re?]close here:if err = rows.Close(); err != nil {    // but what should we do if there's an error?
  
    log.Println(err)
  
}
QueryRow()的error
var name stringerr = db.QueryRow("select name from users where id = ?", 1).Scan(&name)if err != nil {    log.Fatal(err)  
}
  
fmt.Println(name)
  如果id为1的不存在,err为sql.ErrNoRows,一般应用中不存在的情况都需要单独处理。此外,Query返回的错误都会延迟到Scan被调用,所以应该写成如下代码:
var name stringerr = 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)
  
    }
  
}
  
fmt.Println(name)
  把空结果当做Error处理是为了强行让程序员处理结果为空的情况
分析数据库Error
  各个数据库处理方式不太一样,mysql为例:
if driverErr, ok := err.(*mysql.MySQLError); ok {  
    // Now the error number is accessible directly
  
    if driverErr.Number == 1045 {        // Handle the permission-denied error
  
    }
  
}
  MySQLError, Number都是DB特异的,别的数据库可能是别的类型或字段。这里的数字可以替换为常量,例如这个包 MySQL error numbers maintained by VividCortex
连接错误
NULL值处理
  简单说就是设计数据库的时候不要出现null,处理起来非常费力。Null的type很有限,例如没有sql.NullUint64; null值没有默认零值。
for rows.Next() {    var s sql.NullString  
    err := rows.Scan(&s)    // check err
  
    if s.Valid {       // use s.String
  
    } else {       // NULL value
  
    }
  
}
未知Column
  rows.Columns()的使用,用于处理不能得知结果字段个数或类型的情况,例如:
cols, err := rows.Columns()if err != nil {    // handle the error} else {  
    dest := []interface{}{ // Standard MySQL columns
  
        new(uint64), // id
  
        new(string), // host
  
        new(string), // user
  
        new(string), // db
  
        new(string), // command
  
        new(uint32), // time
  
        new(string), // state
  
        new(string), // info
  
    }    if len(cols) == 11 {        // Percona Server
  
    } else if len(cols) > 8 {        // Handle this case
  
    }
  
    err = rows.Scan(dest...)    // Work with the values in dest}
cols, err := rows.Columns() // Remember to check err afterwards  
vals := make([]interface{}, len(cols))
  
for i, _ := range cols {
  
    vals = new(sql.RawBytes)
  
}
  
for rows.Next() {
  
    err = rows.Scan(vals...)
  
    // Now you can check each element of vals for nil-ness,
  
    // and you can use type introspection and type assertions
  
    // to fetch the column into a typed variable.
  
}
关于连接池

  •   避免错误操作,例如LOCK TABLE后用 INSERT会死锁,因为两个操作不是同一个连接,insert的连接没有table lock。
  •   当需要连接,且连接池中没有可用连接时,新的连接就会被创建。
  •   默认没有连接上限,你可以设置一个,但这可能会导致数据库产生错误“too many connections”
  •   db.SetMaxIdleConns(N)设置最大空闲连接数
  •   db.SetMaxOpenConns(N)设置最大打开连接数
  •   长时间保持空闲连接可能会导致db timeout



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-623895-1-1.html 上篇帖子: 在docker中运行sql文件 下篇帖子: java.sql.SQLTimeoutException: Timeout after 30001ms of waiting for a connection.
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表