知识点
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)
}
总结
- 应显式设置一个
MaxOpenConns
的值。这应该低于数据库和基础结构所施加的对链接数的任何硬限制. - 通常较高的
MaxOpenConns
和MaxIdleConns
值会有更好的性能。但收益却在下降,应该意识到空闲的链接池过大实际上会导致性能下降 (链接没有被重用最终变为坏链). - 为了缓解上述第 2 点的问题,可能需要设置相对较短的
ConnMaxLifetime
. 但是你不会希望太短,导致不必要的链接被终止和不必要的重建. MaxIdleConns
应该始终小于或等于MaxOpenConns
.- 对于中小型 Web 应该程序,然后根据负载测试的结果来调整和优化 (具有真实吞吐量水平的测试).
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5*time.Minute)
1.4 各个驱动地址
总:github.com/golang/go/wiki/SQLDrive...
知名数据库各自都有几个驱动可供选择,推荐:
- MySQL/MariaDB —— github.com/go-sql-driver/mysql/
- Postgres SQL —— github.com/lib/pq
- SQLite3 —— github.com/mattn/go-sqlite3
- Oracle —— github.com/mattn/go-oci8
- SQL Server —— github.com/denisenkom/go-mssqldb
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)
- 使用 Prepare 模式会发送两个 SQL 请求到 MySQL 服务器上,而纯文本模式只有一个;
- 在使用路由参数过滤只允许数字的情况下,可以放心使用纯文本模式无需担心 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 小白在成长