2017-01-08 11 views
3

Я использую программу ниже, чтобы вставить значения из очень больших .csv-файлов (~ 2,5 миллиона строк) в SQLite DB. Он начинается очень быстро, но со временем начинает замедляться, прежде чем в конечном итоге висит бесконечно около 900 000 линий. Моя догадка говорит, что это как-то поедает память, но не совсем утечка памяти, поскольку она никогда не бросает OutOfMemoryException или подобное. Чтобы быть ясным, программа никогда не терпит неудачу или не сработает. Он только замедляется, пока не прекратится прогрессирование. Все остальные процессы на моем ноутбуке также затронуты, и в конечном итоге требуется ~ 10 секунд, чтобы даже зарегистрировать движения мыши.Вложения SQLite медленнее сканируются с течением времени

Я не очень опытен с базами данных, поэтому это может быть просто глупо, я делаю с тем, как я выполняю оператор INSERT. Самая последняя модификация, которую я сделал, заключалась в использовании PreparedStatement.addBatch() и PreparedStatement.executeBatch(), и, несмотря на чтение документации, я все еще не очень понимаю, правильно ли я их использую. Я использую sqlite-jdbc-3.7.2.jar, если это имеет значение.

public class Database{ 

     public static void main(String[] args){ 
      Connection c = connect("db.db"); 
//   createTable(c); 
      addCSVToDatabase(c, "test-10000.csv"); 
//   print(c); 
      disconnect(c); 
     } 

     public static void createTable(Connection c) { 
      Statement stmt; 
      String sql = "CREATE TABLE results(" 
        + "ID   INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " 
        + "TITLE   TEXT  NOT NULL, " 
        + "URL   TEXT  NOT NULL UNIQUE, " 
        + "BEAN BLOB" 
        + ");"; 
      System.out.println("QUERY: " + sql); 
      try { 
       stmt = c.createStatement(); 
       stmt.executeUpdate(sql); 
      } catch (SQLException e) { e.printStackTrace();} 
     } 

     public static void addCSVToDatabase(Connection c, String csvFile){ 

      BufferedReader reader = null; 
      int x = 0; 
      DBEntryBean b; 
      String[] vals; 
      ByteArrayOutputStream baos = null; 
      ObjectOutputStream oos = null; 
      PreparedStatement pstmt = null; 
      String sql = "INSERT OR IGNORE INTO results(" 
        + "TITLE, " 
        + "URL, " 
        + "BEAN" 
        + ") VALUES(?, ?, ?);"; 
      try{ 
       pstmt = c.prepareStatement(sql); 
       reader = new BufferedReader(new InputStreamReader(new FileInputStream(csvFile), "UTF-8")); 
       c.setAutoCommit(false); 

       for(String line; (line = reader.readLine()) != null;){ 

        vals = line.split("\\|"); // Each line is of the form: "title|URL|...|...|..." 
        b = new DBEntryBean(); 
        b.setTitle(vals[0]); 
        b.setURL(vals[1]); 

        pstmt.setString(Constants.DB_COL_TITLE, b.getTitle());  
        pstmt.setString(Constants.DB_COL_URL, b.getURL()); 

        // Store the DBEntryBean in the table so I can retrieve it, rather than construct a new one every time I need it. 
        baos = new ByteArrayOutputStream(); 
        oos = new ObjectOutputStream(baos); 
        oos.writeObject(b); 
        pstmt.setBytes(Constants.DB_COL_BEAN, baos.toByteArray()); 
        pstmt.addBatch(); 
        pstmt.executeBatch(); 
        System.out.println("Line: " + x++); 
       } 
      } catch (Exception e){ e.printStackTrace(); 
      } finally{ 
       try{ 
        if(pstmt != null){ pstmt.close(); } 
        c.setAutoCommit(true); 
       } catch (SQLException e) { e.printStackTrace(); } 
      } 
     } 

     private static Connection connect(String path) { 

      String url = "jdbc:sqlite:" + path; 
      Connection conn = null; 
      try { 
       Class.forName("org.sqlite.JDBC"); 
       conn = DriverManager.getConnection(url); 
      } catch (Exception e) { e.printStackTrace(); } 
      return conn; 
     } 

     private static void disconnect(Connection c) { 
      try{ if(c != null){ c.close(); } 
      } catch(SQLException e){ e.printStackTrace(); } 
     } 

     private static void print(Connection c){ 
      Statement stmt = null; 
      String sql = "SELECT * FROM results;"; 
      ResultSet rs = null; 
      try { 
       stmt = c.createStatement(); 
       rs = stmt.executeQuery(sql); 
       while(rs.next()){ 
        System.out.println(rs.getString("TITLE")); 
       } 
      } catch(Exception e){ e.printStackTrace(); } 
     } 


    } 
+1

Добавьте ['ВАКУУМ'] (https://sqlite.org/lang_vacuum.html). –

+0

@ElliottFrisch Спасибо, это звучит многообещающе. Есть ли у вас какие-либо советы относительно того, как часто я должен выполнять инструкцию 'VACUUM' в цикле? – Sam

+1

Я предлагаю попробовать после 10k вставок и посмотреть, улучшит ли это ситуацию. –

ответ

0

Попробуйте удалить setAutoCommit вызовы и выполнять executeBatch только тогда, когда достаточно большое количество вставок было порционный. Кроме того, не печатайте на консоли каждую вставку. Например:

public static void addCSVToDatabase(Connection c, String csvFile) { 

    BufferedReader reader = null; 
    int batch = 0; 
    int total = 0; 
    DBEntryBean b; 
    String[] vals; 
    ByteArrayOutputStream baos = null; 
    ObjectOutputStream oos = null; 
    PreparedStatement pstmt = null; 
    String sql = "INSERT OR IGNORE INTO results(" 
     + "TITLE, " 
     + "URL, " 
     + "BEAN" 
     + ") VALUES(?, ?, ?);"; 

    try { 
     pstmt = c.prepareStatement(sql); 
     reader = new BufferedReader(new InputStreamReader(new FileInputStream(csvFile), "UTF-8")); 

     for(String line; (line = reader.readLine()) != null;) { 

      vals = line.split("\\|"); 
      b = new DBEntryBean(); 
      b.setTitle(vals[0]); 
      b.setURL(vals[1]); 

      baos = new ByteArrayOutputStream(); 
      oos = new ObjectOutputStream(baos); 
      oos.writeObject(b); 

      pstmt.setString(Constants.DB_COL_TITLE, b.getTitle()); 
      pstmt.setString(Constants.DB_COL_URL, b.getURL()); 
      pstmt.setBytes(Constants.DB_COL_BEAN, baos.toByteArray()); 

      pstmt.addBatch(); 
      ++batch; 
      ++total; 

      if (batch == 10000) { 
       pstmt.executeBatch(); 
       System.out.println("Total: " + total); 
       batch = 0; 
      } 
     } 

     if (batch > 0) { 
      pstmt.executeBatch(); 
      System.out.println("Total: " + total); 
     } 

    } catch (Exception e) { e.printStackTrace(); 
    } finally { 
     try{ 
      if(pstmt != null) { pstmt.close(); } 
     } catch (SQLException e) { e.printStackTrace(); } 
    } 
} 

Если производительность все еще страшно, я хотел бы предложить изменения одну вещь вовремя, чтобы увидеть, если вы можете локализовать проблему. Например, удалите индекс UNIQUE в столбце URL, чтобы увидеть, что такое производительность, если он всегда вставляет. Или удалите вставку BLOB и т. Д.

Смежные вопросы