最近遇到个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.
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:
Also checkout the fork by2point0 of the previously mentioned locking example:
|
关于第二种方式,同样也能解决并发操作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对象,然后关闭,性能远远低于单例的方式