Файл базы данных необъяснимо заблокирован во время компиляции 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.