這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
這幾天用Go寫了個簡陋的伺服器,串連Mysql資料庫,提供api給其他程式調用來實現增刪改產等服務。Go的版本是1.2,使用的驅動是go-sql-driver/mysql。但是在有一定量的查詢結果以後,會出先too many connection的錯誤。
google了一下,很多文章都建議修改MySql的設定檔:my.ini。文章是這樣解釋的:MySql的預設串連數是100,當查詢數過多時,就會出現這個錯誤。所以把配置修改:
max_connections=1000
這個欄位後面的數字就是MySql允許的串連數,改的大一些就會解決問題。於是在電腦上直接改成10000,重啟MySql。然後電腦就卡的要崩潰了。看了相關的文檔大約知道,這個參數是控制MySql建立的線程數的。改成10000就會有一萬個線程,電腦自然就會卡的。但是即使是這個樣子,在一定的時間後還是會出現too many connections這樣的錯誤,只是出現的時間會晚一些罷了。所以說這個也只是治標而不治本,根本沒有解決問題。
翻看Go的sql文檔,其中有個func (*DB) SetMaxOpenConns的函數,看名字是可以控制最大的串連數的。很開心的在程式裡設定了個可以接受的數字,然後編譯運行。問題仍然沒有解決,還是會報同樣的錯誤。只能通過不斷的重啟伺服器來解決。一時真的不知道該怎麼解決了。甚至懷疑是不是使用的驅動包有問題。
無意間看到這篇文章Go's database/sql,文章裡解釋了Go中串連資料庫的串連池:當你需要和資料庫通訊時,就會從串連池裡面取出一個串連,和資料庫互動。使用完的閑置的串連會回到串連池,等待下一次的調用。如果串連池裡面沒有閑置的串連,會自動建立一個新的串連出來。其中有一段:
An sql.Row returns the connection when Scan() is called, sql.Rows returns either when Close() is called or all rows have been iterated over with Next(), and sql.Tx will return when Commit or Rollback() are called. If you forget to completely iterate an sql.Rows and you forget to Close it, that connection will never go back to the pool.
從上面可以看到,sql.Row如果不遍曆完或者直接調用Close()方法,執行這次查詢的串連就會一直存在!當串連池裡的可用串連用光後,就開始建立新的串連。這就是為什麼調用SetMaxOpenConns沒有用的原因,因為這個函數只是設定串連池裡的串連數而已!如果因為不及時釋放串連而讓串連池幹掉了,還是會不斷的建立新的串連,直到用光MySql所有的串連,報錯。明白以後,在所有調用DB.Query的函數裡加上了:
defer row.Close()
這樣查詢串連就能在函數結束或者異常的情況下被關閉,就不會持續建立新的串連了。滿以為這樣就可以解決問題了,但是伺服器運行了以後,過段時間仍然會出現相同的錯誤。在phpMyadmin裡的監控頁面,可以看到程式運行以後MySql的串連數猛增。問題又變得無解了,只能重新一行行檢查代碼。
Go中的函數可以有多個傳回值,使用底線可以忽略不需要的傳回值:
_, err := m.DB.Query("sql")
程式中update和del之類的sql語句不需要傳回值,就直接忽略了。猜想這樣也是沒法釋放串連的,因為即使你不接受傳回值,不代表這個變數就不存在了。也就是說返回的sql.Row還是存在的,只是你沒有接收而已。沒接收,就更談不上釋放串連了,所以最後產生了大量的串連繼續報錯。回頭看看那篇文章,看到這麼一段:
Ping and Exec will release the connection right before returning, but the others will pass ownership of the connection to the result, whether that's an sql.Row, sql.Rows, or sql.Tx.
也就是說Ping和Exec方法在調用完之後,會自動釋放串連。把代碼中所有不需要傳回值的語句改成由Exce方法執行,go run 一下,ok,串連數終於正常了!
問題是解決了,總起來以後要注意一下的東西:
程式串連資料庫會有串連泄漏的情況,需要及時釋放串連
Go sql包中的Query和QueryRow兩個方法的串連不會自動釋放串連,只有在遍曆完結果或者調用close方法才會關閉串連
Go sql中的Ping和Exec方法在調用結束以後就會自動釋放串連
忽略了函數的某個傳回值不代表這個值就不存在了,如果該傳回值需要close才會釋放資源,直接忽略了就會導致資源的泄漏。
有close方法的變數,在使用後要及時調用該方法,釋放資源