2010-04-15 51 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; 
}