2010-09-10 13 views
42

मुझे डेटाबेस (PostgreSQL) से सभी पंक्तियों को संसाधित करने में समस्या है। मुझे एक त्रुटि मिलती है: org.postgresql.util.PSQLException: Ran out of memory retrieving query results. मुझे लगता है कि मुझे छोटे टुकड़ों में सभी पंक्तियों को पढ़ने की जरूरत है, लेकिन यह काम नहीं करता है - यह केवल 100 पंक्तियां (नीचे कोड) पढ़ता है। उसको कैसे करे?विशाल तालिका से सभी पंक्तियों को कैसे पढ़ा जाए?

int i = 0;  
    Statement s = connection.createStatement(); 
    s.setMaxRows(100); // bacause of: org.postgresql.util.PSQLException: Ran out of memory retrieving query results. 
    ResultSet rs = s.executeQuery("select * from " + tabName);  
    for (;;) { 
     while (rs.next()) { 
      i++; 
      // do something... 
     } 
     if ((s.getMoreResults() == false) && (s.getUpdateCount() == -1)) { 
      break; 
     }   
    } 

उत्तर

33

उपयोग: जावा कोड में, मैं आपको सरल बयान के बजाय PreparedStatement उपयोग करने के लिए सुझाव है a CURSOR in PostgreSQL या let the JDBC-driver handle this for you

सीमा और जब बड़े डेटा सेट से निपटने के धीमी गति से मिल जाएगा ऑफसेट।

+0

दूसरा लिंक काम नहीं कर रहा है ... – snorbi

+0

इसे आजमाएं: http://jdbc.postgresql.org//documentation/head/query।एचटीएमएल # fetchsize-example –

1

मुझे लगता है कि आपके सवाल का इस सूत्र के समान है: JDBC Pagination जो अपनी जरूरत के लिए समाधान में शामिल है।

विशेष रूप से, PostgreSQL के लिए, आप सीमा का उपयोग करें और अपने अनुरोध में कीवर्ड OFFSET कर सकते हैं: http://www.petefreitag.com/item/451.cfm

पुनश्च: http://download.oracle.com/javase/tutorial/jdbc/basics/prepared.html

+2

बस वसंत का उपयोग करें, व्यावहारिक रूप से जेडीके कक्षाओं के खिलाफ कभी भी कोड की आवश्यकता नहीं है - http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/jdbc.html –

+0

LIMIT और ऑफसेट बहुत बड़े परिणामों के लिए अच्छी तरह से स्केल नहीं करता है: \ – rogerdpack

0

मैंने इसे नीचे जैसा किया था। नहीं सबसे अच्छा तरीका है मुझे लगता है कि है, लेकिन यह काम करता है :)

Connection c = DriverManager.getConnection("jdbc:postgresql://...."); 
    PreparedStatement s = c.prepareStatement("select * from " + tabName + " where id > ? order by id"); 
    s.setMaxRows(100); 
    int lastId = 0; 
    for (;;) { 
     s.setInt(1, lastId); 
     ResultSet rs = s.executeQuery(); 

     int lastIdBefore = lastId; 
     while (rs.next()) { 
      lastId = Integer.parseInt(rs.getObject(1).toString()); 
      // ... 
     } 

     if (lastIdBefore == lastId) { 
      break; 
     } 
    } 
55

लघु संस्करण है, फोन stmt.setFetchSize(50); और conn.setAutoCommitMode(false); स्मृति में पूरे ResultSet पढ़ने से बचने के लिए।

यहाँ डॉक्स का कहना है: एक कर्सर

डिफ़ॉल्ट रूप से आधार पर

परिणाम प्राप्त करना चालक एक ही बार में क्वेरी के लिए सभी परिणाम एकत्र करता है। यह बड़े डेटा सेट के लिए असुविधाजनक हो सकता है, इसलिए जेडीबीसी ड्राइवर डेटाबेस कर्सर पर एक परिणामसेट बेसिंग का आधार प्रदान करता है और केवल पंक्तियों की एक छोटी संख्या लाता है।

कनेक्शन के क्लाइंट साइड पर पंक्तियों की एक छोटी संख्या को कैश किया जाता है और कर्सर को दोबारा बदलकर पंक्तियों के अगले ब्लॉक को समाप्त कर दिया जाता है।

नोट:

  • कर्सर आधारित resultsets सभी परिस्थितियों में नहीं किया जा सकता। प्रतिबंधों की संख्या जो ड्राइवर को चुपचाप पर वापस आती है, पूरे परिणामसेट को एक बार में लाती है।

  • सर्वर से कनेक्शन वी 3 प्रोटोकॉल का उपयोग होना चाहिए। इस के लिए डिफ़ॉल्ट सर्वर संस्करण 7.4 और later.-

  • कनेक्शन autocommit मोड में नहीं होना चाहिए है (और केवल द्वारा समर्थित है)। बैकएंड लेनदेन के अंत में कर्सर बंद कर देता है, autocommit मोड में तो बैकएंड कुछ भी करने से पहले कर्सर बंद कर दिया है जाएगा it.- से दिलवाया जा सकता है

  • वक्तव्य की एक ResultSet प्रकार के साथ बनाया जाना चाहिए परिणामसेट। TYPE_FORWARD_ONLY।यह डिफ़ॉल्ट है, इसलिए कोई कोड इस का लाभ लेने के फिर से लिखा जा करने की आवश्यकता होगी, लेकिन यह भी मतलब है कि आप ResultSet.-

  • क्वेरी दी में चारों ओर पीछे की ओर है या नहीं तो कूद स्क्रॉल नहीं कर सकते अर्धविरामों के साथ मिलकर कई बयानों को नहीं, एक कथन होना चाहिए।

