2012-05-22 13 views
19

Tôi đã viết một ứng dụng java mà thỉnh thoảng ghi các sự kiện vào một cơ sở dữ liệu SQLite từ nhiều luồng. Tôi đã nhận thấy rằng tôi có thể kích hoạt các lỗi "Database Locked" của SQLite tương đối dễ dàng bằng cách sinh ra một số lượng nhỏ các sự kiện cùng một lúc. Điều này đã thúc đẩy tôi viết một chương trình thử nghiệm bắt chước hành vi của trường hợp xấu nhất và tôi đã rất ngạc nhiên khi thấy SQLite hoạt động kém như thế nào trong trường hợp sử dụng này. Mã được đăng dưới đây chỉ cần thêm năm bản ghi vào cơ sở dữ liệu, đầu tiên tuần tự để có được các giá trị "kiểm soát". Sau đó, năm bản ghi cùng được thêm đồng thời.SQLite trong một ứng dụng java đa luồng

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); 
     } 
     } 
    } 
} 

Đây là kết quả khi tôi chạy mã java này:

[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 

Chèn 5 hồ sơ liên tục mất khoảng 750 mili giây, tôi mong chờ sự chèn đồng thời lấy khoảng cùng một lượng thời gian. Nhưng bạn có thể thấy rằng cho một thời gian chờ 3 giây họ thậm chí không kết thúc. Tôi cũng đã viết một chương trình thử nghiệm tương tự trong C, sử dụng các cuộc gọi thư viện bản địa của SQLite và chèn đồng thời hoàn thành trong khoảng thời gian tương tự như các chèn đồng thời đã làm. Vì vậy, vấn đề là với thư viện java của tôi.

Đây là kết quả khi tôi chạy phiên bản 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 

Tôi đã thử mã này với hai tài xế khác nhau JDBC (http://www.zentus.com/sqlitejdbchttp://www.xerial.org/trac/Xerial/wiki/SQLiteJDBC), và các wrapper sqlite4java. Mỗi lần kết quả tương tự nhau. Có ai ngoài đó biết về một thư viện SQLite cho java mà không có hành vi này?

Trả lời

23

Đây là vấn đề với core SQLite library - không phải với bất kỳ trình bao bọc Java nào. SQLite sử dụng các khóa dựa trên hệ thống tệp để đồng bộ hóa truy cập đồng thời giữa các quy trình, vì dưới dạng cơ sở dữ liệu nhúng, nó không có một quy trình (máy chủ) chuyên dụng để lên lịch các hoạt động. Vì mỗi luồng trong mã của bạn tạo kết nối riêng của nó với cơ sở dữ liệu, nó được coi là một quá trình riêng biệt, đồng bộ hóa xảy ra thông qua các khóa dựa trên tệp, chậm hơn đáng kể so với bất kỳ phương thức đồng bộ nào khác.

Ngoài ra, SQLite không hỗ trợ khóa mỗi hàng (chưa?). Về cơ bản, toàn bộ tệp cơ sở dữ liệu trở thành locked cho mỗi thao tác. Nếu bạn may mắn và hệ thống tập tin của bạn hỗ trợ các khóa byte phạm vi, có thể có nhiều người đọc truy cập cơ sở dữ liệu của bạn cùng một lúc, nhưng bạn không nên cho rằng kiểu hành vi đó.

Thư viện SQLite lõi by default allows multiple threads to use the same connection concurrently không có vấn đề gì. Tôi đoán rằng mọi trình bao bọc JDBC lành mạnh sẽ cho phép hành vi đó trong các chương trình Java, mặc dù tôi chưa thực sự thử nó.

Vì vậy, bạn có hai giải pháp:

  • Chia sẻ kết nối JDBC cùng trong số tất cả chủ đề.

  • Kể từ khi các nhà phát triển SQLite dường như nghĩ rằng threads are evil, bạn sẽ khấm khá hơn có một chủ đề xử lý tất cả các hoạt động cơ sở dữ liệu của bạn và serialize nhiệm vụ DB về việc sử dụng mã Java của riêng bạn ...

Bạn có thể muốn xem this old question of mine - dường như đã tích lũy một số mẹo về cải thiện hiệu suất cập nhật trong SQLite theo thời gian.

+0

Vì vậy, nếu gốc của vấn đề là khóa hệ thống tập tin, tại sao mã C của tôi chạy nhanh như vậy? – jlunavtgrad

+2

nhanh chóng! Sử dụng cùng một kết nối JDBC hoàn toàn giải quyết được vấn đề. Bây giờ tôi thấy hiệu suất tốt hơn hoặc bằng mã C của tôi. Tôi đã thử điều này với chương trình java ở trên ngày hôm qua, nhưng bây giờ tôi nhận ra rằng tôi đã ghi đè kết nối của mình bằng một cái mới cho mỗi lần chèn. – jlunavtgrad

+0

Xem thêm: http://stackoverflow.com/questions/24513576/opening-database-connection-with-the-sqlite-open-nomutex-flag-in-java – Stephan

1

Tôi sử dụng cùng một kết nối cho nhiều chuỗi. ngoài ra tôi phải làm cho các phương thức db-write được đồng bộ, nếu không tôi vẫn gặp lỗi bussy

Các vấn đề liên quan