database/sql是golang的標準庫之一,它提供了一系列接口方法,用於訪問關係數據庫。它並不會提供數據庫特有的方法,那些特有的方法交給數據庫驅動去實現。
database/sql庫提供了一些type。這些類型對掌握它的用法很是重要。
DB 數據庫對象。 sql.DB類型表明了數據庫。和其餘語言不同,它並是數據庫鏈接。golang中的鏈接來自內部實現的鏈接池,鏈接的創建是惰性的,當你須要鏈接的時候,鏈接池會自動幫你建立。一般你不須要操做鏈接池。一切都有go來幫你完成。
Results 結果集。數據庫查詢的時候,都會有結果集。sql.Rows類型表示查詢返回多行數據的結果集。sql.Row則表示單行查詢結果的結果集。固然,對於插入更新和刪除,返回的結果集類型爲sql.Result。
Statements 語句。sql.Stmt類型表示sql查詢語句,例如DDL,DML等相似的sql語句。能夠把當成prepare語句構造查詢,也能夠直接使用sql.DB的函數對其操做。mysql
下面就開始咱們的sql數據庫之旅,咱們使用mysql數據庫爲例子,驅動使用go-sql-driver/mysql。
對於其餘語言,查詢數據的時候須要建立一個鏈接,對於go而言則是須要建立一個數據庫抽象對象。鏈接將會在查詢須要的時候,由鏈接池建立並維護。使用sql.Open函數建立數據庫對象。它的第一個參數是數據庫驅動名,第二個參數是一個鏈接字串(符合DSN風格,能夠是一個tcp鏈接,一個unix socket等)。git
import ( "database/sql" "log" _ "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true") if err != nil{ log.Fatal(err) } defer db.Close() }
建立了數據庫對象以後,在函數退出的時候,須要釋放鏈接,即調用sql.Close方法。例子使用了defer語句設置釋放鏈接。
接下來進行一些基本的數據庫操做,首先咱們使用Exec方法執行一條sql,建立一個數據表:github
func main() { db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true") if err != nil{ log.Fatal(err) } defer db.Close() _, err = db.Exec("CREATE TABLE IF NOT EXISTS test.hello(world varchar(50))") if err != nil{ log.Fatalln(err) } }
此時能夠看見,數據庫生成了一個新的表。接下來再插入一些數據。golang
func main() { db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true") ... rs, err := db.Exec("INSERT INTO test.hello(world) VALUES ('hello world')") if err != nil{ log.Fatalln(err) } rowCount, err := rs.RowsAffected() if err != nil{ log.Fatalln(err) } log.Printf("inserted %d rows", rowCount) }
一樣使用Exec方法便可插入數據,返回的結果集對象是是一個sql.Result類型,它有一個LastInsertId方法,返回插入數據後的id。固然此例的數據表並無id字段,就返回一個0.
插入了一些數據,接下來再簡單的查詢一下數據:sql
func main() { db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true") ... rows, err := db.Query("SELECT world FROM test.hello") if err != nil{ log.Fatalln(err) } for rows.Next(){ var s string err = rows.Scan(&s) if err !=nil{ log.Fatalln(err) } log.Printf("found row containing %q", s) } rows.Close() }
咱們使用了Query方法執行select查詢語句,返回的是一個sql.Rows類型的結果集。迭代後者的Next方法,而後使用Scan方法給變量s賦值,以便取出結果。最後再把結果集關閉(釋放鏈接)。
經過上面一個簡單的例子,介紹了database/sql的基本數據查詢操做。而對於開篇所說的幾個結構類型還沒有進行詳細的介紹。下面咱們再針對database/sql庫的類型和數據庫交互作更深的探究。
sql.DB
正如上文所言,sql.DB是數據庫的抽象,雖然一般它容易被誤覺得是數據庫鏈接。它提供了一些跟數據庫交互的函數,同時管理維護一個數據庫鏈接池,幫你處理了單調而重複的管理工做,而且在多個goroutines也是十分安全。
sql.DB表示是數據庫抽象,所以你有幾個數據庫就須要爲每個數據庫建立一個sql.DB對象。由於它維護了一個鏈接池,所以不須要頻繁的建立和銷燬。它須要長時間保持,所以最好是設置成一個全局變量以便其餘代碼能夠訪問。
建立數據庫對象須要引入標準庫database/sql,同時還須要引入驅動go-sql-driver/mysql。使用_表示引入驅動的變量,這樣作的目的是爲了在你的代碼中不至於和標註庫的函數變量namespace衝突。
鏈接池
只用sql.Open函數建立鏈接池,但是此時只是初始化了鏈接池,並無建立任何鏈接。鏈接建立都是惰性的,只有當你真正使用到鏈接的時候,鏈接池纔會建立鏈接。鏈接池很重要,它直接影響着你的程序行爲。
鏈接池的工做原來卻至關簡單。當你的函數(例如Exec,Query)調用須要訪問底層數據庫的時候,函數首先會向鏈接池請求一個鏈接。若是鏈接池有空閒的鏈接,則返回給函數。不然鏈接池將會建立一個新的鏈接給函數。一旦鏈接給了函數,鏈接則歸屬於函數。函數執行完畢後,要不把鏈接所屬權歸還給鏈接池,要麼傳遞給下一個須要鏈接的(Rows)對象,最後使用完鏈接的對象也會把鏈接釋放回到鏈接池。
請求一個鏈接的函數有好幾種,執行完畢處理鏈接的方式稍有差異,大體以下:數據庫
由於每個鏈接都是惰性建立的,如何驗證sql.Open調用以後,sql.DB對象可用呢?一般使用db.Ping()方法初始化:安全
db, err := sql.Open("driverName", "dataSourceName") if err != nil{ log.Fatalln(err) } defer db.Close() err = db.Ping() if err != nil{ log.Fatalln(err) }
調用了Ping以後,鏈接池必定會初始化一個數據庫鏈接。固然,實際上對於失敗的處理,應該定義一個符合本身須要的方式,如今爲了演示,簡單的使用log.Fatalln(err)表示了。
鏈接失敗
關於鏈接池另一個知識點就是你沒必要檢查或者嘗試處理鏈接失敗的狀況。當你進行數據庫操做的時候,若是鏈接失敗了,database/sql會幫你處理。實際上,當從鏈接池取出的鏈接斷開的時候,database/sql會自動嘗試重連10次。仍然沒法重連的狀況下會自動從鏈接池再獲取一個或者新建另一個。比如去買雞蛋,售貨員會從箱子裏掏出雞蛋,若是發現是壞蛋則連續掏10次,仍然找不到合適的就會換一個箱子招,或者從別的庫房再拿一個給你。
鏈接池配置
不管哪個版本的go都不會提供不少控制鏈接池的接口。知道1.2版本之後纔有一些簡單的配置。但是1.2版本的鏈接池有一個bug,請升級更高的版本。
配置鏈接池有兩個的方法:併發
對於鏈接池的使用依賴於你是如何配置鏈接池,若是使用不當會致使下面問題:socket
大量的鏈接空閒,致使額外的工做和延遲。
鏈接數據庫的鏈接過多致使錯誤。
鏈接阻塞。
鏈接池有超過十個或者更多的死鏈接,限制就是10次重連。tcp
大多數時候,如何使用sql.DB對鏈接的影響大過鏈接池配置的影響。這些具體問題咱們會再使用sql.DB的時候逐一介紹。
掌握了database/sql關於數據庫鏈接池管理內容,下一步則是使用這些鏈接,進行數據的交互操做啦。