• yundream
  • 2017-06-18 16:45:00
  • 2017-06-14 16:31:53
  • 112823

Contents

golang mysql

LAMP는 최근 몇년 동안 가장 인기있는 소프트웨어 모음이었다. LAMP에서 MMysql으로 괜찮은 성능과 (특히)뛰어난 사용성으로LAMP 스택의 성공을 견인했다. NoSQL이 주목을 받고 있지만 MysSQL은 여전히 가장 인기 있는 데이터베이스로 웹 기반의 많은 서비스들이 백앤드 데이터베이스로 사용하고 있다.

database/sql

Go언어는 SQL 데이터베이스를 위한 제너릭 인터페이스 패키지 database/sql을 제공한다. database/sql 패키지는 인터페이스만 제공하므로, 인터페이스 구현체인 데이터베이스 드라이버와 함께 사용해야 한다. 아래는 database/sql의 대략적인 특징과 기능들 이다.
  • SQL 기반 데이터베이스에서 범용적으로 사용되는 최소한의 인터페이스만 제공한다.
  • Open, Prepare, Exec, Query, QueryRow, Scan, Close
  • DB, Stmt, Value, Row, Rows, Result
  • Tx, Gegin, Commit, Rollback
sql 패키지는 이 데이터베이스 드라이버와 함께 사용해야 한다. database/sql 패키지에서 지원하는 데이터베이스 드라이버의 목록은 SQL Drivers에서 확인 할 수 있다.

이중 mysql 드라이버를 이용해서 데이터베이스 응용을 개발해볼 생각이다. mysql 드라이버는 https://github.com/zlutek/mysql 과 https://github.com/go-sql-driver/mysql 두 개가 있다. 여기에서는 go-sql-driver/mysql을 사용하기로 했다.

mysql 패키지를 설치한다.
$ go get "github.com/go-sql-driver/mysql"

예제 데이터베이스

데이터베이스 환경을 만들자. 나는 도커 기반으로 만들었다.
$ docker run --name test-mysql -e MYSQL_ROOT_PASSWORD=mypassword -d mysql
mytest 데이터베이스를 만들고 아래와 같은 테이블을 만들었다.
CREATE TABLE `userinfo` (
    `uid` INT(10) NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(64) NULL DEFAULT NULL,
    `departname` VARCHAR(64) NULL DEFAULT NULL,
    `created` DATE NULL DEFAULT NULL,
    PRIMARY KEY (`uid`)
);

예제 프로그램

데이터베이스 조작은 sql 패키지에서 제공하는 공통 인터페이스를 이용하면 된다. 아래는 데이터베이스에 입력하는 간단한 예제다.
package main

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

func CheckErr(err error) {
    if err != nil {
        panic(err)
    }
}
func main() {
    // Database Open
    db, err := sql.Open("mysql", "root:mypass@tcp(172.17.0.2:3306)/mytest")
    CheckErr(err)
    defer db.Close()

    // Insert
    stmt, err := db.Prepare("INSERT userinfo SET username=?, departname=?, created=?")
    CheckErr(err)

    res, err := stmt.Exec("yundream", "software", "2017-06-10")
    CheckErr(err)

    id, err := res.LastInsertId()
    CheckErr(err)

    fmt.Println("Insert ID", id)

    // Update
    stmt, err = db.Prepare("UPDATE userinfo SET username=? where uid=?")
    CheckErr(err)

    res, err = stmt.Exec("yundream@gmail.com", id)
    CheckErr(err)

    affect, err := res.RowsAffected()
    CheckErr(err)

    // Select
    rows, err := db.Query("SELECT uid, username, departname, created FROM userinfo")
    CheckErr(err)

    type userInfo struct {
        Uid        int
        UserName   string
        DepartName string
        Created    string
    }
    var items []userInfo
    for rows.Next() {
        item := userInfo{}
        err := rows.Scan(
            &item.Uid,
            &item.UserName,
            &item.DepartName,
            &item.Created,
        )
        CheckErr(err)
        items = append(items, item)
    }
    fmt.Println("Item num :", len(items))
}

sql.Open() : 데이터베이스를 오픈한다. 다양한 데이터베이스 드라이버를 등록 할 수 있다. 코드에서는 mysql 드라이버를 등록하기 위해서 "mysql"을 사용했다. 두 번째 매개변수는 DSN(Data Source Name)으로, 연결할 데이터베이스 정보를 등록한다. DSN의 형식은 "<username>:<pw>@tcp(<HOST>:<port>)/<dbname>"이다. 아래는 사용 예제다.
user@unix(/path/to/socket)/dbname?charset=utf8
user:password@tcp(localhost:5555)/dbname?charset=utf8
user:password@/dbname
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname
sql.Open()으로 오픈한 데이터베이스는 Close() 메서드를 이용해서 반드시 닫아줘야 한다.

db.Prepare()는 실행할 SQL 작업을 반환한다. 이 자체로 작업을 수행하는 것은 아니고, 준비를 끝낸 상태를 반환 한다.

stmt.Exec() db.Prepare()로 준비된 SQL 작업을 실행한다.

db.Query() SQL문을 실행하고 Rows를 반환한다. 만약 하나의 row만을 가져오고 싶다면 db.QueryRow()를 사용하면 된다.
var username string
err != db.QueryRow("SELECT username FROM userinfo where uid=?", uid).Scan(&username)
if err != nil && err != sql.ErrnoRows {
   // ....
}

=?는 SQL에 매개변수를 전달하기 위해서 사용한다. 이렇게 사용함으로써 SQL 인젝션(injection)공격을 막을 수 있다.

Connection Pool

database/sql 패키지는 기본적인 connection pool 기능을 가지고 있다. 이 connection pool을 이용해서 아래와 같은 지원을 받을 수 있다.
  • connection pool을 사용하기 때문에 단일 데이터베이스에서 두 개의 SQL을 실행하면, 두개의 연결을 사용 해서 동시에 실행 할 수 있다.
  • connection pool은 연결이 필요 할 때 자동으로 만들어지며, 자원을 해제하기 위해서 개발자가 개입할 필요가 없다.
  • 기본적으로 connection 갯수에 제한이 없다. 한번에 많은 일을 하고 싶다면, 많은 연결을 만들 것이다. 다만 데이터베이스가 허용 할 수 있는 연결의 갯수를 넘을 경우 "too many connections"에러가 뜰 수 있다.
  • Go 1.1 이후 버전은 db.SetMaxIdleConns(N)을 제공한다. 이 메서드를 이용해서 유휴 연결을 N개까지 유지 할 수 있다. 유휴 연결이란 사용하지 않은 상태로 남아있는 연결을 의미한다. 유휴 연결은 재사용 할 수 있다.
  • 연결을 빠르게 재 사용 할 수 있다. db.SetMaxIdleConns(N)에 의해서 만들어진 유휴연결이 재 사용된다.
  • Go 1.2.1 이후 버전은 db.SetMaxOpenConns(N)을 제공한다. 이 메서드를 이용해서, 데이터베이스에 대한 최대 연결 갯수를 제한 할 수 있다.
  • 오랜 시간 유휴 상태로 남은 연결은 Azure Mysql 등에서 문제가 될 수 있다. 유휴 시간이 오래되면 서버측에서 연결을 끊어버리기 때문으로 이 경우에는 db.SetMaxIdleConns(0)을 설정하면 된다.