簡體   English   中英

如何在插入一些新記錄之后刪除Android SQLite數據庫上的先前記錄

[英]How to drop previous records on Android SQLite database only after inserting some new ones

我正在構建一個Android應用程序,它可以查看HTML頁面的內容(學校的網站沒有給我們官方應用程序,也沒有公共API)。

我無法確定數據是否完全是新的,因此目前所有獲取數據並將其插入數據庫都是在AsyncTask中完成的,基本上是這樣的:

  • recreateDatabase()//刪除所有表並再次創建它們。
  • insertSubjectList()//遍歷列表中的所有項目並將它們添加到數據庫中

然后在AsyncTask結果中:

  • refreshCurrentFragment();

問題是:如果用戶進入應用程序中從recreateDatabase()和insertSubjectList()之間讀取數據庫中的數據的區域,則數據庫中可能幾乎沒有數據,因為它當前正在重新填充。

它不會使應用程序崩潰,但這是一種不受歡迎的行為。 我的問題是:是否有可能告訴SQLite只有在新數據可用於查詢后才清理舊數據? 由於他們的信息可能會崩潰,因此無法同時讀取新舊內容。

希望有人可以幫我解決這個問題!

聽起來像而不是: -

  • recreateDatabase()//刪除所有表並再次創建它們。
  • insertSubjectList()//遍歷列表中的所有項目並將它們添加到數據庫中

你要

  • insertSubjectList()到一個過渡表(與其對應的名稱不同的表,例如新創建的表)
  • 將原始表重命名為另一個表名,例如使用ALTER TABLE original TO original_renamed
  • 將過渡表重命名為其對應的(原始)表名。
  • 放下舊桌子。

因此,在加載數據時原始表仍然存在,然后僅在相對較短的時間段內將沒有數據可用。

這也可以在事務中完成,這可能會提高性能。

因此,這將回答“是”

是否有可能告訴SQLite只有在新數據可用於查詢后才清理舊數據?

至於

由於他們的信息可能會崩潰,因此無法同時讀取新舊內容。

它們不會同時被閱讀。

例如,考慮以下演示: -

DROP TABLE IF EXISTS master;
CREATE TABLE IF NOT EXISTS master (mydata TEXT);

-- Load the original table with some data 
WITH RECURSIVE cte1(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM cte1 where x < 1000)
INSERT INTO master SELECT * FROM cte1;
SELECT * FROM master LIMIT 10;

BEGIN TRANSACTION;
-- Load the new data (original table still available)
CREATE TABLE IF NOT EXISTS transitional (mydata TEXT);
WITH RECURSIVE cte1(x) AS (SELECT 2000 UNION ALL SELECT x+1 FROM cte1 where x < 3000)
INSERT INTO transitional SELECT * FROM cte1;
SELECT * FROM transitional LIMIT 10;

-- Switch the tables - original not available
ALTER TABLE master RENAME TO master_old;
ALTER TABLE transitional RENAME TO master;
-- original now available

-- Clean-up old
DROP TABLE master_old;
END TRANSACTION;
SELECT * FROM master LIMIT 10;

Android演示

以下是與上述類似的有效Android示例的代碼。

調用活動和數據庫助手是2段代碼

MainActivity.java

public class MainActivity extends AppCompatActivity {

    DBHelper mDBHlpr;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Instantiate the helper noting that data will be loaded
        mDBHlpr = new DBHelper(this); 
        // Get the current data
        Cursor csr = mDBHlpr.getFirst10Rows();
        // Write the current data to the log
        DatabaseUtils.dumpCursor(csr);

        //<<<<<<<<<< do the switch >>>>>>>>>>
        mDBHlpr.switchData(DBHelper.TABLE_MASTER);

        //Again get the data and write to the log
        csr = mDBHlpr.getFirst10Rows();
        DatabaseUtils.dumpCursor(csr);

        //Clean up
        csr.close();
    }
}

DBHelper.java

public class DBHelper extends SQLiteOpenHelper {

    public static final String DBNAME = "mydb";
    private static final int DBVERSION = 1;

    public static final String TABLE_MASTER = "master";
    public static final String COl_MASTER_ID = BaseColumns._ID;
    public static final String COL_MASTER_MYDATA = "mydata";

    SQLiteDatabase mDB;

