Russian Qt Forum
Декабрь 18, 2017, 02:29 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Вложенные транзакции  (Прочитано 335 раз)
lolobotik
Новичок

Offline Offline

Сообщений: 9


Просмотр профиля
« : Ноябрь 16, 2017, 15:15 »

Здравствуйте.

Ситуация следующая. Есть SQL Server 2008, в программе используется драйвер QODBC3.
Соединение установлено, данные пишутся/читаются.

Проблема, видимо, в отсутствии понимания принципа работы с вложенными транзакциями. Судя по документации к восьмому серверу, каждый новый BEGIN TRANSACTION  увеличивает счётчик TRANCOUNT на единицу. Каждый COMMIT уменьшает его на 1. Транзакция завершается и фиксируются изменения, когда счётчик станет равен 0. У меня же выходит, что первый же commit завершает транзакцию вне зависимости от того, сколько было BEGIN TRANSACTION. Пусть имеется база и запрос к ней:  
Код:
db = QSqlDatabase::addDatabase("QODBC3");
...
query = new QSqlQuery(db);
...
Выполняем:
Код:
db.transaction();
      query->exec("INSERT INTO TestOnly (Value) VALUES('1')");
      db.transaction();
            query->exec("INSERT INTO TestOnly (Value) VALUES('2')");
      db.commit();
      query->exec("INSERT INTO TestOnly (Value) VALUES('3')");
db.rollback();
В результате в таблице оказываются все 3 значения ('1','2','3'). И в SQL SMS видно, что после первого же коммита пропадает единственная активная транзакция. Соответвственно последующий RollBack ничего не делает.

В чём здесь проблема? Я совсем не правильно понял идею со счётчиком открытых транзакций, или просто драйвер/Qt не поддерживают такой функционал? Или я упустил что-то где-то в настройках (сервера/драйвера)?

И на случай, если я совсем что-то не то творю и не тем образом, и есть более человеческий способ. Зачем мне вообще это нужно:
Есть необходимость использовать внутри "составной" функции, состоящий из нескольких операций, функции "поменьше "(отдельно на каждую из операций). Функции "поменьше" могут вызываться и самостоятельно, поэтому обрамлены своим набором:db.transaction(),db.rollback(),db.commit(). В "составной" же всё действо обрамлено ещё одним набором открытия/закрытия транзакции ради того, что бы можно было разом откатить все входящие в её состав операции "поменьше", если где-то по ходу дела станет ясно, что всё пошло не так. Но задумка/реализация явно не удалась.

Записан
lolobotik
Новичок

Offline Offline

Сообщений: 9


Просмотр профиля
« Ответ #1 : Ноябрь 16, 2017, 17:09 »

Сделаю пример чуть более страшным:
Код:
    bool ok;
    query->exec("SELECT @@TRANCOUNT");query->first();
    qDebug() << "Transaction Count 1: " << query->value(0).toInt();

    ok = db.transaction(); qDebug() << "Start transaction" << " result = " << ok << "; errorText: " << db.lastError().text();

    query->exec("SELECT @@TRANCOUNT");query->first();
    qDebug() << "Transaction Count 2: " << query->value(0).toInt();

    query->exec("INSERT INTO TestOnly (Value) VALUES('1')");
    ok = db.transaction(); qDebug() << "Start transaction" << " result = " << ok << "; errorText: " << db.lastError().text();

    query->exec("SELECT @@TRANCOUNT");query->first();
    qDebug() << "Transaction Count 3: " << query->value(0).toInt();

    query->exec("INSERT INTO TestOnly (Value) VALUES('2')");
    ok = db.commit(); qDebug() << "Commit transaction" << " result = " << ok << "; errorText: " << db.lastError().text();

    query->exec("SELECT @@TRANCOUNT");query->first();
    qDebug() << "Transaction Count 4: " << query->value(0).toInt();

    query->exec("INSERT INTO TestOnly (Value) VALUES('3')");
    ok = db.rollback();  qDebug() << "Rollback transaction" << " result = " << ok << "; errorText: " << db.lastError().text();

    query->exec("SELECT @@TRANCOUNT");query->first();
    qDebug() << "Transaction Count 5: " << query->value(0).toInt();

В результате имеем:
Код:
Transaction Count 1:  0   
Start transaction  result =  true ; errorText:  " " 
Transaction Count 2:  0   
Start transaction  result =  true ; errorText:  " " 
Transaction Count 3:  1   
Commit transaction  result =  true ; errorText:  " "   
Transaction Count 4:  0   
Rollback transaction  result =  true ; errorText:  " "   
Transaction Count 5:  0

Выглядит так, будто первый вызов transaction() не инкрементирует счётчик транзакций в базе. Соответсвенно rollback() вызывается будто бы без открытой транзакции, так как первый commit() её и закрыл. Вопрос знатокам: Почему первое открытие транзакции может "игнорироваться"?
Записан
lolobotik
Новичок

Offline Offline

Сообщений: 9


Просмотр профиля
« Ответ #2 : Ноябрь 16, 2017, 21:04 »

Покопался ещё немного: понял, что неверно истолковал полученные результаты. После первого INSERT'а из внешней транзакции счётчик всё-таки увеличивается (принимает значение = 1). Вот только после второго вызова transaction(), как и после INSERT'а внутри него, счётчик всё ещё показывает 1. Соответственно первый же COMMIT закрывает транзакцию полностью и откатывать уже нечего. Дабы не засорять тему дальше примерами кода и выводом полученного результата, просто опишу, какое значение счётчика считывается из базы после каждой операции.

db.transaction();                                                                  //@@TRANCOUNT = 0
query->exec("INSERT INTO TestOnly (Value) VALUES('1')");    //@@TRANCOUNT = 1
db.transaction();                                                                  //@@TRANCOUNT = 1
query->exec("INSERT INTO TestOnly (Value) VALUES('2')");    //@@TRANCOUNT = 1
db.commit();                                                                       //@@TRANCOUNT = 0
query->exec("INSERT INTO TestOnly (Value) VALUES('3')");    //@@TRANCOUNT = 0
db.rollback();                                                                       //@@TRANCOUNT = 0

Буду благодарен за любую подсказку по поводу того, почему счётчик не идёт дальше единицы.
Записан
lolobotik
Новичок

Offline Offline

Сообщений: 9


Просмотр профиля
« Ответ #3 : Ноябрь 20, 2017, 10:28 »

Раз уж я тут столько настрочил, отвечу хотя бы сам на свои вопросы. По идее тему можно закрывать (или у вас так не принято после ответа на вопрос? Улыбающийся)

Помогли на русском stack overflow. Судя по всему, надо было мне исходниками по голове настучать, может быстрее разобрался бы. На самом деле transaction() не отправляет никаких команд о начале новой транзакции базе, он просто отключает автокоммит (справедливо, по крайней мере, для QODBC), соответственно каждая следующая операция не подтверждается до ручного COMMIT'а. А так как begin transaction никуда не отправляется, начать вложенную транзакцию описанным мною образом невозможно. "Begins a transaction on the database if the driver supports transactions." в описании функции ввело меня в заблуждение.

Сейчас буду менять в коде все transaction()/commit()/rollback() на query->exec("begin tran"/"commit"/"rollback"). При такой работе с транзакциями всё отрабатывает так, как я хотел.
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  

Страница сгенерирована за 0.049 секунд. Запросов: 22.