我正在开发一个应用程序,该应用程序将数据本地存储在SQLite DB中,然后单击按钮即可同步到服务器。我面临的问题是,当我尝试从具有1000行以上的表中选择数据时,我的应用程序崩溃了。这就是我选择数据的方式:
Cursor crsOutletData = mDatabase.rawQuery("SELECT * FROM table_name WHERE some_column_1='complete' AND some_column_2 IS NULL", null);
请注意some_column_1和some_column_2不是主键。
谢谢。
答案 0 :(得分:1)
尽管游标有局限性,但可以处理数百万行。
限制是,如果一行包含的数据量大于CursorWindow(1M(早期版本)或2M)中可以容纳的数据量。通常,这仅在非常大的项目(例如图像或视频)中发生。
这是一个示例应用程序,该应用程序可处理300万行的插入和提取(虽然非常耗时)。
用于建立非UI线程何时完成的界面
:-
public interface DBDone {
void dbDone();
}
具有一些基本方法的数据库帮助程序,以允许添加和检索数据(也不允许选择WAL或日记模式,默认模式为Android,直到Android 9为止)。
public class DBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "mydb";
public static final int DBVERSION = 1;
public static final String TBL_TABLENAME = "table_name";
public static final String COL_SOMECOLUMN1 = "some_column_1";
public static final String COL_SOMECOLUMN2 = "some_column_2";
public static final String crt_tablename_sql = "CREATE TABLE IF NOT EXISTS " + TBL_TABLENAME + "(" +
COL_SOMECOLUMN1 + " TEXT, " +
COL_SOMECOLUMN2 + " TEXT" +
")";
private static boolean mWALMode = false;
SQLiteDatabase mDB;
public DBHelper(Context context, boolean wal_mode) {
super(context, DBNAME, null, DBVERSION);
mWALMode = wal_mode;
mDB = this.getWritableDatabase();
}
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
if (mWALMode) {
db.enableWriteAheadLogging();
} else {
db.disableWriteAheadLogging();
}
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(crt_tablename_sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
}
public long insert(String c1_value, String c2_value) {
String nullcolumnhack = null;
ContentValues cv = new ContentValues();
if ((c1_value == null && c2_value == null)) {
nullcolumnhack = COL_SOMECOLUMN1;
}
if (c1_value != null) {
cv.put(COL_SOMECOLUMN1,c1_value);
}
if (c2_value != null) {
cv.put(COL_SOMECOLUMN2,c2_value);
}
return mDB.insert(TBL_TABLENAME,nullcolumnhack,cv);
}
public long insertJustColumn1(String c1_value) {
return this.insert(c1_value, null);
}
public long insertJustColumn2(String c2_value) {
return this.insert(null,c2_value);
}
public Cursor getAllFromTableName() {
return this.getAll(TBL_TABLENAME);
}
public Cursor getAll(String table) {
return mDB.query(table,null,null,null,null,null,null);
}
}
MainActivity实例化DatabaseHelper(mDBHlpr指出,当构造函数通过调用 getWritableDatabase 强制创建时,它将创建数据库和基础表)。
然后调用dbDone方法,该方法将mStage设置为0将在新线程中清空表。
当清空表时,将调用dbDone,mStage将为1,因此将添加数据(如果不存在,则不应该将其清空为表)。
插入数据后,将调用dbDone,并且由于mStage现在为2,因此在提取所有行的Cursor后将记录一些信息。游标游标中的所有行,并且将两列都为空的行计数。
:-
public class MainActivity extends AppCompatActivity implements DBDone {
DBHelper mDBHlpr;
int mStage = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDBHlpr = new DBHelper(this,false);
dbDone();
}
// Add some data but not in the UI Thread
private void addData() {
new Thread(new Runnable() {
@Override
public void run() {
addSomeData(3000000);
dbDone(); // All done so notify Main Thread
}
}
).start();
}
public void dbDone() {
switch (mStage) {
case 0:
emptyTable();
break;
case 1:
addData();
break;
case 2:
logSomeInfo();
break;
}
mStage++;
}
/**
* Add some rows (if none exist) with random data
* @param rows_to_add number of rows to add
*/
private void addSomeData(int rows_to_add) {
Log.d("ADDSOMEDATA","The addSomeData method hass been invoked (will run in a non UI thread)");
SQLiteDatabase db = mDBHlpr.getWritableDatabase();
if(DatabaseUtils.queryNumEntries(db,DBHelper.TBL_TABLENAME) > 0) return;
// Random data that can be added to the first column
String[] potential_data1 = new String[]{null,"complete","started","stage1","stage2","stage3","stage4","stage5"};
// Random data that can be added to the second column
String[] potential_data2 = new String[]{null,"something else","another","different","unusual","normal"};
Random r = new Random();
db.beginTransaction();
for (int i=0; i < rows_to_add; i++) {
mDBHlpr.insert(
potential_data1[(r.nextInt(potential_data1.length))],
potential_data2[(r.nextInt(potential_data2.length))]
);
}
db.setTransactionSuccessful();
db.endTransaction();
}
/**
* Log some basic info from the Cursor always traversinf the entire cursor
*/
private void logSomeInfo() {
Log.d("LOGSOMEINFO","The logSomeInfo method has been invoked.");
Cursor csr = mDBHlpr.getAllFromTableName();
StringBuilder sb = new StringBuilder("Rows in Cursor = " + String.valueOf(csr.getCount()));
int both_null_column_count = 0;
while (csr.moveToNext()) {
if (csr.getString(csr.getColumnIndex(DBHelper.COL_SOMECOLUMN1)) == null && csr.getString(csr.getColumnIndex(DBHelper.COL_SOMECOLUMN2)) == null) {
both_null_column_count++;
}
}
sb.append("\n\t Number of rows where both columns are null is ").append(String.valueOf(both_null_column_count));
Log.d("LOGSOMEINFO",sb.toString());
}
/**
* Empty the table
*/
private void emptyTable() {
Log.d("EMPTYTABLE","The emptyTable method has been invoked (will run in a non UI thread)");
new Thread(new Runnable() {
@Override
public void run() {
mDBHlpr.getWritableDatabase().delete(DBHelper.TBL_TABLENAME,null,null);
dbDone();
}
}).start();
}
}
日志很可能包含CursorWindow完整消息,但这些消息将得到处理(因为有问题的行将包含在下一个CursorWindow中),例如:-
2018-12-30 10:19:10.862 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 404 bytes, free space 204 bytes, window size 2097152 bytes
2018-12-30 10:19:11.856 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 24 bytes, free space 11 bytes, window size 2097152 bytes
2018-12-30 10:19:12.377 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 7 bytes, free space 4 bytes, window size 2097152 bytes
2018-12-30 10:19:12.902 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 8 bytes, free space 1 bytes, window size 2097152 bytes
2018-12-30 10:19:13.433 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 24 bytes, free space 1 bytes, window size 2097152 bytes
2018-12-30 10:19:13.971 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 24 bytes, free space 21 bytes, window size 2097152 bytes
2018-12-30 10:19:14.505 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 24 bytes, free space 18 bytes, window size 2097152 bytes
2018-12-30 10:19:15.045 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 404 bytes, free space 187 bytes, window size 2097152 bytes
2018-12-30 10:19:15.598 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 7 bytes, free space 0 bytes, window size 2097152 bytes
...........
日志包括:-
2018-12-30 10:17:04.610 2799-2799/? D/EMPTYTABLE: The emptyTable method has been invoked (will run in a non UI thread)
2018-12-30 10:17:04.615 2799-2817/? D/ADDSOMEDATA: The addSomeData method hass been invoked (will run in a non UI thread)
2018-12-30 10:19:10.506 2799-2817/so53958115.so53958115 D/LOGSOMEINFO: The logSomeInfo method has been invoked.
2018-12-30 10:20:17.803 2799-2817/so53958115.so53958115 D/LOGSOMEINFO: Rows in Cursor = 3000000
Number of rows where both columns are null is 62604
所以花了:- -5毫秒以清空(已为空)表。 -2分6秒添加3,000,000行。 -1分钟7.5秒提取并遍历游标(不是您通常会提取那么多行)
但是最重要的是,游标已经处理了3,000,000行。您还可以看到CursorWindow在这种情况下为2M(2097152字节)。
问题可能不是1000行对于Cursor来说太大,尽管可能是某些行(如果存储图像/视频/长文本)的最大值超过了Cursor可以处理的大小。 / p>
该问题更有可能是由于其他原因,这些原因只能通过日志中的堆栈跟踪来确定。
因此,如果没有堆栈跟踪或更全面的信息,就不可能提供特定的答案。