    //Construct the helper NOTE will create the db if needed
    public DBHelper(Context context) {
        super(context, DBNAME,null, DBVERSION);
        mDB = this.getWritableDatabase(); // force db open
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(create_table_sql(TABLE_MASTER));
        mDB = db;
        addLotsOfData(TABLE_MASTER,1000,1); // Load some data
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    // Generate the table SQL according to the table name passed
    private String create_table_sql(String table) {
        return "CREATE TABLE IF NOT EXISTS " + table + "(" +
                COl_MASTER_ID + " INTEGER PRIMARY KEY," +
                COL_MASTER_MYDATA + " TEXT" +
                ")";
    }

    // Switch the original table for a newly created version
    public void switchData(String table) {

        String transitional_table = "transitional";
        String renamed_master = table + "_old";
        boolean already_in_transaction = mDB.inTransaction();

        if (!already_in_transaction) {
            mDB.beginTransaction();
        }

        //<<<<<<<<<< create and load of new data could be done elsewhere
        mDB.execSQL(create_table_sql(transitional_table));
        addLotsOfData(transitional_table,1000,3001); //<<<<<<<< load new data here
        //>>>>>>>>>>

        mDB.execSQL("ALTER TABLE " + TABLE_MASTER + " RENAME TO " + renamed_master);
        mDB.execSQL("ALTER TABLE " + transitional_table + " RENAME TO " + TABLE_MASTER);
        mDB.execSQL("DROP TABLE IF EXISTS " + renamed_master);

        if (!already_in_transaction) {
            mDB.setTransactionSuccessful();
            mDB.endTransaction();
        }
    }

    // Add some data
    private void addLotsOfData(String table, int rows, int value_offset) {
        boolean already_in_transaction = mDB.inTransaction();
        if (!already_in_transaction) {
            mDB.beginTransaction();
        }
        for (int i = 0; i < rows; i++) {
            addRow(table,String.valueOf(value_offset + i));
        }
        if (!already_in_transaction) {
            mDB.setTransactionSuccessful();
            mDB.endTransaction();
        }
    }

    // Insert a single row
    public long addRow(String table, String value) {
        ContentValues cv = new ContentValues();
        cv.put(COL_MASTER_MYDATA,value);
        return mDB.insert(table,null,cv);
    }

    public Cursor getFirst10Rows() {
        return mDB.query(TABLE_MASTER,null,null,null,null,null,null,"10");
    }
}
  • 注意,上面的設計只運行一次,對於后續運行,前后數據將是相同的。

結果

日志顯示(如預期): -

04-26 08:42:43.556I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@11697ce
04-26 08:42:43.557I/System.out: 0 {
04-26 08:42:43.557I/System.out:    _id=1
04-26 08:42:43.557I/System.out:    mydata=1
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 1 {
04-26 08:42:43.557I/System.out:    _id=2
04-26 08:42:43.557I/System.out:    mydata=2
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 2 {
04-26 08:42:43.557I/System.out:    _id=3
04-26 08:42:43.557I/System.out:    mydata=3
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 3 {
04-26 08:42:43.557I/System.out:    _id=4
04-26 08:42:43.557I/System.out:    mydata=4
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 4 {
04-26 08:42:43.557I/System.out:    _id=5
04-26 08:42:43.557I/System.out:    mydata=5
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 5 {
04-26 08:42:43.557I/System.out:    _id=6
04-26 08:42:43.557I/System.out:    mydata=6
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 6 {
04-26 08:42:43.557I/System.out:    _id=7
04-26 08:42:43.557I/System.out:    mydata=7
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 7 {
04-26 08:42:43.557I/System.out:    _id=8
04-26 08:42:43.557I/System.out:    mydata=8
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 8 {
04-26 08:42:43.557I/System.out:    _id=9
04-26 08:42:43.557I/System.out:    mydata=9
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 9 {
04-26 08:42:43.557I/System.out:    _id=10
04-26 08:42:43.557I/System.out:    mydata=10
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: <<<<<
04-26 08:42:43.652I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@3396ef
04-26 08:42:43.652I/System.out: 0 {
04-26 08:42:43.652I/System.out:    _id=1
04-26 08:42:43.652I/System.out:    mydata=3001
04-26 08:42:43.652I/System.out: }
04-26 08:42:43.652I/System.out: 1 {
04-26 08:42:43.652I/System.out:    _id=2
04-26 08:42:43.652I/System.out:    mydata=3002
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 2 {
04-26 08:42:43.653I/System.out:    _id=3
04-26 08:42:43.653I/System.out:    mydata=3003
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 3 {
04-26 08:42:43.653I/System.out:    _id=4
04-26 08:42:43.653I/System.out:    mydata=3004
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 4 {
04-26 08:42:43.653I/System.out:    _id=5
04-26 08:42:43.653I/System.out:    mydata=3005
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 5 {
04-26 08:42:43.653I/System.out:    _id=6
04-26 08:42:43.653I/System.out:    mydata=3006
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 6 {
04-26 08:42:43.653I/System.out:    _id=7
04-26 08:42:43.653I/System.out:    mydata=3007
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 7 {
04-26 08:42:43.653I/System.out:    _id=8
04-26 08:42:43.653I/System.out:    mydata=3008
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 8 {
04-26 08:42:43.653I/System.out:    _id=9
04-26 08:42:43.653I/System.out:    mydata=3009
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.654I/System.out: 9 {
04-26 08:42:43.654I/System.out:    _id=10
04-26 08:42:43.654I/System.out:    mydata=3010
04-26 08:42:43.654I/System.out: }
04-26 08:42:43.654I/System.out: <<<<<

額外

這是一種更通用的方法,可以處理多個表,禁止顯式的App / table特定加載新數據到轉換表。

已添加日志記錄,這可以啟用計時。

  • 例如,運行此行並加載10,000行(使用mDBHlpr.addLotsOfData(DBHelper.TABLE_MASTER + DBHelper.TRANSITION_SUFFIX,100000,10000); 可以看出: -

  • 即使表加載數據(10,000行)需要0.659秒,表實際上只有0.007秒不可用(如果DROP在切換后完成,則為0.001秒(丟棄表可能是昂貴的時間,同時重命名)一張桌子花很少的時間))。

修訂后的活動是: -

public class MainActivity extends AppCompatActivity {

    DBHelper mDBHlpr;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Instantiate the helper noting that data will be loaded
        mDBHlpr = new DBHelper(this);
        // Get the current data
        Cursor csr = mDBHlpr.getFirst10Rows();
        // Write the current data to the log
        DatabaseUtils.dumpCursor(csr);

        //<<<<<<<<<< do the switch >>>>>>>>>>
        mDBHlpr.switchData(DBHelper.TABLE_MASTER);

        //Again get the data and write to the log
        csr = mDBHlpr.getFirst10Rows();
        DatabaseUtils.dumpCursor(csr);

        //<<<<<<<<<<<<<<< AutoSwitch example >>>>>>>>>>
        //Create the transition tables
        mDBHlpr.prepareSwitchAllAppTables();

        Log.d("NEWDATALOADSTART","Loading of new data has started");
        // Prepare the new data by loading the data into the transition table
        // (only the 1 table for demo)
        // but perhaps 1 load per table according to requirements
        mDBHlpr.addLotsOfData(DBHelper.TABLE_MASTER + DBHelper.TRANSITION_SUFFIX,100000,10000);

        Log.d("TABLESNOTAVAILABLE","Tables will now be unavailable whil switching");

        // Switch all of the tables
        mDBHlpr.doSwitchAllAppTables();
        Log.d("TABLESAVAILABLE","Switch completed, Tables are now available with new data");

        //Again get the data and write to the log
        csr = mDBHlpr.getFirst10Rows();
        DatabaseUtils.dumpCursor(csr);

        //Clean up
        csr.close();
    }
}
  • 即從行//<<<<<<<<<<<<<<< AutoSwitch example >>>>>>>>>>已添加,但csr.close()仍然是最后一行。
  • 看評論
  • 該開關已分為2階段准備(創建轉換表)和實際切換(重命名原始表,然后將轉換表重命名為相應的原始表)。 雖然交換機包括刪除重命名的表,但可以將其移至第三階段,從而進一步減少表不可用的時間。

和修改后的DBHelpr.java

public class DBHelper extends SQLiteOpenHelper {

    public static final String DBNAME = "mydb";
    private static final int DBVERSION = 1;

    public static final String TABLE_MASTER = "master";
    public static final String COl_MASTER_ID = BaseColumns._ID;
    public static final String COL_MASTER_MYDATA = "mydata";

    public static final String TRANSITION_SUFFIX = "_trn";
    public static final String RENAME_SUFFIX = "rename";

    private static final String SQLITE_MASTER = "sqlite_master";
    private static final String SQLITE_MASTER_TYPECOLUMN = "type";
    private static final String SQLITE_MASTER_NAMECOLUMN = "name";
    private static final String SQLITE_MASTER_SQLCOLUMN = "sql";
    private static final String[] SQLITE_MATSER_COLUMNS = new String[]{SQLITE_MASTER_NAMECOLUMN,SQLITE_MASTER_SQLCOLUMN};
    private static final String APPTABLES_WHERECLAUSE =
            "(" +
                    SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'sqlite%' " +
                    " AND " +  SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'android%' " +
                    " AND " + SQLITE_MASTER_NAMECOLUMN + " NOT LIKE '%" + TRANSITION_SUFFIX + "'" +
                    ")" +
                    " AND " + SQLITE_MASTER_TYPECOLUMN + "= '"  + "table" + "'";

    SQLiteDatabase mDB;

    //Construct the helper NOTE will create the db if needed
    public DBHelper(Context context) {
        super(context, DBNAME,null, DBVERSION);
        mDB = this.getWritableDatabase(); // force db open
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(create_table_sql(TABLE_MASTER));
        mDB = db;
        addLotsOfData(TABLE_MASTER,1000,1); // Load some data
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    // Generate the table SQL according to the table name passed
    private String create_table_sql(String table) {
        return "CREATE TABLE IF NOT EXISTS " + table + "(" +
                COl_MASTER_ID + " INTEGER PRIMARY KEY," +
                COL_MASTER_MYDATA + " TEXT" +
                ")";
    }

    // Switch the original table for a newly created version
    public void switchData(String table) {

        String transitional_table = "transitional";
        String renamed_master = table + "_old";
        boolean already_in_transaction = mDB.inTransaction();

        if (!already_in_transaction) {
            mDB.beginTransaction();
        }

        //<<<<<<<<<< create and load of new data could be done elsewhere
        mDB.execSQL(create_table_sql(transitional_table));
        addLotsOfData(transitional_table,1000,3001); //<<<<<<<< load new data here
        //>>>>>>>>>>

        mDB.execSQL("ALTER TABLE " + TABLE_MASTER + " RENAME TO " + renamed_master);
        mDB.execSQL("ALTER TABLE " + transitional_table + " RENAME TO " + TABLE_MASTER);
        mDB.execSQL("DROP TABLE IF EXISTS " + renamed_master);

        if (!already_in_transaction) {
            mDB.setTransactionSuccessful();
            mDB.endTransaction();
        }
    }

    // Add some data
    public void addLotsOfData(String table, int rows, int value_offset) {
        boolean already_in_transaction = mDB.inTransaction();
        if (!already_in_transaction) {
            mDB.beginTransaction();
        }
        for (int i = 0; i < rows; i++) {
            addRow(table,String.valueOf(value_offset + i));
        }
        if (!already_in_transaction) {
            mDB.setTransactionSuccessful();
            mDB.endTransaction();
        }
    }

    // Insert a single row
    public long addRow(String table, String value) {
        ContentValues cv = new ContentValues();
        cv.put(COL_MASTER_MYDATA,value);
        return mDB.insert(table,null,cv);
    }

    public Cursor getFirst10Rows() {
        return mDB.query(TABLE_MASTER,null,null,null,null,null,null,"10");
    }

    /**
     * Create transition copy of all App tables (not sqlite..... tables or android.... tables)
     */
    public void prepareSwitchAllAppTables() {
        boolean already_in_transaction = mDB.inTransaction();
        if (!already_in_transaction) {
            mDB.beginTransaction();
        }

        Cursor csr = mDB.query(SQLITE_MASTER,SQLITE_MATSER_COLUMNS,APPTABLES_WHERECLAUSE,null,null, null,null);
        while (csr.moveToNext()) {
            String original_tablename = csr.getString(csr.getColumnIndex(SQLITE_MASTER_NAMECOLUMN));
            String original_sql = csr.getString(csr.getColumnIndex(SQLITE_MASTER_SQLCOLUMN));
            String transition_tablename = original_tablename + TRANSITION_SUFFIX;
            String transition_sql = original_sql.replace(original_tablename,transition_tablename).replace("CREATE TABLE","CREATE TABLE IF NOT EXISTS");
            Log.d("PREAPRE4SWITCH","Executing the SQL (create transition table for table " + original_sql +
                    ") \n\t" + transition_sql);
            mDB.execSQL(transition_sql);
            mDB.delete(transition_tablename,null,null); // just to make sure that all transition tables are empty
        }
        if (!already_in_transaction) {
            mDB.setTransactionSuccessful();
            mDB.endTransaction();
        }
    }

    public void doSwitchAllAppTables() {

        boolean already_in_transaction = mDB.inTransaction();
        if (!already_in_transaction) {
            mDB.beginTransaction();
        }
        Cursor csr = mDB.query(SQLITE_MASTER,SQLITE_MATSER_COLUMNS,APPTABLES_WHERECLAUSE,null,null, null,null);
        ArrayList<String> tables_to_delete = new ArrayList<>();
        while (csr.moveToNext()) {

            String original_name = csr.getString(csr.getColumnIndex(SQLITE_MASTER_NAMECOLUMN));
            String transition_name = original_name + TRANSITION_SUFFIX;
            String rename_name = RENAME_SUFFIX;
            tables_to_delete.add(rename_name);

            Log.d("SWITCHRENAMEORIG","Executing the SQL to rename(original) " + original_name + " table to " + rename_name);
            mDB.execSQL("ALTER TABLE " + original_name + " RENAME TO " + rename_name);
            Log.d("SWITCHRENAMETRNS","Executing the SQL to rename(transition) from " + transition_name +
                    " to (original)" + original_name);
            mDB.execSQL("ALTER TABLE " + transition_name + " RENAME TO " + original_name);
        }
        csr.close();
        for (String table_to_delete: tables_to_delete) {
            Log.d("SWITCHDROPRENAMED","Dropping renamed original table " + table_to_delete);
            mDB.execSQL("DROP TABLE If EXISTS " + table_to_delete);
        }

        if (!already_in_transaction) {
            mDB.setTransactionSuccessful();
            mDB.endTransaction();
        }
    }
}
  • 這假設您從不創建以androidsqlite開頭的App表

    • (sqlite_受到保護

      以“sqlite_”開頭的表名保留供內部使用。 嘗試創建名稱以“sqlite_”開頭的表是錯誤的。 )。

    • 所以SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'sqlite%' " SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'sqlite_%' "應該是SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'sqlite_%' "

  • sqlite_master用於驅動進程,它是模式並包含表的列表(以及其他實體,如索引,視圖和觸發器)。

  • 請注意,如果不使用FOREIGN KEYS且未使用ON CASCADE DELETE,則無法依賴上述內容。 因為子表必須在父母之前丟棄。

  • 觸發器參見ALTER TABLE - ALTER TABLE RENAME

結果

在日志中運行上述結果包括: -

...........
04-26 13:41:36.000 I/System.out:    mydata=3010
04-26 13:41:36.000 I/System.out: }
04-26 13:41:36.000 I/System.out: <<<<<



04-26 13:41:36.000 D/PREAPRE4SWITCH: Executing the SQL (create transition table for table CREATE TABLE "master"(_id INTEGER PRIMARY KEY,mydata TEXT)) 
        CREATE TABLE IF NOT EXISTS "master_trn"(_id INTEGER PRIMARY KEY,mydata TEXT)
04-26 13:41:36.007 D/NEWDATALOADSTART: Loading of new data has started
04-26 13:41:43.666 D/TABLESNOTAVAILABLE: Tables will now be unavailable whil switching
04-26 13:41:43.666 D/SWITCHRENAMEORIG: Executing the SQL to rename(original) master table to rename
04-26 13:41:43.667 D/SWITCHRENAMETRNS: Executing the SQL to rename(transition) from master_trn to (original)master
04-26 13:41:43.667 D/SWITCHDROPRENAMED: Dropping renamed original table rename
04-26 13:41:43.673 D/TABLESAVAILABLE: Switch completed, Tables are now available with new data
04-26 13:41:43.673 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@9ce45fc
04-26 13:41:43.673 I/System.out: 0 {
04-26 13:41:43.673 I/System.out:    _id=1
04-26 13:41:43.673 I/System.out:    mydata=10000
04-26 13:41:43.673 I/System.out: }
04-26 13:41:43.673 I/System.out: 1 {
04-26 13:41:43.673 I/System.out:    _id=2
04-26 13:41:43.673 I/System.out:    mydata=10001
04-26 13:41:43.673 I/System.out: 
..........

最后

您可能感興趣: -

WAL提供更多的並發性,因為讀者不會阻止編寫者,而編寫者也不會阻止讀者。 閱讀和寫作可以同時進行。

在這種情況下,您可以通過使用enableWriteAheadLogging (使用onConfigure()方法來啟用 WAL)(Android 9+,默認情況下打開它)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM