Для тех, кто работает с Android API:
Locking in SQLite is done on the file level which guarantees locking of changes from different threads and connections. Thus multiple threads can read the database however one can only write to it.
Подробнее о блокировании в SQLite можно прочитать в SQLite документации, но мы очень заинтересованы в API, предоставленной ОС Android.
Запись двумя параллельными потоками может быть выполнена как из одного, так и из нескольких соединений с базой данных. Поскольку только один поток может написать в базу данных, то есть два варианта:
- Если вы пишете из двух потоков одного соединения, то один поток будет ждать с другой стороны, чтобы закончить запись.
- Если вы пишете из двух потоков разных подключений, тогда будет ошибка - все ваши данные не будут записаны в базу данных, а приложение будет прервано с помощью SQLiteDatabaseLockedException. Становится очевидным, что приложение всегда должно иметь только одну копию SQLiteOpenHelper (просто открытое соединение), иначе SQLiteDatabaseLockedException может возникнуть в любой момент.
Различные соединения на одном SQLiteOpenHelper
Всем известно, что SQLiteOpenHelper имеет 2 методы, обеспечивающие доступ к базе данных getReadableDatabase() и getWritableDatabase(), для чтения и записи данных соответственно. Однако в большинстве случаев существует одна реальная связь. Кроме того, это один и тот же объект:
SQLiteOpenHelper.getReadableDatabase()==SQLiteOpenHelper.getWritableDatabase()
Это означает, что нет никакой разницы в использовании методов данные считываются из. Однако есть еще одна недокументированная проблема, которая важнее - внутри класса SQLiteDatabase есть собственные блокировки - переменная mLock. Замки для записи на уровне объекта SQLiteDatabase, и поскольку для чтения и записи имеется только одна копия SQLiteDatabase, чтение данных также блокируется. Это заметно заметно при написании большого объема данных в транзакции.
Давайте рассмотрим пример такого приложения, которое необходимо загрузить большой объем данных (прибл. 7000 строк, содержащих BLOB) в фоновом режиме на первом запуске и сохранить его в базу данных. Если данные сохраняются внутри транзакции, тогда сохранение занимает ок. 45 секунд, но пользователь не может использовать приложение, поскольку любой из запросов чтения заблокирован. Если данные сохраняются небольшими порциями, процесс обновления перетаскивается на довольно длительный период времени (10-15 минут), но пользователь может использовать приложение без каких-либо ограничений и неудобств. «Двойной ребро меч» - быстрый или удобный.
Google уже исправили часть вопросов, связанных с функциональностью SQLiteDatabase, как были добавлены следующие методы:
beginTransactionNonExclusive() - создает транзакцию в «непосредственном режиме».
yieldIfContendedSafely() - временный захват транзакции, чтобы разрешить выполнение задач другими потоками.
isDatabaseIntegrityOk() - проверяет целостность
базы данных Пожалуйста, прочитайте более детально в documentation.
Однако для старых версий Android эта функция также требуется.
Решение
Первый замок должен быть выключен и позволяет считывать данные в любой ситуации.
SQLiteDatabase.setLockingEnabled(false);
отменяет с использованием внутренней блокировки запросов - на логическом уровне класса Java (не связанные с блокировкой с точкой зрения SQLite)
SQLiteDatabase.execSQL(“PRAGMA read_uncommitted = true;”);
Позволяет чтение данных из кэша. Фактически, меняется уровень изоляции. Этот параметр должен быть установлен для каждого соединения заново. Если есть несколько соединений, то это влияет только на соединение, которое вызывает эту команду.
SQLiteDatabase.execSQL(“PRAGMA synchronous=OFF”);
Изменить метод записи в базу данных - без «синхронизации». При активации этой опции база данных может быть повреждена, если система неожиданно выходит из строя или питание отключено. Однако, согласно документации SQLite, некоторые операции выполняются в 50 раз быстрее, если опция не активирована.
К сожалению, не все из PRAGMA поддерживаются Android. «PRAGMA locking_mode = NORMAL» и «PRAGMA journal_mode = OFF», а некоторые другие не поддерживаются. При попытке вызвать данные PRAGMA приложение не работает.
В документации по методу setLockingEnabled говорится, что этот метод рекомендуется использовать только в том случае, если вы уверены, что вся работа с базой данных выполняется из одного потока. Мы должны гарантировать, что в то время как только одна сделка проводится. Также вместо транзакций по умолчанию (эксклюзивная транзакция) следует использовать немедленную транзакцию. В старых версиях Android (ниже API 11) нет возможности создать немедленную транзакцию через оболочку java, однако SQLite поддерживает эту функцию. Для инициализации транзакции в непосредственном режиме следующего SQLite запрос должен быть выполнен непосредственно в базу данных, - например, через метод ExecSQL:
SQLiteDatabase.execSQL(“begin immediate transaction”);
Поскольку сделка инициализируется путем прямого запроса, то он должен быть закончил Точно так же:
SQLiteDatabase.execSQL(“commit transaction”);
Тогда TransactionManager единственное, что осталось реализовать который будет инициировать и отделка сделок требуемого типа. Цель TransactionManager - гарантировать, что все запросы на изменения (вставка, обновление, удаление, DDL-запросы) происходят из одного потока.
Надеюсь, что это поможет будущим посетителям !!!
Я был вокруг и вокруг этой темы пару раз. Единственное безопасное действие, если вы используете транзакции, - это сериализовать БД по всей транзакции. –