G01學習筆記-4

zcold發表於2021-05-18

閱讀位置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. 通常較高的 MaxOpenConnsMaxIdleConns 值會有更好的效能。但收益卻在下降,應該意識到空閒的連結池過大實際上會導致效能下降 (連結沒有被重用最終變為壞鏈).
  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 協議》,轉載必須註明作者和本文連結

相關文章