golang学习笔记-4

Golang
482
0
0
2022-04-14

知识点

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. 应显式设置一个 MaxOpenConns 的值。这应该低于数据库和基础结构所施加的对链接数的任何硬限制.
  2. 通常较高的 MaxOpenConnsMaxIdleConns 值会有更好的性能。但收益却在下降,应该意识到空闲的链接池过大实际上会导致性能下降 (链接没有被重用最终变为坏链).
  3. 为了缓解上述第 2 点的问题,可能需要设置相对较短的 ConnMaxLifetime. 但是你不会希望太短,导致不必要的链接被终止和不必要的重建.
  4. MaxIdleConns 应该始终小于或等于 MaxOpenConns.
  5. 对于中小型 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 值,否则返回 0LastInsertId() (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 小白在成长