उदाहरण 5.2। कर्सर को चालू और बंद करने के लिए फ़ेच आकार सेट करना।

कर्सर मोड में कोड बदलना उतना सरल है जितना कि स्टेटमेंट के लांच आकार को उचित आकार में सेट करना आसान है। लाने का आकार वापस 0 पर सेट करने से सभी पंक्तियों को कैश किया जा सकता है (डिफ़ॉल्ट व्यवहार)।

// make sure autocommit is off 
conn.setAutoCommit(false); 
Statement st = conn.createStatement(); 

// Turn use of the cursor on. 
st.setFetchSize(50); 
ResultSet rs = st.executeQuery("SELECT * FROM mytable"); 
while (rs.next()) { 
    System.out.print("a row was returned."); 
} 
rs.close(); 

// Turn the cursor off. 
st.setFetchSize(0); 
rs = st.executeQuery("SELECT * FROM mytable"); 
while (rs.next()) { 
    System.out.print("many rows were returned."); 
} 
rs.close(); 

// Close the statement. 
st.close(); 

+0

क्या इसका कोई नुकसान है? क्या मुझे इसे सभी प्रश्नों के लिए सक्षम करना चाहिए (दस्तावेज़ में शब्द से यह सभी मामलों में बेहतर लगता है; यदि आप बड़ी टेबल पढ़ रहे हैं तो यह बेहतर है और यदि आप छोटी टेबल पढ़ रहे हैं तो इससे कोई फर्क नहीं पड़ता) –

0

मेरे मामले समस्या में ऐसा न हो कि पर ग्राहक परिणाम लाने के लिए कोशिश करता है पर था।

सभी परिणामों के साथ .csv प्राप्त करना चाहता था।

मैं

psql -U postgres -d dbname -c "COPY (SELECT * FROM T) TO STDOUT WITH DELIMITER ','" 

(जहां db ... के नाम DBNAME) का उपयोग कर और एक फ़ाइल के लिए पुनः निर्देशित द्वारा समाधान मिल गया।

0

तो यह पता चला है कि समस्या की जड़ यह है कि डिफ़ॉल्ट रूप से, Postgres, "autoCommit" मोड में शुरू होता है और यह भी/कर्सर का उपयोग करता है डेटा के माध्यम से "पृष्ठ" करने में सक्षम होने की जरूरत है (पूर्व: पहले 10K पढ़ नतीजे, फिर अगला, फिर अगला), हालांकि कर्सर केवल लेनदेन के भीतर ही मौजूद हो सकते हैं। तो डिफॉल्ट सभी पंक्तियों को हमेशा रैम में पढ़ना है, और फिर अपने प्रोग्राम को "पहली परिणाम पंक्ति, फिर दूसरा" प्रसंस्करण शुरू करने की अनुमति देता है, दो कारणों से, यह लेनदेन में नहीं है (इसलिए कर्सर काम न करें), और एक fetch आकार भी सेट नहीं किया गया है।

तो psql कमांड लाइन टूल प्रश्नों के लिए बैच प्रतिक्रिया (अपने FETCH_COUNT सेटिंग) को प्राप्त होता है, एक अल्पकालिक लेन-देन के भीतर अपनी चुनिंदा प्रश्नों "रैप" (यदि किसी लेन-देन अभी तक खुला नहीं है), तो वह यह है कि कर्सर काम कर सकते हैं। भले ही आप राम को बचाने के लिए की जरूरत नहीं है

static void readLargeQueryInChunksJdbcWay(Connection conn, String originalQuery, int fetchCount, ConsumerWithException<ResultSet, SQLException> consumer) throws SQLException { 
    boolean originalAutoCommit = conn.getAutoCommit(); 
    if (originalAutoCommit) { 
     conn.setAutoCommit(false); // start temp transaction 
    } 
    try (Statement statement = conn.createStatement()) { 
     statement.setFetchSize(fetchCount); 
     ResultSet rs = statement.executeQuery(originalQuery); 
     while (rs.next()) { 
     consumer.accept(rs); // or just do you work here 
     } 
    } finally { 
     if (originalAutoCommit) { 
     conn.setAutoCommit(true); // reset it, also ends (commits) temp transaction 
     } 
    } 
    } 
    @FunctionalInterface 
    public interface ConsumerWithException<T, E extends Exception> { 
    void accept(T t) throws E; 
    } 

यह कम रैम की आवश्यकता होती है के लाभ देता है, और, मेरे परिणामों में, कुल मिलाकर तेजी से चलाने के लिए लग रहा था,: आप JDBC के साथ भी ऐसा ही कुछ कर सकते हैं। अजीब। यह लाभ भी देता है कि पहली पंक्ति की आपकी प्रसंस्करण "तेज़ी से शुरू होती है" (क्योंकि यह एक समय में एक पृष्ठ को संसाधित करती है)।

और यहाँ कैसे "कच्चे postgres कर्सर" जिस तरह से यह करने के लिए, पूर्ण डेमो code के साथ साथ, हालांकि मेरे प्रयोगों में यह JDBC तरह से लग रहा था है, ऊपर, थोड़ा जो भी कारण के लिए तेजी से किया गया था।

एक और विकल्प autoCommit मोड बंद होना होगा, हालांकि आपको अभी भी प्रत्येक नए स्टेटमेंट के लिए मैन्युअल रूप से एक fetchSize निर्दिष्ट करना होगा (या आप URL स्ट्रिंग में डिफ़ॉल्ट फ़ेच आकार सेट कर सकते हैं)।

संबंधित मुद्दे