SQLite在多線程並發訪問的應用


SQLite在多線程並發訪問的應用

 

最近遇到個SQLite的問題把我卡住了小半天,最后總結一句話:SQLite不支持多線程

研究一下,發現有以下2種方案可行

1.首先當多個線程並發操作同一個數據庫,同時存在insert、delete和select操作,數據是不安全的,在Android內置的SQLite中也是不允許的,這時會造成沖突異常。不允許多線程,則必須實現多線程同步

多線程同步鎖的訪問SQLite使用:始終讓整個Application保持一個database連接,這樣的話即使多線程同時訪問sqlite,database對象使用java鎖會保持訪問的序列化。我們一般都是用SQLHelper來管理數據庫,而一個Helper實例會產生一個database連接,所以我們只需要讓整個Application產生一個SQLHelper的實例就行了

public class SQLHelper extends SQLiteOpenHelper {

private static final int VERSION_CODE = 1;
private static SQLHelper helper;

/**
* 同步鎖 +單例模式獲得唯一數據庫幫助類實例
*
* @param context
* @return
*/
public synchronized static SQLHelper getInstance(Context context) {
if (helper == null) {
helper = new SQLHelper(context);
}
return helper;
}

private SQLHelper(Context context) {
super(context, "person.db", null, VERSION_CODE);
}

private SQLHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, VERSION_CODE);
}

@Override
public void onCreate(SQLiteDatabase db) {
Log.i("sqlite", "數據庫被創建");
// 創建數據庫表
String sql = "create table t_student(_id integer primary key autoincrement,id integer,name varchr(20),age int,address varchr(40))";
db.execSQL(sql);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
String sql = "drop table t_student";
db.execSQL(sql);
}

@Override
public synchronized void close() {
super.close();
}
}

在程序的入口處,Application中產生一個SQLHelper的實例,注意此處的onTerminate()不一定每次退出時都能執行,建議在首頁(主activity)的onDestory()方法中調用dbHelper.close();來關閉數據庫連接

public class MyApplication extends Application {
private Context context;
private DBHelper dbHelper;

public void onCreate() {
super.onCreate();
context = getApplicationContext();
dbHelper = DBHelper.getInstance(context);
}

public void onTerminate() {
super.onTerminate();
dbHelper.close();
}
}
總結:多線程 同步鎖的方式訪問SQLite,保證了同一個時刻SQLite 只有一個連接,多個線程排隊依次訪問數據庫,完美解決了沖突問題。

這個問題確實讓我困擾了小半天,自己摸索的確是比較痛苦,最后在國外一片帖子中看到這么一段描述,有種豁然開朗的感覺。

Inserts, updates, deletes and reads are generally OK from multiple threads, but Brad'sanswer is not correct.  You have to be careful with how you create your connections and use them.  There are situations where your update calls will fail, even if your database doesn't get corrupted.

The basic answer.

The SqliteOpenHelper object holds on to one database connection.  It appears to offer you a read and write connection, but it really doesn't.  Call the read-only, and you'll get the write database connection regardless.

So, one helper instance, one db connection.  Even if you use it from multiple threads, one connection at a time.  The SqliteDatabase object uses java locks to keep access serialized.  So, if 100 threads have one db instance, calls to the actual on-disk database are serialized.

So, one helper, one db connection, which is serialized in java code.  One thread, 1000 threads, if you use one helper instance shared between them, all of your db access code is serial.  And life is good (ish).

If you try to write to the database from actual distinct connections at the same time, one will fail.  It will not wait till the first is done and then write.  It will simply not write your change.  Worse, if you don’t call the right version of insert/update on the SQLiteDatabase, you won’t get an exception.  You’ll just get a message in your LogCat, and that will be it.

So, multiple threads?  Use one helper.  Period.  If you KNOW only one thread will be writing, you MAY be able to use multiple connections, and your reads will be faster, but buyer beware.  I haven't tested that much.

Here's a blog post with far more detail and an example app.

Android Sqlite Locking (Updated link 6/18/2012)
Android-Database-Locking-Collisions-Example by touchlab on GitHub
Gray and I are actually wrapping up an ORM tool, based off of his Ormlite, that works natively with Android database implementations, and follows the safe creation/calling structure I describe in the blog post.  That should be out very soon.  Take a look.

In the meantime, there is a follow up blog post:

Single SQLite connection
Also checkout the fork by2point0 of the previously mentioned locking example:

Android-Database-Locking-Collisions-Example by 2point0 onGitHub


 

關於第二種方式,同樣也能解決並發操作SQLite

2.“一個database connect,既有查詢又有更新(不同的statement,且不論順序),執行完之后,不關閉,會產生一個擴展名為s3db-journal的(臨時)文件,若再次使用該connect執行更新,則產生“unable to open database file”的異常。 所以,一個connect執行完之后要么close,要么自己處理臨時文件。”畢竟不同設備的數據庫文件存儲路徑有所區別,刪除可能遇到文件路徑錯誤,文件不存在等問題,所以推薦使用前者,執行完畢,立即關閉close()

@Override
public synchronized void close() {
super.close();
}

 注意:此處和上面案例恰恰相反,每次使用數據都要通過構造函數構造一個SQLHelper實例,如果每次使用同一個SQLHelper,那么關閉后就不能再打開數據庫

Cursor cursor = null;
try {
// helper = SQLHelper.getInstance(context);
//注意:此處和上面案例恰恰相反,每次使用數據都要通過構造函數構造一個sqlhelper實例
helper = new SQLHelper(context);
SQLiteDatabase db = helper.getReadableDatabase();
cursor = db.query("t_student", null, null, null, null, null, null);
} catch (Exception e) {
// TODO: handle exception
} finally {
helper.close(); // 用完立即關閉
}

 

總結:比較了這兩種方法,測試過后,強烈推薦使用第一個,從性能的角度,第一種使用單例加同步鎖的模式,全局只有一個SQLHelper對象,而第二種方式則是需要多次創建SQLHelper對象,然后關閉,性能遠遠低於單例的方式

 
————————————————
版權聲明:本文為CSDN博主「小熊先生kisCode」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/LANG791534167/article/details/38984887


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM