戻る

Go言語MySQL解説

Nov 14, 2020

go言語ではphpとは違いsqlは直接goからデータベースへ接続せず, sql.DBインターフェースからデータベースドライバーを介して実行することとなる. ドライバーの実装はSQLによって様々なものがあるが, MySQLではgo-sql-driverを使うのが良い. Go database/sql tutorial Improve this page を参考にしている.

次のimport文の_は見慣れないかもしれないが, ドライバーを直接操作しないのでimport時にドライバーのinit()だけを呼び出す.


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

データベースに接続

まず次のコードで大体始まる.


func main() {
	db, err := sql.Open("mysql",
		"user:password@tcp(127.0.0.1:3306)/hello")
  checkErr(err)
	defer db.Close()

  err := db.Ping()
  checkErr(err)
}

func checkErr(err error) {
  log.Fatal(err)
}

sql.Open()の第一引数はドライバー名(ドライバーのパッケージ名). 第二引数はドライバーによって異なるが, mysqlでは上のような書式となる. tcp(127.0.0.1:3306)は省略してもいいはず(ポートを変更していない限り). user:passwordはもちろんMySQLでのユーザーとパスワード. ここで注意することは, この時点ではまだデータベースと接続していない, つまり接続できるかまだわからないのでPing()を使って応答を確認する. また, 頻繁にOpen(), Close()をすると接続のリソースが重くなってしまうので接続はなるべく長く保つようにする.

データベースからデータを読み取る

クエリする. クエリの関数として, Query(), Exec()があるが, Query()はrowsを返すが, Exec()はrowsを返さない, つまりデータベースに変更を与える操作をする.

クエリしたデータは明示的に解放しなくてはならないのでdefer rows.Close()とする. rows.Next()が最後まで到達すれば自動的に解放されるが, 途中でエラーが起きた場合はClose()してくれない. Scan()でポインターを渡して読み取るが, Goはデータを自動的にポインターの指す変数の型に変換してくれる. 変換できない時にエラーを返す.


	rows, err := db.Query("SELECT id, name, FROM users where id = ?", 1); 
	checkErr(err)
	defer rows.Close()

	for rows.Next() {
		var id int
		var name string
		err := rows.Scan(&id, &name)
		checkErr(err)
		log.Println(id, name)
	}
	err := rows.Err()
	checkErr(err)

同じようなクエリを実行する場合, Prepare()関数でplaceholderつきのクエリを用意する. placeholderを使うことでクエリ分をstringで結合して作ることによるSQLインジェクションの脆弱性が改善される.


	stmt, err := db.Prepare("select id, name from users where id = ?")
	checkErr(err)
	defer stmt.Close()
	rows, err := stmt.Query(1)
	checkErr(err)
	defer rows.Close()
	for rows.Next() {
		// ...
	}

一行だけしかデータをクエリしないなら, 次のように書くこともできる


var name string
err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
checkErr(err)

Prepare()を使う場合は次のようになる


smt, err := db.Prepare("select name from users where id = ?")
checkErr(err)
defer stmt.Close()
var name string
err = stmt.QueryRow(1).Scan(&name)
checkErr(err)

データを更新する

Query()ではなくExec()を使う


	stmt, er := db.Prepare("INSERT INTO users(name) VALUES(?)")
	checkErr(err)
	res, err := stmt.Exec("Dolly")
	checkErr(err)
	lastId, err := res.LastInsertId()
	checkErr(err)
	rowCnt, err := res.RowsAffected()
	checkErr(err)
	log.Printf("ID = %d, affected = %d\n", lastId, rowsCnt)