日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

oracle 在此 select 语句中缺少 into 子句,Go database/sql文档

發布時間:2024/3/13 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 oracle 在此 select 语句中缺少 into 子句,Go database/sql文档 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

No.1 文檔概要

在Golang中使用SQL或類似SQL的數據庫的慣用方法是通過 database/sql 包操作。它為面向行的數據庫提供了輕量級的接口。這篇文章是關于如何使用它,最常見的參考。

為什么需要這個?包文檔告訴你每件事情都做了什么,但它并沒有告訴你如何使用這個包。我們很多人都希望自己能快速參考和入門的方法,而不是講故事。歡迎捐款;請在這里發送請求。

在Golang中你用sql.DB訪問數據庫。你可以使用此類型創建語句和事務,執行查詢,并獲取結果。下面的代碼列出了sql.DB是一個結構體,點擊 database/sql/sql.go 查看官方源碼。

首先你應該知道一個sql.DB不是一個數據庫的連接。它也沒有映射到任何特點數據庫軟件的“數據庫”或“模式”的概念。它是數據庫的接口和數據庫的抽象,它可能與本地文件不同,可以通過網絡連接訪問,也可以在內存和進程中訪問。

sql.DB為你在幕后執行一些重要的任務:

? 通過驅動程序打開和關閉實際的底層數據庫的連接。

? 它根據需要管理一個連接池,這可能是如上所述的各種各樣的事情。

sql.DB抽象旨在讓你不必擔心如何管理對基礎數據存儲的并發訪問。一個連接在使用它執行任務時被標記為可用,然后當它不在使用時返回到可用的池中。這樣的后果之一是,如果你無法將連接釋放到池中,則可能導致db.SQL打開大量連接,可能會耗盡資源(連接太多,打開的文件句柄太多,缺少可用網絡端口等)。稍后我們將進一步討論這個問題。

在創建sql.DB之后,你可以用它來查詢它所代表的數據庫,以及創建語句和事務。

No.2 導入數據庫驅動

要使用 database/sql,你需要 database/sql 自身,以及需要使用的特定的數據庫驅動。

你通常不應該直接使用驅動包,盡管有些驅動鼓勵你這樣做。(在我們看來,這通常是個壞主意。) 相反的,如果可能,你的代碼應該僅引用 database/sql 中定義的類型。這有助于避免使你的代碼依賴于驅動,從而可以通過最少的代碼來更改底層驅動(因此訪問的數據庫)。它還強制你使用Golang習慣用法,而不是特定驅動作者可能提供的特定的習慣用法。

在本文檔中,我們將使用@julienschmidt 和 @arnehormann中優秀的MySql驅動。

將以下內容添加到Go源文件的頂部(也就是package name下面):

import (

"database/sql"

_ "github.com/go-sql-driver/mysql"

)

注意我們正在加載的驅動是匿名的,將其限定符別名為_,因此我們的代碼中沒有一個導出的名稱可見。在引擎下,驅動將自身注冊為可用于 database/sql 包,但一般來說沒有其他情況發生。

現在你已經準備好訪問數據庫了。

No.3 訪問數據庫

現在你已經加載了驅動包,就可以創建一個數據庫對象sql.DB。創建一個sql.DB你可以使用sql.Open()。Open返回一個*sql.DB。

func main() {

db, err := sql.Open("mysql",

"user:password@tcp(127.0.0.1:3306)/hello")

if err != nil {

log.Fatal(err)

}

defer db.Close()

}

在示例中,我們演示了幾件事:

sql.Open的第一個參數是驅動名稱。這是驅動用來注冊database/sql的字符串,并且通常與包名相同以避免混淆。例如,它是github.com/go-sql-driver/mysql的MySql驅動(作者:jmhodges)。某些驅動不遵守公約的名稱,例如github.com/mattn/go-sqlite3的sqlite3(作者:matte)和github.com/lib/pq的postgres(作者:mjibson)。

第二個參數是一個驅動特定的語法,它告訴驅動如何訪問底層數據存儲。在本例中,我們將連接本地的MySql服務器實例中的“hello”數據庫。

你應該(幾乎)總是檢查并處理從所有database/sql操作返回的錯誤。有一些特殊情況,我們稍后將討論這樣做事沒有意義的。

