> ## Content Index
> Fetch the complete content index at: https://www.popit.kr/llms.txt
> Use this file to discover other available public pages before exploring further.

# Golang database/sql 패키지 삽질기 - 2편 SQLite 메모리 데이터베이스
- URL: https://www.popit.kr/golang-database-sql-2-sqlite-in-memory/
- Published: 2019-05-30T22:27:14.000Z
- Updated: 2019-06-19T23:36:07.000Z
- Author: 유 영모
- Tags: #소프트웨어, golang, Go, database, sqlite, 메모리 데이터베이스, sqlite3

SQLite 데이터베이스는 파일뿐만 아니라 메모리 모드도 지원한다. 그래서 필자는 [데이터베이스 테스트 픽스처](https://www.popit.kr/writing-tests-with-test-fixtures/)로서 주로 SQLite 메모리 데이터베이스를 사용한다. 데이터베이스 드라이버는 프로그래밍 언어에서 데이터베이스를 다룰 때 필요하다. 필자의 경우 [go-sqlite3 드라이버](https://github.com/mattn/go-sqlite3?ref=popit.kr) 사용했다. 문제는 **데이터베이스 접속 URL에 따라 상이하게 동작한다는 것**이다.

![https://github.com/mattn/go-sqlite3](https://www.popit.kr/content/images/wp/2019/05/image2019-5-27_8-28-2-600x244.png)

https://github.com/mattn/go-sqlite3

이 글은 엄밀히 말하면 [go-sqlite3 드라이버](https://github.com/mattn/go-sqlite3?ref=popit.kr)를 사용하면서 겪었던 삽질이다. 다만 database/sql 패키지를 함께 쓰면서 겪었던 일이라서 엮어서 소개한다.

---

1. [매개변수 표시자](https://www.popit.kr/golang-database-sql-1-parameter-placeholders/)
2. **SQLite 메모리 데이터베이스**
3. [커넥션 풀](https://www.popit.kr/golang-database-sql-3-connection-pool/)

---

# No such table?

아래 코드는 SQLite를 메모리 데이터베이스로 사용하며 parent, child 테이블을 만들고 테스트 데이터를 넣는다. 그리고 parent 테이블과 child 테이블의 데이터를 출력한다.

```go
package main
 
import (
    "database/sql"
    "fmt"
    _ "github.com/mattn/go-sqlite3"
    "log"
)
 
func main() {
    // 메모리 데이터베이스
    db, err := sql.Open("sqlite3", ":memory:")
 
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
 
    db.Exec("CREATE TABLE `parent` (`id` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`))")
    db.Exec("INSERT INTO `parent` (`id`,`name`) VALUES (1,'부모A')")
    db.Exec("INSERT INTO `parent` (`id`,`name`) VALUES (2,'부모B')")
 
    db.Exec("CREATE TABLE `child` (`id` int(11) NOT NULL, `name` varchar(45) DEFAULT NULL, `parent_id` int(11) NOT NULL,PRIMARY KEY (`id`))")
    db.Exec("INSERT INTO `child` (`id`,`name`,`parent_id`) VALUES (1,'자식C',1)")
    db.Exec("INSERT INTO `child` (`id`,`name`,`parent_id`) VALUES (2,'자식D',1)")
    db.Exec("INSERT INTO `child` (`id`,`name`,`parent_id`) VALUES (3,'자식E',2)")
 
    parentRows, err := db.Query("SELECT id, name FROM parent")
    if err != nil {
        log.Fatal(err)
    }
     
    var parentId int64
    var parentName string
    for parentRows.Next() {
        err := parentRows.Scan(&parentId, &parentName)
        if err != nil {
            log.Fatal(err)
        }
 
        fmt.Println(parentId, ":" ,parentName)
 
        var childId int64
        var childName string
        childRows, err := db.Query("SELECT id, name FROM child WHERE parent_id = $1", parentId)
        if err != nil {
            log.Fatal(err)
        }
 
        for childRows.Next() {
            err := childRows.Scan(&childId, &childName)
            if err != nil {
                log.Fatal(err)
            }
 
            fmt.Println("ㄴ", childId, ":" ,childName)
        }
        childRows.Close()
    }
    parentRows.Close()
}
```

코드를 실행해 보면 **no such table: child** 오류를 만난다.

![image2019-5-20_9-16-51](https://www.popit.kr/content/images/wp/2019/05/image2019-5-20_9-16-51.png)

오류 지점은 바로 아래 코드이다.

```go
childRows, err := db.Query("SELECT id, name FROM child WHERE parent_id = $1", parentId)
```

무슨 말일까? parent 테이블은 있고, child 테이블은 없다니...

같은 코드를 메모리 데이터베이스가 아닌 파일 데이터베이스로 변경해서 실행해보자.

```go
package main
//...
func main() {
    // 파일 데이터베이스
    db, err := sql.Open("sqlite3", "file:article.db")
    //...
}
```

오류 없이 동작한다.

![image2019-5-24_8-5-29](https://www.popit.kr/content/images/wp/2019/05/image2019-5-24_8-5-29-600x219.png)

**왜 메모리 데이터베이스에만 오류가 나는 것일까?** 답은 역시 go-sqlite3 드라이버 문서에서 찾을 수 있었다. 그것도 FAQ에서..(많이 질문한다는 의미)

# 문서에 따르면...

필자는 앞서 예시 코드에서 메모리 데이터베이스를 사용하기 위해 ":memory:" 로 선언했다.

```go
// 메모리 데이터베이스
db, err := sql.Open("sqlite3", ":memory:")
```

이 경우 데이터베이스 커넥션들은 동시에 하나의 메모리 데이터베이스를 공유하지 않는다. 즉 **데이터베이스 커넥션이 열려 있는 상태에서 새로운 커넥션을 열게 되면 기존 데이터베이스가 아닌 새로운 빈 데이터베이스를** 할당받는다는 의미이다.

> Why I'm getting `no such table `error? Why is it racy if I use a`sql.Open("sqlite3", ":memory:")`database? Each connection to :memory: opens a brand new in-memory sql database, so if the stdlib's sql engine happens to open another connection and you've only specified ":memory:", that connection will see a brand new database. A workaround is to use "file::memory:?mode=memory&cache=shared". Every connection to this string will point to the same in-memory database. - [https://github.com/mattn/go-sqlite3#faq](https://github.com/mattn/go-sqlite3?ref=popit.kr#faq)

코드를 보며 좀 더 구체적으로 알아보자. 예시 코드에서는 커넥션 1을 생성하고 종료하지 않는 상태에서 커넥션 2를 생성한다. **이미 커넥션 1에서 메모리 데이터베이스를 사용하고 있기 때문에 커넥션 2는 새로운 빈 데이터베이스를 할당받는다. 따라서 테이블 스키마가 존재하지 않기 때문에 no such table 오류가 난 것이다.**

```go
// 커넥션 1 생성
parentRows, err := db.Query("SELECT id, name FROM parent")
if err != nil {
    log.Fatal(err)
}
// ...
for parentRows.Next() {
    // ...
    // 커넥션 2 생성
    childRows, err := db.Query("SELECT id, name FROM child WHERE parent_id = $1", parentId)
    if err != nil {
        log.Fatal(err)
    }
 
    // ...
    // 커넥션 2 종료
    childRows.Close()
}
 
// 커넥션 1 종료
parentRows.Close()
```

# 결론

[go-sqlite3 드라이버](https://github.com/mattn/go-sqlite3?ref=popit.kr) 문서 권고 따라 아래처럼 변경하면 모든 커넥션이 같은 데이터베이스를 공유하기 때문에 오류 없이 동작한다.

```go
// 메모리 데이터베이스
db, err := sql.Open("sqlite3", "file::memory:?mode=memory&cache=shared")
```