2013-05-02 26 views
3

我正在做我认为是一个相当直接的任务:使用sqlkorma库(http://sqlkorma.com)运行一个sql查询(超过约65K行数据),并为每一行转换它某种方式,然后写入CSV文件。我并不真的认为65K行的容量非常大,因为我有8GB的笔记本电脑,但我也认为一个sql结果集会被延迟取出,所以整个事情永远不会同时存在内存中。所以,我真的很惊讶,当我结束了与该堆栈跟踪:clojure sqlkorma库:内存不足错误

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
at clojure.lang.PersistentHashMap$BitmapIndexedNode.assoc(PersistentHashMap.java:777) 
at clojure.lang.PersistentHashMap.createNode(PersistentHashMap.java:1101) 
at clojure.lang.PersistentHashMap.access$600(PersistentHashMap.java:28) 
at clojure.lang.PersistentHashMap$BitmapIndexedNode.assoc(PersistentHashMap.java:749) 
at clojure.lang.PersistentHashMap$TransientHashMap.doAssoc(PersistentHashMap.java:269) 
at clojure.lang.ATransientMap.assoc(ATransientMap.java:64) 
at clojure.lang.PersistentHashMap.create(PersistentHashMap.java:56) 
at clojure.lang.PersistentHashMap.create(PersistentHashMap.java:100) 
at clojure.lang.PersistentArrayMap.createHT(PersistentArrayMap.java:61) 
at clojure.lang.PersistentArrayMap.assoc(PersistentArrayMap.java:201) 
at clojure.lang.PersistentArrayMap.assoc(PersistentArrayMap.java:29) 
at clojure.lang.RT.assoc(RT.java:702) 
at clojure.core$assoc.invoke(core.clj:187) 
at clojure.core$zipmap.invoke(core.clj:2715) 
at clojure.java.jdbc$resultset_seq$thisfn__204.invoke(jdbc.clj:243) 
at clojure.java.jdbc$resultset_seq$thisfn__204$fn__205.invoke(jdbc.clj:243) 
at clojure.lang.LazySeq.sval(LazySeq.java:42) 
at clojure.lang.LazySeq.seq(LazySeq.java:60) 
at clojure.lang.Cons.next(Cons.java:39) 
at clojure.lang.PersistentVector.create(PersistentVector.java:51) 
at clojure.lang.LazilyPersistentVector.create(LazilyPersistentVector.java:31) 
at clojure.core$vec.invoke(core.clj:354) 
at korma.db$exec_sql$fn__343.invoke(db.clj:203) 
at clojure.java.jdbc$with_query_results_STAR_.invoke(jdbc.clj:669) 
at korma.db$exec_sql.invoke(db.clj:202) 
at korma.db$do_query$fn__351.invoke(db.clj:225) 
at clojure.java.jdbc$with_connection_STAR_.invoke(jdbc.clj:309) 
at korma.db$do_query.invoke(db.clj:224) 
at korma.core$exec.invoke(core.clj:474) 
at db$query_db.invoke(db.clj:23) 
at main$_main.doInvoke(main.clj:32) 
at clojure.lang.RestFn.applyTo(RestFn.java:137) 

据我可以从栈来讲,它并没有查询代码之外使它(这意味着它没有达到我的转变/写入CSV代码)。如果它很重要,我的SQL是相当简单的,基本上SELECT * FROM my_table WHERE SOME_ID IS NOT NULL AND ROWNUM < 65000 ORDER BY some_id ASC。这是oracle(解释上面的rownum),但我不认为这很重要。

编辑:

代码示例:

(defmacro query-and-print [q] `(do (dry-run ~q) ~q)) 
(defn query-db [] 
    (query-and-print 
     (select my_table 
      (where (and (not= :MY_ID "BAD DATA") 
         (not= :MY_ID nil) 
         (raw (str "rownum < " rows)))) 
      (order :MY_ID :asc)))) 

; args contains rows 65000, and configure-app sets up the jdbc 
; connection string, and sets a var with rows value 
(defn -main [& args] 
    (when (configure-app args) 
     (let [results (query-db) 
       dedup (dedup-with-merge results)] 
      (println "Result size: " (count results)) 
      (println "Dedup size: " (count dedup)) 
      (to-csv "target/out.csv" (transform-data dedup))))) 
+0

你可以编辑你的OP并添加一些源代码吗?同时建议将错误块修剪一下。 – octopusgrabbus 2013-05-02 15:45:35

+0

完成。不知道要从错误块中删除什么:它显示我的代码没有超过'(query-db)'调用,它也显示了在'clojure.java.jdbc'内OOM正在发生的位置。作为一个便笺,我开始研究'clojure.java.jdbc'代码,并且它并不像它那样懒惰(这对我来说很疯狂)。 – Kevin 2013-05-02 16:03:01

+0

当我添加'(println“结果类型:”(类型结果))'我得到'结果类型:clojure.lang.PersistentVector',我想这回答我的问题。 – Kevin 2013-05-02 16:28:03

回答

2

clojure.java.sql创建懒惰序列:

(defn resultset-seq 
"Creates and returns a lazy sequence of maps corresponding to 
the rows in the java.sql.ResultSet rs. Based on clojure.core/resultset-seq 
but it respects the current naming strategy. Duplicate column names are 
made unique by appending _N before applying the naming strategy (where 
N is a unique integer)." 
[^ResultSet rs] 
(let [rsmeta (.getMetaData rs) 
     idxs (range 1 (inc (.getColumnCount rsmeta))) 
     keys (->> idxs 
      (map (fn [^Integer i] (.getColumnLabel rsmeta i))) 
      make-cols-unique 
      (map (comp keyword *as-key*))) 
     row-values (fn [] (map (fn [^Integer i] (.getObject rs i)) idxs)) 
     rows (fn thisfn [] 
      (when (.next rs) 
       (cons (zipmap keys (row-values)) (lazy-seq (thisfn)))))] 
    (rows))) 

科尔马通过丢弃每行的矢量充分实现序列:

(defn- exec-sql [{:keys [results sql-str params]}] 
(try 
(case results 
    :results (jdbc/with-query-results rs (apply vector sql-str params) 
      (vec rs)) 
    :keys (jdbc/do-prepared-return-keys sql-str params) 
    (jdbc/do-prepared sql-str params)) 
(catch Exception e 
    (handle-exception e sql-str params)))) 
+0

如何解决这个问题,一次取一行并处理? – octopusgrabbus 2013-05-02 19:33:20

+0

不懒惰的原因很明显,因为你永远不知道什么时候实现了懒惰数据,并且当时底层sql连接将被关闭 – Ankur 2013-05-03 04:36:11

+2

有几个与此相关的pull请求:https://github.com/korma/Korma/pull/66和https://github.com/korma/Korma/pull/151 – Eelco 2013-05-05 07:07:41

1

除了with-lazy-results路由https://github.com/korma/Korma/pull/66,作为解决问题的完全不同的方式,您可以通过设置适当的标志来简单地增加JVM可用的堆大小。 JVM不允许使用您计算机上的所有可用内存;他们严格限于您告诉他们允许使用的数量。0

一种方法是在您的project.clj文件中设置:jvm-opts ["-Xmx4g"]。 (调整确切堆的大小是必要的。)另一种方法是做这样的事情:

export JAVA_OPTS=-Xmx:4g 
lein repl # or whatever lanuches your Clojure process 

with-lazy-results路线是,你可以在任何大小的结果集操作感更好,但它不是合并到主线科尔马并需要一些更新才能使用最新版本。无论如何,知道如何调整JVM允许的堆大小是很好的。