G01学习笔记-4

阅读位置6.10

知识点

1、数据库驱动

1.1 基础

Go 官方提供的 database/sql 包封装的一个数据库操作对象,包含了操作数据库的基本方法,是 接口和规范

理解:驱动都是database/sql的数据库驱动具体实现,类似laravel的门面。至于 gorm 是对实现的封装、提供更多便捷,常用的数据库操作方法

1.2 初始化 sql.DB
var db *sql.DB
.
.
.
func initDB() {
    var err error
    // https://github.com/go-sql-driver/mysql
    config := mysql.Config{
        User:                 "homestead",
        Passwd:               "secret",
        Addr:                 "127.0.0.1:33060",
        Net:                  "tcp",
        DBName:               "goblog",
        AllowNativePasswords: true,
    }

    // 准备数据库连接池
    //config.FormatDSN() = [用户名[:密码]@][协议(数据库服务器地址)]]/数据库名称?参数列表
    //例:homestead:secret@tcp(192.168.10.10:3306)/goblog?checkConnLiveness=false&maxAllowedPacket=0

    //返回一个 `*sql.DB` 结构体实例
    db, err = sql.Open("mysql", config.FormatDSN())
    checkError(err)

    // 设置最大连接数
    db.SetMaxOpenConns(25)
    // 设置最大空闲连接数
    db.SetMaxIdleConns(25)
    // 设置每个链接的过期时间
    db.SetConnMaxLifetime(5 * time.Minute)
    // 尝试连接,失败会报错
    err = db.Ping()
    checkError(err)
}
1.3 如何配置

详见: 翻译:如何配置 sql.DB 的 SetMaxOpenConns SetMaxIdleConns 和 SetConnMax...

总结

  1. 应显式设置一个 MaxOpenConns 的值。这应该低于数据库和基础结构所施加的对链接数的任何硬限制.
  2. 通常较高的 MaxOpenConns 和 MaxIdleConns 值会有更好的性能。但收益却在下降,应该意识到空闲的链接池过大实际上会导致性能下降 (链接没有被重用最终变为坏链).
  3. 为了缓解上述第 2 点的问题,可能需要设置相对较短的 ConnMaxLifetime. 但是你不会希望太短,导致不必要的链接被终止和不必要的重建.
  4. MaxIdleConns 应该始终小于或等于 MaxOpenConns.
    对于中小型 Web 应该程序,然后根据负载测试的结果来调整和优化 (具有真实吞吐量水平的测试).
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5*time.Minute)
1.4 各个驱动地址

总:github.com/golang/go/wiki/SQLDrive...

知名数据库各自都有几个驱动可供选择,推荐:

2、使用

2.1 db.Exec 方法

执行没有返回结果集的 SQL 语句。
例如 INSERT, UPDATE, DELETE 等语句
返回值为一个实现了 sql.Result 接口的类型

type Result interface {
    // 方法只用在 INSERT 语句且数据表有自增 ID 时才有返回自增 ID 值,否则返回 0
    LastInsertId() (int64, error)
    // 表示影响的数据表行数,常用于 UPDATE/DELETE 等 SQL 语句中
    RowsAffected() (int64, error)
}
2.2 db.Prepare

可以理解为php的PDO,
方法返回一个 *sql.Stmt 指针对象

stmt.Exec()
stmt.Query()
stmt.QueryRow()
stmt.Close()

做单独的语句查询时,谨记调用 defer stmt.Close() 来关闭 SQL 连接。

stmt.QueryRow().Scan(), db.QueryRow().Scan()
返回的 sql.Row 是个指针变量,保存有 SQL 连接。当调用 Scan() 时,就会将连接释放。所以在每次 QueryRow 后使用 Scan 是必须的。

当 Scan() 发现没有返回数据的话,会返回 sql.ErrNoRows 类型的错误

2.3 db.QueryRow

读取单条信息

db.QueryRow(query, id).Scan(&article.ID,  &article.Title,  &article.Body)

等同于

stmt, err := db.Prepare(query)
checkError(err)
defer stmt.Close()
err = stmt.QueryRow(id).Scan(&article.ID, &article.Title, &article.Body)
  1. 使用 Prepare 模式会发送两个 SQL 请求到 MySQL 服务器上,而纯文本模式只有一个;
  2. 在使用路由参数过滤只允许数字的情况下,可以放心使用纯文本模式无需担心 SQL 注入;
2.3 db.Query

一般使用 sql.DB 中的 Query() 来查询得到多条数据

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

Exec 只会返回最后插入 ID 和影响行数,而 Query 会返回数据表里的内容(结果集)。

Query 中文译为 查询,而 Exec 译为 执行。想查询数据,使用 Query。想执行命令,使用 Exec

2.4 sql.Rows

db.Query 的返回

func (rs *Rows) Close() error                            //关闭结果集
func (rs *Rows) ColumnTypes() ([]*ColumnType, error)    //返回数据表的列类型
func (rs *Rows) Columns() ([]string, error)             //返回数据表列的名称
func (rs *Rows) Err() error                      // 错误集
func (rs *Rows) Next() bool                      // 游标,下一行
func (rs *Rows) Scan(dest ...interface{}) error  // 扫描结构体
func (rs *Rows) NextResultSet() bool     

结果集在检出完 err 以后,遍历数据之前,应调用 defer rows.Close() 来关闭 SQL 连接。
一般会使用 rows.Next() 来遍历数据

var articles []Article
//2. 循环读取结果
for rows.Next() {
    var article Article
    // 2.1 扫码每一行的结果并赋值到一个 article 对象中
    //数据库字段顺序
    err := rows.Scan(&article.ID, &article.Title, &article.Body)
    checkError(err)
    // 2.2 将 article 追加到 articles 的这个数组中
    articles = append(articles, article)
}
// 2.3 检测循环时是否发生错误
err = rows.Err()
checkError(err)
2.5 Context 上下文
2.6 事务处理 sql.Tx(先照搬一下总结)

使用以下可以开启事务:

func (db *DB) Begin() (*Tx, error)
func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)

Begin() 和 BeginTxt() 方法返回一个 sql.Tx 结构体,他支持以上我们提到过的几种查询方法:

func (tx *Tx) Exec(query string, args ...interface{}) (Result, error)
func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryRow(query string, args ...interface{}) *Row
func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row

// 预编译 Prepare
func (tx *Tx) Stmt(stmt *Stmt) *Stmt
func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt
func (tx *Tx) Prepare(query string) (*Stmt, error)
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error)

例:

func (s Service) DoSomething() (err error) {
    // 1. 创建事务
    tx, err := s.db.Begin()
    if err != nil {
        return
    }
    // 2. 如果请求失败,就回滚所有 SQL 操作,否则提交
    //    defer 会在当前方法的最后执行
    defer func() {
        if err != nil {
            tx.Rollback()
            return err
        }
        err = tx.Commit()
    }()

    // 3. 执行各种请求
    if _, err = tx.Exec(...); err != nil {
        return err
    }
    if _, err = tx.Exec(...); err != nil {
        return err
    }
    // ...
    return nil
}
  • 贵在坚持,自我驱动,go 小白在成长
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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