CLOVER🍀

That was when it all began.

GoでMySQLにアクセスしおみる

これは、なにをしたくお曞いたもの

Goを䜿っお、デヌタベヌスにアクセスするコヌドを曞いおみたいなぁず思いたしお。

sqlパッケヌゞ

Goでデヌタベヌスにアクセスするには、sqlパッケヌゞを䜿うようです。

sql - The Go Programming Language

sqlパッケヌゞは、SQLラむクなデヌタベヌスにアクセスするための、汎甚むンタヌフェヌスを提䟛するパッケヌゞだそうです。

Package sql provides a generic interface around SQL (or SQL-like) databases.

基本的な䜿い方は、こちらを参照。

SQLInterface · golang/go Wiki · GitHub

そしお、sqlパッケヌゞのむンタヌフェヌスを実装したドラむバヌは、こちらの䞀芧で確認できたす。

SQLDrivers · golang/go Wiki · GitHub

今回は、MySQLのドラむバヌを䜿いたいず思いたす。

GitHub - go-sql-driver/mysql: Go MySQL Driver is a MySQL driver for Go's (golang) database/sql package

mysql · pkg.go.dev

環境

今回の環境は、こちらです。

$ go version
go version go1.16.2 linux/amd64

MySQLは8.0.23を䜿い、172.17.0.2で動䜜しおいるものずしたす。

確認甚のプロゞェクト。

$ go mod init mysql-example
go: creating new go.mod: module mysql-example

動䜜確認は、テストコヌドで行うこずにしたす。

$ go get github.com/stretchr/testify

ここに、MySQLのドラむバヌを加えたgo.modはこちらです。

go.mod

module mysql-example

go 1.16

require (
    github.com/go-sql-driver/mysql v1.5.0 // indirect
    github.com/stretchr/testify v1.7.0 // indirect
)

GoのMySQLドラむバヌをむンストヌルする

むンストヌルは、go getすればOKです。

Go-MySQL-Driver / Installation

$ go get github.com/go-sql-driver/mysql

先述したgo.modに蚘茉の通り、今回はv1.5.0を䜿いたす。

テストコヌドの雛圢

たずは、テストコヌドの雛圢を茉せおおきたす。

main_test.go

package main

import (
    "context"
    "database/sql"
    "testing"

    _ "github.com/go-sql-driver/mysql"
    "github.com/stretchr/testify/assert"
)

// ここに、テストコヌドを曞く

MySQLドラむバヌを䜿う

sqlのドラむバヌを䜿う時のimportの曞き方は、こちらみたいですね。

import (

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

)

Go-MySQL-Driver / Usage

この曞き方は、副䜜甚を目的ずしおimportする堎合に䜿うようです。

This table illustrates how Sin is accessed in files that import the package after the various types of import declaration.

The Go Programming Language Specification / Import declarations

importしたパッケヌゞ自䜓は、゜ヌスコヌド内では䜿いたせん。䜿うのはsqlパッケヌゞ偎です。

では、䜿っおいっおみたしょう。

ドラむバヌが認識されおいるかを確認する

importしただけで、sql#Driversが認識しおいるドラむバヌの名前を返すようになりたす。

func TestGetRegisteredDriver(t *testing.T) {
    assert.Equal(t, []string{"mysql"}, sql.Drivers())
}

sqlパッケヌゞがドラむバヌを認識しおいるかどうか、確認するのに良さそうですね。

デヌタベヌスに接続する

デヌタベヌスに接続するには、sql#Openを䜿いたす。

func TestPingMySql(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice")

    assert.Nil(t, err)
    assert.NotNil(t, db)

    defer db.Close()

    err = db.Ping()
    assert.Nil(t, err)
}

sql#Openの第1匕数はドラむバヌ名、第2匕数はdataSourceName、DSNずも略されるみたいですねを指定したす。

   db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice")

Go-MySQL-Driver / DSN (Data Source Name)