如果sql.DB不應該超出該函數的作用范圍,則延遲函數defer db.Close()是慣用的。

也許是反直覺的,sql.Open()不建立與數據庫的任何連接,也不會驗證驅動連接參數。相反,它只是準備數據庫抽象以供以后使用。首次真正的連接底層數據存儲區將在第一次需要時懶惰地建立。如果你想立即檢查數據庫是否可用(例如,檢查是否可以建立網絡連接并登陸),請使用db.Ping()來執行此操作,記得檢查錯誤:

err = db.Ping()

if err != nil {

// do something here

}

雖然在完成數據庫之后Close()數據庫是慣用的,但是sql.DB對象被設計為長連接。不要經常Open()和Close()數據庫。相反,為你需要訪問的每個不同的數據存儲創建一個sql.DB對象,并保留它,直到程序訪問數據存儲完畢。在需要時傳遞它,或在全局范圍內使其可用,但要保持開放。并且不要從短暫的函數中Open()和Close()。相反,通過sql.DB作為參數傳遞給該短暫的函數。

如果你不把sql.DB視為長期存在的對象,則可能會遇到諸如重復使用和連接共享不足,耗盡可用的網絡資源以及由于TIME_WAIT中剩余大量TCP連接而導致的零星故障的狀態。這些問題表明你沒有像設計的那樣使用database/sql的跡象。

現在是時候使用你的sql.DB對象了。

No.4 檢索結果集

有幾個慣用的操作來從數據存儲中檢索結果。

執行返回行的查詢。

準備重復使用的語句,多次執行并銷毀它。

以一次關閉的方式執行語句,不準備重復使用。

執行一個返回單行的查詢。這種特殊情況有一個捷徑。

Golang的database/sql函數名非常重要。如果一個函數名包含查詢Query(),它被設計為詢問數據庫的問題,并返回一組行,即使它是空的。不返回行的語句不應該使用Query()函數;他們應該使用Exec()。

從數據庫獲取數據

讓我們來看一下如何查詢數據庫,使用Query的例子。我們將向用戶表查詢id為1的用戶,并打印出用戶的id和name。我們將使用rows.Scan()將結果分配給變量,一次一行。

var (

id int

name string

)

rows, err := db.Query("select id, name from users where id = ?", 1)

if err != nil {

log.Fatal(err)

}

defer rows.Close()

for rows.Next() {

err := rows.Scan(&id, &name)

if err != nil {

log.Fatal(err)

}

log.Println(id, name)

}

err = rows.Err()

if err != nil {

log.Fatal(err)

}

下面是上面代碼中正在發生的事情:

我們使用db.Query()將查詢發送到數據庫。我們像往常一樣檢查錯誤。

我們用defer內置函數推遲了rows.Close()的執行。這個非常重要。

我們用rows.Next()遍歷了數據行。

我們用rows.Scan()讀取每行中的列變量。

我們完成遍歷行之后檢查錯誤。

這幾乎是Golang中唯一的辦法。例如,你不能將一行作為映射來獲取。這是因為所有東西都是強類型的。你需要創建正確類型的變量并將指針傳遞給它們,如圖所示。

其中的幾個部分很容易出錯,可能會產生不良后果。

? 你應該總是檢查rows.Next()循環結尾處的錯誤。如果循環中出現錯誤,則需要了解它。不要僅僅假設循環遍歷,直到你已經處理了所有的行。

? 第二,只要有一個打開的結果集(由行代表),底層連接就很忙,不能用于任何其他查詢。這意味著它在連接池中不可用。如果你使用rows.Next()遍歷所有行,最終將讀取最后一行,rows.Next()將遇到內部EOF錯誤,并為你調用rows.Close()。但是,如果由于某種原因退出該循環-提前返回,那么行不會關閉,并且連接保持打開狀態。(如果rows.Next()由于錯誤而返回false,則會自動關閉)。這是一種簡單耗盡資源的方法。

? rows.Close()是一種無害的操作,如果它已經關閉,所以你可以多次調用它。但是請注意,我們首先檢查錯誤,如果沒有錯誤,則調用rows.Close(),以避免運行時的panic。

? 你應該總是用延遲語句defer推遲rows.Close(),即使你也在循環結束時調用rows.Close(),這不是一個壞主意。

