知識點
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...
總結
- 應顯式設定一個
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 值,否則返回 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)
- 使用 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 小白在成長
本作品採用《CC 協議》,轉載必須註明作者和本文連結