在 Android 上使用 SQLite 时如何避免并发问题?

Posted

技术标签:

【中文标题】在 Android 上使用 SQLite 时如何避免并发问题?【英文标题】:How can I avoid concurrency problems when using SQLite on Android? 【发布时间】:2011-01-30 09:39:56 【问题描述】:

android 应用中对 SQLite 数据库执行查询时,最佳做法是什么?

从 AsyncTask 的 doInBackground 运行插入、删除和选择查询是否安全?还是我应该使用 UI 线程?我想数据库查询可能很“繁重”并且不应该使用 UI 线程,因为它可以锁定应用程序 - 导致 Application Not Responding (ANR)。

如果我有多个 AsyncTask,它们应该共享一个连接还是应该各自打开一个连接?

这些场景有什么最佳实践吗?

【问题讨论】:

无论你做什么,如果你的内容提供者(或 SQLite 接口)是公开的,请记得清理你的输入! 你绝对不应该从 UI 线程访问数据库,我可以告诉你这么多。 @EdwardFalk 为什么不呢?确实存在这样做有效的用例吗? 如果您从 UI 线程进行任何 I/O、网络访问等,整个设备将冻结,直到操作完成。如果它在 1/20 秒内完成,那很好。如果需要更长的时间,您的用户体验就会很差。 【参考方案1】: 使用ThreadAsyncTask 进行长时间运行的操作(50 毫秒以上)。测试您的应用程序以查看它在哪里。大多数操作(可能)不需要线程,因为大多数操作(可能)只涉及几行。使用线程进行批量操作。 在线程之间为磁盘上的每个 DB 共享一个 SQLiteDatabase 实例,并实施计数系统来跟踪打开的连接。

这些场景有什么最佳实践吗?

在所有类之间共享一个静态字段。我曾经为那个和其他需要共享的东西保留一个单身人士。还应使用计数方案(通常使用 AtomicInteger)来确保您永远不会提前关闭数据库或使其保持打开状态。

我的解决方案:

我写的旧版本可以在https://github.com/Taeluf/dev/tree/main/archived/databasemanager 获得,并且没有维护。如果您想了解我的解决方案,请查看代码并阅读我的笔记。我的笔记通常很有帮助。

    将代码复制/粘贴到名为DatabaseManager 的新文件中。 (或从 github 下载) 像往常一样扩展DatabaseManager 并实现onCreateonUpgrade。您可以创建一个 DatabaseManager 类的多个子类,以便在磁盘上拥有不同的数据库。 实例化您的子类并调用getDb() 以使用SQLiteDatabase 类。 为您实例化的每个子类调用 close()

复制/粘贴的代码:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;

import java.util.concurrent.ConcurrentHashMap;

/** Extend this class and use it as an SQLiteOpenHelper class
 *
 * DO NOT distribute, sell, or present this code as your own. 
 * for any distributing/selling, or whatever, see the info at the link below
 *
 * Distribution, attribution, legal stuff,
 * See https://github.com/JakarCo/databasemanager
 * 
 * If you ever need help with this code, contact me at support@androidsqlitelibrary.com (or support@jakar.co )
 * 
 * Do not sell this. but use it as much as you want. There are no implied or express warranties with this code. 
 *
 * This is a simple database manager class which makes threading/synchronization super easy.
 *
 * Extend this class and use it like an SQLiteOpenHelper, but use it as follows:
 *  Instantiate this class once in each thread that uses the database. 
 *  Make sure to call @link #close() on every opened instance of this class
 *  If it is closed, then call @link #open() before using again.
 * 
 * Call @link #getDb() to get an instance of the underlying SQLiteDatabse class (which is synchronized)
 *
 * I also implement this system (well, it's very similar) in my <a href="http://androidslitelibrary.com">Android SQLite Libray</a> at http://androidslitelibrary.com
 * 
 *
 */
abstract public class DatabaseManager 
    
    /**See SQLiteOpenHelper documentation
    */
    abstract public void onCreate(SQLiteDatabase db);
    /**See SQLiteOpenHelper documentation
     */
    abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
    /**Optional.
     * *
     */
    public void onOpen(SQLiteDatabase db)
    /**Optional.
     * 
     */
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) 
    /**Optional
     * 
     */
    public void onConfigure(SQLiteDatabase db)



    /** The SQLiteOpenHelper class is not actually used by your application.
     *
     */
    static private class DBSQLiteOpenHelper extends SQLiteOpenHelper 

        DatabaseManager databaseManager;
        private AtomicInteger counter = new AtomicInteger(0);

        public DBSQLiteOpenHelper(Context context, String name, int version, DatabaseManager databaseManager) 
            super(context, name, null, version);
            this.databaseManager = databaseManager;
        

        public void addConnection()
            counter.incrementAndGet();
        
        public void removeConnection()
            counter.decrementAndGet();
        
        public int getCounter() 
            return counter.get();
        
        @Override
        public void onCreate(SQLiteDatabase db) 
            databaseManager.onCreate(db);
        

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 
            databaseManager.onUpgrade(db, oldVersion, newVersion);
        

        @Override
        public void onOpen(SQLiteDatabase db) 
            databaseManager.onOpen(db);
        

        @Override
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) 
            databaseManager.onDowngrade(db, oldVersion, newVersion);
        

        @Override
        public void onConfigure(SQLiteDatabase db) 
            databaseManager.onConfigure(db);
        
    

    private static final ConcurrentHashMap<String,DBSQLiteOpenHelper> dbMap = new ConcurrentHashMap<String, DBSQLiteOpenHelper>();

    private static final Object lockObject = new Object();


    private DBSQLiteOpenHelper sqLiteOpenHelper;
    private SQLiteDatabase db;
    private Context context;

    /** Instantiate a new DB Helper. 
     * <br> SQLiteOpenHelpers are statically cached so they (and their internally cached SQLiteDatabases) will be reused for concurrency
     *
     * @param context Any @link android.content.Context belonging to your package.
     * @param name The database name. This may be anything you like. Adding a file extension is not required and any file extension you would like to use is fine.
     * @param version the database version.
     */
    public DatabaseManager(Context context, String name, int version) 
        String dbPath = context.getApplicationContext().getDatabasePath(name).getAbsolutePath();
        synchronized (lockObject) 
            sqLiteOpenHelper = dbMap.get(dbPath);
            if (sqLiteOpenHelper==null) 
                sqLiteOpenHelper = new DBSQLiteOpenHelper(context, name, version, this);
                dbMap.put(dbPath,sqLiteOpenHelper);
            
            //SQLiteOpenHelper class caches the SQLiteDatabase, so this will be the same SQLiteDatabase object every time
            db = sqLiteOpenHelper.getWritableDatabase();
        
        this.context = context.getApplicationContext();
    
    /**Get the writable SQLiteDatabase
     */
    public SQLiteDatabase getDb()
        return db;
    

    /** Check if the underlying SQLiteDatabase is open
     *
     * @return whether the DB is open or not
     */
    public boolean isOpen()
        return (db!=null&&db.isOpen());
    


    /** Lowers the DB counter by 1 for any @link DatabaseManagers referencing the same DB on disk
     *  <br />If the new counter is 0, then the database will be closed.
     *  <br /><br />This needs to be called before application exit.
     * <br />If the counter is 0, then the underlying SQLiteDatabase is <b>null</b> until another DatabaseManager is instantiated or you call @link #open()
     *
     * @return true if the underlying @link android.database.sqlite.SQLiteDatabase is closed (counter is 0), and false otherwise (counter > 0)
     */
    public boolean close()
        sqLiteOpenHelper.removeConnection();
        if (sqLiteOpenHelper.getCounter()==0)
            synchronized (lockObject)
                if (db.inTransaction())db.endTransaction();
                if (db.isOpen())db.close();
                db = null;
            
            return true;
        
        return false;
    
    /** Increments the internal db counter by one and opens the db if needed
    *
    */
    public void open()
        sqLiteOpenHelper.addConnection();
        if (db==null||!db.isOpen())
                synchronized (lockObject)
                    db = sqLiteOpenHelper.getWritableDatabase();
                
         
    

【讨论】:

