简体   繁体   English

在多线程应用程序Android中访问数据库的最佳方法?

[英]Best way to access database in multi threaded application Android?

Note: Please don't mark this question as a duplicate. 注意:请不要将此问题标记为重复。 I have gone through several similar questions but couldn't find a satisfactory answer. 我已经经历了几个类似的问题,但找不到满意的答案。

I have been working on an application which uses Sqlite Database. 我一直在研究使用Sqlite数据库的应用程序。 We are following singleton pattern which ensures that we can create only one instance of our helper class throughout our application. 我们遵循单例模式,确保我们只能在整个应用程序中创建一个辅助类实例。

public class CustomSqliteHelper extends SQLiteOpenHelper {

  public static CustomSqliteHelper getInstance(Context context) {
    if (instance == null) {
      synchronized (CustomSqliteHelper.class) {
        if (instance == null) {
          instance = new CustomSqliteHelper(context);
        }
      }
    }
    return instance;
  }
}

But sometimes the application crashes with SQLiteDatabaseLockedException . 但有时应用程序崩溃与SQLiteDatabaseLockedException I understand this exception comes when more than one thread/process tries to write to the database at one time. 我理解当多个线程/进程尝试一次写入数据库时​​会出现此异常。 Even if one thread/process tries to read the database when the write operation is still going on, this exception will be thrown. 即使一个线程/进程在写入操作仍在进行时尝试读取数据库,也会抛出此异常。

So I have been reading a lot about this and the possible ways to prevent this from happening. 所以我一直在阅读很多关于这个以及防止这种情况发生的可能方法。 A lot of posts suggests using ContentProvider instead of directly extending SqliteOpenHelper class and performing operations on the database object. 很多帖子建议使用ContentProvider而不是直接扩展SqliteOpenHelper类并对数据库对象执行操作。 While reading one of the posts, this post mentioned that while using Content Provider, you don't need to manually take care of the multi threaded environment. 在阅读其中一篇帖子时,这篇文章提到在使用Content Provider时,您不需要手动处理多线程环境。

Although the ContentProvider lacks in thread-safety, often times you will find that no further action is required on your part with respect to preventing potential race conditions. 尽管ContentProvider缺乏线程安全性,但通常您会发现在防止潜在的竞争条件方面您无需采取进一步行动。 The canonical example is when your ContentProvider is backed by a SQLiteDatabase; 规范示例是您的ContentProvider由SQLiteDatabase支持的时间; when two threads attempt to write to the database at the same time, the SQLiteDatabase will lock itself down, ensuring that one will wait until the other has completed. 当两个线程同时尝试写入数据库时​​,SQLiteDatabase将自行锁定,确保一个等待直到另一个完成。 Each thread will be given mutually exclusive access to the data source, ensuring the thread safety is met. 每个线程都将获得对数据源的互斥访问,从而确保满足线程安全性。

The above quote seems sounds confusing because first it mentions that ContentProvider does not support thread safety. 以上引用似乎令人困惑,因为首先它提到ContentProvider不支持线程安全。 But he concludes that the application developer doesn't need to do anything on his part to achieve concurrency. 但他总结说,应用程序开发人员不需要做任何事情来实现并发。

Also, if I choose to use SqliteOpenHelper, what will be the best way to prevent these crashes? 另外,如果我选择使用SqliteOpenHelper,那么防止这些崩溃的最佳方法是什么? I have been thinking of using locks for every db operation. 我一直在考虑为每个db操作使用锁。

 public class CustomSqliteHelper extends SQLiteOpenHelper {

 private String lock = "lock";

 public void insert(){
    synchronized(lock){
       // Do the insert operation here.
    }
 }


 public void update(){
    synchronized(lock){
       // Do the update operation here.
    }
 }
 }

But one of my team members advised me not to do so because Java locks are expensive. 但我的一个团队成员建议我不要这样做,因为Java锁很贵。

After going through one of the most popular projects on Github, I found that the developers have advised to wrap every database operation inside a transaction. 在浏览了Github上最受欢迎的项目之一后,我发现开发人员建议将每个数据库操作包装在一个事务中。

public void insert(ContentValues values) {
    // Create and/or open the database for writing
    SQLiteDatabase db = getWritableDatabase();

    // It's a good idea to wrap our insert in a transaction. This helps with performance and ensures
    // consistency of the database.
    db.beginTransaction();
    try {
        // The user might already exist in the database (i.e. the same user created multiple posts).

        db.insertOrThrow(TABLE_POSTS, null, values);
        db.setTransactionSuccessful();
    } catch (Exception e) {
        Log.d(TAG, "Error while trying to add post to database");
    } finally {
        db.endTransaction();
    }
}

