多线程 Java 应用程序中的 SQLite

Posted

技术标签:

【中文标题】多线程 Java 应用程序中的 SQLite【英文标题】:SQLite in a multithreaded java application 【发布时间】:2012-05-29 06:18:41 【问题描述】:

我编写了一个 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 库?

【问题讨论】:

【参考方案1】:

这是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 更新性能的技巧。

【讨论】:

万岁!使用相同的 JDBC 连接完全解决了这个问题。我现在看到性能优于或等于我的 C 代码。我昨天用上面的java程序试过这个,但我现在意识到我正在用一个新的连接覆盖我的连接。 另见:***.com/questions/24513576/…【参考方案2】:

我对多个线程使用相同的连接。 另外我必须使 db-write 方法同步,否则我仍然会得到 bussy 错误

【讨论】:

以上是关于多线程 Java 应用程序中的 SQLite的主要内容,如果未能解决你的问题,请参考以下文章

SQLite中的多线程数据写入[重复]

SQLite在多线程并发访问的应用

可能是东半球最好的多线程讲义!

JAVA-初步认识-第十二章-多线程概述

Java多线程基础

python之协程