? 不要在循環中用defer推遲。延遲語句在函數退出之前不會執行,所以長時間運行的函數不應該使用它。如果你這樣做,你會慢慢積累記憶。如果你在循環中反復查詢和使用結果集,則在完成每個結果后應顯示的調用rows.Close(),而不用延遲語句defer。

Scan()如何工作

當你遍歷行并將其掃描到目標變量中時,Golang會在幕后為你執行數據類型轉換。它基于目標變量的類型。意識到這一點可以干凈你的代碼,并幫助避免重復工作。

例如,假設你從表中選擇了一些行,這是用字符串列定義的。如varchar(45)或類似的列。然而,你碰巧知道表格總是包含數字。如果傳遞指向字符串的指針,Golang會將字節復制到字符串中。現在可以使用strconv.ParseInt()或類似的方式將值轉換為數字。你必須檢查SQL操作中的錯誤以及解析整數的錯誤。這又亂又糟糕。

或者,你可以通過Scan()指向一個整數即可。Golang會檢測到并為你調用strconv.ParseInt()。如果有轉換錯誤,則調用Scan()將返回它。你的代碼現在更小更整潔。這是推薦使用database/sql的方法。

準備查詢

一般來說,你應該總是準備多次使用查詢。準備查詢的結果是一個準備語句,可以為執行語句時提供的參數,提供占位符(a.k.a bind值)。這比連接字符串更好,出于所有通常的理由(例如避免SQL注入攻擊)。

在MySql中,參數占位符為?,在PostgreSql中為$N,其中N為數字。SQLite接受這兩者之一。在Oracle中占位符以冒號開始,并命名為:param1。本文檔中我們使用?占位符,因為我們使用MySql作為示例。

stmt, err := db.Prepare("select id, name from users where id = ?")

if err != nil {

log.Fatal(err)

}

defer stmt.Close()

rows, err := stmt.Query(1)

if err != nil {

log.Fatal(err)

}

defer rows.Close()

for rows.Next() {

// ...

}

if err = rows.Err(); err != nil {

log.Fatal(err)

}

在引擎下,db.Query()實際上準備,執行和關閉一個準備好的語句。這是數據庫的三次往返。如果你不小心,可以使應用程序的數據庫交互數量增加三倍!有些驅動可以在特定情況下避免這種情況,但并非所有驅動都可以這樣做。點擊prepared statements查看更多聲明。

單行查詢

如果一個查詢返回最多一行,可以使用一些快速的樣板代碼:

var name string

err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)

if err != nil {

log.Fatal(err)

}

fmt.Println(name)

來自查詢的錯誤將被推遲到Scan(),然后返回。你也可以在準備的語句中調用QueryRow():

stmt, err := db.Prepare("select name from users where id = ?")

if err != nil {

log.Fatal(err)

}

var name string

err = stmt.QueryRow(1).Scan(&name)

if err != nil {

log.Fatal(err)

}

fmt.Println(name)

No.5 修改數據和使用事務

現在我們已經準備好了如何修改數據和處理事務。如果你習慣于使用“statement”對象來獲取行并更新數據,那么這種區別可能視乎是認為的,但是在Golang中有一個重要的原因。

修改數據的statements

使用Exec(),最好用一個準備好的statement來完成INSERT,UPDATE,DELETE或者其他不返回行的語句。下面的示例演示如何插入行并檢查有關操作的元數據:

stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)")

if err != nil {

log.Fatal(err)

}

res, err := stmt.Exec("Dolly")

if err != nil {

log.Fatal(err)

}

lastId, err := res.LastInsertId()

if err != nil {

log.Fatal(err)

}

rowCnt, err := res.RowsAffected()

if err != nil {

log.Fatal(err)

}

log.Printf("ID = %d, affected = %d\n", lastId, rowCnt)

執行該語句將生成一個sql.Result,該語句提供對statement元數據的訪問:最后插入的ID和行數受到影響。

如果你不在乎結果怎么辦?如果你只想執行一個語句并檢查是否有錯誤,但忽略結果該怎么辦?下面兩個語句不會做同樣的事情嗎?

_, err := db.Exec("DELETE FROM users") // OK

_, err := db.Query("DELETE FROM users") // BAD