当你调用“close”然后尝试重用类时会发生什么?它会崩溃吗?还是会自动重新初始化以再次使用数据库? @androiddeveloper,如果您调用close,则必须再次调用open,然后才能使用该类的相同实例,或者您可以创建一个新实例。由于在close 代码中,我设置了db=null,因此您将无法使用来自getDb 的返回值(因为它将为空),因此如果您做了某事,您将得到NullPointerException喜欢myInstance.close(); myInstance.getDb().query(...); 为什么不将getDb()open() 合并为一个方法? @Burdu,提供对数据库计数器的额外控制和糟糕的设计的混合。不过,这绝对不是最好的方法。过几天我会更新的。 @Burdu,我刚刚更新了它。您可以从here 获取新代码。我还没有测试过,所以如果我应该提交更改,请告诉我。【参考方案2】:

Dmytro 的回答适用于我的情况。 我认为最好将函数声明为同步。至少在我的情况下,否则它会调用空指针异常,例如getWritableDatabase 尚未在一个线程中返回,同时 openDatabse 在另一个线程中调用。

public synchronized SQLiteDatabase openDatabase() 
    if(mOpenCounter.incrementAndGet() == 1) 
        // Opening new database
        mDatabase = mDatabaseHelper.getWritableDatabase();
    
    return mDatabase;

【讨论】:

mDatabaseHelper.getWritableDatabase();这不会创建新的数据库对象【参考方案3】:

并发数据库访问

Same article on my blog(I like formatting more)

我写了一篇小文章,描述了如何安全地访问你的 android 数据库线程。


假设您有自己的 SQLiteOpenHelper

public class DatabaseHelper extends SQLiteOpenHelper  ... 

现在您想在单独的线程中将数据写入数据库。

 // Thread 1
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

 // Thread 2
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

您将在 logcat 中收到以下消息,并且您的更改之一不会被写入。

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

发生这种情况是因为每次创建新的 SQLiteOpenHelper 对象时,实际上都是在建立新的数据库连接。如果您尝试同时从实际的不同连接写入数据库,则会失败。 (来自上面的答案)

要使用多线程数据库,我们需要确保我们使用的是一个数据库连接。

让我们创建一个单例类Database Manager,它将保存并返回单个SQLiteOpenHelper对象。

public class DatabaseManager 

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) 
        if (instance == null) 
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        
    

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

        return instance;
    

    public SQLiteDatabase getDatabase() 
        return new mDatabaseHelper.getWritableDatabase();
    


在不同线程中将数据写入数据库的更新代码将如下所示。

 // In your application class
 DatabaseManager.initializeInstance(new mysqliteOpenHelper());
 // Thread 1
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

 // Thread 2
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

这会给你带来另一个崩溃。

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

由于我们只使用一个数据库连接,方法 getDatabase()Thread1Thread2 返回相同的 SQLiteDatabase 对象实例。发生了什么,Thread1 可能会关闭数据库,而 Thread2 仍在使用它。这就是我们有 IllegalStateException 崩溃的原因。

我们需要确保没有人在使用数据库,然后才将其关闭。 stackoveflow 上的一些人建议永远不要关闭您的 SQLiteDatabase。这将导致以下 logcat 消息。

Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

工作样本

public class DatabaseManager 

    private int mOpenCounter;

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mDatabase;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) 
        if (instance == null) 
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        
    

    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() 
        mOpenCounter++;
        if(mOpenCounter == 1) 
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        
        return mDatabase;
    

    public synchronized void closeDatabase() 
        mOpenCounter--;
        if(mOpenCounter == 0) 
            // Closing database
            mDatabase.close();

        
    


如下使用。

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way

每次你需要数据库时,你应该调用DatabaseManager类的openDatabase()方法。在这个方法中,我们有一个计数器,它指示数据库打开了多少次。如果等于1,则表示我们需要创建新的数据库连接,否则,数据库连接已经创建。

closeDatabase() 方法也是如此。每次我们调用这个方法,计数器都会减少,只要它变为零,我们就关闭数据库连接。


现在您应该能够使用您的数据库并确保它是线程安全的。

【讨论】:

我是那些建议你永远不要关闭数据库的人之一。如果您打开数据库,您只会收到“发现泄漏”错误,不要关闭它,然后尝试再次打开。如果您只使用一个打开的帮助程序,并且从不 关闭数据库,则不会出现该错误。如果您发现其他情况,请告诉我(附代码)。我在某个地方有更长的帖子,但找不到。 commonsware 在这里回答的问题,在 SO 点部门中,谁比我们都强:***.com/questions/7211941/… 更多想法。 #1,我会在你的经理内部创建助手。问问题把它放在外面。新开发者可能出于某种疯狂的原因直接调用助手。此外,如果您需要一个 init 方法,如果实例已经存在,则抛出异常。多数据库应用程序显然会失败。 #2,为什么是 mDatabase 字段?它可以从助手那里获得。 #3,作为你迈向“永不关闭”的第一步,当你的应用程序崩溃并且没有“关闭”时,你的数据库会发生什么?提示,没什么。很好,因为 SQLite 非常稳定。这是弄清楚为什么你不需要关闭它的第一步。 在获取实例之前使用公共初始化方法调用的任何原因?为什么不使用名为if(instance==null) 的私有构造函数?您别无选择,只能每次调用初始化;你怎么知道它是否已经在其他应用程序等中初始化? initializeInstance() 有一个SQLiteOpenHelper 类型的参数,但在您的评论中您提到使用DatabaseManager.initializeInstance(getApplicationContext());。到底是怎么回事?这怎么可能起作用? @DmytroDanylyk “DatabaseManager 是线程安全的单例,因此它可以在任何地方使用”对于 virsir 的问题,这不是真的。对象不是共享的跨进程。您的 DatabaseManager 在不同的进程中将没有状态,例如在同步适配器中 (android:process=":sync")【参考方案4】:

从多个线程中插入、更新、删除和读取一般都可以,但 Brad 的 answer 不正确。您必须小心创建和使用连接的方式。在某些情况下,即使您的数据库没有损坏,更新调用也会失败。

基本答案。

SqliteOpenHelper 对象保持一个数据库连接。它似乎为您提供了读写连接,但实际上并没有。调用只读,无论如何都会得到写数据库连接。

所以,一个辅助实例,一个数据库连接。即使您从多个线程中使用它,一次一个连接。 SqliteDatabase 对象使用 java 锁来保持访问序列化。因此,如果 100 个线程有一个 db 实例,则对实际磁盘数据库的调用会被序列化。

所以,一个助手,一个数据库连接,在java代码中序列化。一个线程,1000 个线程,如果您使用它们之间共享的一个帮助程序实例,那么您所有的数据库访问代码都是串行的。生活是美好的(ish)。

如果您尝试同时从实际的不同连接写入数据库,则会失败。它不会等到第一个完成后再写入。它根本不会写你的改变。更糟糕的是,如果您没有在 SQLiteDatabase 上调用正确版本的插入/更新,您将不会收到异常。您只会在 LogCat 中收到一条消息,就是这样。

那么,多线程?使用一名助手。时期。如果你知道只有一个线程在写,你可以使用多个连接,你的读取速度会更快,但买家要小心。我没有测试那么多。

这是一篇包含更多详细信息和示例应用的博文。

Android Sqlite Locking(2012 年 6 月 18 日更新链接) Android-Database-Locking-Collisions-Example by touchlab 在 GitHub 上

Gray 和我实际上正在整理一个基于他的 Ormlite 的 ORM 工具,该工具可与 Android 数据库实现原生配合使用,并遵循我在博文中描述的安全创建/调用结构。那应该很快就出来了。看看吧。


同时,还有一篇后续博文:

Single SQLite connection

同样检查前面提到的锁定示例的 2point0 的分叉:

Android-Database-Locking-Collisions-Example by 2point0 在 GitHub 上

【讨论】:

顺便说一句,Ormlite 的 android 支持可以在 ormlite.sourceforge.net/sqlite_java_android_orm.html 找到。有示例项目、文档和 jar。 第二个。 ormlite 代码具有可用于管理 dbhelper 实例的帮助程序类。您可以使用 ormlite 的东西,但它不是必需的。您可以使用辅助类来进行连接管理。 Kāgii,谢谢你的详细解释。你能澄清一件事吗——我知道你应该有一个助手,但你也应该只有一个连接(即一个 SqliteDatabase 对象)吗?换句话说,你应该多久调用一次getWritableDatabase?同样重要的是,您何时调用 close()? 我更新了代码。当我切换博客主机时,原件丢失了,但我添加了一些精简的示例代码来演示这个问题。另外,您如何管理单个连接?最初我有一个更复杂的解决方案,但后来我修改了它。看这里:touchlab.co/uncategorized/single-sqlite-connection 是否需要处理连接以及在哪里处理?【参考方案5】:

我知道响应迟了,但在 android 中执行 sqlite 查询的最佳方式是通过自定义内容提供程序。这样,UI 与数据库类(扩展 SQLiteOpenHelper 类的类)分离。查询也在后台线程(游标加载器)中执行。

【讨论】:

【参考方案6】:

您可以尝试在 Google I/O 2017 上应用新的架构方法 anounced。

它还包括名为 Room 的新 ORM 库

它包含三个主要组件:@Entity、@Dao 和@Database

用户.java

@Entity
public class User 
  @PrimaryKey
  private int uid;

  @ColumnInfo(name = "first_name")
  private String firstName;

  @ColumnInfo(name = "last_name")
  private String lastName;

  // Getters and setters are ignored for brevity,
  // but they're required for Room to work.

UserDao.java

@Dao
public interface UserDao 
  @Query("SELECT * FROM user")
  List<User> getAll();

  @Query("SELECT * FROM user WHERE uid IN (:userIds)")
  List<User> loadAllByIds(int[] userIds);

  @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
       + "last_name LIKE :last LIMIT 1")
  User findByName(String first, String last);

  @Insert
  void insertAll(User... users);

  @Delete
  void delete(User user);

AppDatabase.java

@Database(entities = User.class, version = 1)
public abstract class AppDatabase extends RoomDatabase 
  public abstract UserDao userDao();

【讨论】:

我不建议将 Room 用于具有多个 N 对 N 关系的数据库,因为它不能很好地处理这种方法,您必须编写大量代码才能找到解决这些问题的方法关系。【参考方案7】:

遇到了一些问题,我想我已经明白我为什么会出错了。

我编写了一个数据库包装类,其中包含一个close(),它调用助手关闭作为open() 的镜像,它调用getWriteableDatabase,然后迁移到ContentProviderContentProvider 的模型不使用 SQLiteDatabase.close(),我认为这是一个很大的线索,因为代码确实使用了 getWriteableDatabase 在某些情况下我仍在进行直接访问(主要是屏幕验证查询,所以我迁移到 getWriteableDatabase/ rawQuery 模型。

我使用的是单例,并且在关闭文档中有一点不祥的评论

关闭任何打开的数据库对象

(我的粗体字)。

所以我在使用后台线程访问数据库并且它们与前台同时运行时出现间歇性崩溃。

所以我认为close() 会强制关闭数据库,而不管任何其他持有引用的线程 - 所以close() 本身并不是简单地撤消匹配的getWriteableDatabase 而是强制关闭任何 打开请求。大多数时候这不是问题,因为代码是单线程的,但在多线程的情况下,总是有可能打开和关闭不同步。

在其他地方阅读了解释 SqLiteDatabaseHelper 代码实例计数的 cmets,那么您想要关闭的唯一时间是您想要执行备份副本的情况,并且您想要强制关闭所有连接并且强制 SqLite 写掉任何可能徘徊的缓存内容 - 换句话说,停止所有应用程序数据库活动,关闭以防万一 Helper 丢失轨道,执行任何文件级活动(备份/恢复)然后重新开始。

虽然尝试以受控方式关闭听起来是个好主意,但现实情况是 Android 保留丢弃您的 VM 的权利,因此任何关闭都可以降低缓存更新不被写入的风险,但不能保证如果设备受到压力,并且您已正确释放游标和对数据库的引用(不应是静态成员),那么帮助程序无论如何都会关闭数据库。

所以我的看法是:

使用 getWriteableDatabase 从单例包装器打开。 (我使用派生的应用程序类从静态提供应用程序上下文,以解决对上下文的需求)。

永远不要直接调用close。

永远不要将生成的数据库存储在没有明显范围的任何对象中,并依赖引用计数来触发隐式 close()。

如果进行文件级处理,请停止所有数据库活动,然后调用 close正确的事务,而不是部分事务的文件级副本。

【讨论】:

【参考方案8】:

在为此苦苦挣扎了几个小时后,我发现每次执行 db 只能使用一个 db helper 对象。例如,

for(int x = 0; x < someMaxValue; x++)

    db = new DBAdapter(this);
    try
    

        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    
    catch (Exception e)
    
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    
    db.close();

相对于:

db = new DBAdapter(this);
for(int x = 0; x < someMaxValue; x++)


    try
    
        // ask the database manager to add a row given the two strings
        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    
    catch (Exception e)
    
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    


db.close();

每次循环迭代时创建一个新的 DBAdapter 是我可以通过帮助程序类将字符串放入数据库的唯一方法。

【讨论】:

【参考方案9】:

我对 SQLiteDatabase API 的理解是,如果您有一个多线程应用程序,您不能承受超过 1 个 SQLiteDatabase 对象指向单个数据库。

对象肯定可以创建,但如果不同的线程/进程(也)开始使用不同的 SQLiteDatabase 对象(就像我们在 JDBC 连接中使用的方式),则插入/更新会失败。

这里唯一的解决方案是坚持使用 1 个 SQLiteDatabase 对象,并且每当在超过 1 个线程中使用 startTransaction() 时,Android 管理不同线程之间的锁定,并且一次只允许 1 个线程具有独占更新访问权限。

您还可以从数据库中“读取”并在不同的线程中使用相同的 SQLiteDatabase 对象(而另一个线程写入),并且永远不会出现数据库损坏,即“读取线程”不会从数据库中读取数据直到“写入线程”提交数据,尽管两者都使用相同的 SQLiteDatabase 对象。

这与 JDBC 中的连接对象不同,如果您在读取和写入线程之间传递(使用相同的)连接对象,那么我们很可能也会打印未提交的数据。

在我的企业应用程序中,我尝试使用条件检查,以便 UI 线程永远不必等待,而 BG 线程则持有 SQLiteDatabase 对象(独占)。我尝试预测 UI 操作并推迟 BG 线程运行“x”秒。也可以维护 PriorityQueue 来管理分发 SQLiteDatabase Connection 对象,以便 UI 线程首先获得它。

【讨论】:

那么您在 PriorityQueue 中放入了什么 - 侦听器(想要获取数据库对象)或 SQL 查询? 我没有使用优先队列方法,但本质上是“调用者”线程。 @Swaroop: PCMIIW,"read thread" wouldn't read the data from the database till the "write thread" commits the data although both use the same SQLiteDatabase object。并非总是如此,如果您在“写入线程”之后启动“读取线程”,您可能无法获得新更新的数据(在写入线程中插入或更新)。读线程可能在写线程开始之前读取数据。发生这种情况是因为写操作最初启用的是保留锁而不是独占锁。【参考方案10】:

数据库在多线程方面非常灵活。我的应用程序同时从许多不同的线程访问它们的数据库,并且效果很好。在某些情况下,我有多个进程同时访问数据库,而且效果也很好。

您的异步任务 - 尽可能使用相同的连接,但如果必须,可以从不同任务访问数据库。

【讨论】:

另外,您是否有不同连接的读者和作者,或者他们应该共享一个连接?谢谢。 @Gray - 正确,我应该明确提到这一点。至于连接,我会尽可能使用相同的连接,但由于锁定是在文件系统级别处理的,您可以在代码中多次打开它,但我会尽可能使用单个连接。 Android sqlite DB 非常灵活和宽容。 @Gray,只是想为可能正在使用此方法的人发布更新信息。文档说: 这个方法现在什么都不做。不要使用。 我发现这种方法非常失败,我们正在切换到 ContentProvider 以从多个应用程序访问。我们将不得不对我们的方法进行一些并发处理,但这应该可以解决所有进程同时访问数据的任何问题。 我知道这是旧的,但它不正确。 可能可以从不同 AsyncTasks/Threads 上的不同 SQLiteDatabase 对象访问 DB,但有时会导致错误,这就是为什么 SQLiteDatabase(第 1297 行)使用Locks

以上是关于在 Android 上使用 SQLite 时如何避免并发问题?的主要内容,如果未能解决你的问题,请参考以下文章

android上如何使用sqlite数据库

在 Android SQLite 中处理日期的最佳方式 [关闭]

在 Android 上使用来自 libGDX 的 SQLite

如何在android中启动活动时从sqlite数据库中删除所有行(数据)

如何在 Android Studio 中使用按钮按初始筛选并使用 SQLite 将其显示在另一个活动上

如何在 android 中打开 SQLite 数据库?