2010-04-15 50 views
28

我們使用AsyncTasks來訪問數據庫表和遊標。Android線程和數據庫鎖定

不幸的是,我們偶爾會看到有關數據庫被鎖定的例外情況。

E/SQLiteOpenHelper(15963): Couldn't open iviewnews.db for writing (will try read-only): 
E/SQLiteOpenHelper(15963): android.database.sqlite.SQLiteException: database is locked 
E/SQLiteOpenHelper(15963): at  android.database.sqlite.SQLiteDatabase.native_setLocale(Native Method) 
E/SQLiteOpenHelper(15963): at  android.database.sqlite.SQLiteDatabase.setLocale(SQLiteDatabase.java:1637) 
E/SQLiteOpenHelper(15963): at  android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.java:1587) 
E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:638) 
E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:659) 
E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:652) 
E/SQLiteOpenHelper(15963): at android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.java:482) 
E/SQLiteOpenHelper(15963): at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:193) 
E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:98) 
E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:158) 
E/SQLiteOpenHelper(15963): at com.iview.android.widget.IViewNewsTopStoryWidget.initData(IViewNewsTopStoryWidget.java:73) 
E/SQLiteOpenHelper(15963): at com.iview.android.widget.IViewNewsTopStoryWidget.updateNewsWidgets(IViewNewsTopStoryWidget.java:121) 
E/SQLiteOpenHelper(15963): at com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:338) 
E/SQLiteOpenHelper(15963): at com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:1) 
E/SQLiteOpenHelper(15963): at android.os.AsyncTask$2.call(AsyncTask.java:185) 
E/SQLiteOpenHelper(15963): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:256) 
E/SQLiteOpenHelper(15963): at java.util.concurrent.FutureTask.run(FutureTask.java:122) 
E/SQLiteOpenHelper(15963): at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:648) 
E/SQLiteOpenHelper(15963): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:673) 
E/SQLiteOpenHelper(15963): at java.lang.Thread.run(Thread.java:1060) 

有誰知道代碼從不同的線程比一個讀取寫入到數據庫的一般的例子,我們如何能保證線程安全。

我有一個建議是使用ContentProvider,因爲這將處理從多個線程訪問數據庫。我會看看這個,但這是處理這個問題的推薦方法嗎?考慮到我們在前面或後面討論,這似乎相當重量級。

回答

16

我解決了這個相同的異常只是確保我所有的數據庫都打開關閉,以及(更重要的),以確保這一點,使每一個數據庫實例的範圍當地只需要它的方法。 ContentProvider的是一個很好的,安全的訪問類從多個線程分貝時使用,而且要確保你使用好的分貝做法:

  • 保持數據庫實例本地(無SQLiteDatabase類的成員!)
  • 呼叫close()的分貝在其中它打開
  • 通話close()你從數據庫中
  • 得到光標同樣的方法聽來logcat的任何抱怨,SQLiteDatabse可能
+8

經過一番閱讀,我認爲這不是推薦的方式,每個請求打開自己的數據庫實例。 AFAIK如果2個線程同時使用2個差異數據庫實例寫入數據庫,只有一個會寫入,另一個線程會被忽略而沒有錯誤(只是日誌消息)。數據庫鎖是樂觀的,您可以打開儘可能多的連接,但您應該只使用一個數據庫實例進行讀取。如我錯了請糾正我 – Hiep 2014-08-25 12:07:31

-6

您是否在談論單個用戶操作,在您的程序中,會導致運行多個線程,其中多個線程可能以更新模式訪問數據庫?

這是糟糕的設計,時期。您無法知道您的操作系統(/ VM)按照哪種順序排定線程,因此無法知道數據庫以何種順序進行訪問,這很可能意味着沒有辦法讓你知道數據庫訪問總是按照你期望的順序發生。

由/來自某個用戶操作生成的所有數據庫訪問都應該在一個單獨的線程中完成。

+0

否,我不是,但感謝。 – Pandalover 2010-04-16 08:13:32

9

考慮到SQLite數據庫是基於文件的,並不打算能夠以多進程方式訪問。將SQLite與多處理混合的最佳過程是在每個數據庫相關訪問中使用信號量(aquire(),release())。

如果您創建一個獲取/釋放全局信號量的Db包裝器,則您的數據庫訪問將是線程安全的。事實上,這意味着你可以得到一個bootleneck,因爲你在訪問數據庫。因此,除了可以修改數據庫的操作外,您還可以使用信號量來封裝訪問,因此當您在修改數據庫時,沒有人能夠訪問它並等待寫入過程完成。

28

最後我們使用了ContentProvider。這似乎是爲了解決問題。

+51

ContentProvider解決問題的原因是因爲它通常使用一個「SQLiteOpenHelper」,這意味着只有一個連接到數據庫,並且底層的'SQLiteDatabase'負責鎖定。 您不需要ContentProvider - 只需確保不使用2個不同的數據庫連接寫入數據庫。 本文解釋了鎖定如何在Android上工作。 http://kagii.squarespace.com/journal/2010/9/10/android-sqlite-locking.html – 2011-05-30 11:22:03

+9

更新後的文章鏈接:http://kagii.com/post/6828016869/android-sqlite-locking – orip 2011-09-04 20:40:38

+7

源代碼從第二個鏈接死了:/ – seb 2012-09-24 23:25:15

-3

您必須從調用一個函數getWritableDatabase()而不是數據庫輔助類的構造函數。如果使用SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);或類似方法創建db助手類對象,然後從函數調用getWritableDatabase(),它將嘗試對DB進行同步調用,從而導致DB鎖異常。

+1

只有在數據庫文件不存在的情況下,纔會調用SQLiteOpenHelper的onCreate()方法。 – Hal 2012-01-16 21:10:04

+0

