2012-05-22 22 views
19

我已经编写了一个Java应用程序,它将事件从多个线程偶尔记录到SQLite数据库。我注意到,通过同时产生少量事件,我可以相对容易地触发SQLite的“数据库锁定”错误。这使我写了一个模拟最坏情况行为的测试程序,我对SQLite在这个用例中表现得有多糟糕感到惊讶。下面发布的代码只是将五条记录添加到数据库中,首先依次获取“控制”值。然后同时添加相同的五条记录。多线程java应用程序中的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/sqlitejdbchttp://www.xerial.org/trac/Xerial/wiki/SQLiteJDBC),以及sqlite4java包装。每次结果都是相似的。有没有人知道没有这种行为的Java的SQLite库?

回答

23

这是core SQLite library的问题 - 不适用于任何Java包装。 SQLite使用基于文件系统的锁进行进程间的并发访问同步,因为作为嵌入式数据库,它没有专门的进程(服务器)来调度操作。由于代码中的每个线程都创建了自己的与数据库的连接,因此它被视为一个单独的进程,同步通过基于文件的锁进行,这比其他同步方法慢得多。

另外,SQLite不支持每行锁定(还?)。基本上,对于每个操作,整个数据库文件变为locked。如果你很幸运并且你的文件系统支持字节范围的锁定,多个阅读器可能同时访问你的数据库,但你不应该假设这种行为。

核心SQLite库by default allows multiple threads to use the same connection concurrently没有问题。我认为任何理智的JDBC包装器都会在Java程序中允许这种行为,尽管我没有真正尝试过它。

因此有两种解决方法:

  • 分享所有线程相同的JDBC连接。

  • 由于SQLite的开发者似乎认为threads are evil,你会过得更好为一个线程处理所有数据库操作和对自己使用Java代码序列DB任务...

你可能想看看this old question of mine - 它似乎已经积累了一些技巧来提高SQLite的更新性能。

+0

因此,如果问题的根源是文件系统锁,为什么我的C代码运行速度如此快呢? – jlunavtgrad

+2

欢呼!使用相同的JDBC连接完全解决了这个问题。我现在看到的性能好于或等于我的C代码。我昨天曾经尝试过使用上述java程序,但现在我意识到我正在用每个插入的新程序覆盖我的连接。 – jlunavtgrad

+0

另请参阅:http://stackoverflow.com/questions/24513576/opening-database-connection-with-the-sqlite-open-nomutex-flag-in-java – Stephan

1

我对多个线程使用相同的连接。 此外,我必须使数据库写入方法同步,否则我仍然得到bussy错误