6/7/2017
ツイート
clojure.java.jdbcのマルチインサートは引数に注意
ClojureのJDBCラッパーであるclojure.java.jdbcを用いて、MySQLなどのRDBにデータを高速インサートしたい場合、insert-multi!
を用いて一括インサートすると良い。しかし、この関数は引数の与え方によってパフォーマンスが大きく変わってくるので注意しなければならない。
insert-multi!
はインサートしたいデータに対応する引数として、
- マップのシーケンスを渡す:
({:foo 1 :bar -1} {:foo 2 :bar -2} ...)
- ベクタ+ベクタのシーケンスを渡す:
[:foo :bar] ([1 -1] [2 -2] ...)
の2種類の方法がある。実は、方法2のほうが高速である。
clojure.java.jdbc 0.7.0-alpha3を使用して検証する。たとえば次のテーブル、
CREATE TABLE example (
id INT NOT NULL AUTO_INCREMENT,
val1 INT,
val2 INT,
PRIMARY KEY (id))
ENGINE = InnoDB;
に対して、1,000レコードのマルチインサートを100回行ってみる。
方法1を用いてマルチインサートを行うと、
(time
(dotimes [_ 100]
(jdbc/insert-multi! db-spec :example
(map (fn [n]
{:val1 n, :val2 (inc n)})
(take 1000 (repeatedly #(rand-int 100)))))))
;; "Elapsed time: 11948.622137 msecs"
だが、方法2を用いてマルチインサートを行うと、
(time
(dotimes [_ 100]
(jdbc/insert-multi! db-spec :example
[:val1 :val2]
(map (fn [n]
[n (inc n)])
(take 1000 (repeatedly #(rand-int 100)))))))
;; "Elapsed time: 6845.109233 msecs"
となり、この例では方法2のほうが1.74倍速い。
ソースコードを読むとわかるが、方法1ではマップごとに別々のSQLが生成され、まとめて実行される。これでもinsert!
を繰り返すよりは十分速いが、方法2ではひとつのSQLにデータを全て入れて実行されるため、本来の意味でのマルチインサートとなっており、さらに高速である。
一方で、方法1ではAUTO INCREMENTなid
の値が:generated_key
として返ってくるが、方法2では何も返らない、という違いもある。使用シーンに応じて適切に使い分けるのが良い。