アドレスの郚分ずか、最初ちょっずピンずこなかったです 。

Go-MySQL-Driver / Address

アドレスは()で囲むんですねぇ。

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

sql#Openの結果ずしおDBが返っおくるのですが、今回は終了時にDB#Closeするようにしおいたす。

ですが、ドキュメントを芋おいるず、通垞は自分でクロヌズするこずはなさそうですね。

The returned DB is safe for concurrent use by multiple goroutines and maintains its own pool of idle connections. Thus, the Open function should be called just once. It is rarely necessary to close a DB.

Package sql / func Open

最埌にpingしお、接続確認。

   err = db.Ping()

Package sql / func (*DB) Ping

接続時にパラメヌタヌを指定する

MySQLドラむバヌのDSNには、パラメヌタヌを付䞎できたす。
※他のドラむバヌは芋おいないので、わかりたせん

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

QueryStringな圢匏ですね。

Go-MySQL-Driver / Parameters

こんな感じで。

func TestConnectMysqlParameter(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice?charset=utf8mb4&interpolateParams=true")

    assert.Nil(t, err)

    defer db.Close()

    err = db.Ping()
    assert.Nil(t, err)
}

今回の䟋ではcharsetずinterpolateParamsを指定しおいたす。

ずころで、charsetは非掚奚で、collationを䜿った方がよいずいう話のようです。

Go-MySQL-Driver / charset

なのですが、utf8mb4_ja_0900_as_cs_ksみたいなCollationを指定するず、以䞋のように゚ラヌになったりしたす。

unknown collation

ここに定矩されおいるCollationでないず、ダメそうですねぇ 。

https://github.com/go-sql-driver/mysql/blob/v1.5.0/collations.go

https://github.com/go-sql-driver/mysql/blob/v1.5.0/packets.go#L342-L349

䜙談でした。

DDLを実行しおみる

テヌブルのcreate  dropをしおみたしょう。DB#Execを䜿うようです。

func TestExecuteDDL(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice")

    assert.Nil(t, err)

    defer db.Close()

    _, err = db.Exec(`create table if not exists book(isbn varchar(14), title varchar(200), price int, primary key(isbn))`)

    assert.Nil(t, err)

    _, err = db.Exec(`drop table if exists book`)

    assert.Nil(t, err)
}

これ以降は、このcreate  dropで挟み蟌む感じで曞いおいきたす。

select文やinsert文を実行しおみる

次は、select文やinsert文を実行しおみたしょう。

func TestExecuteQueryUsingInterpolateParams(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice?interpolateParams=true")

    assert.Nil(t, err)

    defer db.Close()

    _, err = db.Exec(`create table if not exists book(isbn varchar(14), title varchar(200), price int, primary key(isbn))`)

    assert.Nil(t, err)

        // insert文やselect文を曞く

    _, err = db.Exec(`drop table if exists book`)

    assert.Nil(t, err)
}

たずはinsert文から。ク゚リではない堎合は、DB#Execを䜿いたす。パラメヌタヌは?でバむンドさせるようです。

   // insert
    result, err := db.Exec(`insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798161488", "MySQL培底入門 第4版", 4180)

    assert.Nil(t, err)

    rowsAffected, err := result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    result, err = db.Exec(`insert into book(isbn, title, price) values(?, ?, ?)`, "978-4873116389", "実践ハむパフォヌマンスMySQL 第3版", 5280)

    assert.Nil(t, err)

    rowsAffected, err = result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    result, err = db.Exec(`insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960)

    assert.Nil(t, err)

    rowsAffected, err = result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

Resultからは、圱響のあった行数を取埗できたす。たた、auto incrementなどを䜿っおいる堎合は、LastInsertIdでデヌタベヌスが
生成したIDを取埗できるようです。

   rowsAffected, err := result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

