2013-05-02 24 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允許的堆大小是很好的。