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を利用したので
?
を指定しています。
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