Lapis Lazuli

technical blog for web developer

【SQL】大量のinsert処理DBに投げたときに時間を短縮する方法

それは突然起きた・・・

こんにちはユウスケです。
ちょっと前に、大きめのCSVをDBにインポートする処理を仕事で作りました。
処理自体はGoでCSVを1行ずつ読み取ってデータベースにinsertするというシンプルなものでしたが・・・

とてつもなく遅い

という問題が発生しました。
行数的に14万行程度だったのですが、なんと2時間近くかかりました。

原因

結論から書くと、原因は1行のinsert毎にコミットしてたからです。
恥ずかしながら今の今まで意識したことがなかったのですが、SQLを流した時、実際に反映するコミット処理はとても重い処理のようで、これが毎行やることでDB側の負荷が増大してました。
しかも1行で2つのテーブルにまたがって一気にinsertする仕組みなので、コミットの負荷がとてつもない事になってました。

ちなみに最初、「毎回レコードの多いテーブルをselectしてるから重いのかな?」と思い、select文を流す回数を減らし効率化しましたが、それでもあまり変わりませんでした。

対処法

最初、Multipulinsertを使って複数行をまとめてコミットしようとしました。
が、GoのORMパッケージはgormを使っているのですが、これにはMultipulinsertが存在しなく・・・コミットのタイミングを調整することが出来ませんでした。
結局トランザクションを使って全部まとめてコミットする。というところに落ち着きました。

gorm.io


この結果、なんと5分弱で完了しました。実に1時間55分の短縮です。
なんというスピード。今までの時間は何だったんでしょうか。っていうレベルです。

14万行まとめてコミットでもこのスピードなので満足ですが、コミット時の負荷を考えるなら1万行毎にコミットでもいいかもしれませんね。

おわりに

大量のクエリを投げるという経験がないと、なかなかSQLの仕組みの深いところまで理解する。っていう事がないと思います。
特にフレームワークを使っているとORMがいい感じにしてくれるので、これに頼ってしまうんですよねぇ・・・便利な反面、内部の構造的な知見が溜まりにくいといいますか。
なので今回の経験はいい経験でしたし、次回のプロジェクトにも活かせるのではないかな。と、思っています。