Что будет считаться наилучшей практикой при выполнении запросов к базе данных SQLite в приложении Android?
Безопасно ли выполнять операции вставки, удаления и выбора запросов из doynBackground AsyncTask? Или я должен использовать поток пользовательского интерфейса? Я полагаю, что запросы к базе данных могут быть «тяжелыми» и не должны использовать поток пользовательского интерфейса, поскольку он может заблокировать приложение - в результате приложение не отвечает (ANR).
Если у меня есть несколько AsyncTasks, они должны совместно использовать соединение или они должны открывать соединение каждый?
Есть ли лучшие практики для этих сценариев?
Ответы:
Вставки, обновления, удаления и чтения, как правило, в порядке из нескольких потоков, но ответ Брэда не является правильным. Вы должны быть осторожны с тем, как вы создаете свои связи и используете их. Существуют ситуации, когда ваши вызовы обновления не будут работать, даже если ваша база данных не будет повреждена.
Основной ответ.
Объект SqliteOpenHelper удерживает одно соединение с базой данных. Похоже, предлагает вам соединение для чтения и записи, но на самом деле это не так. Вызовите только для чтения, и вы получите соединение для записи базы данных независимо от.
Итак, один вспомогательный экземпляр, одно соединение с БД. Даже если вы используете его из нескольких потоков, по одному соединению за раз. Объект SqliteDatabase использует блокировки Java для сохранения сериализации доступа. Таким образом, если 100 потоков имеют один экземпляр базы данных, вызовы фактической базы данных на диске сериализуются.
Итак, один помощник, одно соединение с БД, которое сериализуется в коде Java. Один поток, 1000 потоков, если вы используете один экземпляр помощника, совместно используемый ими, весь ваш код доступа к БД является последовательным. И жизнь хороша (иш).
Если вы попытаетесь одновременно выполнить запись в базу данных из разных соединений, произойдет сбой. Это не будет ждать, пока первое будет сделано, а затем напишите. Это просто не будет писать ваши изменения. Хуже того, если вы не вызовете правильную версию вставки / обновления в базе данных SQLiteD, вы не получите исключение. Вы просто получите сообщение в своем LogCat, и это будет оно.
Итак, несколько потоков? Используйте один помощник. Период. Если вы ЗНАЕТЕ, что будет писать только один поток, вы МОЖЕТЕ использовать несколько соединений, и ваше чтение будет быстрее, но покупатель остерегается. Я не так много тестировал.
Вот сообщение в блоге с более подробной информацией и примером приложения.
Грей и я на самом деле завершили работу над инструментом ORM, основанным на его Ormlite, который изначально работает с реализациями баз данных Android и следует структуре безопасного создания / вызова, которую я описал в посте блога. Это должно быть очень скоро. Взглянуть.
А пока есть пост в блоге:
Также проверьте ответвление по 2point0 из ранее упомянутого примера блокировки:
источник
Параллельный доступ к базе данных
Та же статья в моем блоге (мне больше нравится форматирование)
Я написал небольшую статью, в которой описывается, как сделать доступ к вашей базе данных Android безопасным.
Предполагая, что у вас есть свой собственный SQLiteOpenHelper .
Теперь вы хотите записать данные в базу данных в отдельных потоках.
Вы получите следующее сообщение в своем logcat, и одно из ваших изменений не будет записано.
Это происходит потому, что каждый раз, когда вы создаете новый объект SQLiteOpenHelper, вы фактически устанавливаете новое соединение с базой данных. Если вы попытаетесь одновременно выполнить запись в базу данных из разных соединений, произойдет сбой. (из ответа выше)
Чтобы использовать базу данных с несколькими потоками, нам нужно убедиться, что мы используем одно соединение с базой данных.
Давайте создадим одноэлементный класс Database Manager, который будет содержать и возвращать один объект SQLiteOpenHelper .
Обновленный код, который записывает данные в базу данных в отдельных потоках, будет выглядеть следующим образом.
Это принесет вам еще одну аварию.
Так как мы используем только одно соединение с базой данных, метод getDatabase () возвращать тот же экземпляр SQLiteDatabase объекта для thread1 и thread2 . Что происходит, Thread1 может закрыть базу данных, в то время как Thread2 все еще использует ее. Вот почему у нас происходит сбой IllegalStateException .
Мы должны убедиться, что никто не использует базу данных, и только потом закрывать ее. Некоторым людям по стеку рекомендуется не закрывать вашу базу данных SQLite . Это приведет к следующему сообщению logcat.
Рабочий образец
Используйте это следующим образом.
Каждый раз , когда вам нужна база данных , вы должны позвонить OpenDatabase () метод DatabaseManager класса. Внутри этого метода у нас есть счетчик, который указывает, сколько раз база данных открыта. Если он равен единице, это означает, что нам нужно создать новое соединение с базой данных, если нет, соединение с базой данных уже создано.
То же самое происходит в методе closeDatabase () . Каждый раз, когда мы вызываем этот метод, счетчик уменьшается, когда он равен нулю, мы закрываем соединение с базой данных.
Теперь вы должны иметь возможность использовать свою базу данных и быть уверенным, что она безопасна для потоков.
источник
if(instance==null)
? Вы не оставляете выбора, кроме как каждый раз вызывать инициализацию; как еще узнать, инициализирован ли он или нет в других приложениях и т. д.?initializeInstance()
имеет параметр типаSQLiteOpenHelper
, но в своем комментарии вы упомянули об использованииDatabaseManager.initializeInstance(getApplicationContext());
. Что здесь происходит? Как это может работать?Thread
илиAsyncTask
для длительных операций (50 мс +). Протестируйте свое приложение, чтобы увидеть, где это. Большинство операций (вероятно) не требуют потока, потому что большинство операций (вероятно) включают только несколько строк. Используйте поток для массовых операций.SQLiteDatabase
экземпляр для каждой БД на диске между потоками и внедрите систему подсчета для отслеживания открытых соединений.Разделите статическое поле между всеми вашими классами. Я имел обыкновение держать синглтон для этого и других вещей, которыми нужно поделиться. Следует также использовать схему подсчета (обычно с использованием AtomicInteger), чтобы вы никогда не закрывали базу данных раньше времени и не оставляли ее открытой.
Самую свежую версию см. На странице https://github.com/JakarCo/databasemanager, но я постараюсь и здесь обновлять код. Если вы хотите понять мое решение, посмотрите код и прочитайте мои заметки. Мои заметки обычно очень полезны.
DatabaseManager
. (или загрузите его с github)DatabaseManager
и реализоватьonCreate
иonUpgrade
как обычно. Вы можете создать несколько подклассов одногоDatabaseManager
класса, чтобы иметь разные базы данных на диске.getDb()
чтобы использоватьSQLiteDatabase
класс.close()
для каждого созданного вами подклассаКод для копирования / вставки :
источник
close
, вы должны позвонитьopen
снова, прежде чем использовать тот же экземпляр класса ИЛИ вы можете создать новый экземпляр. Поскольку вclose
коде, который я установилdb=null
, вы не сможете использовать возвращаемое значение изgetDb
(поскольку оно будет нулевым), так что вы получите,NullPointerException
если бы вы сделали что-то вродеmyInstance.close(); myInstance.getDb().query(...);
getDb()
иopen()
в один метод?База данных очень гибкая с многопоточностью. Мои приложения попадают в их базы данных из разных потоков одновременно, и это прекрасно. В некоторых случаях у меня есть несколько процессов, одновременно работающих с БД, и это тоже отлично работает.
Ваши асинхронные задачи - используйте одно и то же соединение, когда можете, но если нужно, все нормально, чтобы получить доступ к БД из разных задач.
источник
SQLiteDatabase
объектов на разныхAsyncTask
с /Thread
с, но иногда это приводит к ошибкам, поэтому SQLiteDatabase (строка 1297) используетLock
sОтвет Дмитрия отлично подходит для моего случая. Я думаю, что лучше объявить функцию синхронизированной. по крайней мере, в моем случае это вызовет исключение нулевого указателя, в противном случае, например, getWritableDatabase еще не возвращен в одном потоке, а openDatabse вызывается в другом потоке.
источник
После нескольких часов борьбы с этим я обнаружил, что вы можете использовать только один вспомогательный объект БД на выполнение БД. Например,
в отношении:
создание нового DBAdapter каждый раз при повторении цикла было единственным способом, которым я мог получить свои строки в базу данных через мой вспомогательный класс.
источник
Насколько я понимаю API-интерфейсы SQLiteDatabase, если у вас многопоточное приложение, вы не можете позволить себе иметь более одного объекта SQLiteDatabase, указывающего на одну базу данных.
Объект определенно может быть создан, но вставки / обновления завершатся неудачно, если разные потоки / процессы (тоже) начнут использовать разные объекты SQLiteDatabase (например, как мы используем в JDBC Connection).
Единственное решение здесь - придерживаться 1 объекта SQLiteDatabase, и каждый раз, когда startTransaction () используется в более чем 1 потоке, Android управляет блокировкой между различными потоками и позволяет только одному потоку одновременно иметь эксклюзивный доступ к обновлениям.
Также вы можете выполнять «Чтение» из базы данных и использовать один и тот же объект SQLiteDatabase в другом потоке (в то время как другой поток записывает), и никогда не произойдет повреждение базы данных, т.е. «чтение потока» не будет считывать данные из базы данных до Запись потока "фиксирует данные, хотя оба используют один и тот же объект SQLiteDatabase.
Это отличается от того, как объект соединения находится в JDBC, где, если вы передадите (используете то же самое) объект соединения между потоками чтения и записи, то мы, вероятно, также будем печатать незафиксированные данные.
В моем корпоративном приложении я пытаюсь использовать условные проверки, чтобы потоку пользовательского интерфейса никогда не приходилось ждать, пока поток BG содержит объект SQLiteDatabase (исключительно). Я пытаюсь предсказать действия пользовательского интерфейса и отложить запуск потока BG на «х» секунд. Также можно поддерживать PriorityQueue для управления раздачей объектов Соединения SQLiteDatabase, чтобы поток пользовательского интерфейса получал его первым.
источник
"read thread" wouldn't read the data from the database till the "write thread" commits the data although both use the same SQLiteDatabase object
. Это не всегда так, если вы начнете «читать поток» сразу после «записи потока», вы можете не получить недавно обновленные данные (вставленные или обновленные в записи потока). Читать поток может прочитать данные до начала записи потока. Это происходит потому, что операция записи изначально включает зарезервированную блокировку вместо эксклюзивной блокировки.Вы можете попробовать применить новый архитектурный подход, анонсированный на Google I / O 2017.
Он также включает новую библиотеку ORM под названием Room
Он содержит три основных компонента: @Entity, @Dao и @Database
User.java
UserDao.java
AppDatabase.java
источник
После некоторых проблем, я думаю, я понял, почему я иду не так.
Я написал класс обертки базы данных, который включал a,
close()
который называл помощник close как зеркало,open()
которое вызывало getWriteableDatabase, а затем мигрировал вContentProvider
. Модель дляContentProvider
не использует,SQLiteDatabase.close()
что я думаю, является большой подсказкой, поскольку код используетgetWriteableDatabase
В некоторых случаях я все еще делал прямой доступ (запросы проверки экрана в основном, поэтому я перешел на модель getWriteableDatabase / rawQuery.Я использую синглтон, и в закрытой документации есть немного зловещий комментарий
(мой смелый).
Поэтому у меня периодически возникали сбои, когда я использовал фоновые потоки для доступа к базе данных, и они выполнялись одновременно с передним планом.
Поэтому я думаю,
close()
что база данных закрывается независимо от любых других потоков, содержащих ссылки, поэтомуclose()
сама по себе не просто отменяет сопоставление,getWriteableDatabase
а принудительно закрывает любые открытые запросы. В большинстве случаев это не является проблемой, поскольку код является однопоточным, но в многопоточных случаях всегда есть вероятность открытия и закрытия из синхронизации.Прочитав комментарии в другом месте, в которых объясняется, что экземпляр кода SqLiteDatabaseHelper имеет значение, единственное время, когда вам нужно закрытие, - это ситуация, когда вы хотите сделать резервную копию, и вы хотите принудительно закрыть все соединения и заставить SqLite выполнить запишите все кэшированные данные, которые могли бы слоняться - другими словами, остановите все действия базы данных приложения, закройте на случай, если Помощник потерял отслеживание, выполните любое действие на уровне файлов (резервное копирование / восстановление), затем начните все заново.
Хотя это и кажется хорошей идеей, чтобы попытаться закрыть контролируемым образом, реальность такова, что Android оставляет за собой право уничтожить вашу виртуальную машину, поэтому любое закрытие снижает риск того, что кэшированные обновления не будут записаны, но это не может быть гарантировано, если устройство подчеркнуто, и если вы правильно освободили свои курсоры и ссылки на базы данных (которые не должны быть статическими членами), то помощник все равно закроет базу данных.
Поэтому я считаю, что подход заключается в следующем:
Используйте getWriteableDatabase для открытия из одноэлементной оболочки. (Я использовал производный класс приложения для предоставления контекста приложения из статического кода, чтобы удовлетворить потребность в контексте).
Никогда не звоните напрямую.
Никогда не храните результирующую базу данных ни в каком объекте, который не имеет очевидной области видимости и использует подсчет ссылок для запуска неявного close ().
Если вы выполняете обработку на уровне файлов, остановите все действия с базой данных, а затем вызовите close на тот случай, если существует неконтролируемый поток, исходя из предположения, что вы пишете правильные транзакции, поэтому сбойный поток завершится неудачно, и закрытая база данных будет по крайней мере иметь правильные транзакции. чем потенциально файловая копия частичной транзакции.
источник
Я знаю, что ответ запаздывает, но лучший способ выполнить sqlite-запросы в Android - через собственного провайдера контента. Таким образом, пользовательский интерфейс отделен от класса базы данных (класса, который расширяет класс SQLiteOpenHelper). Также запросы выполняются в фоновом потоке (Cursor Loader).
источник