同意。如果DB對象已經存在,問題不在於調用oncreate()而是對數據庫的同步調用。 – user868114 2012-01-19 17:54:47

11

之前一些代碼,讓我們恢復了一些技術途徑的:

  • Semaphores:迄今爲止提出的最佳解決方案。它陷入了問題的核心:資源共享!它將處理數據庫訪問的鎖定,避免衝突(database is locked)。

  • Java synchronization:一種信號量實現,但不太複雜。使用​​你不會輕易解決一些涉及交易的案例。

  • ContentProvider:實施ContentProvider解決問題只針對某些情況下(或掃在地毯下的問題)。你還會面臨同樣的問題。區別在於ContentProvider模式將指導您在訪問Sqlite數據庫時不會犯一些常見錯誤。 ContentProvider docs說:「如果使用完全在您自己的應用程序中,則不需要提供者使用SQLite數據庫。」

  • Almost mandatory:保持數據庫實例的地方,叫close()在其中它的使用finally陳述,close()使用finally聲明光標,等開了同樣的方法分貝是幾乎必須避免使用SQLite問題。

讓我們顯示的信號解決方案的一個例子給出by Moss,這是我從CL.拿去improoved支付交易。

class DataAccess { 
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
    private final Lock r = rwl.readLock(); 
    private final Lock w = rwl.writeLock(); 

    public Data readSomething(int id) { 
     Cursor c = null; 
     r.lock(); 
     try { 
      c = getReadableDatabase().query(...); 
      return c.getString(0); 
     } finally { 
      if (c != null) c.close(); 
      r.unlock(); 
     } 
    } 

    public void changeSomething(int id, int value) { 
     w.lock(); 
     try { 
      getWritableDatabase().update(...); 
     } finally { 
      w.unlock(); 
     } 
    } 

    private void beginTransactionWithSemaphores() { 
     getWritableDatabase().beginTransactionWithListener(new SQLiteTransactionListener() { 
      @Override 
      public void onBegin() { 
       w.lock(); 
      } 

      @Override 
      public void onRollback() { 
       w.unlock(); 
      } 

      @Override 
      public void onCommit() { 
       w.unlock(); 
      } 
     }); 
    } 
} 
1

我們不能共享多個線程來執行讀取和數據庫simultaniously.We寫操作將不得不作出DB的單個對象使用同步化的概念,我們會在一個時間。我們將執行一項任務DB連接使用單例模式來創建數據庫對象,它將在多個線程中共享。一段時間將執行單個任務。那麼我們將開始其他任務或對數據庫的任何操作。內容提供者不是數據庫鎖定問題的解決方案。

import java.util.concurrent.atomic.AtomicInteger; 
import android.database.sqlite.SQLiteDatabase; 
import android.database.sqlite.SQLiteOpenHelper; 
import android.util.Log; 

public class DatabaseManager { 

private AtomicInteger mOpenCounter = new AtomicInteger(); 

private static DatabaseManager instance; 
private static SQLiteOpenHelper mDatabaseHelper; 
private SQLiteDatabase mDatabase; 
//private static String DB_PATH = ""; 
// private static String DB_NAME = "xyz.db";// Database name 
private static String dbPathh; 

public static synchronized void initializeInstance(SQLiteOpenHelper helper, 
     String dbPath) { 
    if (instance == null) { 
     instance = new DatabaseManager(); 
     mDatabaseHelper = helper; 
     dbPathh=dbPath; 
    } 
    } 

public static synchronized DatabaseManager getInstance() { 
    if (instance == null) { 
     throw new IllegalStateException(DatabaseManager.class.getSimpleName() + 
       " is not initialized, call initializeInstance(..) method first."); 
    } 

    return instance; 
} 

    public synchronized SQLiteDatabase openDatabase(String thread) { 

    if(mOpenCounter.get() == 0) { 
     // Opening new database 
     // mDatabase = mDatabaseHelper.getWritableDatabase(); 
     MyLog.e("Path Of DataBase", dbPathh); 
     // mDatabase=mDatabaseHelper.getWritableDatabase(); 
     mOpenCounter.incrementAndGet(); 
     mDatabase=SQLiteDatabase.openDatabase(dbPathh, null, 
SQLiteDatabase. CREATE_IF_NECESSARY|SQLiteDatabase.OPEN_READWRITE); 
     MyLog.e("Open Data Base", " New Connection created" +thread); 
    } 
    else{ 
     MyLog.e("Open Data Base", " Old Connection given " +thread); 
    } 
    // Toast.makeText(NNacres.getConfig(), "open conn: present connection = 
    " +mOpenCounter.get(), Toast.LENGTH_LONG).show(); 
    return mDatabase; 
    } 

    public synchronized void closeDatabase() { 
    MyLog.e("Close db connection", ""+mOpenCounter.get()); 

    if(mOpenCounter.get() == 1) { 
     // Closing database 

     mDatabase.close(); 
     mOpenCounter.decrementAndGet(); 

     Log.e("DB CLOSED", "DONE"); 
    } 
    //Toast.makeText(NNacres.getConfig(), "close conn: after close = 
" +mOpenCounter.get(), Toast.LENGTH_LONG).show(); 
    } 

    } 

,寫在你的YourSQLiteDataABse助手類此方法延伸SQLiteOpenHelper類

 public SQLiteDatabase getWritableDatabase() { 
DatabaseManager.initializeInstance(this,"data/data/your packgae name/databases/xyz"); 
    return DatabaseManager.getInstance().openDatabase(getClass().getSimpleName()); 

} 



public static String getMyDbPath(String DB_NAME, Context context) { 

    String myDbPath = context.getDatabasePath(DB_NAME).getPath(); 
    MyLog.e("DB Path: "+myDbPath); 
    return myDbPath; 
}