[英]SQLite in a multithreaded java application
我编写了一个java应用程序,它偶尔会将事件从多个线程记录到SQLite数据库中。 我注意到我可以通过同时生成少量事件来相对容易地触发SQLite的“数据库锁定”错误。 这促使我编写了一个模拟最坏情况行为的测试程序,我很惊讶SQLite在这个用例中的表现有多糟糕。 下面发布的代码只是将五条记录添加到数据库中,首先按顺序获取“控制”值。 然后同时添加相同的五个记录。
import java.sql.*;
public class Main {
public static void main(String[] args) throws Exception {
Class.forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite:test.db");
Statement stat = conn.createStatement();
stat.executeUpdate("drop table if exists people");
stat.executeUpdate("create table people (name, occupation)");
conn.close();
SqlTask tasks[] = {
new SqlTask("Gandhi", "politics"),
new SqlTask("Turing", "computers"),
new SqlTask("Picaso", "artist"),
new SqlTask("shakespeare", "writer"),
new SqlTask("tesla", "inventor"),
};
System.out.println("Sequential DB access:");
Thread threads[] = new Thread[tasks.length];
for(int i = 0; i < tasks.length; i++)
threads[i] = new Thread(tasks[i]);
for(int i = 0; i < tasks.length; i++) {
threads[i].start();
threads[i].join();
}
System.out.println("Concurrent DB access:");
for(int i = 0; i < tasks.length; i++)
threads[i] = new Thread(tasks[i]);
for(int i = 0; i < tasks.length; i++)
threads[i].start();
for(int i = 0; i < tasks.length; i++)
threads[i].join();
}
private static class SqlTask implements Runnable {
String name, occupation;
public SqlTask(String name, String occupation) {
this.name = name;
this.occupation = occupation;
}
public void run() {
Connection conn = null;
PreparedStatement prep = null;
long startTime = System.currentTimeMillis();
try {
try {
conn = DriverManager.getConnection("jdbc:sqlite:test.db");
prep = conn.prepareStatement("insert into people values (?, ?)");
prep.setString(1, name);
prep.setString(2, occupation);
prep.executeUpdate();
long duration = System.currentTimeMillis() - startTime;
System.out.println(" SQL Insert completed: " + duration);
}
finally {
if (prep != null) prep.close();
if (conn != null) conn.close();
}
}
catch(SQLException e) {
long duration = System.currentTimeMillis() - startTime;
System.out.print(" SQL Insert failed: " + duration);
System.out.println(" SQLException: " + e);
}
}
}
}
这是我运行这个java代码时的输出:
[java] Sequential DB access:
[java] SQL Insert completed: 132
[java] SQL Insert completed: 133
[java] SQL Insert completed: 151
[java] SQL Insert completed: 134
[java] SQL Insert completed: 125
[java] Concurrent DB access:
[java] SQL Insert completed: 116
[java] SQL Insert completed: 1117
[java] SQL Insert completed: 2119
[java] SQL Insert failed: 3001 SQLException: java.sql.SQLException: database locked
[java] SQL Insert completed: 3136
顺序插入5个记录大约需要750毫秒,我希望并发插入大约花费相同的时间。 但你可以看到,如果超时3秒,他们甚至都没有完成。 我还在C中编写了一个类似的测试程序,使用SQLite的本机库调用,同时插入在与并发插入大致相同的时间内完成。 所以问题在于我的java库。
这是我运行C版本时的输出:
Sequential DB access:
SQL Insert completed: 126 milliseconds
SQL Insert completed: 126 milliseconds
SQL Insert completed: 126 milliseconds
SQL Insert completed: 125 milliseconds
SQL Insert completed: 126 milliseconds
Concurrent DB access:
SQL Insert completed: 117 milliseconds
SQL Insert completed: 294 milliseconds
SQL Insert completed: 461 milliseconds
SQL Insert completed: 662 milliseconds
SQL Insert completed: 862 milliseconds
我用两个不同的JDBC驱动程序( http://www.zentus.com/sqlitejdbc和http://www.xerial.org/trac/Xerial/wiki/SQLiteJDBC )和sqlite4java包装器尝试了这个代码。 每次结果都相似。 有没有人知道没有这种行为的java的SQLite库?
这是核心SQLite库的问题 - 而不是任何Java包装器。 SQLite使用基于文件系统的锁来实现进程间的并发访问同步,因为作为嵌入式数据库,它没有专门的进程(服务器)来安排操作。 由于代码中的每个线程都创建了自己与数据库的连接,因此它被视为一个单独的进程,通过基于文件的锁定进行同步,这比任何其他同步方法都慢得多。
另外,SQLite不支持每行锁定(但是?)。 基本上整个数据库文件对于每个操作都被锁定 。 如果你很幸运并且你的文件系统支持字节范围锁,那么多个读者可能同时访问你的数据库,但你不应该假设这种行为。
默认情况下 ,核心SQLite库允许多个线程同时使用相同的连接 ,没有任何问题。 我认为任何理智的JDBC包装器都会在Java程序中允许这种行为,尽管我还没有尝试过。
因此,您有两个解决方案:
在所有线程之间共享相同的JDBC连接。
由于SQLite开发人员似乎认为线程是邪恶的 ,所以最好让一个线程处理所有数据库操作并使用Java代码自行序列化数据库任务...
您可能想看看我的这个老问题 - 它似乎已经积累了一些提高SQLite中的更新性能的技巧。
我对多个线程使用相同的连接。 另外我不得不使db-write方法同步,否则我仍然会遇到bussy错误
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.