Файл базы данных необъяснимо заблокирован во время фиксации SQLite - PullRequest
10 голосов
/ 04 августа 2009

Я выполняю большое количество INSERTS для базы данных SQLite. Я использую только одну ветку. Я делаю записи для улучшения производительности и обеспечения безопасности в случае сбоя. По сути, я кеширую кучу данных в памяти, а затем, когда считаю нужным, зацикливаю все эти данные и выполняю ВСТАВКИ. Код для этого показан ниже:

    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.
  • БД является локальным файлом.
  • Все это действие инкапсулировано в одном потоке / процессе.
  • Защита от вирусов отключена (хотя я думаю, что это было актуально, только если вы подключались по сети?).
  • Согласно сообщению шотландца, я внес следующие изменения:
  • Режим журнала установлен на Persist
  • Файлы БД, хранящиеся в C: \ Docs + Settings \ ApplicationData через System.Windows.Forms.Application.AppData вызов Windows
  • Нет внутреннего исключения
  • Виден на двух разных машинах (хотя и очень похожих аппаратных и программных)
  • Был запущен Process Monitor - никакие посторонние процессы не присоединяются к файлам БД - проблема определенно в моем коде ...

Кто-нибудь знает, что здесь происходит?

Я знаю, что я просто отбросил весь беспорядок кода, но я пытался понять это слишком долго. Спасибо всем, кто дошел до конца этого вопроса!

1055 * Брайен *

ОБНОВЛЕНИЕ:

Спасибо за предложения! Я реализовал многие из предложенных изменений. Я чувствую, что мы приближаемся к ответу ... однако ...

Технически приведенный выше код работает, однако он не является детерминированным! Это не гарантировано, что делать что-либо кроме вращения в нейтральном навсегда. На практике это работает где-то между 1-й и 10-й итерациями. Если я сделаю серию своих коммитов с разумным интервалом, ущерб будет уменьшен, но я действительно не хочу оставлять вещи в этом состоянии ...

Дополнительные предложения приветствуются!

Ответы [ 9 ]

9 голосов
/ 12 августа 2009

Похоже, вам не удалось связать команду с созданной вами транзакцией. Вместо:

using (SQLiteCommand command = conn.CreateCommand())

Вы должны использовать:

using (SQLiteCommand command = new SQLiteCommand("<INSERT statement here>", conn, trans))

Или вы можете установить его свойство Transaction после его построения.

Пока мы находимся в этом - ваша обработка ошибок неверна:

Метод команды 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 для хранения состояния незавершенной транзакции.

5 голосов
/ 07 апреля 2010

У нас была очень похожая проблема с использованием вложенных транзакций с классом TransactionScope. Мы думали , что все действия с базой данных происходили в одном и том же потоке ... однако мы были обнаружены механизмом транзакций ... точнее, транзакцией Ambient.

По сути, была транзакция выше по цепочке, которая, по волшебству ado, автоматически включала соединение. В результате мы даже думали, что записываем в базу данных в одном потоке, запись не выполнялась это действительно происходит до тех пор, пока не будет совершена самая верхняя транзакция. В этот «неопределенный» момент база данных была записана так, что она была заблокирована вне нашего контроля.

Решение состояло в том, чтобы гарантировать, что база данных sqlite не принимала непосредственное участие в транзакции окружения, гарантируя, что мы использовали что-то вроде:

using(TransactionScope scope = new TransactionScope(TransactionScopeOptions.RequiresNew))
{
  ...
  scope.Complete()
}
5 голосов
/ 10 августа 2009

Выполнить Sysinternals Process Monitor и отфильтровать имя файла при запуске вашей программы, чтобы исключить, если какой-либо другой процесс что-либо с ним сделает, и посмотреть, что ваша программа делает с файлом. Длинный выстрел, но может дать подсказку.

3 голосов
/ 23 января 2011

Сегодня я столкнулся с той же проблемой: я изучаю asp.net mvc, полностью собираю свое первое приложение с нуля. Иногда, когда я писал в базу данных, я получал то же исключение, говоря, что файл базы данных был заблокирован.

