Транзакция SQLite не фиксируется

Для приложения, которое мы разрабатываем, нам нужно прочитать n строк из таблицы, а затем выборочно обновить эти строки на основе критериев, специфичных для предметной области. Во время этой операции все остальные пользователи базы данных должны быть заблокированы, чтобы избежать некорректного чтения.

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

 private static SQLiteConnection OpenNewConnection()
        {

        try
        {
            SQLiteConnection conn = new SQLiteConnection();
            conn.ConnectionString = ConnectionString;//System.Configuration.ConfigurationManager.AppSettings["ConnectionString"];
            conn.Open();
            return conn;
        }               
        catch (SQLiteException e)
        {
            LogEvent("Exception raised when opening connection to [" + ConnectionString + "].  Exception Message " + e.Message);
            throw e;
        }
    }

    SQLiteConnection conn = OpenNewConnection();
            SQLiteCommand command = new SQLiteCommand(conn);
            SQLiteTransaction transaction = conn.BeginTransaction();
// Also fails           transaction = conn.BeginTransaction();
            transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted);
            command.CommandType = CommandType.Text;
            command.Transaction = transaction;
            command.Connection = conn;
            try
            {
                string sql = "select * From X Where Y;";
                command.CommandText = sql;
                SQLiteDataReader ranges;

                ranges = command.ExecuteReader();
                sql = string.Empty;
                ArrayList ret = new ArrayList();
                while (MemberVariable > 0 && ranges.Read())
                {
                    // Domain stuff

                    sql += "Update X Set Z = 'foo' Where Y;";
                }
                ranges.Close();
                command.CommandText = sql;
                command.ExecuteNonQuery();
                                // UPDATES NOT BEING APPLIED
                transaction.Commit();
                return ret;

            }
            catch (Exception ex)
            {
                transaction.Rollback();
                throw;
            }
            finally
            {
                transaction.Dispose();
                command.Dispose();
                conn.Close();
            }

            return null;

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


person MikeP    schedule 18.11.2009    source источник


Ответы (2)


Когда вы ставите точку останова в строке transaction.Commit(), вы видите, что она срабатывает?

Окончательный ответ:

Блокировка SQLite не работает, как вы предполагаете, см. http://www.sqlite.org/lockingv3.html . Учитывая это, я думаю, что у вас возникла проблема с областью действия транзакции, которую можно легко решить, реорганизовав ваш код как таковой:

string selectSql = "select * From X Where Y;";      
using(var conn = OpenNewConnection()){
    StringBuilder updateBuilder = new StringBuilder();

    using(var cmd = new SQLiteCommand(selectSql, conn))
    using(var ranges = cmd.ExecuteReader()) {
        while(MemberVariable > 0 && ranges.Read()) {
            updateBuilder.Append("Update X Set Z = 'foo' Where Y;");
        }
    }

    using(var trans = conn.BeginTransaction())
    using(var updateCmd = new SQLiteCommand(updateBuilder.ToString(), conn, trans) {
        cmd.ExecuteNonQuery();
        trans.Commit();
    }
}   
person JeffreyABecker    schedule 18.11.2009
comment
да, и вызов ExecuteNonQuery возвращает правильное количество измененных строк. - person MikeP; 18.11.2009
comment
Давайте сделаем шаг назад: почему вы так рано открываете транзакцию? - person JeffreyABecker; 18.11.2009
comment
Читаемая и записываемая таблица — это одна и та же таблица, и все ее обновления должны быть последовательными между разными пользователями. Приложение должно блокировать дополнительные чтения, пока текущий запрос не будет записан. - person MikeP; 18.11.2009
comment
Ах, это не то, как работают транзакции SQLite. Он не будет блокироваться, пока не попытается записать данные. - person JeffreyABecker; 18.11.2009
comment
Согласно документации sqlite, именно так работают эксклюзивные транзакции. Конечно, я не использую встроенные транзакции sqlite. - person MikeP; 19.11.2009

Дополнительные примечания относительно некоторых комментариев в этом посте/ответе о транзакциях в SQLite. Они применяются к SQLite 3.x с использованием ведения журнала и могут применяться или не применяться к различным конфигурациям — WAL немного отличается, но я не знаком с ним. Подробную информацию см. в разделе блокировка в SQLite.

Все транзакции в SQLite являются SERIALIZABLE (см. прагму read_uncommitted за одним небольшим исключением). новое чтение не заблокируется/не завершится ошибкой, если процесс записи не начался (существует блокировка EXCLUSIVE/PENDING), и запись не начнется, пока не будут выполнены все ожидающие чтения завершены, и он может получить ЭКСКЛЮЗИВНУЮ блокировку (это неверно для WAL, но изоляция транзакций все такой же).

То есть вся приведенная выше последовательность не будет атомарной в коде, и последовательность может быть прочитана(A) -> прочитана(B) -> записана(A) -> прочитана(B), где A и B представляют разные соединения (представьте, что это разные потоки). При обоих чтениях (B) данные по-прежнему согласуются, даже несмотря на то, что между ними была запись.

Чтобы сделать саму последовательность кода атомарной, требуется lock или аналогичный механизм синхронизации. Кроме того, блокировку/синхронизацию можно создать с помощью самого SQLite, используя прагму locking_mode «исключительно». Однако, даже если приведенный выше код не является атомарным, данные будут соответствовать сериализуемому контракту SQL (исключая серьезную ошибку ;-)

Удачного кодирования


См. Блокировка в SQLite, Прагмы SQLite и Atomic Commit в SQLite

person Community    schedule 20.03.2011