添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
id INTEGER NOT NULL AUTO_INCREMENT, first_name VARCHAR(50), last_name VARCHAR(50), age INTEGER, created DATETIME DEFAULT CURRENT_TIMESTAMP, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE posts id INTEGER NOT NULL AUTO_INCREMENT, user_id INTEGER NOT NULL, content TEXT NOT NULL, created DATETIME DEFAULT CURRENT_TIMESTAMP, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO users (first_name, last_name, age) VALUES ("りな", "みかみ", 43), ("じゅん", "くさの", 34), ("ひでき", "やまだ", 23); func main() { db, err := sql.Open("mysql", "test_user:test_password@tcp(127.0.0.1:13306)/test_db") if err != nil { log.Fatalf("main sql.Open error err:%v", err) defer db.Close()

Open関数で取得した sql.DB構造体のインスタンス は、コネクションプールを管理しています。

このインスタンスを通じて操作することにより、以下のようにコネクションを活用できます。

func main() { db, err := sql.Open("mysql", "test_user:test_password@tcp(127.0.0.1:13306)/test_db?parseTime=true&loc=Asia%2FTokyo") if err != nil { log.Fatalf("main sql.Open error err:%v", err) defer db.Close() fmt.Println("------------------") getRows(db) fmt.Println("------------------") getSingleRow(db, 1) fmt.Println("------------------") getSingleRow(db, 4) // 存在しないUserID func getRows(db *sql.DB) { rows, err := db.Query("SELECT * FROM users") if err != nil { log.Fatalf("getRows db.Query error err:%v", err) defer rows.Close() for rows.Next() { u := &User{} if err := rows.Scan(&u.ID, &u.FirstName, &u.LastName, &u.Age, &u.Created, &u.Updated); err != nil { log.Fatalf("getRows rows.Scan error err:%v", err) fmt.Println(u) err = rows.Err() if err != nil { log.Fatalf("getRows rows.Err error err:%v", err) func getSingleRow(db *sql.DB, userID int) { u := &User{} err := db.QueryRow("SELECT * FROM users WHERE id = ?", userID). Scan(&u.ID, &u.FirstName, &u.LastName, &u.Age, &u.Created, &u.Updated) if errors.Is(err, sql.ErrNoRows) { fmt.Println("getSingleRow no records.") return if err != nil { log.Fatalf("getSingleRow db.QueryRow error err:%v", err) fmt.Println(u)
------------------
&{1 りな みかみ 43 2022-04-30 14:48:05 +0900 JST 2022-04-30 14:48:10 +0900 JST}
&{2 じゅん くさの 34 2022-04-30 14:48:05 +0900 JST 2022-04-30 14:48:11 +0900 JST}
&{3 ひでき やまだ 23 2022-04-30 14:48:05 +0900 JST 2022-04-30 14:48:12 +0900 JST}
------------------
&{1 りな みかみ 43 2022-04-30 14:48:05 +0900 JST 2022-04-30 14:48:10 +0900 JST}
------------------
getSingleRow no records.

プレースホルダー

パラメータのプレースホルダー( "SELECT * FROM users WHERE id = ?" ? の部分 )は利用するDBによって異なります。
今回、MySQLを利用したので ? を指定しています。

time.Time型の利用

sql.Open の第二引数も修正しています。( ?parseTime=true&loc=Asia%2FTokyo を追加 )

parseTime=true を追加しないと以下エラーが発生するためです。

sql: Scan error on column index 4, name "created": unsupported Scan, storing driver.Value type []uint8 into type *time.Time

time.Time型 を利用したいときは、指定必要です。

※参考
https://github.com/go-sql-driver/mysql#parsetime

INSERT / UPDATE / DELETE
( Exec )

INSERT / UPDATE / DELETE といったクエリを実行したい場合、 Execメソッド を活用できます。

package main
import (
	"database/sql"
	"log"
	_ "github.com/go-sql-driver/mysql"
func main() {
	db, err := sql.Open("mysql", "test_user:test_password@tcp(127.0.0.1:13306)/test_db?parseTime=true&loc=Asia%2FTokyo")
	if err != nil {
		log.Fatalf("main sql.Open error err:%v", err)
	defer db.Close()
	userID := insertUser(db, "さとし", "やまだ", 27)
	insertPost(db, userID, "hello world")
func insertUser(db *sql.DB, firstName, lastName string, age int) int64 {
	res, err := db.Exec(
		"INSERT INTO users (first_name, last_name, age) VALUES (?, ?, ?)",
		firstName,
		lastName,
	if err != nil {
		log.Fatalf("insertUser db.Exec error err:%v", err)
	id, err := res.LastInsertId()
	if err != nil {
		log.Fatalf("insertUser res.LastInsertId error err:%v", err)
	return id
func insertPost(db *sql.DB, userID int64, content string) int64 {
	res, err := db.Exec("INSERT INTO posts (user_id, content) VALUES (?, ?)",
		userID,
		content,
	if err != nil {
		log.Fatalf("insertPost db.Exec error err:%v", err)
	id, err := res.LastInsertId()
	if err != nil {
		log.Fatalf("insertPost res.LastInsertId error err:%v", err)
	return id
func main() {
	db, err := sql.Open("mysql", "test_user:test_password@tcp(127.0.0.1:13306)/test_db?parseTime=true&loc=Asia%2FTokyo")
	if err != nil {
		log.Fatalf("main sql.Open error err:%v", err)
	defer db.Close()
	transaction(db)
func transaction(db *sql.DB) {
	tx, err := db.Begin()
	if err != nil {
		log.Fatal(err)
	defer func() {
		if err := recover(); err != nil {
			if err := tx.Rollback(); err != nil {
				log.Fatalf("transaction rollback error err:%v", err)
	userID, err := insertUserTx(tx, "さとし", "やまだ", 27)
	if err != nil {
		if err := tx.Rollback(); err != nil {
			log.Fatalf("transaction rollback error err:%v", err)
		log.Fatalf("transaction insertUserTx error err:%v", err)
	_, err = insertPostTx(tx, *userID, "hello world")
	if err != nil {
		if err := tx.Rollback(); err != nil {
			log.Fatalf("transaction rollback error err:%v", err)
		log.Fatalf("transaction insertPostTx error err:%v", err)
	if err := tx.Commit(); err != nil {
		log.Fatalf("transaction commit error err:%v", err)
func insertUserTx(tx *sql.Tx, firstName, lastName string, age int) (*int64, error) {
	res, err := tx.Exec(
		"INSERT INTO users (first_name, last_name, age) VALUES (?, ?, ?)",
		firstName,
		lastName,
	if err != nil {
		return nil, err
	id, err := res.LastInsertId()
	if err != nil {
		return nil, err
	return &id, nil
func insertPostTx(tx *sql.Tx, userID int64, content string) (*int64, error) {
	res, err := tx.Exec("INSERT INTO posts (user_id, content) VALUES (?, ?)",
		userID,
		content,
	if err != nil {
		return nil, err
	id, err := res.LastInsertId()
	if err != nil {
		return nil, err
	return &id, nil

コネクション数、ライフタイムの設定

動作確認用のテーブル追加

時間のかかるクエリを作りたいため、動作確認用のテーブルを追加します。

CREATE TABLE `tests` (
  `content` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `tests`
    (`content`)
VALUES
    ('xxx'),
    ('xxx'),
    ('xxx'),
    ('xxx'),
    ('xxx'),
    ('xxx'),
    ('xxx'),
    ('xxx'),
    ('xxx'),
    ('xxx');
INSERT INTO `tests` (`content`)
SELECT `t1`.`content` FROM `tests` t1, `tests` t2, `tests` t3, `tests` t4, `tests` t5, `tests` t6;

testsテーブル に1000010レコードを登録しました。

mysql> SELECT COUNT(*) FROM tests;
+----------+
| COUNT(*) |
+----------+
|  1000010 |
+----------+
1 row in set (1.93 sec)
func main() { db, err := sql.Open("mysql", "test_user:test_password@tcp(127.0.0.1:13306)/test_db?parseTime=true&loc=Asia%2FTokyo") if err != nil { log.Fatalf("main sql.Open error err:%v", err) defer db.Close() fmt.Printf("%+v\n", db.Stats()) db.SetConnMaxLifetime(time.Minute * 1) db.SetMaxOpenConns(1) db.SetMaxIdleConns(1) fmt.Printf("%+v\n", db.Stats()) var wg sync.WaitGroup s := time.Now() for i := 0; i < 2; i++ { wg.Add(1) go request(&wg, db, i) wg.Wait() e := time.Now() fmt.Printf("処理秒数: %v\n", e.Sub(s).Round(time.Millisecond)) func request(wg *sync.WaitGroup, db *sql.DB, i int) { defer wg.Done() fmt.Printf("[request start] i: %v\n", i) defer fmt.Printf("[request end] i: %v\n", i) rows, err := db.Query("SELECT * FROM tests") if err != nil { log.Fatalf("request db.Query error err:%v", err) defer rows.Close()

最大コネクション数が1のとき

最大コネクションが1なので、コネクションが空くまで待ち時間が発生します。

{MaxOpenConnections:0 OpenConnections:0 InUse:0 Idle:0 WaitCount:0 WaitDuration:0s MaxIdleClosed:0 MaxIdleTimeClosed:0 MaxLifetimeClosed:0}
{MaxOpenConnections:1 OpenConnections:0 InUse:0 Idle:0 WaitCount:0 WaitDuration:0s MaxIdleClosed:0 MaxIdleTimeClosed:0 MaxLifetimeClosed:0}
[request start] i: 1
[request start] i: 0
[request end] i: 1
[request end] i: 0
処理秒数: 9.034s

最大コネクション数が2のとき

以下のように修正して再確認してみます。

	db.SetMaxOpenConns(2)
	db.SetMaxIdleConns(2)

最大コネクションが2なので、コネクションの空き待ちが発生せず、処理時間が短くなりました。

{MaxOpenConnections:0 OpenConnections:0 InUse:0 Idle:0 WaitCount:0 WaitDuration:0s MaxIdleClosed:0 MaxIdleTimeClosed:0 MaxLifetimeClosed:0}
{MaxOpenConnections:2 OpenConnections:0 InUse:0 Idle:0 WaitCount:0 WaitDuration:0s MaxIdleClosed:0 MaxIdleTimeClosed:0 MaxLifetimeClosed:0}
[request start] i: 1
[request start] i: 0
[request end] i: 0
[request end] i: 1
処理秒数: 4.915s