为啥 SQLite 使用 JDBC 这么慢?
Posted
技术标签:
【中文标题】为啥 SQLite 使用 JDBC 这么慢?【英文标题】:Why SQLite is so slow with JDBC?为什么 SQLite 使用 JDBC 这么慢? 【发布时间】:2018-05-11 12:28:13 【问题描述】:阅读了很多东西,比如这里: SQLite queries much slower using JDBC than in Firefox SqliteManager plugin
但我不知道有什么问题。 情况是,我有一个 SQLite 数据库(来自 android 平板电脑)和一个不太大的表(其中约 50.000 行) 例如,如果我在 Sqlite Manager 中运行“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的版本。我可以整天更改 JDBC 驱动程序(我做过几次),但我怎么知道我需要哪个?
有人有什么想法吗?我完全被它困住了。
编辑: 好的,JDBC jar 来自这里:https://bitbucket.org/xerial/sqlite-jdbc/downloads/
而且我的代码真的很基础,一开始我只是想测量一下速度。
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 分钟。
【问题讨论】:
请添加相关的java代码,执行查询需要20分钟。 这种性能差异绝对不是(仅)与 JDBC API 相关的。可能是一个特定的驱动程序错误,但您没有确切提到您一直在使用什么驱动程序,也没有提到您运行 JDBC 逻辑的执行环境,也没有显示任何代码,这是重现此问题所需要的 我们没有玻璃球...我们无法知道问题出在您的代码中还是在驱动程序/库/sqlite 版本中。 好吧,您正在比较在 SQLite 管理中完成的Select
与插入到表中的代码(使用 String
连接)。首先,使用PreparedStatement.addBatch()
插入每一行(检查SQLITE 中是否管理批处理)。然后,不要连接 yoru String
。
Axel,没有实际的插入,我只是构建了一个未执行的字符串。 (其实我做到了,几秒钟就完成了,但问题出在上面那部分代码上)
【参考方案1】:
您正在创建一个有 50k 行的 String
,这意味着您正在创建 50k * 5 String
(每个连接都会创建一个新的 String
实例。这会影响您的性能。
while (rs.next())
script += "(";
script += rs.getInt(1) + ", '" + rs.getString(2) + "', '" + rs.getString(3) + "'";
script += "),";
我注意到你没有执行String script
,所以如果你只想创建一个String
,请使用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();
那么如果你想改进这一点,请使用批处理系统发送小块插入。使用Statement.addBatch()
和Statement.executeBatch()
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();
StringBuilder 基准测试
不是官方的,只是一个简单执行的 Duration
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));
这需要 15 纳秒
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));
这需要 >6 秒
【讨论】:
我明白你的意思,但事实并非如此。我的计划是将这个数据库转移到 mysql 中。如果我生成 50k 插入(即使在事务中)也会非常慢。正如我之前所经历的,插入 MySQL 的最佳方法是一个插入命令,而不是像 (1, 1, "abc"), (2, 3, "abc")... 等所有数据。所以我需要那个形式的那个字符串。 @Rezmalac,我刚刚编辑(在开头)向您展示了如何使用更少的String
s 和StringBuilder
构建String
查询。但这不会改变插入这些值的时间。如果您发现大量插入存在问题,您可以检查配置和使用的代码,因为PreparedStatement
更适合(并且更安全!)
@Rezmalac 给你一个想法,连接 100k String
需要 7 秒,因为它使用的是 StringBuilder
,在 ms
中甚至无法测量。
F@ck 我,这似乎真的是那个愚蠢的 java 字符串连接。你得到了我的选票。我应该更改问题的标题吗?因为它不再相关......
@Rezmalac 真的是“愚蠢的 java 连接”的错吗? ;-) 永远不要在循环中连接String
,这是一个很好的规则。每种语言的实例化都需要时间。 “更改”一个不可变对象意味着创建一个新实例,所以这并不是String
java 的错;)嗯,这是 SQLite 查询中的执行,“在 SQLte ResultSet 中构建查询”或类似的东西可能是更正确,但这取决于你。【参考方案2】:
在成功的应用程序中,我们使用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>
我们在使用 sqlite 时没有拖延时间的访问问题。
在访问大型数据库表时,一般都知道需要为 sql-query 中使用的每一列定义一个索引,以确保快速的查询响应时间。这也适用于 JPA(通常是“findall”查询)。
【讨论】:
以上是关于为啥 SQLite 使用 JDBC 这么慢?的主要内容,如果未能解决你的问题,请参考以下文章
Node - 为啥我的 gif 在使用 GifEncoder 时这么慢