Java SQLite插入会随着时间的推移缓慢爬行
我正在使用下面的程序将非常大的.csv文件(约250万行)中的值插入到SQLite数据库中。它开始非常快,但随着时间的推移开始变慢,最后无限期地挂在90万行左右。我的直觉是,它在某种程度上消耗内存,但不完全是内存泄漏,因为它从不抛出Java SQLite插入会随着时间的推移缓慢爬行,java,performance,sqlite,jdbc,database-performance,Java,Performance,Sqlite,Jdbc,Database Performance,我正在使用下面的程序将非常大的.csv文件(约250万行)中的值插入到SQLite数据库中。它开始非常快,但随着时间的推移开始变慢,最后无限期地挂在90万行左右。我的直觉是,它在某种程度上消耗内存,但不完全是内存泄漏,因为它从不抛出OutOfMemoryException或类似的异常。明确地说,该程序从未失败或崩溃。它只是变得越来越慢,直到停止前进。我的笔记本电脑上的所有其他进程也会受到影响,最终甚至需要10秒钟才能注册鼠标移动 我对数据库不是很有经验,所以我在执行INSERT语句时很可能会做一
OutOfMemoryException
或类似的异常。明确地说,该程序从未失败或崩溃。它只是变得越来越慢,直到停止前进。我的笔记本电脑上的所有其他进程也会受到影响,最终甚至需要10秒钟才能注册鼠标移动
我对数据库不是很有经验,所以我在执行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(); }
}
}
尝试删除
setAutoCommit
调用,并仅在批处理了相当多的插入时执行executeBatch
。另外,不要在每次插入时打印到控制台。例如:
publicstaticvoid addcsvtodabase(连接c,字符串csvFile){
BufferedReader reader=null;
int批=0;
int-total=0;
菜豆b;
字符串[]VAL;
ByteArrayOutputStream baos=null;
ObjectOutputStream oos=null;
PreparedStatement pstmt=null;
String sql=“在结果中插入或忽略(”
+“头衔,”
+“URL,”
+“豆子”
+)值(?,?);”;
试一试{
pstmt=c.prepareStatement(sql);
reader=新的BufferedReader(新的InputStreamReader(新的文件InputStream(csvFile),“UTF-8”);
for(字符串行;(line=reader.readLine())!=null;){
VAL=行分割(“\\\\”);
b=新的DBEntryBean();
b、 setTitle(VAL[0]);
b、 setURL(VAL[1]);
bas=新的ByteArrayOutputStream();
oos=新对象输出流(BAS);
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();
++批次;
++总数;
如果(批次==10000){
pstmt.executeBatch();
系统输出打印项次(“总计:+总计);
批次=0;
}
}
如果(批次>0){
pstmt.executeBatch();
系统输出打印项次(“总计:+总计);
}
}catch(异常e){e.printStackTrace();
}最后{
试试{
如果(pstmt!=null){pstmt.close();}
}catch(SQLException e){e.printStackTrace();}
}
}
如果性能仍然很糟糕,我建议您一次更改一件事情,看看是否可以隔离问题。例如,删除
URL
列上的UNIQUE
索引,查看它总是插入时的性能。或者删除BLOB的插入,等等。添加一个。@ElliottFrisch谢谢,这听起来很有希望。关于我应该多长时间执行一次循环中的VACUUM
语句,您有什么建议吗?我建议尝试执行10k插入,看看这是否会改善情况。不要将所有插入都放在一个批次中。正如您的回答中所述,批处理应保持相对较小的大小。此外,使用executeBatch
,切换设置自动提交
并不重要,因为没有提交
,因此应将其删除。