答案是否定的。他們不做同樣的事情,你不應該使用Query()。Query()將返回一個sql.Rows,它保留數據庫連接,直到sql.Rows關閉。由于可能有未讀數據(例如更多的數據行),所以不能使用連接。在上面的示例中,連接將永遠不會被釋放。垃圾回收器最終會關閉底層的net.Conn,但這可能需要很長時間。此外,database/sql包將繼續跟蹤池中的連接,希望在某個時候釋放它,以便可以再次使用連接。因此,這種反模式是耗盡資源的好方法(例如連接數太多)。

事務處理

在Golang中,事務本質上是保留與數據存儲的連接的對象。它允許你執行我們迄今為止所看到的所有操作,但保證它們將在同一連接上執行。

你可以通過調用db.Begin()開始一個事務,并在結果Tx變量上用Commit()或Rollback()方法關閉它。在封面下,Tx從池中獲取連接,并保留它僅用于該事務。Tx上的方法一對一到可以調用數據本本身的方法,例如Query()等等。

在事務中創建的Prepare語句僅限于該事務。點擊prepared statements查看更多準備的聲明。

你不應該在SQL代碼中混合BEGIN和COMMIT相關的函數(如Begin()和Commit()的SQL語句),可能會導致悲劇:

? Tx對象可以保持打開狀態,從池中保留連接而不返回。

? 數據庫的狀態可能與代表它的Golang變量的狀態不同步。

? 你可能會認為你是在事務內部的單個連接上執行查詢,實際上Golang已經為你創建了幾個連接,而且一些語句不是事務的一部分。

當你在事務中工作時,你應該注意不要對Db變量進行調用。應當使用db.Begin()創建的Tx變量進行所有調用。Db不在一個事務中,只有Tx是。如果你進一步調用db.Exec()或類似的函數,那么這些調用將發生在事務范圍之外,是在其他的連接上。

如果你需要處理修改連接狀態的多個語句,即使你不希望事務本身,也需要一個Tx。例如:

? 創建僅在一個連接中可見的臨時表。

? 設置變量,如MySql's SET @var := somevalue語法。

? 更改連接選項,如字符集或超時。

如果你需要執行任何這些操作,則需要把你的作業(也可以說Tx操作語句)綁定到單個連接,而在Golang中執行此操作的唯一方法是使用Tx。

No.6 使用預處理語句

準備語句(db.Prepare()或者tx.Prepare())在Golang中具有所有常見的優點:安全性,效率,方便性。但是他們的實現方式與你習慣的方式可能有所不同,特別是關于它們如何與database/sql的一些內部組件進行交互的方式。

準備語句和連接

在數據庫級別,將準備好的語句綁定到單個數據庫連接。典型的流程是:客戶端向服務器發送帶有占位符的SQL語句以進行準備,服務器使用語句ID進行響應,然后客戶端通過發送其ID和參數來執行該語句。

然而在Golang中,連接不會直接暴露給database/sql包的用戶。你不準備連接上語句。你準備好在一個db或tx。并且database/sql具有一些便捷的行為,如自動重試。由于這些原因,準備好的語句和連接(存在于驅動級別)之間的潛在關聯被隱藏在代碼中。

下面是它的工作原理:

準備一個語句時,它會在池中的連接上準備好。

Stmt對象記住使用哪個連接。

當你執行Stmt時,它試圖使用Stmt對象記住的那個連接(后面我們將這里的連接稱為原始連接)。如果它不可用,因為它關閉或忙于做其他事情,它從池中獲取另一個連接,并在另一個連接上重新準備與數據庫的語句。

因為在原始連接繁忙時,會根據需要重新準備語句,因此數據庫的高并發使用可能會導致大量連接繁忙,從而創建大量的準備語句。這會導致語句的明顯泄露,正在準備和重新準備的語句比你想象的更多,甚至會影響到服務器端對語句數量的限制。

避免準備好的語句

Golang將為你在封面下創建準備好的聲明。例如,一個簡單的db.Query(sql,param1,param2)通過準備sql,然后使用參數執行它,最后關閉語句。

有時,準備好的語句并不是你想要的。這可能有幾個原因。

數據庫不支持準備好的語句。例如,當使用MySql驅動時,你可以連接到MemSql和Sphinx,因為它們支持MySql線路協議。但是它們不支持包含準備語句的“二進制”協議,因此它們會以混亂的方式失敗。

