go語言多goroutine共享數據庫連接及優雅關閉的最佳實踐
在Go語言中,多個goroutine共享數據庫連接并確保正確關閉是并發編程中的常見挑戰。本文將分析一個新手常見的錯誤示例,并提供最佳解決方案。
新手通常會嘗試使用defer db.Close()來關閉數據庫連接,但這種方法在多goroutine場景下無效。defer語句僅在當前goroutine結束時執行,無法保證所有goroutine結束后再關閉連接。將defer db.Close()放在共享數據庫連接的函數內部同樣無效,因為這會導致多個goroutine嘗試同時關閉同一個連接,引發錯誤。
一些新手嘗試使用sync.WaitGroup來協調goroutine的執行,并在所有goroutine完成后關閉連接。雖然可行,但代碼較為復雜。
更優雅的解決方案:使用連接池
立即學習“go語言免費學習筆記(深入)”;
為了解決這個問題,并提高數據庫連接的利用率,建議使用連接池。連接池預先創建一定數量的數據庫連接,goroutine從池中獲取連接使用,用完后歸還到池中。當程序退出時,關閉連接池即可釋放所有連接。
以下是一個使用連接池的示例:
package main import ( "database/sql" "fmt" "sync" _ "github.com/go-sql-driver/mysql" // 替換成你的數據庫驅動 ) type dbConn struct { conn *sql.DB mu sync.Mutex } func (dc *dbConn) GetConn() (*sql.DB, error) { dc.mu.Lock() defer dc.mu.Unlock() return dc.conn, nil } func (dc *dbConn) Close() error { dc.mu.Lock() defer dc.mu.Unlock() return dc.conn.Close() } func querydb(dc *dbConn, i int) { conn, err := dc.GetConn() if err != nil { fmt.Printf("Error getting connection: %vn", err) return } defer conn.Close() // 這里關閉的是從池中獲取的連接,而不是池本身 // ... 數據庫操作 ... } func main() { db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database") if err != nil { panic(err) } defer db.Close() // 這里關閉的是連接池 dbConn := &dbConn{conn: db} var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() querydb(dbConn, i) }(i) } wg.Wait() }
這個示例中,dbConn 結構體管理數據庫連接,GetConn 方法從連接池獲取連接,Close 方法關閉連接池。 querydb 函數從連接池獲取連接,使用后歸還。主函數負責創建和關閉連接池。sync.WaitGroup 用于等待所有goroutine完成。 請記得替換成你的數據庫驅動和連接字符串。
使用連接池是處理Go語言多goroutine共享數據庫連接并確保正確關閉的最佳實踐,它不僅保證了連接的正確關閉,也提高了連接的復用率,避免了頻繁創建和銷毀連接的開銷。