Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Database sql

7,075 views

Published on

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

Published in: Engineering
  • Be the first to comment

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. 正常にクエリ発行

×