#android #sqlite #csv #import
#Android #База данных #Производительность #sqlite #вставить
Вопрос:
Мне нужно проанализировать довольно большой XML-файл (от примерно ста килобайт до нескольких сотен килобайт), который я использую Xml#parse(String, ContentHandler)
. В настоящее время я тестирую это с файлом размером 152 КБ.
Во время синтаксического анализа я также вставляю данные в базу данных SQLite, используя вызовы, подобные следующим: getWritableDatabase().insert(TABLE_NAME, "_id", values)
. Все это вместе занимает около 80 секунд для тестового файла размером 152 КБ (что сводится к вставке примерно 200 строк).
Когда я комментирую все инструкции insert (но оставляю все остальное, например, создание ContentValues
и т. Д.), Один и тот же файл занимает всего 23 секунды.
Нормально ли, что операции с базой данных требуют таких больших накладных расходов? Могу ли я что-нибудь с этим сделать?
Ответ №1:
Вы должны выполнять пакетные вставки.
Псевдокод:
db.beginTransaction();
for (entry : listOfEntries) {
db.insert(entry);
}
db.setTransactionSuccessful();
db.endTransaction();
Это чрезвычайно увеличило скорость вставок в моих приложениях.
Обновление:
@Yuku предоставил очень интересное сообщение в блоге: Android использует inserthelper для более быстрой вставки в базу данных sqlite
Комментарии:
1. Вставка всех значений содержимого таким образом занимает всего секунду или две. Большое спасибо!
2. Безопасно ли открывать длительную транзакцию, охватывающую всю продолжительность операции синтаксического анализа XML, а затем фиксировать ее в конце? Или список вставок должен быть локально кэширован внутри анализатора XML, а затем должна быть открыта и зафиксирована кратковременная транзакция после завершения синтаксического анализа?
3. обертывание моих 60 вставок транзакцией увеличило производительность в 10 раз. обертывание ее транзакцией и использование подготовленного оператора (SQLiteStatement) увеличили ее в 20 раз!
4. benvd спасибо за комментарий. Я вставлял 20 тысяч записей, это заняло около 8 минут, но после использования транзакции это занимает всего 20 секунд 🙂
5. В этой записи в блоге обсуждается другая оптимизация с использованием почти скрытого InsertHelper outofwhatbox.com/blog/2010/12 /…
Ответ №2:
Поскольку InsertHelper, упомянутый Юку и Бреттом, теперь устарел (уровень API 17), кажется, что подходящей альтернативой, рекомендованной Google, является использование SQLiteStatement.
Я использовал метод вставки базы данных следующим образом:
database.insert(table, null, values);
После того, как я также столкнулся с некоторыми серьезными проблемами с производительностью, следующий код ускорил мои 500 вставок с 14,5 секунды до всего 270 мс, удивительно!
Вот как я использовал SQLiteStatement:
private void insertTestData() {
String sql = "insert into producttable (name, description, price, stock_available) values (?, ?, ?, ?);";
dbHandler.getWritableDatabase();
database.beginTransaction();
SQLiteStatement stmt = database.compileStatement(sql);
for (int i = 0; i < NUMBER_OF_ROWS; i ) {
//generate some values
stmt.bindString(1, randomName);
stmt.bindString(2, randomDescription);
stmt.bindDouble(3, randomPrice);
stmt.bindLong(4, randomNumber);
long entryID = stmt.executeInsert();
stmt.clearBindings();
}
database.setTransactionSuccessful();
database.endTransaction();
dbHandler.close();
}
Комментарии:
1. Здесь следует избегать одной уловки: индекс в bindString основан на 1, а не на 0
2. @qefzec Благодарит вас за это решение.. Это просто сократило время вставки в моем приложении с 78 секунд до 4 для добавленных 900 строк..
3. Спасибо, сэр. 20000 записей с 6 полями данных в каждом, включая VARCHAR(80) от 4 минут до 8 секунд. На самом деле это должно быть отмечено как лучший ответ, ИМХО.
4. Отлично! Наш тестовый тест на 200 тестовых вставках одновременно с 15 столбцами на вставку сгенерировал улучшение от 4100% до 10400% в зависимости от устройства и внутренней / внешней памяти. Предыдущая производительность обрекла бы наш проект на провал еще до того, как он сдвинулся с мертвой точки.
5. от 15 минут до 45 секунд для 13600 строк
Ответ №3:
Компиляция инструкции sql insert помогает ускорить процесс. Также может потребоваться больше усилий для поддержки всего и предотвращения возможного внедрения, поскольку теперь все это лежит на ваших плечах.
Другой подход, который также может ускорить процесс, — это недостаточно документированный android.database.DatabaseUtils.Класс InsertHelper. Насколько я понимаю, она фактически обертывает скомпилированные инструкции insert. Переход от некомпилированных транзакционных вставок к скомпилированным транзакционным вставкам привел к увеличению скорости примерно в 3 раза (от 2 мс на вставку до 6 мс на вставку) для моих больших (более 200 тыс. записей), но простых вставок SQLite.
Пример кода:
SQLiteDatabse db = getWriteableDatabase();
//use the db you would normally use for db.insert, and the "table_name"
//is the same one you would use in db.insert()
InsertHelper iHelp = new InsertHelper(db, "table_name");
//Get the indices you need to bind data to
//Similar to Cursor.getColumnIndex("col_name");
int first_index = iHelp.getColumnIndex("first");
int last_index = iHelp.getColumnIndex("last");
try
{
db.beginTransaction();
for(int i=0 ; i<num_things ; i)
{
//need to tell the helper you are inserting (rather than replacing)
iHelp.prepareForInsert();
//do the equivalent of ContentValues.put("field","value") here
iHelp.bind(first_index, thing_1);
iHelp.bind(last_index, thing_2);
//the db.insert() equilvalent
iHelp.execute();
}
db.setTransactionSuccessful();
}
finally
{
db.endTransaction();
}
db.close();
Комментарии:
1. и как добавить значение содержимого в iHelp.bind(first_index, thing_1); ?
Ответ №4:
Если в таблице есть индекс, подумайте о том, чтобы удалить его перед вставкой записей, а затем добавить его обратно после того, как вы зафиксировали свои записи.
Ответ №5:
При использовании ContentProvider:
@Override
public int bulkInsert(Uri uri, ContentValues[] bulkinsertvalues) {
int QueryType = sUriMatcher.match(uri);
int returnValue=0;
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
switch (QueryType) {
case SOME_URI_IM_LOOKING_FOR: //replace this with your real URI
db.beginTransaction();
for (int i = 0; i < bulkinsertvalues.length; i ) {
//get an individual result from the array of ContentValues
ContentValues values = bulkinsertvalues[i];
//insert this record into the local SQLite database using a private function you create, "insertIndividualRecord" (replace with a better function name)
insertIndividualRecord(uri, values);
}
db.setTransactionSuccessful();
db.endTransaction();
break;
default:
throw new IllegalArgumentException("Unknown URI " uri);
}
return returnValue;
}
Затем частная функция для выполнения вставки (все еще внутри вашего контент-провайдера):
private Uri insertIndividualRecord(Uri uri, ContentValues values){
//see content provider documentation if this is confusing
if (sUriMatcher.match(uri) != THE_CONSTANT_IM_LOOKING_FOR) {
throw new IllegalArgumentException("Unknown URI " uri);
}
//example validation if you have a field called "name" in your database
if (values.containsKey(YOUR_CONSTANT_FOR_NAME) == false) {
values.put(YOUR_CONSTANT_FOR_NAME, "");
}
//******add all your other validations
//**********
//time to insert records into your local SQLite database
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(YOUR_TABLE_NAME, null, values);
if (rowId > 0) {
Uri myUri = ContentUris.withAppendedId(MY_INSERT_URI, rowId);
getContext().getContentResolver().notifyChange(myUri, null);
return myUri;
}
throw new SQLException("Failed to insert row into " uri);
}