這些語句沒有重用到足以使它們變得有價值,而安全問題則以其他方式處理,因此性能開銷是不需要的。這方面點擊VividCortex博客可以看到一個例子。

如果不想使用預處理語句,則需要使用fmt.Sprint()或類似的方法來組合SQL,并將其作為db.Query()或db.QueryRow()的唯一參數傳遞。你的驅動需要支持明文查詢執行,這是通過執行器(Execer是一個結構體)和查詢器(Queryer是一個結構體)接口在Golang 1.1中添加的,在此記錄。

事務中的準備語句

在Tx中創建的準備語句僅限于它,因此早期關于重新準備的注意事項不適用。當你對Tx對象進行操作時,你的操作直接映射到它下面唯一的一個連接上。

這也意味著在Tx內創建的準備語句不能與之分開使用。同樣,在DB中創建的準備語句不能再事務中使用,因為它們將被綁定到不同的連接。

要在Tx中使用事務外的預處理語句,可以使用Tx.Stmt(),它將從事務外部準備一個新的特定于事務的語句。它通過采用現有的預處理語句,設置與事務的連接,并在執行時重新準備所有語句。這個行為及其實現是不可取的,甚至在databse/sql源代碼中有一個TODO來改進它;我們建議不要使用這個。

在處理事務中的預處理語句時,必須小心謹慎。請考慮下面的示例:

tx, err := db.Begin()

if err != nil {

log.Fatal(err)

}

defer tx.Rollback()

stmt, err := tx.Prepare("INSERT INTO foo VALUES (?)")

if err != nil {

log.Fatal(err)

}

defer stmt.Close() // danger!

for i := 0; i < 10; i++ {

_, err = stmt.Exec(i)

if err != nil {

log.Fatal(err)

}

}

err = tx.Commit()

if err != nil {

log.Fatal(err)

}

// stmt.Close() runs here!

之前Golang1.4關閉*sql.Tx將與之關聯的連接返還到池中,但是,在預處理語句結束后,延遲調用時在那之后發生的,這可能導致并發訪問底層的連接,使連接狀態不一致。如果使用Golang1.4或更高的版本,則應確保在提交事務或回滾之前聲明始終關閉。點擊查看這個issues。

參數占位符語法

預處理語句中的占位符參數的語法是特定于數據庫的。例如,比較MySql,PostgreSQL,Oracle:

MySQL PostgreSQL Oracle

===== ========== ======

WHERE col = ? WHERE col = $1 WHERE col = :col

VALUES(?, ?, ?) VALUES($1, $2, $3) VALUES(:val1, :val2, :val3)

No.7 錯誤處理

幾乎所有使用database/sql類型的操作都會返回一個錯誤作為最后一個值。你應該總是檢查這些錯誤,千萬不要忽視它們。有幾個地方錯誤行為是特殊情況,還有一些額外的東西可能需要知道。

遍歷結果集的錯誤

請思考下面的代碼:

for rows.Next() {

// ...

}

if err = rows.Err(); err != nil {

// handle the error here

}

來自rows.Err()的錯誤可能是rows.Next()循環中各種錯誤的結果。除了正常完成循環之外,循環可能會退出,因此你總是需要檢查循環是否正常終止。異常終止自動調用rows.Close(),盡管多次調用它是無害的。

關閉結果集的錯誤

如上所述,如果你過早的退出循環,則應該總是顯式的關閉sql.Rows。如果循環正常退出或通過錯誤,它會自動關閉,但你可能會錯誤的執行此操作:

for rows.Next() {

// ...

break; // whoops, rows is not closed! memory leak...

}

// do the usual "if err = rows.Err()" [omitted here]...

// it's always safe to [re?]close here:

if err = rows.Close(); err != nil {

// but what should we do if there's an error?

log.Println(err)

}

rows.Close()返回的錯誤是一般規則的唯一例外,最好是捕獲并檢查所有數據庫操作中的錯誤。如果rows.Close()返回錯誤,那么你應該怎么做。記錄錯誤信息或panic可能是唯一明智的事情,如果這不明智,那么也許你應該忽略錯誤。

QueryRow()的錯誤

思考下面的代碼來獲取一行數據:

var name string

err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)

if err != nil {

log.Fatal(err)

}

fmt.Println(name)

