與資料庫打交道

pardon110發表於2020-01-02

夠浪的官方database/sql 庫實際只提供了一套運算元據庫的介面和規範。如 抽象好的SQL預處理(prepare),連線池管理,資料繫結,事務,錯誤處理等等,並未提供具體某種資料庫實現的協議支援。

database/sql

DB 扮演的角色與laravel資料庫管理例項,同nodejs中的sequelizejs連線大抵一樣,都是掌權者,負責指揮不幹活。

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

是故需要匿名匯入包含init函式的驅動包,實現官方介面

func init() {
    sql.Register("mysql", &MySQLDriver{})
}

sql.Open返回的DB是這貨

type Conn interface {
    Prepare(query string) (Stmt, error)
    Close() error
    Begin() (Tx, error)
}

Placeholder

不同型別的資料庫佔位符,有所不同

MySQL               PostgreSQL            Oracle
=====               ==========            ======
WHERE col = ?       WHERE col = $1        WHERE col = :col
VALUES(?, ?, ?)     VALUES($1, $2, $3)    VALUES(:val1, :val2, :val3)

DB

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "log"
)

// sql.DB sql.Rows sql.Scan
func main() {
    // *sql.DB
    db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/shop58")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    var (
        id    int
        name  string
        email string
    )

    // *sql.Rows
    rows, err := db.Query("select id, name,email from users where id = ?", 1)
    if err != nil {
        log.Fatal(err)
    }
    // 防止進一步列舉(換而言之,讀完會釋放連線資源),很重要!!!
    defer rows.Close()

    // 讀取下一行結果集
    // 必須要把 rows 裡的內容讀完,或者顯式呼叫 Close() 方法,
    // 否則在 defer 的 rows.Close() 執行之前,連線永遠不會釋放
    for rows.Next() {
        err := rows.Scan(&id, &name, &email)
        if err != nil {
            log.Fatal(err)
        }
        log.Println(id, name, email)
    }
    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
}

Tx

事務在夠浪也有涉及,官方僅一個介面規範,管理了一個標準庫

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "log"
)

func main() {
    db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // *sql.Tx
    tx, err := db.Begin()
    if err != nil {
        log.Fatal(err)
    }
    defer tx.Rollback()

    // *sql.Stmt
    stmt, err := tx.Prepare("insert into account (id,price) values (null,?)")
    if err != nil {
        log.Fatal(err)
    }
    defer stmt.Close()

    for i := 0; i < 5; i++ {
        _, err = stmt.Exec(i)
        if err != nil {
            log.Fatal(err)
        }
    }
    err = tx.Commit()
    if err != nil {
        log.Fatal(err)
    }
    stmt.Close()
}

官方

包括該庫的功能介紹、用法、注意事項和一些非人類方法實現,參閱 http://go-database-sql.org/
如反直覺的一些實現方式(同一個goroutine內對sql.DB的查詢,可能在多個連線上)。

官方東西總是大道至簡,夠用,多點操作,可能就要用到高效率的ORM和SQL Builder。這些工具遮蔽了DB層一些細節,讓你幾乎感覺不到資料庫的存在,只是使用物件導向的編碼。中小型公司可能還湊合,到大廠不行了。比如某些orm會生成的SQL語句會自動limit 1000,這是後話。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章