Database sql

3,463 views

Published on

There are some things to keep in mind about using golang's database / SQL library.

Published in: Engineering
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,463
On SlideShare
0
From Embeds
0
Number of Embeds
3,165
Actions
Shares
0
Downloads
7
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Database sql

  1. 1. database/sqlとコネクション Talos208
  2. 2. TL;DR database/sqlには意図した使用法がある database/sqlにはDBとの接続数を設定するAPIがいくつかある ライブラリのソースも読んでみよう
  3. 3. WhoamI 株式会社スプラウトR&D TechLead 組込み→Windowsゲーム→SIer(Web系BtoB)→セキュリティと流 れてきたので、無駄に色々できる Twitter : Talos208 GitHub: https://github.com/Talos208
  4. 4. database/sql go標準のSQLライブラリ 使用法 db, err := sql.Open("driver name", "dsn") if err != nil { return nil, err } rows, err := db.Query(...) db.Close() ……ってしたくなりますよね?
  5. 5. Db.Close()のドキュメント こんな記述が…… It is raretoCloseaDB , as theDB handleis meant tobelong‑ livedandsharedbetweenmany goroutines. 訳) DBをCloseすることは、ほとんどない。なぜなら、DBハンドルは長期間 生存して複数のgoroutine間で共有することを意図しているから。
  6. 6. ソースも見てみよう type DB struct { driver driver.Driver dsn string numClosed uint64 mu sync.Mutex freeConn []*driverConn connRequests []chan connRequest numOpen int // (後略) }
  7. 7. ソースも見てみよう(2) func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) { // (略) numFree := len(db.freeConn) if strategy == cachedOrNewConn && numFree > 0 { conn := db.freeConn[0] copy(db.freeConn, db.freeConn[1:]) db.freeConn = db.freeConn[:numFree-1] conn.inUse = true // (略) return conn, nil // freeConnの先頭を再利用 } // (略) db.numOpen++ // optimistically db.mu.Unlock() ci, err := db.driver.Open(db.dsn) // ここでOpen // (略) }
  8. 8. ソースも見てみよう(3) func (db *DB) Close() error { // (略) var err error fns := make([]func() error, 0, len(db.freeConn)) for _, dc := range db.freeConn { fns = append(fns, dc.closeDBLocked()) } db.freeConn = nil // (略) for _, fn := range fns { err1 := fn() if err1 != nil { err = err1 } } return err }
  9. 9. ソースから読み取れたこと  sql.Open() で返ってくるDB構造体は、コネクションプールを持 っている 接続をするときには、まずプールの空きコネクションを使用する  DB.Exec() / DB.Query() などから自動的に DB.conn() が呼 ばれる。あれば空きコネクションが利用され、無ければ新規コ ネクションが取得される  DB.Close() は、全ての空きコネクションを切断する
  10. 10. つまり 都度Open()/Close()すると、その度にコネクション接続/切断のコス トがかかる DBへのログインのコストもかかる 並行して複数のDB構造体を持つと、余計なメモリを消費する これを避けるために  sql.Open() は起動時に1回だけ呼ぶ  DB.Close() は最後に1回呼ぶ
  11. 11. 接続数の制限 func(*DB) SetMaxIdleConns  func (db *DB) SetMaxIdleConns(n int)  空きコネクションの最大数を指定。0だとコネクションプーリング を行わない func(*DB) SetMaxOpenConns  func (db *DB) SetMaxOpenConns(n int)  (空きも含めた)全コネクションの最大数。0だと上限なし 最大値しか設定できない。しばらく経つと、最大数に張り付いたままに なる
  12. 12. What happen? 症状 sql.Open()は出来る DB.Ping()も成功する DB.Query()を実行するとコネクションエラー ????
  13. 13. Condition サーバとDBとの間に、MariDB Maxscaleを使用していた Maxscale MySQL(MariaDB)向けのL7ロードバランサ クエリ内容を見て SELECT はリードレプリカに、 UPDATE はマ スターにとか出来る
  14. 14. Cause 1. サーバ<‑> Maxscaleのコネクションは存在 Maxscaleとはつながるので、DB.Ping()は成功 2. Maxscale<‑> MySQLの接続がタイムアウトしていた サーバにタイムアウトが通知されない 3. クエリ発行時にコネクションエラー じつはMaxscaleがエラーを出していた
  15. 15. SetConnMaxLifetime() Go1.6から新しいAPIが導入された func(*DB) SetConnMaxLifetime  func (db *DB) SetConnMaxLifetime(d time.Duration)  一定時間以上経った接続は再利用しない これを使うだけのためにGo1.4‑>Go1.6に移行
  16. 16. 解決 1.  SetConnMaxLifetime() の設定により、サーバ<‑> Maxscale間 のコネクションのほうが先に切れる 2. DB.Ping()の時点で、コネクションが再接続される 3. 正常にクエリ発行

×