如果沒有id = 1的用戶怎么辦?那么結果中不會有行,而.Scan()不會將值掃描到name中。那會怎么樣?

Golang定義了一個特殊的錯誤常量,稱為sql.ErrNoRows,當結果為空時,它將從QueryRow()返回。這在大多數情況下需要作為特殊情況來處理。空的結果通常不被應用程序代碼認為是錯誤的,如果不檢查錯誤是不是這個特殊常量,那么會導致你意想不到的應用程序代碼錯誤。

來自查詢的錯誤被推遲到調用Scan(),然后從中返回。上面的代碼可以更好地寫成這樣:

var name string

err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)

if err != nil {

if err == sql.ErrNoRows {

// there were no rows, but otherwise no error occurred

} else {

log.Fatal(err)

}

}

fmt.Println(name)

有人可能會問為什么一個空的結果集被認為是一個錯誤。空集沒有什么錯誤。原因是QueryRow()方法需要使用這種特殊情況才能讓調用者區分是否QueryRow()實際上找到一行;沒有它,Scan(0不會做任何事情,你可能不會意識到你的變量畢竟沒有從數據庫中獲取任何值。

當你使用QueryRow()時,你應該只會遇到此錯誤。如果你在別處遇到這個錯誤,你就做錯了什么。

識別特定的數據庫錯誤

像下面這樣編寫代碼是很有誘惑力的:

rows, err := db.Query("SELECT someval FROM sometable")

// err contains:

// ERROR 1045 (28000): Access denied for user 'foo'@'::1' (using password: NO)

if strings.Contains(err.Error(), "Access denied") {

// Handle the permission-denied error

}

這不是最好的方法。例如,字符串值可能會取決于服務器使用什么語言發送錯誤消息。比較錯誤編號以確定具體錯誤是啥要好得多。

但是,驅動的機制不同,因為這不是database/sql本身的一部分。在本教程重點介紹的MySql驅動中,你可以編寫以下代碼:

if driverErr, ok := err.(*mysql.MySQLError); ok { // Now the error number is accessible directly

if driverErr.Number == 1045 {

// Handle the permission-denied error

}

}

再次,這里的MySQLError類型由此特定驅動程序提供,并且驅動程序之間的.Number字段可能不同。然而,該值是從MySql的錯誤消息中提取的,因此是特定于數據庫的,而不是特定于驅動的。

這段代碼還是很丑相對于1045,一個魔術數字是一種代碼氣味。一些驅動(雖然不是MySql的驅動程序,因為這里的主題的原因)提供錯誤標識符的列表。例如Postgres pg驅動程序在error.go中。還有一個由VividCortex維護的MySql錯誤號的外部包。使用這樣的列表,上面的代碼寫的更漂亮:

if driverErr, ok := err.(*mysql.MySQLError); ok {

if driverErr.Number == mysqlerr.ER_ACCESS_DENIED_ERROR {

// Handle the permission-denied error

}

}

處理連接錯誤

如果與數據庫的連接被丟棄,殺死或發生錯誤該怎么辦?

當發生這種情況時,你不需要實現任何邏輯來重試失敗的語句。作為database/sql連接池的一部分,處理失敗的連接是內置的。如果你執行查詢或其他語句,底層連接失敗,則Golang將重新打開一個新的連接(或從連接池中獲取另一個連接),并重試10次。

然而,可能會產生一些意想不到的后果。當某些類型錯誤可能會發生其他錯誤條件。這也可能是驅動程序特定的。MySql驅動程序發生的一個例子是使用KILL取消不需要的語句(例如長時間運行的查詢)會導致語句被重試10次。

No.8 使用空值

可以為空的字段是令人煩惱的,并導致很多丑陋的代碼。如果可以,避開它們。如果沒有,那么你需要使用database/sql包中的特殊類型來處理它們,或者定義你自己的類型。

有可以空的布爾值,字符串,整數和浮點數的類型。下面是你使用它們的方法:

for rows.Next() {

var s sql.NullString

err := rows.Scan(&s)

// check err

if s.Valid {

// use s.String

} else {

// NULL value

}

}

可以空的類型的限制和避免的理由的情況下你需要更有說服力的可以為空的列:

沒有sql.NullUint64或sql.NullYourFavoriteType。你需要為這個定義你自己的。

可空性可能會非常棘手,并不是未來的證明。如果你認為某些內容不會為空,但是你錯了,你的程序將會崩潰,也許很少會發生錯誤。

Golang的好處之一是為每個變量設置一個有用的默認零值。這不是空的工作方式。

如果你需要定義自己的類型來處理NULLS,則可以復制sql.NullString的設計來實現。

如果你不能避免在你的數據庫中具有空值,周圍有多數數據庫系統支持的另一項工作是COALESCE()。像下面這樣的東西可能是可以使用的,而不需要引入大量的sql.Null*類型

rows, err := db.Query(`

SELECT

name,

COALESCE(other_field, '') as other_field

WHERE id = ?

`, 42)

for rows.Next() {

err := rows.Scan(&name, &otherField)

// ..

// If `other_field` was NULL, `otherField` is now an empty string. This works with other data types as well.

}

No.9 使用未知列

Scan()函數要求你準確傳遞正確數目的目標變量。如果你不知道查詢將返回什么呢?

如果你不知道查詢將返回多少列,則可以使用Columns()來查詢列名稱列表。你可以檢查此列表的長度以查看有多少列,并且可以將切片傳遞給具有正確數值的Scan()。列如,MySql的某些fork為SHOW PROCESSLIST命令返回不同的列,因此你必須為此準備好,否則將導致錯誤,這是一種方法;還有其他的方法:

cols, err := rows.Columns()

if err != nil {

// handle the error

} else {

dest := []interface{}{ // Standard MySQL columns

new(uint64), // id

new(string), // host

new(string), // user

new(string), // db

new(string), // command

new(uint32), // time

new(string), // state

new(string), // info

}

if len(cols) == 11 {

// Percona Server

} else if len(cols) > 8 {

// Handle this case

}

err = rows.Scan(dest...)

// Work with the values in dest

}

如果你不知道這些列或者它們的類型,你應該使用sql.RawBytes。

cols, err := rows.Columns() // Remember to check err afterwards

vals := make([]interface{}, len(cols))

for i, _ := range cols {

vals[i] = new(sql.RawBytes)

}

for rows.Next() {

err = rows.Scan(vals...)

// Now you can check each element of vals for nil-ness,

// and you can use type introspection and type assertions

// to fetch the column into a typed variable.

}

No.10 連接池

database/sql包中有一個基本的連接池。沒有很多的控制或檢查能力,但這里有一些你可能會發現有用的知識:

? 連接池意味著在單個數據庫上執行兩個連續的語句可能會打開兩個鏈接并單獨執行它們。對于程序員來說,為什么它們的代碼行為不當,這是相當普遍的。例如,后面跟著INSERT的LOCK TABLES可能會被阻塞,因為INSERT位于不具有表鎖定的連接上。

? 連接是在需要時創建的,池中沒有空閑連接。

? 默認情況下,連接數量沒有限制。如果你嘗試同時執行很多操作,可以創建任意數量的連接。這可能導致數據庫返回錯誤,例如“連接太多”。

? 在Golang1.1或更新版本中,你可以使用db.SetMaxIdleConns(N)來限制池中的空閑連接數。這并不限制池的大小。

? 在Golang1.2.1或更新版本中,可以使用db.SetMaxOpenConns(N)來限制于數據庫的總打開連接數。不幸的是,一個死鎖bug(修復)阻止db.SetMaxOpenConns(N)在1.2中安全使用。

? 連接回收相當快。使用db.SetMaxIdleConns(N)設置大量空閑連接可以減少此流失,并有助于保持連接以重新使用。

? 長期保持連接空閑可能會導致問題(例如在微軟azure上的這個問題)。嘗試db.SetMaxIdleConns(0)如果你連接超時,因為連接空閑時間太長。

No.11 驚喜,反模式和限制

雖然database/sql很簡單,但一旦你習慣了它,你可能會對它支持的用例的微妙之處感到驚訝。這是Golang的核心庫通用的。

資源枯竭

如本網站所述,如果你不按預期使用database/sql,你一定會為自己造成麻煩,通常是通過消耗一些資源或阻止它們被有效的重用:

? 打開和關閉數據庫可能會導致資源耗盡。

? 沒有讀取所有行或使用rows.Close()保留來自池的連接。

? 對于不返回行的語句,使用Query()將從池中預留一個連接。

? 沒有意識到預處理語句如何工作會導致大量額外的數據庫活動。

巨大的uint64值

這里有一個令人吃驚的錯誤。如果設置了高位,就不能將大的無符號整數作為參數傳遞給語句:

_, err := db.Exec("INSERT INTO users(id) VALUES", math.MaxUint64) // Error

這將拋出一個錯誤。如果你使用uint64值要小心,因為它們可能開始小而且無錯誤的工作,但會隨著時間的推移而增加,并開始拋出錯誤。

連接狀態不匹配

有些事情可以改變連接狀態,這可能導致的問題有兩個原因:

某些連接狀態,比如你是否處于事務中,應該通過Golang類型來處理。

你可能假設你的查詢在單個連接上運行。

例如,使用USE語句設置當前數據庫對于很多人來說是一個典型的事情。但是在Golang中,它只會影響你運行的連接。除非你處于事務中,否則你認為在該連接上執行的其他語句實際上可能在從池中獲取的不同的連接上運行,因此它們不會看到這些更改的影響。

此外,在更改連接后,它將返回到池,并可能會污染其他代碼的狀態。這就是為什么你不應該直接將BEGIN或COMMIT語句作為SQL命令發出的原因之一。

數據庫特定的語法

database/sql API提供了面向行的數據庫抽象,但是具體的數據庫和驅動程序可能會在行為或語法上有差異,例如預處理語句占位符。

多個結果集

Golang驅動程序不以任何方式支持單個查詢中的多個結果集,盡管有一個支持大容量操作(如批量復制)的功能請求似乎沒有任何計劃。

這意味著,除了別的以外,返回多個結果集的存儲過程將無法正常工作。

調用存儲過程

調用存儲過程是特定于驅動程序的,但在MySql驅動程序中,目前無法完成。看來你可以調用一個簡單的過程來返回一個單一的結果集,通過執行如下的操作:

err := db.QueryRow("CALL mydb.myprocedure").Scan(&result) // Error

事實上這行不通。你將收到以下錯誤1312:PROCEDURE mydb.myprocedure無法返回給定上下文中的結果集。這是因為MySql希望將連接設置為多語句模式,即使單個結果,并且驅動程序當前沒有執行此操作(盡管看到這個問題)。

多個聲明支持

database/sql沒有顯式的擁有多個語句支持,這意味著這個行為是后端依賴的:

_, err := db.Exec("DELETE FROM tbl1; DELETE FROM tbl2") // Error/unpredictable result

服務器可以解釋它想要的,它可以包括返回的錯誤,只執行第一個語句,或執行兩者。

同樣,在事務中沒有辦法批處理語句。事務中的每個語句必須連續執行,并且結果中的資源(如行或行)必須被掃描或關閉,以便底層連接可供下一個語句使用。這與通常不在事務中工作時的行為不同。在這種情況下,完全可以執行查詢,循環遍歷行,并在循環中對數據庫進行查詢(這將發生在一個新的連接上):

rows, err := db.Query("select * from tbl1") // Uses connection 1

for rows.Next() {

err = rows.Scan(&myvariable)

// The following line will NOT use connection 1, which is already in-use

db.Query("select * from tbl2 where id = ?", myvariable)

}

但是事務只綁定到一個連接,所以事務不可能做到這一點:

tx, err := db.Begin()

rows, err := tx.Query("select * from tbl1") // Uses tx's connection

for rows.Next() {

err = rows.Scan(&myvariable)

// ERROR! tx's connection is already busy!

tx.Query("select * from tbl2 where id = ?", myvariable)

}

不過,Golang不會阻止你去嘗試。因此,如果你試圖在第一個釋放資源并自行清理之前嘗試執行另一個語句,可能會導致一個損壞的連接。這也意味著事務中的每個語句都會產生一組單獨的網絡往返數據庫。

No.12 相關資料

以下是我們發現有幫助的一些外部信息來源。

我們希望本教程是有幫助的。如果你有任何改進意見,請在https://github.com/VividCortex/go-database-sql-tutorial.database-sql-tutorial)的ISSUE中提出。

總結

以上是生活随笔為你收集整理的oracle 在此 select 语句中缺少 into 子句,Go database/sql文档的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。