Звучит как вместо: -
- refreshateDatabase () // Удалить все таблицы и создать их заново.
- 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 Demo
Ниже приведен код для рабочего примера 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: <<<<<
Дополнительный
Вот более универсальный подход, который бы справлялся с обработкой нескольких таблиц, исключая явную загрузку новых данных в таблицы переходов для приложений / таблиц.
Добавлено ведение журнала, это включает тайминги.
Например, запустив это и загрузив 10000 строк (используя mDBHlpr.addLotsOfData(DBHelper.TABLE_MASTER + DBHelper.TRANSITION_SUFFIX,100000,10000);
) . Видно, что: -
Несмотря на то, что для загрузки данных (10000 строк) таблице требуется 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();
}
}
}
Предполагается, что вы никогда не создадите таблицы приложений, начинающиеся с android или 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 обеспечивает больше параллелизма, поскольку читатели не блокируют писателей, а писатель не блокирует читателей.Чтение и запись могут выполняться одновременно.
В этом случае вы можете включить WAL (Android 9+ включен по умолчанию) с помощью enableWriteAheadLogging (переопределить onConfigure () метод, чтобы использовать это).