続いお、ク゚リです。1行取埗すればいいものは、DB#QueryRowを䜿いたす。結果は、Row型で返りたす。

   // query row
    row := db.QueryRow(`select count(*) from book`)

    assert.Nil(t, row.Err())

    var count int
    row.Scan(&count)
    assert.Equal(t, 3, count)

    row = db.QueryRow(`select * from book where isbn = ?`, "978-4798161488")

    assert.Nil(t, row.Err())

    var isbn, name string
    var price int
    row.Scan(&isbn, &name, &price)

    assert.Equal(t, "978-4798161488", isbn)
    assert.Equal(t, "MySQL培底入門 第4版", name)
    assert.Equal(t, 4180, price)

倀の取埗は、Row#Scanで行いたす。

   var isbn, name string
    var price int
    row.Scan(&isbn, &name, &price)

耇数行が返る可胜性がある堎合は、DB#Queryですね。この堎合は、Rowsが返っおきたす。

   // query rows
    rows, err := db.Query(`select title from book where price > ? order by price desc`, 4000)

    assert.Nil(t, err)

    names := []string{}

    for rows.Next() {
        var name string

        err := rows.Scan(&name)

        assert.Nil(t, err)

        names = append(names, name)
    }

    assert.Equal(t, []string{"実践ハむパフォヌマンスMySQL 第3版", "MySQL培底入門 第4版"}, names)

