Java 为什么SQLite在JDBC中速度如此之慢?
A读了很多东西,比如: 但我不知道问题出在哪里。 情况是,我有一个SQLite数据库(来自Android平板电脑)和一个不太大的表(其中约有50000行) 例如,如果在Sqlite管理器中运行“select*from table”,则需要0.11秒,正确 但是。。。如果我在Java程序(使用SQLite JDBC)中执行此操作,则需要20分钟!!!不是开玩笑 有人(某处)说这取决于版本。 但我的问题是怎么做 因为此命令:“SELECT sqlite_version()”在不同情况下对同一.db文件给出不同的结果:Java 为什么SQLite在JDBC中速度如此之慢?,java,sqlite,jdbc,Java,Sqlite,Jdbc,A读了很多东西,比如: 但我不知道问题出在哪里。 情况是,我有一个SQLite数据库(来自Android平板电脑)和一个不太大的表(其中约有50000行) 例如,如果在Sqlite管理器中运行“select*from table”,则需要0.11秒,正确 但是。。。如果我在Java程序(使用SQLite JDBC)中执行此操作,则需要20分钟!!!不是开玩笑 有人(某处)说这取决于版本。 但我的问题是怎么做 因为此命令:“SELECT sqlite_version()”在不同情况下对同一.db
- 在一个非常旧的sqlite管理器中,它给出了3.6.19
- 在Sqlite Studio 3.15中
- 在sqlite.org上最新的.exe中,它给出了3.23.1 所以这不是一个与数据库相关的东西,我认为这是使用的sqlite3.exe版本
Class.forName("org.sqlite.JDBC");
Connection c1 = DriverManager.getConnection("jdbc:sqlite:" + "c:\\database.db");
PreparedStatement stmt1 = c1.prepareStatement("select * from table1;");
ResultSet rs = stmt1.executeQuery();
String script = "insert into table1 values ";
while (rs.next()) {
script += "(";
script += rs.getInt(1) + ", '" + rs.getString(2) + "', '" + rs.getString(3) + "'";
script += "),";
}
stmt1.close();
c1.close();
executeQuery()行需要20分钟。在一个成功的应用程序中,我们使用
sqlite
作为数据库。在我们的应用程序中,我们还使用JPA,并将数据库定义为Java资源目录中的持久化单元:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:sqlite:/ourdata/mySqliteDB.db" />
<property name="javax.persistence.jdbc.driver" value="org.sqlite.JDBC" />
<property name="eclipselink.logging.level" value="SEVERE"/>
<property name="eclipselink.jdbc.cache-statements" value="true"/>
<property name="eclipselink.weaving" value="false"/>
<property name="eclipselink.weaving.fetch-groups" value="false"/>
<property name="showSql" value="false"/>
</properties>
</persistence-unit>
</persistence>
org.eclipse.persistence.jpa.PersistenceProvider
我们没有时间用sqlite解决访问问题
在访问大型数据库表时,通常都知道需要为sql查询中使用的每一列定义一个索引,以确保快速的查询响应时间。这也适用于JPA(通常是“findall”查询) 您正在创建一个包含50k行的
字符串,这意味着您正在创建50k*5字符串(每个连接都会创建一个新的字符串实例。这会降低您的性能
while (rs.next()) {
script += "(";
script += rs.getInt(1) + ", '" + rs.getString(2) + "', '" + rs.getString(3) + "'";
script += "),";
}
我注意到您不执行字符串脚本
,因此如果您只想创建字符串
,请使用StringBuilder
StringBuilder script = new StringBuilder("insert into table1 values ");
while (rs.next()) {
script.append("(")
.append(rs.getInt(1)).append(", '")
.append(rs.getString(2)).append("', '")
.append(rs.getString(3)).append("'")
.append("),");
}
script.setLength(script.length() - 1); //to remove the last comma.
String query = script.toString();
StringBuilder
可以防止大量的String
实例被免费创建
如果要在此之后插入这些值,请直接使用PreparedStatement
,而不是构建查询:
PreparedStatement psInsert = c1.prepareStatement("insert into table1 values (?,?,?)");
while (rs.next()) {
psInsert.setInt(1, rs.getInt(1));
psInsert.setString(2, rs.getString(2));
psInsert.setString(2,rs.getString(3));
psInsert.execute();
}
然后,如果您想改进这一点,请使用批处理系统发送小块插入。使用和
StringBuilder基准
不是正式的,只是一次简单的执行
LocalTime start=LocalTime.now();
StringBuilder sb=新的StringBuilder(“Foo;”);
对于(int i=0;i<50_000;i++){
附加(i)附加(;\n);
}
System.out.println(Duration.between(start,LocalTime.now()).toNanos());
字符串s=sb.toString();
System.out.println(s.substring(0,50));
这需要15纳秒
LocalTime start=LocalTime.now();
字符串s=“Foo;”;
对于(int i=0;i<50_000;i++){
s+=“行”+i+“;\n”;
}
System.out.println(Duration.between(start,LocalTime.now()).toMillis());
System.out.println(s.substring(0,50));
这需要>6秒
请添加相关的java代码,执行查询需要20分钟。这种性能上的差异肯定不是(唯一的)JDBC API相关。可能是一个特定的驱动程序错误,但您没有确切提到您使用的驱动程序,也没有提到运行JDBC逻辑的执行环境,也没有显示任何代码,这将需要复制此代码。我们没有玻璃球…我们无法知道问题是在您的代码中还是在驱动程序/库中ry/sqlite版本。您将在sqlite管理中完成的Select
与插入到表中的代码(使用String
串联)进行比较。首先,使用PreparedStatement.addBatch()
插入每一行(检查是否在sqlite中管理批处理)。然后,不要连接YRUString
。Axel,没有实际的插入,我只是构建了一个未执行的字符串。(实际上我做了,它在几秒钟内完成了,但问题是上面代码的那部分)我明白你的意思,但事实并非如此。我的计划是将此数据库传输到MySQL。如果我生成50k insert(即使在事务中)也会非常慢。正如我之前所经历的,插入MySQL的最好方法是一个insert命令,而不是所有的数据,比如(1,1,“abc”),(2,3,“abc”)…等等。所以我需要那种形式的字符串。@Rezmalac,我刚刚编辑过(在开头)演示如何使用StringBuilder
使用较少的String
s构建String
查询。但这不会改变插入这些值的时间。如果您注意到大量插入时出现问题,您可以检查配置和使用的代码,因为PreparedStatement
更适合于此(而且更安全!)@Rezmalac给你一个想法,连接100kString
花了7秒,我们使用的是StringBuilder
它甚至在ms
中都无法测量。F@ck我,这似乎是愚蠢的java字符串连接。你得到了我的投票。我应该改变问题的标题吗?因为它不再相关了…@Rezmalac这真的是“愚蠢的java连接”的错误?;-)永远不要在循环中连接String
,这是一个好规则。在每种语言中实例化都需要时间。“更改”不可变对象意味着创建一个新实例,所以这不是String
java;)的错误好的,这是SQLite查询中的执行,“在SQLte ResultSet中构建查询”或类似的内容可能更正确,但这取决于您。
while (rs.next()) {
psInsert.setInt(1, rs.getInt(1));
psInsert.setString(2, rs.getString(2));
psInsert.setString(2,rs.getString(3));
psInsert.addBatch();
if(batchSize++ > 100){ //Execute every 100 rows
psInsert.executeBatch();
batchSize = 0;
}
}
if(batchSize > 0){ //execute the remainings data
psInsert.executeBatch();
}
LocalTime start = LocalTime.now();
StringBuilder sb = new StringBuilder("Foo;");
for(int i = 0; i < 50_000; i++){
sb.append("Row").append(i).append(";\n");
}
System.out.println(Duration.between(start, LocalTime.now()).toNanos());
String s = sb.toString();
System.out.println(s.substring(0, 50));
LocalTime start = LocalTime.now();
String s = "Foo;";
for(int i = 0; i < 50_000; i++){
s += "Row" + i + ";\n";
}
System.out.println(Duration.between(start, LocalTime.now()).toMillis());
System.out.println(s.substring(0, 50));