- Date:2015-4-23
- Tag:android; sqlite; 事务; 多线程
- Author:踏雪
- Email:[email protected]
SQLiteDatabase db = openOrCreateDatabase(DATABASE_NAME, MODE_PRIVATE, null);
db.execSQL("DROP TABLE IF EXISTS " + Student.TABLE_NAME);
db.execSQL("CREATE TABLE " + Student.TABLE_NAME
+ "("
+ Student.ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ Student.NAME + " VARCHAR, "
+ Student.SCORE + " VARCHAR "
+ ");");
if(db.delete(Student.TABLE_NAME, Student.NAME + "=?", new String[]{"wgc"}) == 0) {
Log.v("base_operation", "删除失败!");
} else {
Log.v("base_operation", "删除成功!");
}
values.put(Student.NAME, "wgc2");
values.put(Student.SCORE, "99");
int rows = db.update(Student.TABLE_NAME, values, Student.NAME + "=?", new String[]{"wgc"});
Cursor cursor = db.rawQuery(
"select * from "
+ Student.TABLE_NAME
+ " where " + Student.NAME + "=?"
, new String[] {"wgc2"});
Student student = new Student();
while(cursor.moveToNext()){
student.id = cursor.getString(cursor.getColumnIndex(Student.ID));
student.name = cursor.getString(cursor.getColumnIndex(Student.NAME));
student.score = cursor.getString(cursor.getColumnIndex(Student.SCORE));
Log.v("base_operation", student.toString());
}
应用程序初始化时如果单独使用for+Insert的方式插入大量的数据,会导致应用响应慢。因为SQLite的数据库本质上是一个磁盘上的文件,所以一些的数据库操作其实就是对文件的操作。而频繁的文件操作是一个很耗时的过程,极大地影响了数据库的存储速度。
sqlite插入数据的时候默认一条语句就是一个事务,有多少条数据就有多少次磁盘操作。初始化1000条数据,也就要1000次磁盘的操作,将会重复的打开关闭数据库1000次,所以速度回满,而且还不能保证所有的数据都能同时插入。
解决方法是使用事务处理。
(1)db.beginTransaction();//手动设置开始事务
(2)db.setTransactionSuccessful();//设置事务处理成功,不设置会自动回滚不提交,在setTransactionSuccessful和endTransaction之间不进行任何数据库操作
(3) db.endTransaction();//处理完成
这样SQLite将把全部要执行的SQL语句先缓存到内存当中,然后等到commit的时候一次性写入到数据库。这样数据库文件只被打开关闭了一次,效率就大大提高。
long startMillis = System.currentTimeMillis();
for(int i = 0; i < 1000; i++) {
ContentValues val = new ContentValues();
val.put(Student.NAME, "wgc"+i);
val.put(Student.SCORE, "99");
db.insert(Student.TABLE_NAME, null, values);
}
Log.v("transaction", "未开始事务属性之前总共耗时:" + (System.currentTimeMillis() - startMillis) + "");
db.beginTransaction();
startMillis = System.currentTimeMillis();
for(int i = 0; i < 1000; i++) {
ContentValues val = new ContentValues();
val.put(Student.NAME, "wgc"+i);
val.put(Student.SCORE, "99");
db.insert(Student.TABLE_NAME, null, val);
}
db.setTransactionSuccessful();
db.endTransaction();
Log.v("transaction", "使用事务属性之后总共耗时:" + (System.currentTimeMillis() - startMillis) + "");
下面是一个对比结果,可以明显看出,使用事务属性后,效率大大提高了。
我们可以得知SQLite是文件级别的锁:多个县城可以同时读,但是同时只能只有一个线程写。如果多线程同时读写(这里指不同的线程使用的是不同的SQLiteDatabase实例),后面就会遇到android.database.sqlite.SQLiteException:database is locked这样的异常。
解决办法是保持一个sqlite连接,保持单个SQLiteDatabase实例。同时对所有数据库操作方法添加synchroned关键字。
class WriteThread extends Thread{
@Override
public void run() {
SQLiteDatabase db = openOrCreateDatabase("demo.db", MODE_PRIVATE, null);
db.beginTransaction();
for(int i = 0; i < 8000; i++) {
ContentValues val = new ContentValues();
val.put(Student.NAME, "wgc"+i);
val.put(Student.SCORE, "99");
db.insert(Student.TABLE_NAME, null, val);
}
db.setTransactionSuccessful();
db.endTransaction();
Log.v("multiThread2", "Thread ID:"+getId());
}
}
…………………..
//开启5个线程执行写操作
for(int i = 0; i < 5; i++) {
new WriteThread().start();
}
运行程序,会见到程序奔溃,并报错,错误提示如下:
class ReadThread extends Thread {
@Override
public void run() {
SQLiteDatabase db = openOrCreateDatabase("demo.db", MODE_PRIVATE,
null);
for (int i = 0; i < 8000; i++) {
StringBuilder builder = new StringBuilder();
builder.append("select * from ");
builder.append(Student.TABLE_NAME);
builder.append(" where " + Student.NAME + "=?");
Cursor cursor = db.rawQuery(builder.toString(),
new String[] { "wgc2" });
Student student = new Student();
if (cursor.moveToNext()) {
student.id = cursor.getString(cursor
.getColumnIndex(Student.ID));
student.name = cursor.getString(cursor
.getColumnIndex(Student.NAME));
student.score = cursor.getString(cursor
.getColumnIndex(Student.SCORE));
}
Log.v("multiThread",
"Thread ID:" + getId() + "===" + student.toString());
}
}
}
…………………..
//开启5个线程执行读操作
for(int i = 0; i < 5; i++) {
new ReadThread().start();
}
程序运行后没有任何异常,也没有报错。
下面给出解决多线程同时写的解决方法:
(1) 首先是使用单例模式,始终保持一个连接,使用一个SQLiteDatabase实例。
public class SQLiteDatabaseManager {
private static SQLiteDatabase db = null;
public static SQLiteDatabase getInstance(Context context, String name, int mode) {
if(db == null) {
db = context.openOrCreateDatabase(name, mode, null);
}
return db;
}
}
通过SQLiteDatabaseManager的getInstance方法就能保证始终使用同一个SQLiteDatabase实例。给write函数添加synchronized关键字能保证该方法统一时刻不会被同时调用。
然后我们队对第一次多线程写操作的代码做一些修改:
private synchronized void write() {
SQLiteDatabase db = SQLiteDatabaseManager.getInstance(
getApplicationContext(), "demo.db", MODE_PRIVATE);
db.beginTransaction();
for (int i = 0; i < 8000; i++) {
ContentValues val = new ContentValues();
val.put(Student.NAME, "wgc" + i);
val.put(Student.SCORE, "99");
db.insert(Student.TABLE_NAME, null, val);
}
db.setTransactionSuccessful();
db.endTransaction();
}
class WriteThread extends Thread {
@Override
public void run() {
write();
}
}
private synchronized void read() {
SQLiteDatabase db = SQLiteDatabaseManager.getInstance(
getApplicationContext(), "demo.db", MODE_PRIVATE);
for (int i = 0; i < 8000; i++) {
StringBuilder builder = new StringBuilder();
builder.append("select * from ");
builder.append(Student.TABLE_NAME);
builder.append(" where " + Student.NAME + "=?");
Cursor cursor = db.rawQuery(builder.toString(),
new String[] { "wgc2" });
Student student = new Student();
if (cursor.moveToNext()) {
student.id = cursor
.getString(cursor.getColumnIndex(Student.ID));
student.name = cursor.getString(cursor
.getColumnIndex(Student.NAME));
student.score = cursor.getString(cursor
.getColumnIndex(Student.SCORE));
}
}
}
class ReadThread extends Thread {
@Override
public void run() {
read();
}
}
我们再次进行多线程的读写操作:
// 开启5个线程执行读操作
for (int i = 0; i < 5; i++) {
new ReadThread().start();
}
// 开启5个线程执行写操作
for (int i = 0; i < 5; i++) {
new WriteThread().start();
}
这次程序就没有异常,也没有报错了。
源码下载地址:
http://download.csdn.net/detail/wen294299195/9308083