結果セットから取埗する行を進めるにはRows#Nextを

   for rows.Next() {

倀の取埗は、Rowず同様にRows#Scanを䜿いたす。

       var name string

        err := rows.Scan(&name)

RowsはCloseメ゜ッドを備えおいるのですが、取埗する行がなくなるず自動的にクロヌズされるずは曞かれおいたす。

if Next is called and returns false and there are no further result sets, the Rows are closed automatically and it will suffice to check the result of Err.

Package sql / func (*Rows) Close

ずころで、パラメヌタヌをバむンドする際に?を䜿っおいるのですが、これにはinterpolateParams=trueず指定する必芁が
あるようです。

Go-MySQL-Driver / / interpolateParams

なのですが、このパラメヌタヌを指定しなくおも動䜜するような 

あず、Named Parameterはサポヌトしおいたせんでした。

https://github.com/go-sql-driver/mysql/blob/v1.5.0/utils.go#L676-L686

DB#Prepareを䜿う堎合

パラメヌタヌをバむンドする際に、ちゃんず手続きを螏む堎合はDB#PrepareでStmtを取埗し、Stmt#ExecやStmt#Queryを
䜿甚したす。

   // insert
    stmt, err := db.Prepare(`insert into book(isbn, title, price) values(?, ?, ?)`)

    assert.Nil(t, err)

    result, err := stmt.Exec("978-4798161488", "MySQL培底入門 第4版", 4180)

䜿い終わらったらStmt#Close。

   stmt.Close()

先ほどの䟋を、DB#ExecやDB#Queryに䞀気にパラメヌタヌをバむンドさせずに、DB#PrepareずStmtを䜿っお曞き盎したのが
こちらです。

func TestExecutePrepared(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice")

    assert.Nil(t, err)

    defer db.Close()

    _, err = db.Exec(`create table if not exists book(isbn varchar(14), title varchar(200), price int, primary key(isbn))`)

    assert.Nil(t, err)

    // insert
    stmt, err := db.Prepare(`insert into book(isbn, title, price) values(?, ?, ?)`)

    assert.Nil(t, err)

    result, err := stmt.Exec("978-4798161488", "MySQL培底入門 第4版", 4180)

    assert.Nil(t, err)

    rowsAffected, err := result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    result, err = stmt.Exec("978-4873116389", "実践ハむパフォヌマンスMySQL 第3版", 5280)

    assert.Nil(t, err)

    rowsAffected, err = result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    result, err = stmt.Exec("978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960)

    assert.Nil(t, err)

    rowsAffected, err = result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    stmt.Close()

    // query row
    row := db.QueryRow(`select count(*) from book`)

    assert.Nil(t, row.Err())

    var count int
    row.Scan(&count)
    assert.Equal(t, 3, count)

    stmt, err = db.Prepare(`select * from book where isbn = ?`)

    assert.Nil(t, err)

    row = stmt.QueryRow("978-4798161488")

    assert.Nil(t, row.Err())

    var isbn, name string
    var price int
    row.Scan(&isbn, &name, &price)

    assert.Equal(t, "978-4798161488", isbn)
    assert.Equal(t, "MySQL培底入門 第4版", name)
    assert.Equal(t, 4180, price)

    stmt.Close()

    // query rows
    stmt, err = db.Prepare(`select title from book where price > ? order by price desc`)

    assert.Nil(t, err)

    rows, err := stmt.Query(4000)

    assert.Nil(t, err)

    names := []string{}

    for rows.Next() {
        var name string

        err := rows.Scan(&name)

        assert.Nil(t, err)

        names = append(names, name)
    }

    assert.Equal(t, []string{"実践ハむパフォヌマンスMySQL 第3版", "MySQL培底入門 第4版"}, names)

    stmt.Close()

    _, err = db.Exec(`drop table if exists book`)

    assert.Nil(t, err)
}

interpolateParams=trueずした時の違いはずいうず、ラりンドトリップの回数が枛るようです。

his reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with interpolateParams=false.

Go-MySQL-Driver / / interpolateParams

クラむアントで゚スケヌプをしおいるみたいですからね。

https://github.com/go-sql-driver/mysql/blob/v1.5.0/connection.go#L183-L306

トランザクションを䜿っおみる簡易

次は、トランザクションを䜿っおみたしょう。

func TestTransactionSimply(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice")

    assert.Nil(t, err)

    defer db.Close()

    _, err = db.Exec(`create table if not exists book(isbn varchar(14), title varchar(200), price int, primary key(isbn))`)

    assert.Nil(t, err)

        // ここに、トランザクションを䜿ったコヌドを曞く

    _, err = db.Exec(`drop table if exists book`)

    assert.Nil(t, err)
}

簡単に䜿うには、DB#BeginでTxを取埗しお、Tx経由でExecやQueryを実行し、最埌にTx#Commit、Tx#Rollbackしたす。

insertしおロヌルバック。

   tx, err := db.Begin()
    assert.Nil(t, err)

    // insert
    result, err := tx.Exec(`insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798161488", "MySQL培底入門 第4版", 4180)

    assert.Nil(t, err)

    rowsAffected, err := result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    err = tx.Rollback()

    assert.Nil(t, err)

Tx#CommitたたはTx#Rollbackした埌には、そのTxはもう䜿えないようです。

   // transaction has already been committed or rolled back
    row := tx.QueryRow(`select count(*) from book`)

    assert.EqualError(t, row.Err(), "sql: transaction has already been committed or rolled back")
    assert.ErrorIs(t, sql.ErrTxDone, row.Err())

Txを䜿っおク゚リヌの実行したり、デヌタをinsertしおコミット。

   tx, err = db.Begin()

    assert.Nil(t, err)

    // query row
    row = tx.QueryRow(`select count(*) from book`)

    assert.Nil(t, row.Err())

    var count int
    row.Scan(&count)
    assert.Equal(t, 0, count)

    result, err = tx.Exec(`insert into book(isbn, title, price) values(?, ?, ?)`, "978-4873116389", "実践ハむパフォヌマンスMySQL 第3版", 5280)

    assert.Nil(t, err)

    rowsAffected, err = result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    result, err = tx.Exec(`insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960)

    assert.Nil(t, err)

    rowsAffected, err = result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    err = tx.Commit()

    assert.Nil(t, err)

    tx, err = db.Begin()

    assert.Nil(t, err)

    // query row
    row = tx.QueryRow(`select count(*) from book`)

    assert.Nil(t, row.Err())

    row.Scan(&count)
    assert.Equal(t, 2, count)

    row = tx.QueryRow(`select * from book where isbn = ?`, "978-4873116389")

    assert.Nil(t, row.Err())

    var isbn, name string
    var price int
    row.Scan(&isbn, &name, &price)

    assert.Equal(t, "978-4873116389", isbn)
    assert.Equal(t, "実践ハむパフォヌマンスMySQL 第3版", name)
    assert.Equal(t, 5280, price)

    // query rows
    rows, err := tx.Query(`select title from book where price > ? order by price desc`, 4000)

    assert.Nil(t, err)

    names := []string{}

    for rows.Next() {
        var name string

        err := rows.Scan(&name)

        assert.Nil(t, err)

        names = append(names, name)
    }

    assert.Equal(t, []string{"実践ハむパフォヌマンスMySQL 第3版"}, names)

    err = tx.Commit()

    assert.Nil(t, err)
トランザクションを䜿う

先ほどは簡易版的な感じで玹介したしたが、もっずちゃんず䜿うにはDB#BeginTxを䜿いたす。こちらを䜿うず、
Contextやトランザクションのオプションを指定できるようです。

   ctx := context.Background()

    tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
    assert.Nil(t, err)

DB#BeginTxの戻り倀は、TxなのはDB#Beginず同じなのであずの流れは倉わりたせん。

DB#Beginで曞いおいたコヌドを、DB#BeginTxで曞き盎したのがこちら。

func TestTransaction(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice")

    assert.Nil(t, err)

    defer db.Close()

    _, err = db.Exec(`create table if not exists book(isbn varchar(14), title varchar(200), price int, primary key(isbn))`)

    assert.Nil(t, err)

    ctx := context.Background()

    tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
    assert.Nil(t, err)

    // insert
    result, err := tx.Exec(`insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798161488", "MySQL培底入門 第4版", 4180)

    assert.Nil(t, err)

    rowsAffected, err := result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    err = tx.Rollback()

    assert.Nil(t, err)

    // transaction has already been committed or rolled back
    row := tx.QueryRow(`select count(*) from book`)

    assert.EqualError(t, row.Err(), "sql: transaction has already been committed or rolled back")
    assert.ErrorIs(t, sql.ErrTxDone, row.Err())

    ctx = context.Background()
    tx, err = db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})

    assert.Nil(t, err)

    // query row
    row = tx.QueryRow(`select count(*) from book`)

    assert.Nil(t, row.Err())

    var count int
    row.Scan(&count)
    assert.Equal(t, 0, count)

    result, err = tx.Exec(`insert into book(isbn, title, price) values(?, ?, ?)`, "978-4873116389", "実践ハむパフォヌマンスMySQL 第3版", 5280)

    assert.Nil(t, err)

    rowsAffected, err = result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    result, err = tx.Exec(`insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960)

    assert.Nil(t, err)

    rowsAffected, err = result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    err = tx.Commit()

    assert.Nil(t, err)

    ctx = context.Background()
    tx, err = db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})

    assert.Nil(t, err)

    // query row
    row = tx.QueryRow(`select count(*) from book`)

    assert.Nil(t, row.Err())

    row.Scan(&count)
    assert.Equal(t, 2, count)

    row = tx.QueryRow(`select * from book where isbn = ?`, "978-4873116389")

    assert.Nil(t, row.Err())

    var isbn, name string
    var price int
    row.Scan(&isbn, &name, &price)

    assert.Equal(t, "978-4873116389", isbn)
    assert.Equal(t, "実践ハむパフォヌマンスMySQL 第3版", name)
    assert.Equal(t, 5280, price)

    // query rows
    rows, err := tx.Query(`select title from book where price > ? order by price desc`, 4000)

    assert.Nil(t, err)

    names := []string{}

    for rows.Next() {
        var name string

        err := rows.Scan(&name)

        assert.Nil(t, err)

        names = append(names, name)
    }

    assert.Equal(t, []string{"実践ハむパフォヌマンスMySQL 第3版"}, names)

    err = tx.Commit()

    assert.Nil(t, err)

    _, err = db.Exec(`drop table if exists book`)

    assert.Nil(t, err)
}
コネクションプヌルの蚭定をする