Мне это показалось странным, поскольку я был полностью уверен, что в то время было открыто только одно соединение (на основе списка активных дескрипторов файлов в проводнике процессов).

Я также создал весь слой доступа к данным с нуля, используя провайдера System.Data.SQLite .Net, и, когда я планировал это, я уделял особое внимание соединениям и транзакциям, чтобы гарантировать отсутствие соединения или транзакции. был оставлен без дела.

Сложность заключалась в том, что установка точки останова для команды ExecuteNonQuery () и запуск приложения в режиме отладки приводили к исчезновению ошибки! Гугл, я нашел что-то интересное на этом сайте: http://www.softperfect.com/board/read.php?8,5775. Там кто-то ответил на ветку, предлагающую автору включить путь к базе данных в список игнорируемых антивирусами.

Я добавил файл базы данных в список игнорирования моего антивируса (Microsoft Security Essentials), и это решило мою проблему. Больше нет ошибок заблокированных баз данных!

3 голосов
/ 04 августа 2009

На что обратить внимание:

  • не использовать соединения между несколькими потоками / процессами.

  • Я видел, как это происходит, когда антивирусный сканер обнаруживает изменения в файле и пытается его сканировать. Это заблокирует файл на короткий промежуток времени и вызовет хаос.

2 голосов
/ 11 августа 2009

Работает ли у вас Google Desktop Search (или другой индексатор файлов)? Как упоминалось ранее, Sysinternals Process Monitor может помочь вам отследить его.

Кроме того, каково имя файла базы данных? От PerformanceTuningWindows :

Будьте ОЧЕНЬ, ОЧЕНЬ осторожны в том, что вы называете своей базой данных, особенно расширение

Например, если вы дадите всем своим базам данных расширение .sdb (База данных SQLite, хорошее имя, эй? Я так и думал, когда выбираю его в любом случае ...), вы обнаружите, что расширение SDB уже связано с ПАКЕТАМИ APPFIX.

Теперь, вот милая часть, APPFIX - это исполняемый файл / пакет, который распознает Windows XP, и он будет (выделено мое) ДОБАВИТЬ БАЗУ ДАННЫХ В СИСТЕМУ ВОССТАНОВЛЕНИЯ СИСТЕМЫ

Это означает, что оставайтесь со мной здесь, каждый раз, когда вы пишете НИЧЕГО в базу данных, система Windows XP думает, что кровавый исполняемый файл изменился, и копирует вашу ВСЕ 800 мегабайтную базу данных в каталог восстановления системы ....

Я рекомендую что-то вроде DB или DAT.

2 голосов
/ 10 августа 2009

Я бы всегда использовал Соединение, Транзакцию и Команду в предложении using. В первом листинге кода вы это сделали, а в третьем (создавая таблицы) - нет. Я предлагаю вам сделать это тоже, потому что (кто знает?) Может быть, команды, которые создают таблицу, каким-то образом продолжают блокировать файл. Длинный выстрел ... но стоит выстрел?

2 голосов
/ 04 августа 2009

У этих парней были похожие проблемы (похоже, в основном, с заблокированным файлом журнала, возможно взаимодействия TortoiseSVN ... проверьте статьи, на которые есть ссылки).

Они разработали ряд рекомендаций (правильные каталоги, изменение типов журналирования с удаления на сохранение и т. Д.). http://sqlite.phxsoftware.com/forums/p/689/5445.aspx#5445


Опции режима журнала обсуждаются здесь: http://www.sqlite.org/pragma.html. Вы можете попробовать TRUNCATE.

Есть ли трассировка стека во время исключения в SQL Lite?

Вы указываете, что «пакетируете мои коммиты с разумным интервалом». Какой интервал?

2 голосов
/ 04 августа 2009

Файл вашей базы данных находится на том же компьютере, что и приложение, или он хранится на сервере?

Вы должны создать новое соединение в каждом потоке. Я бы упростил создание соединения, используя везде: connection = new SQLiteConnection (connString.ToString ());

и используйте файл базы данных на том же компьютере, что и приложение, и повторите тестирование.

Почему два разных способа создания соединения?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...