在事務操做中,要求事務的各個階段都使用一個Conn
鏈接。在鏈接被關閉以前,還須要執行rollback
操做。sql
文章翻譯了Go
源碼下database.sql.driver
的接口規範,具體實現能夠查看源碼。數據庫
// 包driver定義了數據驅動要實現的接口,具體的實現會在包sql中用到。 // // 更多仍是使用包sql中的代碼 package driver import ( "context" "errors" "reflect" ) // Value必須是一個驅動能夠處理的值、NamedValueChecker接口可以處理的類型 // 或者下面這些類型的實例 // // int64 // float64 // bool // []byte // string // time.Time // // 若是驅動支持遊標,返回值可能也實現Rows接口。舉例,當用戶 // 執行"select cursor(select * from my_table) from dual"。 // 若是返回的Rows被Close掉了,遊標指向的數據也會被Close掉。 type Value interface{} // NameValue 同時包括name和value type NamedValue struct { // 若是Name不爲空,它應該被用於參數標識符,而非序號位置。 // // Name 沒有符號前綴 Name string // 參數從1開始的序號位置,而且老是被設置 Ordinal int // Value是參數值 Value Value } // Driver是一個必須被各個數據庫driver實現的接口 // // 數據庫驅動能夠實現DriverContext來訪問上下文,而且只解析一次鏈接池的名稱, // 而非每一個鏈接都解析一次。 // type Driver interface { // Open返回數據庫的一個新鏈接,參數name是驅動特定格式的字符串 // // Open也能夠返回一個緩存的鏈接(以前被close掉的),但這樣 // 作其實不必。sql包爲了鏈接重複使用維護了一個空閒鏈接池 // // 返回的鏈接一次只被一個goroutinue中使用 Open(name string) (Conn, error) } // 若是Driver實現了DriverContext接口,那麼sql.DB就會調用OpenConnector // 來獲取一個Connector,調用Connector的Conn方法來獲取每一個須要的鏈接, // 以此代替調用Drive的Open方法。這樣容許drivers僅解析一次name,同時提供 // 對每一個鏈接上下文的訪問 type DriverContext interface { // OpenConnector解析name的方式必須跟Driver.Open的方式保持一致 OpenConnector(name string) (Connector, error) } // 一個Connector表示一個固定配置的driver,可以建立任意數量的等效Conn, // 供多個goroutinue使用 // // 一個Connector能被傳遞給sql.OpenDB方法,去容許驅動實現本身的sql.DB。 // 或者經過調用DriverContext的OpenConnector方法,來返回一個Connector, // 這樣容許驅動訪問鏈接的上下文,避免頻繁的解析驅動配置。 type Connector interface { // Connect返回一個數據庫的鏈接 // Connect可能返回一個以前緩存的鏈接(以前被close掉了),但這樣去 // 作實際上是不必的。sql包維護了一個高效重複使用的空閒鏈接池。 // // // 被提供的context.Context參數僅僅被用於建立鏈接的目的 //(看net.DialContext),不該該被存儲或用於其餘別的目的。 // // 返回的鏈接一次只能被一個goroutine使用 Connect(context.Context) (Conn, error) // Driver返回Connector的底層驅動,在sql.DB中,主要用於維護 // 驅動的擴展性 Driver() Driver } // ErrSkip 可能被一些可選接口的方法返回,用於在運行時標識該路徑無效。 // 包sql應該繼續去執行,就當類型沒有實現這個接口同樣。 // ErrSkip 只有在被明確說明後纔會被支持 // var ErrSkip = errors.New("driver: skip fast-path; continue as if unimplemented") // 當驅動給sql包標識一個driver.Conn處於壞的狀態時,ErrBadConn應該被返回( // 好比服務端已經關閉了這個鏈接),sql包已經使用一個新的鏈接進行重試 // // 爲了不重複操做,若是服務端可能已經完成操做的話,ErrBadConn不該該被返回。 // 即便服務端返回了一個錯誤,你也不該該返回ErrBadConn var ErrBadConn = errors.New("driver: bad connection") // Pinger是一個可選的接口,它可能會被Conn實現 // // 若是Conn沒有實現Pinger接口,那麼sql包的DB.Ping和DB.PingContext // 將會執行檢查,是否至少存在一個可用鏈接 // // 若是Conn.Ping返回了ErrBadConn,DB.Ping 和 DB.PingContext將會從鏈接池中 // 將Conn溢出 type Pinger interface { Ping(ctx context.Context) error } // Execer 是一個能夠被Conn實現的,可選的接口 // // 若是Conn實現了ExecerContext或Excer, // 包sql下的DB.Exec將首先prepare查詢語句,執行而後關閉。 // // Exec可能返回ErrSkip錯誤 // // 棄用:Drivers應該實現ExecerContext接口來替代Execer type Execer interface { Exec(query string, args []Value) (Result, error) } // ExecerContext是能夠被Conn實現的、可選的接口 // // 若是Conn並無實現ExecerContext接口,那sql包的DB.Exec將會向後調用Excer // 若是Conn也沒有實現Execer接口, // DB.Exec將首先prepare查詢,執行語句、而後關閉語句 // // ExecerContext 可能返回 ErrSkip錯誤. // // ExecerContext必須認真對待context的超時,當context被取消時,須要返回。 // type ExecerContext interface { ExecContext(ctx context.Context, query string, args []NamedValue) (Result, error) } // Queryer 是一個可選的接口,Conn可能會實現它。 // // 若是Conn既沒有實現QueryerContext,也沒有實現Queryer // 那麼sql包的DB.Query首先會prepare一個查詢語句,而後執行語句,關閉語句 // // Query可能會返回 ErrSkip錯誤. // // 棄用:Drivers應該實現QueryerContext接口來替代Queryer type Queryer interface { Query(query string, args []Value) (Rows, error) } // QueryerContext 是一個可選的接口,Conn可能會實現它 // // 若是Conn沒有實現QueryerContext,那麼sql包在執行DB.Query會降級調用Queryer; // 若是Conn也沒有實現Queryer,DB.Query 首先會prepare一個查詢語句,而後執行這個語句 // 而後再關閉它 // // QueryerContext可能會返回 ErrSkip. // // QueryerContext必須認真對待context的超時,當context被cancel掉時,須要返回 type QueryerContext interface { QueryContext(ctx context.Context, query string, args []NamedValue) (Rows, error) } // Conn是一條數據庫的鏈接,它不能在多個goroutine中同時使用。 // // Conn被假定爲是有狀態的 type Conn interface { // Prepare返回一個準備好的語句,綁定到這個鏈接上 Prepare(query string) (Stmt, error) // Close會使當前準備好的語句和事物失效,並可能中止它們執行, // 將這個鏈接標記爲再也不使用 // // 由於sql包維護了一個空閒鏈接池,僅當前有多餘的空閒鏈接時, // 纔會調用Close。對於驅動來講,實現本身的鏈接緩存是沒必要要的 Close() error // Begin 啓動並返回一個新的事務 // // 棄用:驅動應該經過實現ConnBeginTx來替換Begin Begin() (Tx, error) } // ConnPrepareContext經過使用context,增強了Conn接口 type ConnPrepareContext interface { // context被用來對語句作預處理 // 在語句自己中是不能夠存儲context的 PrepareContext(ctx context.Context, query string) (Stmt, error) } // IsolationLevel在TxOptions類型中記錄事物的隔離級別 // // 這個類型應該認被爲跟sql.IsolationLevel是一致的,以及定義這個類型的其餘值。 // type IsolationLevel int // TxOptions 設置事物的選項 // // 這個類型應該被認爲跟sql.TxOptions是一致的 type TxOptions struct { Isolation IsolationLevel ReadOnly bool } // ConnBeginTx經過context和TxOptions提升了Conn接口 type ConnBeginTx interface { // BeginTx啓動並返回一個新的事務 // 若是context被用戶取消了,sql包會在丟棄和關閉這個鏈接以前 // 執行Tx.Rollback // // 函數必須檢查opts.Isolation,肯定是否存在設置的額隔離級別。 // 若是驅動不支持一個非默認的隔離級別和被設置的級別,或者 // 存在一個非默認的隔離級別是不支持的,必須返回一個錯誤 // // 函數也必須檢查opts.ReadOnly,若是ReadOnly值爲真, // 若是支持設置的話,則設置只讀事務屬性。若是不支持的話,返回error // BeginTx(ctx context.Context, opts TxOptions) (Tx, error) } // Conn可能會實現SessionResetter接口,用於重置當前鏈接上的會話狀態 // 並將當前鏈接標識爲壞鏈接 type SessionResetter interface { // 當鏈接在鏈接池中時,調用ResetSession方法。該鏈接不會再承載任何查詢操做 // 直接方法返回 // // 若是鏈接是壞的,方法應該返回driver.ErrBadConn錯誤,來阻止鏈接被放回到 // 鏈接池。其餘別的錯誤將會被丟棄 ResetSession(ctx context.Context) error } // Result是查詢執行的結果 type Result interface { // LastInsertId返回數據庫自動生成的ID,好比,用主鍵插入表的操做 LastInsertId() (int64, error) // RowsAffected返回查詢影響的行數 RowsAffected() (int64, error) } // Stmt是一個準備好的語句,它被綁定到一個Conn,且不能被多個goroutine併發 // 使用 type Stmt interface { // Close關閉這個語句 // // 截止到Go 1.1,若是Stmt在被一些查詢使用,Stmt將不會被關閉 Close() error // NumInput返回佔位符的個數 // // 若是NumInput返回值大於等於0,sql包會明智的檢查調用者的參數個數, // 在Exec或Query被調用以前,返回錯誤給調用者。 // // 若是驅動不知道佔位符的個數,NumInput可能會返回-1。在這種狀況下, // sql包將不會檢查Exec和Query的參數個數 NumInput() int // Exec執行一個不返回數據行的查詢,好比INSERT或UPDATE // // 棄用:驅動應實現StmtExecContext來替代 Exec(args []Value) (Result, error) // Query執行一個返回數據行的查詢,好比SELECT // // 棄用:驅動應實現StmtQueryContext來替代 Query(args []Value) (Rows, error) } // StmtExecContext升級了Stmt接口,它提供了一個有context的Exec, type StmtExecContext interface { // ExecContext執行一個不返回數據行的查詢,好比INSERT或UPDATE // // ExecContext必須遵照context超時,當context被取消時,函數須要返回 ExecContext(ctx context.Context, args []NamedValue) (Result, error) } // StmtQueryContext升級了Stmt接口,它提供了一個有context的Query type StmtQueryContext interface { // QueryContext執行一個返回數據行的查詢,好比SELECT // // ExecContext必須遵照context超時,當context被取消時,函數須要返回 QueryContext(ctx context.Context, args []NamedValue) (Rows, error) } // ErrRemoveArgument可能被NamedValueChecker返回,用來指示sql包不要 // 給驅動的query接口傳遞這個參數。 // 當接收到不是查詢參數的特定屬性或結構時,返回該錯誤 var ErrRemoveArgument = errors.New("driver: remove argument from query") // Conn或Stmt可選擇是否要實現NamedValueChecker接口。接口提供給驅動 // 更多的控制,去處理超出Go和數據庫容許的默認值類型。 // // 對於值的檢查對象,sql包按以下順序進行檢查。當第一次發現匹配時中止: // Stmt.NamedValueChecker, Conn.NamedValueChecker, Stmt.ColumnConverter, // DefaultParameterConverter. // // 若是CheckNamedValue返回ErrRemoveArgument錯誤,那麼這個NamedValue將不會 // 被包含在最終的查詢參數中。這可能會被用於給查詢傳遞特殊的選項。 // // 若是ErrSkip錯誤被返回,則使用列轉換器錯誤檢查路徑做爲參數。 // 驅動可能但願在耗盡本身特殊case後返回ErrSkip type NamedValueChecker interface { // 在傳遞參數給驅動以前,CheckNamedValue會被調用。 // 在任何ColumnConverter的地方也會調用CheckNamedValue。 // CheckNamedValue必須根據驅動的須要作類型校驗和轉換 CheckNamedValue(*NamedValue) error } // 若是語句知道本身列的類型,而且可以從任何類型轉換爲驅動的Value, // 那麼Stmt 能夠選擇性的實現ColumnConverter // // 棄用:驅動應實現NamedValueChecker type ColumnConverter interface { // 根據提供的列序號,ColumnConverter返回一個 ValueConverter。 // 若是該列是未知的或者不須要被特殊處理,方法返回DefaultValueConverter ColumnConverter(idx int) ValueConverter } // Rows是一個查詢結果的迭代器 type Rows interface { // Columns返回列的名字集,它的個數是從slice的長度中推斷出來的。 // 若是不知道特定的列名,應該爲該條目返回一個空的字符串 Columns() []string // 關閉行迭代器 Close() error // Next用於把數據集中的下一行填入提供的slice中。該slice的 // 長度應該跟Columns()的長度一致 // // 當沒有數據行時,Next應該返回io.EOF // // dest不該被聲明在Next以外(應該做爲Next的一個成員變量) // 關閉Rows時應特別注意,不要修改dest中緩衝區的值 Next(dest []Value) error } // RowsNextResultSet擴展了Rows接口,它提供了一個方式,讓驅動向前移動 // 到下一個結果集 type RowsNextResultSet interface { Rows // 在當前結果集的末尾調用HasNextResultSet,報告當前結果集以後是否還存在 // 別的結果集 HasNextResultSet() bool // NextResultSet向前移動驅動到下一個結果集,即便當前結果集 // 仍然存在剩餘的數據行 // // 當再也不有數據集時,NextResultSet應該返回io.EOF NextResultSet() error } // RowsColumnTypeScanType能夠經過Rows實現,它應該返回可用於掃描的數據類型。 // 好比,數據庫的列類型`bigint`應該返回"reflect.TypeOf(int64(0))". type RowsColumnTypeScanType interface { Rows ColumnTypeScanType(index int) reflect.Type } // RowsColumnTypeDatabaseTypeName能夠經過Rows實現。它應該返回不包括字段長度的數據庫類型, // 類型名應該全大寫。諸如:"VARCHAR", "NVARCHAR", "VARCHAR2", "CHAR", "TEXT", // "DECIMAL", "SMALLINT", "INT", "BIGINT", "BOOL", "[]BIGINT", "JSONB", "XML", // "TIMESTAMP". type RowsColumnTypeDatabaseTypeName interface { Rows ColumnTypeDatabaseTypeName(index int) string } // RowsColumnTypeLength能夠經過Rows實現,若是列是可變長度的類型,它應該返回 // 類型的長度。若是列是不可變長度的類型,ok返回false。 // 若是類型長度只受系統限制,則應該返回math.MaxInt64 // 下面是變量類型的返回值示例 // TEXT (math.MaxInt64, true) // varchar(10) (10, true) // nvarchar(10) (10, true) // decimal (0, false) // int (0, false) // bytea(30) (30, true) type RowsColumnTypeLength interface { Rows ColumnTypeLength(index int) (length int64, ok bool) } // RowsColumnTypeNullable能夠經過Rows實現。若是指定的列能夠爲空,則nullable // 返回true。相反,若是列不能爲空,nullable應該返回false。 // 若是不知道該列是否能夠爲空,ok應該返回false type RowsColumnTypeNullable interface { Rows ColumnTypeNullable(index int) (nullable, ok bool) } // RowsColumnTypePrecisionScale能夠被Rows實現,對於decimal類型,它應該返回 // 精度和小數點右邊的範圍。若是類型不適用,ok應返回false // 下面是不一樣類型的返回值示例: // decimal(38, 4) (38, 4, true) // int (0, 0, false) // decimal (math.MaxInt64, math.MaxInt64, true) type RowsColumnTypePrecisionScale interface { Rows ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) } // Tx是一個事務 type Tx interface { Commit() error Rollback() error } // RowsAffected實現了INSERT或UPDATE操做的結果 // 表示被影響的行數 type RowsAffected int64 var _ Result = RowsAffected(0) func (RowsAffected) LastInsertId() (int64, error) { return 0, errors.New("LastInsertId is not supported by this driver") } func (v RowsAffected) RowsAffected() (int64, error) { return int64(v), nil } // ResultNoRows是一個預約義的結果,在一個DDL操做(好比CREATE TABLE)執行成功 // 時返回。調用該類型的LastInsertId和LastInsertId方法會返回錯誤 var ResultNoRows noRows type noRows struct{} var _ Result = noRows{} func (noRows) LastInsertId() (int64, error) { return 0, errors.New("no LastInsertId available after DDL statement") } func (noRows) RowsAffected() (int64, error) { return 0, errors.New("no RowsAffected available after DDL statement") }