Файл базы данных необъяснимо заблокирован во время компиляции SQLite
Я выполняю большое количество INSERTS в базе данных SQLite. Я использую только один поток. Я загружаю записи для повышения производительности и немного защиты в случае сбоя. В основном я кэширую кучу данных в памяти, а затем, когда я считаю нужным, я просматриваю все эти данные и выполняю INSERTS. Код для этого показан ниже:
public void Commit()
{
using (SQLiteConnection conn = new SQLiteConnection(this.connString))
{
conn.Open();
using (SQLiteTransaction trans = conn.BeginTransaction())
{
using (SQLiteCommand command = conn.CreateCommand())
{
command.CommandText = "INSERT OR IGNORE INTO [MY_TABLE] (col1, col2) VALUES (?,?)";
command.Parameters.Add(this.col1Param);
command.Parameters.Add(this.col2Param);
foreach (Data o in this.dataTemp)
{
this.col1Param.Value = o.Col1Prop;
this. col2Param.Value = o.Col2Prop;
command.ExecuteNonQuery();
}
}
this.TryHandleCommit(trans);
}
conn.Close();
}
}
Теперь я использую следующий трюк, чтобы заставить вещь в конечном итоге работать:
private void TryHandleCommit(SQLiteTransaction trans)
{
try
{
trans.Commit();
}
catch (Exception e)
{
Console.WriteLine("Trying again...");
this.TryHandleCommit(trans);
}
}
Я создаю свою БД следующим образом:
public DataBase(String path)
{
//build connection string
SQLiteConnectionStringBuilder connString = new SQLiteConnectionStringBuilder();
connString.DataSource = path;
connString.Version = 3;
connString.DefaultTimeout = 5;
connString.JournalMode = SQLiteJournalModeEnum.Persist;
connString.UseUTF16Encoding = true;
using (connection = new SQLiteConnection(connString.ToString()))
{
//check for existence of db
FileInfo f = new FileInfo(path);
if (!f.Exists) //build new blank db
{
SQLiteConnection.CreateFile(path);
connection.Open();
using (SQLiteTransaction trans = connection.BeginTransaction())
{
using (SQLiteCommand command = connection.CreateCommand())
{
command.CommandText = DataBase.CREATE_MATCHES;
command.ExecuteNonQuery();
command.CommandText = DataBase.CREATE_STRING_DATA;
command.ExecuteNonQuery();
//TODO add logging
}
trans.Commit();
}
connection.Close();
}
}
}
Затем я экспортирую строку подключения и использую ее для получения новых подключений в разных частях программы.
По-видимому, случайные интервалы, хотя и слишком велики, чтобы игнорировать или иным образом обходить эту проблему, я получаю необработанное SQLiteException: файл базы данных заблокирован. Это происходит, когда я пытаюсь совершить транзакцию. Кажется, что ошибки до этого не возникли. Это не всегда. Иногда все работает без заминки.
- Чтение не выполняется над этими файлами до завершения коммитов.
- У меня есть самый последний SQLite файл.
- Я компилирую для .NET 2.0.
- Я использую VS 2008.
- db - локальный файл.
- Все это действие инкапсулируется в один поток/процесс.
- Защита от вирусов отключена (хотя я думаю, что это было актуально только в том случае, если вы подключались по сети?).
- По сообщению Scotsman я внедрил следующие изменения:
- Режим журнала установлен на "Постоянно"
- Файлы DB, хранящиеся в C:\Docs + Settings\ApplicationData через
System.Windows.Forms.Application.AppData
вызов Windows
- Внутреннее исключение
- Проверено на двух разных машинах (хотя очень похожее аппаратное и программное обеспечение)
- Был запущен Process Monitor - никакие посторонние процессы не привязаны к файлам DB - проблема определенно в моем коде...
Кто-нибудь знает, что происходит здесь?
Я знаю, что я просто сбросил весь беспорядок кода, но я пытался понять это слишком долго. Спасибо всем, кто дойдет до конца этого вопроса!
Брайен
ОБНОВЛЕНИЕ:
Спасибо за предложения! Я реализовал многие из предлагаемых изменений. Я чувствую, что мы приближаемся к ответу... однако...
Приведенный выше технический код работает, однако он не детерминирован! Не гарантируется что-либо сделать, кроме спина в нейтральной навсегда. На практике это работает где-то между 1-й и 10-й итерациями. Если я улажу свои коммиты с разумным интервалом, урон будет уменьшен, но я действительно не хочу оставлять вещи в этом состоянии...
Дополнительные предложения приветствуются!
Ответы
Ответ 1
Запустите Sysinternals Process Monitor и отфильтруйте имя файла во время запуска вашей программы, чтобы исключить, если какой-либо другой процесс что-то делает с ней, и чтобы увидеть, что беззаботно ваша программа делает с файлом. Длинный выстрел, но может дать ключ.
Ответ 2
Похоже, что вы не смогли связать команду с созданной транзакцией.
Вместо:
using (SQLiteCommand command = conn.CreateCommand())
Вы должны использовать:
using (SQLiteCommand command = new SQLiteCommand("<INSERT statement here>", conn, trans))
Или вы можете установить его свойство транзакции после его построения.
Пока мы находимся в этом - неправильное обращение с ошибками:
Метод ExecuteNonQuery также может завершиться неудачей, и вы на самом деле не защищены. Вы должны изменить код на что-то вроде:
public void Commit()
{
using (SQLiteConnection conn = new SQLiteConnection(this.connString))
{
conn.Open();
SQLiteTransaction trans = conn.BeginTransaction();
try
{
using (SQLiteCommand command = conn.CreateCommand())
{
command.Transaction = trans; // Now the command is linked to the transaction and don't try to create a new one (which is probably why your database gets locked)
command.CommandText = "INSERT OR IGNORE INTO [MY_TABLE] (col1, col2) VALUES (?,?)";
command.Parameters.Add(this.col1Param);
command.Parameters.Add(this.col2Param);
foreach (Data o in this.dataTemp)
{
this.col1Param.Value = o.Col1Prop;
this. col2Param.Value = o.Col2Prop;
command.ExecuteNonQuery();
}
}
trans.Commit();
}
catch (SQLiteException ex)
{
// You need to rollback in case something wrong happened in command.ExecuteNonQuery() ...
trans.Rollback();
throw;
}
}
}
Другое дело, что вам не нужно ничего кэшировать в памяти. Вы можете зависеть от механизма ведения журнала SQLite для хранения незавершенного состояния транзакции.
Ответ 3
У нас была очень похожая проблема с использованием вложенных транзакций с классом TransactionScope. Мы думали, что все действия с базой данных произошли в одном потоке... однако мы были пойманы механизмом транзакций... более конкретно транзакция Ambient.
В основном была транзакция выше цепи, которая благодаря магии ado автоматически включала соединение. В результате мы думали, что мы пишем в базу данных в одном потоке, запись didn ' действительно произойдет до тех пор, пока не будет совершена самая верхняя транзакция. В этой "неопределенной" точке база данных была написана так, что она была заблокирована вне нашего контроля.
Решение заключалось в том, чтобы база данных sqlite не принимала непосредственного участия во внешней транзакции, гарантируя, что мы использовали что-то вроде:
using(TransactionScope scope = new TransactionScope(TransactionScopeOptions.RequiresNew))
{
...
scope.Complete()
}
Ответ 4
Что нужно посмотреть:
-
не использовать соединения для нескольких потоков/процессов.
-
Я видел, как это произошло, когда сканер вирусов обнаружил изменения в файле и попытался его отсканировать. Он блокирует файл на короткий промежуток времени и вызывает хаос.
Ответ 5
Сегодня я столкнулся с такой же проблемой: я изучаю asp.net mvc, полностью создаю свое первое приложение с нуля. Иногда, когда я писал в базу данных, я получал бы такое же исключение, заявив, что файл базы данных заблокирован.
Мне показалось, что это действительно странно, так как я был абсолютно уверен, что в то время было открыто только одно соединение (на основе списка активных обработчиков файлов).
Я также построил весь уровень доступа к данным с нуля, используя поставщик System.Data.SQLite.Net, и, когда я его планировал, я проявил особую осторожность при подключении и транзакциях, чтобы не было никакого соединения или транзакции остался висящим вокруг.
Трудная часть заключалась в том, что установка контрольной точки в команде ExecuteNonQuery() и запуск приложения в режиме отладки заставили бы эту ошибку исчезнуть!
Googling, я нашел что-то интересное на этом сайте: http://www.softperfect.com/board/read.php?8,5775. Там кто-то ответил нить, предлагая автору поместить путь базы данных в список игнорирования антивируса.
Я добавил файл базы данных в список игнорирования моего антивируса (Microsoft Security Essentials), и он решил мою проблему. Больше ошибок с блокировкой базы данных!
Ответ 6
Является ли ваш файл базы данных на том же компьютере, что и приложение, или он хранится на сервере?
Вам нужно создать новое соединение в каждом потоке. Я бы упростил создание соединения, использую везде: connection = new SQLiteConnection (connString.ToString());
и снова используйте файл базы данных на том же компьютере, что и приложение, и выполните проверку.
Почему два разных способа создания соединения?
Ответ 7
У этих парней были сходные проблемы (в основном, похоже, с заблокированным журнальным файлом, возможно, взаимодействием TortoiseSVN... проверьте ссылки).
Они придумали набор рекомендаций (правильные каталоги, изменяя типы ведения журнала от delete до persist и т.д.). http://sqlite.phxsoftware.com/forums/p/689/5445.aspx#5445
Параметры режима журнала обсуждаются здесь: http://www.sqlite.org/pragma.html. Вы можете попробовать TRUNCATE.
Есть ли трассировка стека во время исключения в SQL Lite?
Вы указываете, что вы "поручили мне совершить за разумный промежуток времени". Что такое интервал?
Ответ 8
Я бы всегда использовал Connection, Transaction и Command в предложении using
. В вашем первом списке кодов вы сделали, а третий (создание таблиц) вы этого не сделали. Я предлагаю вам это сделать, потому что (кто знает?), Возможно, команды, которые создают таблицу, как-то продолжают блокировать файл. Длинный выстрел... но стоит выстрел?
Ответ 9
У вас есть Google Desktop Search (или другой файловый индексатор)? Как уже упоминалось, Sysinternals Process Monitor может помочь вам отследить его.
Кроме того, что такое имя файла базы данных? Из PerformanceTuningWindows:
ОЧЕНЬ, ОЧЕНЬ осторожно, что вы называете своей базой данных, особенно расширение
Например, если вы даете всем своим базам данных расширение .sdb(SQLite Database, славное имя эй? Я так думал, когда я его выбираю), вы обнаружите, что расширение SDB уже связано с APPFIX PACKAGES.
Теперь, вот милая часть, APPFIX - это исполняемый файл/пакет, который распознает Windows XP, и он будет (выделено мной) ДОБАВИТЬ БАЗА ДАННЫХ В СИСТЕМУ ВОССТАНОВЛЕНИЯ ФУНКЦИОНАЛЬНОСТИ
Это означает, что оставайтесь со мной здесь, каждый раз, когда вы пишете НИЧЕГО в базе данных, система Windows XP считает, что кровавый исполняемый файл изменился и скопировал вашу ENTRE 800 мегабайтную базу данных в каталог восстановления системы....
Я рекомендую что-то вроде DB или DAT.