I am really not sure if this can prevent the Lock exception? 我真的不确定这是否可以阻止Lock异常? This seems more like a performance oriented step. 这似乎更像是一个以绩效为导向的步骤。

So finally after reading all those blogs and tutorials, I am still confused. 所以在阅读完所有博客和教程之后,我仍然感到困惑。 My main questions are 我的主要问题是

  1. Which is a better choice to use ContentProviders or extending SqliteOpenHelper given that my application doesn't share data with other applications. 鉴于我的应用程序不与其他应用程序共享数据,使用ContentProviders或扩展SqliteOpenHelper是更好的选择。
  2. Is putting Java locks on all the operations the best approach or is there any other approach which is better than this? 将Java锁定放在所有操作上是最好的方法还是有比这更好的其他方法?

Update: Based on the answer by @Mustanar, it seems that the SQLiteDatabase takes care of the locking mechanism. 更新:根据@Mustanar的回答,似乎SQLiteDatabase负责锁定机制。 It means that if you are doing a write opertaion, the database will be locked. 这意味着如果您正在执行写操作,则数据库将被锁定。 But at the very same time, if some other thread tries to perform a write operation, then will the second operation be in waiting till the lock is released or will it throw an android.sqlite.database.SQLiteDatabaseLockedException exception ? 但同时,如果某个其他线程尝试执行写操作,那么第二个操作是否会等待直到锁被释放或者是否会抛出android.sqlite.database.SQLiteDatabaseLockedException异常?

Update 2 : Putting a bounty on this question, because the answer still seems to be not clear to me. 更新2 :给这个问题一个赏金,因为答案似乎仍然不清楚。 I am using only one instance of the Helper class. 我只使用Helper类的一个实例。 But still getting this error. 但仍然得到这个错误。

PS: Thanks for bearing for such a long question. PS:感谢您提出这么长的问题。

Use a Singleton pattern to instantiate the SQLiteOpenHelper , so throughout the application one instance of singleton should exist. 使用Singleton模式来实例化SQLiteOpenHelper ,因此在整个应用程序中应该存在一个单例实例。 This will ensure that no leaks occur, and will make your life a lot easier since it eliminates the possibility of forgetting to close your database as you code. 这将确保不会发生泄漏,并且可以使您的生活更轻松,因为它消除了在编码时忘记关闭数据库的可能性。 It also will ensure safe access to the database throughout the application. 它还将确保在整个应用程序中安全访问数据库。

Moreover, You don't have to implement your own locking mecanism. 而且,您不必实现自己的锁定机制。 SQLiteDatabase maintain the locking mecanism. SQLiteDatabase维护锁定机制。 So, There will only one transaction going at a particular time, all other transactions will be in Queue using the Sigleton.getInstance() approach. 因此,在特定时间只有一个事务发生,所有其他事务将使用Sigleton.getInstance()方法在Queue Ensure a single access point to the database. 确保数据库的单个访问点。

Moreover, in this approach, You don't have to close your database connections, as SQLiteDatabase will be releasing all references once transaction has been done. 此外,在此方法中, 您不必关闭数据库连接,因为SQLiteDatabase将在事务完成后释放所有引用。 So CustomSQLiteHelper.getInstance() should be used whenever you want to perform CRUD operations and remove your locking mecansim. 因此,只要您想执行CRUD操作并删除锁定mecansim,就应该使用CustomSQLiteHelper.getInstance()

More information, Please go through the blog http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html and see the comments also. 更多信息,请访问博客http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html并查看评论。

Hope this helps. 希望这可以帮助。

Query 1 (Singleton) : 查询1(单例):

Can you change below double locking code to create a true Singleton object? 你能改变下面的双锁定代码来创建一个真正的Singleton对象吗?

public class CustomSqliteHelper extends SQLiteOpenHelper {
      private CustomSqliteHelper instance;
      public static CustomSqliteHelper getInstance(Context context) {
        if (instance == null) {
          synchronized (CustomSqliteHelper.class) {
            if (instance == null) {
              instance = new CustomSqliteHelper(context);
            }
          }
        }
        return instance;
      }
    }

Have a look at this article to understand the issues with double locking. 看看这篇文章 ,了解双重锁定的问题。

Have a look at related SE question: 看看相关的SE问题:

