2013-06-27 5 views
6

С SQLITE FAQ Я знаю, что:Как предотвратить блокировки базы данных SQLite?

Multiple processes can have the same database open at the same time. Multiple processes can be doing a SELECT at the same time. But only one process can be making changes to the database at any moment in time, however.

Итак, насколько я понимаю, я могу: 1) Читать децибел из нескольких потоков (SELECT) 2) Читать децибел из нескольких потоков (SELECT) и написать от одного потока (CREATE, INSERT, DELETE)

Но я читал о Write-Ahead Logging, что обеспечивает более параллелизм, как читатели не блокируют писателей и писатель не блокирует читателей. Чтение и запись могут продолжаться одновременно.

Наконец, я окончательно запутался, когда я нашел it, когда указано:

Here are other reasons for getting an SQLITE_LOCKED error:

  • Trying to CREATE or DROP a table or index while a SELECT statement is still pending.
  • Trying to write to a table while a SELECT is active on that same table.
  • Trying to do two SELECT on the same table at the same time in a multithread application, if sqlite is not set to do so.
  • fcntl(3,F_SETLK call on DB file fails. This could be caused by an NFS locking issue, for example. One solution for this issue, is to mv the DB away, and copy it back so that it has a new Inode value

Итак, я хотел бы прояснить для себя, когда я должен избежать блокировки? Могу ли я читать и писать одновременно с двух разных потоков? Благодарю.

+0

Я был вокруг и вокруг этой темы пару раз. Единственное безопасное действие, если вы используете транзакции, - это сериализовать БД по всей транзакции. –

ответ

1

Все в порядке с многопоточным режимом. На странице, которую вы указываете, перечислены то, что вы не можете сделать, пока вы зацикливаете на результатах вашего SELECT (т. Е. Ваш выбор активен/ожидает) в том же потоке.

4

не специфичны для SQLite:

1) Написать свой код, чтобы корректно обрабатывать ситуацию, когда вы получаете запирающий конфликт на уровне приложений; даже если вы написали свой код, чтобы это было «невозможно». Использовать транзакционные повторы (т. Е. SQLITE_LOCKED может быть одним из многих кодов, которые вы интерпретируете как «повторите попытку» или «подождите и попробуйте еще раз»), и скопируйте это с помощью кода уровня приложения. Если вы думаете об этом, получение SQLITE_LOCKED лучше, чем просто попытка зависания, потому что она заблокирована - потому что вы можете сделать что-то еще.

2) Приобретите замки. Но вы должны быть осторожны, если вам нужно приобрести более одного. Для каждой транзакции на уровне приложения вы приобретаете все ресурсы (блокировки), которые вам понадобятся в последовательном (то есть: алфавитном?) Порядке, чтобы предотвратить взаимоблокировки при приобретении блокировок в базе данных. Иногда вы можете игнорировать это, если база данных будет надежно и быстро обнаруживать взаимоблокировки и исключать исключения; в других системах он может просто повесить, не обнаружив тупик, что делает абсолютно необходимым предпринять усилия для правильного захвата замков.

Помимо фактов жизни с блокировкой, вы должны попытаться сконструировать данные и структуры памяти с одновременным слиянием и откатом, запланированными с самого начала. Если вы можете проектировать данные таким образом, чтобы результат гонки данных давал хороший результат для всех заказов, тогда вам не нужно иметь дело с замками в этом случае. Хорошим примером является увеличение счетчика, не зная его текущего значения, вместо чтения значения и отправки нового значения для обновления. Это похоже на добавление к набору (т. Е. Добавление строки, так что не имеет значения, в каком порядке вставлены строки).

Хорошая система должна переходить от одного действительного состояния к другому, и вы можете думать об исключениях (даже в коде с внутренней памятью) как о прекращении попытки перехода к следующему состоянию; с возможностью игнорировать или повторять попытку.

3

Для тех, кто работает с 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.

Запись двумя параллельными потоками может быть выполнена как из одного, так и из нескольких соединений с базой данных. Поскольку только один поток может написать в базу данных, то есть два варианта:

  1. Если вы пишете из двух потоков одного соединения, то один поток будет ждать с другой стороны, чтобы закончить запись.
  2. Если вы пишете из двух потоков разных подключений, тогда будет ошибка - все ваши данные не будут записаны в базу данных, а приложение будет прервано с помощью 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-запросы) происходят из одного потока.

Надеюсь, что это поможет будущим посетителям !!!

+1

Спасибо, что поделились этим PN10, очень полезно. Если кому-то интересно, вот определение и некоторые встроенные комментарии вокруг 'mLock', блокировки базы данных уровня инфраструктуры: https://github.com/android/platform_frameworks_base/blob/master/core/java/android/database/sqlite /SQLiteDatabase.java#L112 – Matthias