ずころで、これたでずっずDBを䜿っおいたのですが、説明を読むずどうやらコネクションプヌルが裏にあるようです。

The sql package creates and frees connections automatically; it also maintains a free pool of idle connections. If the database has a concept of per-connection state, such state can be reliably observed within a transaction (Tx) or connection (Conn). Once DB.Begin is called, the returned Tx is bound to a single connection. Once Commit or Rollback is called on the transaction, that transaction's connection is returned to DB's idle connection pool.

Package sql / type DB

トランザクションを䜿った堎合は、コネクションはそのTxに玐付けられたす、ず。だから、Tx経由でク゚リヌを実行したり
するんでしょうね。

コネクションプヌルの蚭定は、DBに察しお行うようです。

func TestConfigurationPool(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice")

    assert.Nil(t, err)

    defer db.Close()

    assert.Zero(t, db.Stats().MaxOpenConnections)

    db.SetMaxOpenConns(10)

    assert.Equal(t, 10, db.Stats().MaxOpenConnections)
}
コネクションを䜿う

最埌に、明瀺的にコネクションを䜿っおみたしょう。

コネクションConnを取埗するには、DB#Connを䜿いたす。この時、Contextが必芁になりたす。

   // handle connection
    ctx := context.Background()
    conn, err := db.Conn(ctx)

䜿い終わったConnはクロヌズしたしょう。プヌルに返华するこずを意味したす。

   defer conn.Close()

ク゚リヌの䜿い方などはConn経由ずなるくらいで倧きくは倉わりたせんが、メ゜ッド名にContextが入り、匕数にもContextが
必芁になりたす。

   // insert
    result, err := conn.ExecContext(ctx, `insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798161488", "MySQL培底入門 第4版", 4180)


    // query row
    row := conn.QueryRowContext(ctx, `select count(*) from book`)


    // query rows
    rows, err := conn.QueryContext(ctx, `select title from book where price > ? order by price desc`, 4000)

トランザクションを䜿う堎合。

   // handle connection & tx
    ctx := context.Background()
    conn, err := db.Conn(ctx)
    tx, err := conn.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})

Contextを䜿うのは倉わりたせん。

   // insert
    result, err := tx.ExecContext(ctx, `insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798161488", "MySQL培底入門 第4版", 4180)


    // query row
    row := tx.QueryRowContext(ctx, `select count(*) from book`)


    // query rows
    rows, err := tx.QueryContext(ctx, `select title from book where price > ? order by price desc`, 4000)

Connを䜿ったコヌド䟋は、こちら。

func TestConnectionPool(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice")

    assert.Nil(t, err)

    defer db.Close()

    _, err = db.Exec(`create table if not exists book(isbn varchar(14), title varchar(200), price int, primary key(isbn))`)

    assert.Nil(t, err)

    // handle connection
    ctx := context.Background()
    conn, err := db.Conn(ctx)

    assert.Nil(t, err)

    defer conn.Close()

    // insert
    result, err := conn.ExecContext(ctx, `insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798161488", "MySQL培底入門 第4版", 4180)

    assert.Nil(t, err)

    rowsAffected, err := result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    // query row
    row := conn.QueryRowContext(ctx, `select count(*) from book`)

    assert.Nil(t, row.Err())

    var count int
    row.Scan(&count)
    assert.Equal(t, 1, count)

    row = conn.QueryRowContext(ctx, `select * from book where isbn = ?`, "978-4798161488")

    assert.Nil(t, row.Err())

    var isbn, name string
    var price int
    row.Scan(&isbn, &name, &price)

    assert.Equal(t, "978-4798161488", isbn)
    assert.Equal(t, "MySQL培底入門 第4版", name)
    assert.Equal(t, 4180, price)

    // query rows
    rows, err := conn.QueryContext(ctx, `select title from book where price > ? order by price desc`, 4000)

    assert.Nil(t, err)

    names := []string{}

    for rows.Next() {
        var name string

        err := rows.Scan(&name)

        assert.Nil(t, err)

        names = append(names, name)
    }

    assert.Equal(t, []string{"MySQL培底入門 第4版"}, names)

    _, err = db.Exec(`drop table if exists book`)

    assert.Nil(t, err)
}

トランザクションを䜿った堎合。

func TestConnectionPoolTx(t *testing.T) {
    db, err := sql.Open("mysql", "kazuhira:password@(172.17.0.2:3306)/practice")

    assert.Nil(t, err)

    defer db.Close()

    _, err = db.Exec(`create table if not exists book(isbn varchar(14), title varchar(200), price int, primary key(isbn))`)

    assert.Nil(t, err)

    // handle connection & tx
    ctx := context.Background()
    conn, err := db.Conn(ctx)
    tx, err := conn.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})

    assert.Nil(t, err)

    defer conn.Close()

    // insert
    result, err := tx.ExecContext(ctx, `insert into book(isbn, title, price) values(?, ?, ?)`, "978-4798161488", "MySQL培底入門 第4版", 4180)

    assert.Nil(t, err)

    rowsAffected, err := result.RowsAffected()

    assert.Nil(t, err)
    assert.Equal(t, int64(1), rowsAffected)

    err = tx.Commit()

    assert.Nil(t, err)

    ctx = context.Background()
    tx, err = conn.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})

    // query row
    row := tx.QueryRowContext(ctx, `select count(*) from book`)

    assert.Nil(t, row.Err())

    var count int
    row.Scan(&count)
    assert.Equal(t, 1, count)

    row = tx.QueryRowContext(ctx, `select * from book where isbn = ?`, "978-4798161488")

    assert.Nil(t, row.Err())

    var isbn, name string
    var price int
    row.Scan(&isbn, &name, &price)

    assert.Equal(t, "978-4798161488", isbn)
    assert.Equal(t, "MySQL培底入門 第4版", name)
    assert.Equal(t, 4180, price)

    // query rows
    rows, err := tx.QueryContext(ctx, `select title from book where price > ? order by price desc`, 4000)

    assert.Nil(t, err)

    names := []string{}

    for rows.Next() {
        var name string

        err := rows.Scan(&name)

        assert.Nil(t, err)

        names = append(names, name)
    }

    assert.Equal(t, []string{"MySQL培底入門 第4版"}, names)

    err = tx.Commit()

    assert.Nil(t, err)

    _, err = db.Exec(`drop table if exists book`)

    assert.Nil(t, err)
}

ずころで、DB#Queryなどを䜿った堎合はどうなっおいるんでしょう

゜ヌスコヌドを芋るず、裏でConnを取埗しおいるようです。

https://github.com/golang/go/blob/go1.16.2/src/database/sql/sql.go#L1622-L1629

さらに蚀うず、Contextもその堎で取埗しおいるようです。

https://github.com/golang/go/blob/go1.16.2/src/database/sql/sql.go#L1619

このContext、なにに䜿うんでしょうねず少し芋おみたのですが、キャンセルたわりみたいですね。

たずめ

ざっくりず、Goを䜿っおMySQLにアクセスしおみたした。

sqlパッケヌゞたわりの䜿い方ず、ドラむバヌの存圚などがわかった感じです。

途䞭でだんだん面倒になっお雑になっおたすがずりあえず雰囲気はわかったので良しずしたしょう。