What is an efficient way to implement a singleton pattern in Java? 在Java中实现单例模式的有效方法是什么?

I prefer to create Singleton 我更喜欢创建Singleton

 private static final CustomSqliteHelper instance = new CustomSqliteHelper ();

Other approach: 其他方法:

enum CustomSqliteHelper {
    INSTANCE;
}

One more approach if you still need lazy Singleton. 如果你还需要懒惰的Singleton,还有一种方法。

Have a look at @ Nathan Hughes answer in this SE question: 在这个SE问题中看看@ Nathan Hughes回答:

Synchronized and locks in singleton factory 单件工厂中的同步和锁定

Query 2: ( Locking) 查询2 :(锁定)

Using java lock is not a bad idea to improve the consistency of the application. 使用java锁定可以提高应用程序的一致性。 Consistency wins over expense. 一致性胜过费用。

From SQLiteDatabaseLockedException page, SQLiteDatabaseLockedException页面,

Thrown if the database engine was unable to acquire the database locks it needs to do its job. 如果数据库引擎无法获取数据库锁,则需要执行其工作。 If the statement is a [COMMIT] or occurs outside of an explicit transaction, then you can retry the statement. 如果语句是[COMMIT]或发生在显式事务之外,则可以重试该语句。 If the statement is not a [COMMIT] and occurs within a explicit transaction then you should rollback the transaction before continuing. 如果该语句不是[COMMIT]并且在显式事务中发生,那么您应该在继续之前回滚事务。

I hope you should follow above recommendation along with code snippet you have posted in your question (Github) project. 我希望您应该遵循上面的建议以及您在问题(Github)项目中发布的代码片段。 I like that idea and above recommendation is in-line with Github example code. 我喜欢这个想法,上面的建议与Github示例代码一致。

I would definitely recommend that you wrap your database in a ContentProvider . 我肯定会建议您将数据库包装在ContentProvider

It provides nice features out of the box and shifts a lot of potentially tricky tasks to the framework. 它提供了开箱即用的好功能,并将许多潜在的棘手任务转移到框架中。 You won't need to deal with singleton database helpers, because you are guaranteed that you have a single ContentResolver instance for your app that will forward commands to the intended provider. 您不需要处理单例数据库帮助程序,因为您可以保证您的应用程序有一个ContentResolver实例,可以将命令转发给目标提供程序。

You can make use of the whole Loader framework and nice helper classes like AsyncQueryHandler to deal with some of the most common caveats of DB access. 您可以使用整个Loader框架和漂亮的帮助程序类(如AsyncQueryHandler来处理一些最常见的数据库访问警告。

I would advice against synchronising the access to your CRUD methods. 我建议不要同步对CRUD方法的访问。 As mentioned in the documentation and the previous answers, this should be taken care of by the database itself. 正如文档和之前的答案中所提到的,这应该由数据库本身来处理。 Not to mention the potential performance penalty in an app that requires intensive database usage. 更不用说在需要密集数据库使用的应用程序中潜在的性能损失。

Side note: As a first step, it may be useful if you try and pinpoint what causes these crashes - are they sporadic? 旁注:作为第一步,如果您尝试找出导致这些崩溃的原因可能会有用 - 它们是否零星? A more concrete example may prove useful while troubleshooting. 一个更具体的例子可能在故障排除时有用。

您可以使用内容提供商。

I think using a read-write lock is enough in most cases. 我认为在大多数情况下使用读写锁就足够了。 See this answer . 看到这个答案 The solution does not use ContentProvider . 该解决方案不使用ContentProvider Instead, it uses ReentrantReadWriteLock class in java.util.concurrent.locks package. 相反,它在java.util.concurrent.locks包中使用ReentrantReadWriteLock类。

  1. Which is a better choice to use ContentProviders or extending SqliteOpenHelper given that my application doesn't share data with other applications. 鉴于我的应用程序不与其他应用程序共享数据,使用ContentProviders或扩展SqliteOpenHelper是更好的选择。

I'd say extending SqliteOpenHelper is much better choice if you are not sharing any data. 我想如果你不共享任何数据,扩展SqliteOpenHelper是更好的选择。 This is mainly due to it's more dev-friendly API than ContentProvider :-) And there is other thing described in official documentation : 这主要是因为它比ContentProvider更加开发友好的API :-)还有官方文档中描述的其他内容

Decide if you need a content provider. 确定您是否需要内容提供商。 You need to build a content provider if you want to provide one or more of the following features: 如果要提供以下一项或多项功能,则需要构建内容提供程序:

  • You want to offer complex data or files to other applications. 您希望向其他应用程序提供复杂的数据或文件。
  • You want to allow users to copy complex data from your app into other apps. 您希望允许用户将应用中的复杂数据复制到其他应用中。
  • You want to provide custom search suggestions using the search framework. 您希望使用搜索框架提供自定义搜索建议。

So in your case - it is rather redundant 所以在你的情况下 - 它是多余的


For singleton initializing - sometimes you don't need it to be lazy initialized: 对于单例初始化 - 有时你不需要它进行惰性初始化:

public class CustomSqliteHelper {
    private static final CustomSqliteHelper instance = new CustomSqliteHelper(YourApplication.getContext());

    public static CustomSqliteHelper getInstance() {
        return instance;
    }

    // all the super staff below :)
}

But you need to expose app context with your application class: 但是您需要在应用程序类中公开应用程序上下文:

public class YourApplication extends Application {
    private static final YourApplication instance;

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
    }

    public static Context getContext() {
        return instance.getApplicationContext();
    }
}

plus configuration in Manifest. 加上Manifest中的配置。 This way you avoid synchronization during creation. 这样可以避免在创建过程中同步。


If you really need this singleton to be lazy initialized - than I will say that double check locking (DCL) is very bad practice. 如果你真的需要这个单例进行延迟初始化 - 比我说双重检查锁定 (DCL)是非常糟糕的做法。 For safe lazy initialization you should use the following pattern: 对于安全的延迟初始化,您应该使用以下模式:

public class CustomSqliteHelper {
    private static class InstanceHolder {
        public static CustomSqliteHelper instance = new CustomSqliteHelper(YourApplication.getContext());
    }

    public CustomSqliteHelper getInstance() {
        return InstanceHolder.instance;
    }

    // all the super staff below :)
}

  1. Is putting Java locks on all the operations the best approach or is there any other approach which is better than this? 将Java锁定放在所有操作上是最好的方法还是有比这更好的其他方法? This provides safe lazy initialization without locking anything. 这提供安全的延迟初始化而不锁定任何东西。

There is no need to do any locking (till your DAO is stateless). 没有必要进行任何锁定(直到您的DAO无状态)。 Check documentation for SQLiteDatabase 检查SQLiteDatabase的文档

In addition to making sure SQLiteOpenHelper is a singleton, you need to enable write-ahead logging if you want multiple threads to access the database: 除了确保SQLiteOpenHelper是单例之外,如果希望多个线程访问数据库,还需要启用预写日志记录:

https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#enableWriteAheadLogging() https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#enableWriteAheadLogging()

Doing this fixed the issue for me. 这样做可以解决我的问题。

As discussed on comments, there will be no Blocking Lock if you use one DB instance. 正如在注释中所讨论的,如果您使用一个数据库实例,则不会出现阻塞锁定。 So I am doubting DeadLock cause you are using transaction. 所以我怀疑DeadLock是因为你正在使用交易。

a kind of DeadLock example is : 一种DeadLock示例是:

  1. in one transaction : 在一次交易中:

    update A table --> update B table -->commit 更新表 - >更新B表 - >提交

  2. in other transaction : 在其他交易中:

    update B table --> update A table -->commit 更新B表 - >更新表 - >提交

so in the middle time, 1 waits 2 and 2 waits 1 at the same time, so Deadlock happens. 所以在中间时间,1等待2和2同时等待1,所以死锁发生。

If it is really Deadlock, you can fix query to not to make this happen or you can use Queued query(or async query) like Actor. 如果它确实是死锁,您可以修复查询以不发生这种情况,或者您可以使用像Actor这样的排队查询(或异步查询)。

for example, The commons-dbutils library has support for an AsyncQueryRunner which you provide an ExecutorService to and it returns a Future. 例如,commons-dbutils库支持AsyncQueryRunner,它提供ExecutorService并返回Future。 (from: Is asynchronous jdbc call possible? ) (来自: 是否可以进行异步jdbc调用?

To prevent locking errors, set a busy timeout of twenty seconds or so in every connection. 要防止锁定错误,请在每个连接中将忙碌超时设置为20秒左右。 Then you will get an exception only if your app hangs runs a transaction longer than that. 然后,只有当您的应用程序挂起运行的事务超过该事务时,才会获得异常。

You could also use a single SQLiteDatabase object from all threads, but then you would have to lock it manually around all transactions. 您还可以使用来自所有线程的单个SQLiteDatabase对象,但是您必须在所有事务周围手动锁定它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM