database/sqlのrowsのメモリ量を調査する
今回はGoの「database/sql」を使って、結果が大きいクエリを発行したときにヒープのメモリ量を見ていきたいと思います。
検証理由
多くの場合LimitとOffsetを指定するのですが、大量の結果が返されるときに rows がどのくらいメモリを使っているのか知りたくなりました。
rows.Next と rows.Scan を利用するので、 rows に結果が一度に入りヒープのメモリ量が増えることはないだろうという前提での検証です。準備
MySqlにUUIDが入っているカラムと、テキトウな文字を入れるカラムを用意して、そこに30,000件のデータを登録しておきます。
1 2 3 4 5 6 |
mysql> select count(*) from dummy; +----------+ | count(*) | +----------+ | 30000 | +----------+ |
1 2 3 |
*************************** 30000. row *************************** id: fffedc81-f3f0-4de3-86b5-3b029f502e6a content: Voluptatem sit accusantium perferendis aut consequatur. Aut perferendis voluptatem sit accusantium consequatur. Voluptatem sit accusantium aut perferendis consequatur. Voluptatem aut perferendis consequatur sit accusantium. |
内容はこんな感じになります。
次に、検証用のコードを書きます。
今回は、簡単にヒープに割り当てられたメモリ量を表示しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package query import ( "database/sql" "fmt" "log" "runtime" _ "github.com/go-sql-driver/mysql" ) var mem runtime.MemStats func Query() { db, err := sql.Open("mysql", "user:password@tcp(localhost:49152)/foo") if err != nil { panic(err) } db.SetMaxOpenConns(20) db.SetMaxIdleConns(20) var ( id string content string ) rows, err := db.Query("SELECT id, content FROM dummy") if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { err := rows.Scan(&id, &content) if err != nil { log.Fatal(err) } } // runtime.GC() runtime.ReadMemStats(&mem) fmt.Println(mem.Alloc, mem.TotalAlloc) err = rows.Err() if err != nil { log.Fatal(err) } } |
実際に動かしてメモリを計測する
それでは、sql部分にLimitを追加して検証していきたいと思います。
左から、ヒープに割り当てられたメモリ量、ヒープに割り当てられたメモリの総量(減らない)、OSから取得したメモリ量です。
Limit: 10
376048 376048 71650320
376256 376256 71912464
376544 376544 71650320
Limit: 10000
1202960 4504840 73679880
1235760 4505128 73942024
1134624 4504952 73483272
Limit: 20000
1510472 8630744 74007560
1460512 8629504 74269704
1462568 8632184 74269704
Limit: 30000
1823432 12766528 74269704
1945400 12764736 74269704
1959984 12764528 74269704
Limitを上げると順調にヒープが育っていってます。
次に、上記コードのGC部分のコメントを外して実行します。(検証用にGCをコールしています)
Limit: 10
223784 382464 71715600
226216 384672 72239888
226264 385360 72239888
Limit: 10000
224800 4503592 74269704
224472 4504720 74269704
226152 4504200 74269704
Limit: 20000
226848 8632624 74269704
229000 8633136 74007560
224312 8628920 74007560
Limit: 30000
228488 12765656 74269704
226488 12764600 74269704
224776 12764296 74007560
今度はGCによって削除されているのがわかります。
次に、ループ中のメモリを調べたいので、少し強引ですが rows.Next のループ毎にGCを呼んでみます。
毎回GCを呼ぶのでかなり遅くなることが予想されます。
1 2 3 4 5 6 7 8 9 10 |
for rows.Next() { err := rows.Scan(&id, &content) if err != nil { log.Fatal(err) } runtime.GC() runtime.ReadMemStats(&mem) fmt.Println(mem.Alloc, mem.TotalAlloc, mem.Sys) } |
Limit: 10
228624 399640 74007560
228640 401680 74007560
228632 403752 74007560
Limit: 10000
250304 22738464 74269704
250304 22741000 74269704
250400 22743632 74269704
Limit: 20000
251448 45285408 74335240
251448 45287880 74335240
251448 45290352 74335240
Limit: 30000
253800 67179272 74269704
253800 67181200 74269704
253800 67183336 74269704
手元の環境だと30000レコードで9秒かかりました。
こちらもヒープのメモリ量は一定になりましたが、GCが多発すると遅くなることが実感できます。
まとめ
簡易的な検証でしたが、以下のような予想ができます。
- 一度に大量の行をクエリすると、database/sqlのrowsのヒープは増えていく
- GCが実行されると削除されるが、GCが過剰に実行されると遅くなる

tkr2f

最新記事 by tkr2f (全て見る)
- 安全性、速度、並行性を兼ね備えた言語と、巷でうわさの「Rust」を覗いてみる(その3) - 2022年6月29日
- 安全性、速度、並行性を兼ね備えた言語と、巷でうわさの「Rust」を覗いてみる(その2) - 2022年4月19日
- 安全性、速度、並行性を兼ね備えた言語と、巷でうわさの「Rust」を覗いてみる(その1) - 2022年3月8日
- database/sqlのrowsのメモリ量を調査する - 2021年12月8日
- Golangでデッドロックを作って遊んでみる~並行処理でデッドロックを起こさないために~ - 2021年10月7日
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー