Sqlite数据库实例是否线程安全?

36

我有一个包含若干张表的数据库。 我想使用多线程更新这些表。 所有线程都将使用同一实例的SQLiteDatabase。

请问这种做法是否正确? Sqlite数据库是线程安全的吗? 不同的两个线程能否同时更新同一张表但是不同的数据集?


可能是在Android上使用SQLite的最佳实践是什么?的重复问题。 - Samuel Peter
请按照 https://dev59.com/iOo6XIcBkEYKwwoYPR_f 中所述的方式使用锁定。 - Boris Treukhov
5个回答

30

默认情况下,它不是线程安全的。你应该使用与锁相关的SQLiteHelper方法来提供线程安全。

[编辑]: SQLiteDatabase类提供了一个默认的锁定机制(请参见注释),如果您在多线程上运行,则无需考虑更改任何内容以实现线程安全。

在此文档中搜索“thread”:http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html

并阅读更多:


11
这段内容的意思是:通过在关键部分使用锁来控制 SQLiteDatabase 是否变得线程安全。如果您知道您的数据库只会被单个线程使用,那么将此设置为 false 可以避免不必要的开销。默认值为 true。这是否意味着它默认情况下是线程安全的?是的,这段话的含义是默认情况下 SQLiteDatabase 是线程安全的,但通过设置 setLockingEnabled(false),可以禁用线程安全机制,从而提高性能。 - Ryan
1
哦,应该是这样的,现在我错了。嗯。 - ahmet alp balkan
3
此方法已在API级别16中被弃用。 此方法现在不再起作用,请勿使用。 - Marian Paździoch
1
那么现在当这个方法被弃用时,SQLiteDatabase实例在任何情况下都不是线程安全的了吗? - Ivan
1
好的,我重新阅读了这个回答——也许你应该将这个回答放到实际状态而不是[正确][错误][错误],这非常令人困惑。简单的答案是否定的。它不是线程安全的,就是这样。https://dev59.com/iOo6XIcBkEYKwwoYPR_f - Boris Treukhov
显示剩余4条评论

5

Android使用Java锁机制来保持SQLite数据库的访问序列化。因此,如果多个线程有一个db实例,它总是以串行方式调用数据库,并且当然数据库是线程安全的

如果我们确定我们只从单个线程使用数据库,我们可以通过调用setLockingEnable(false)来禁用数据库内部锁定,但是这个方法已经在API级别16中弃用并不再使用。如果你查看SQLiteDatabase类中这个方法的实现,你会发现什么也没有写,即空方法。

public void setLockingEnabled (boolean lockingEnabled)

该方法现在不起作用,请勿使用。

我们需要注意的一件事是,应该创建您的助手类的一个实例(即通过使其成为单例)并将同一实例共享到多个线程中,在操作之间不要调用数据库上的close(),否则您可能会收到以下异常:

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

因此,在访问数据库时不要调用database.close(),当所有操作完成时,数据库将自动执行关闭操作。


是的,在onDestroy中调用sqLiteHelper.close时出现了一些错误。那么我应该怎么做呢?不要完全不调用它吗?当所有操作完成时,_database将在内部自行执行关闭操作。但是,public SQLiteDatabase getWritableDatabase()说__确保在不再需要数据库时调用close__。 - user25
如何创建线程安全的单例,以便您真正拥有一个数据库实例? - NeverEndingQueue

2
您可以通过使用setLockingEnabled来控制数据库是否支持线程安全。这是非常昂贵的,因此如果您知道您的数据库只会被单个线程使用,则应将其设置为false。默认值为true。
所以我认为这回答了你的问题。
方法setLockingEnabled在API级别16中已被弃用。

0

我认为API 16之后这里的答案不再准确。

简而言之:我认为API 16及更高版本不会阻止您在不同线程上同时执行多个SQL语句。

在API 16之前,方法setLockingEnabled确实存在,javadoc清楚地说明它默认设置为true。一旦该方法在API 16中被弃用并设置为无效,文档中就没有关于锁定是否启用的官方信息了。但是我们可以通过查看代码来获取一些信息:https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/database/sqlite/SQLiteDatabase.java

有一个名为mLock的字段明确表示它仅用于全局状态更改,而不用于执行SQL语句:

// Shared database state lock.
// This lock guards all of the shared state of the database, such as its
// configuration, whether it is open or closed, and so on.  This lock should
// be held for as little time as possible.
//
// The lock MUST NOT be held while attempting to acquire database connections or
// while executing SQL statements on behalf of the client as it can lead to deadlock.
//
// It is ok to hold the lock while reconfiguring the connection pool or dumping
// statistics because those operations are non-reentrant and do not try to acquire
// connections that might be held by other threads.
//
// Basic rule: grab the lock, access or modify global state, release the lock, then
// do the required SQL work.
private final Object mLock = new Object();

此外,所有的SQL工作都是在SQL会话中完成的,每个线程都有自己的会话(下面的引用来自SQLiteSession):

会话对象不是线程安全的。事实上,会话对象是与线程绑定的。 {@link SQLiteDatabase}使用线程本地变量将会话与每个线程关联,仅供该线程使用。 因此,每个线程都有自己的会话对象,因此其事务状态独立于其他线程。

这与API 15及更早版本不同,在那些版本中,执行语句是直接从数据库中进行的,而不是在会话中进行的:例如https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-4.0.4_r2.1/core/java/android/database/sqlite/SQLiteStatement.java中的executeUpdateDelete方法实际上会自行获取和释放锁。它调用SQLiteDatabase上的lock方法,该方法会执行mLockingEnabled的检查,然后锁定mLock对象。通过这种方式,不同线程上不能同时执行两个SQL语句。

相比之下,在现代版本的Android中,SQLiteDatabase中的synchronized(mLock)仅围绕全局状态更改,如上面的注释所示 - 并且SQLiteDatabase上不再有任何lock方法(由语句等调用)。因此,我找不到任何证据表明Android仍然确保两个不同线程上的SQL语句不能同时执行。

-2

如果你做到了...

setLockingEnabled(boolean lockingEnabled) 控制是否通过在关键部分周围使用锁来使SQLiteDatabase线程安全。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接