Russian Qt Forum

Qt => Базы данных => Тема начата: lolobotik от Ноябрь 16, 2017, 15:15



Название: Вложенные транзакции
Отправлено: lolobotik от Ноябрь 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(). В "составной" же всё действо обрамлено ещё одним набором открытия/закрытия транзакции ради того, что бы можно было разом откатить все входящие в её состав операции "поменьше", если где-то по ходу дела станет ясно, что всё пошло не так. Но задумка/реализация явно не удалась.



Название: Re: Вложенные транзакции
Отправлено: lolobotik от Ноябрь 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() её и закрыл. Вопрос знатокам: Почему первое открытие транзакции может "игнорироваться"?


Название: Re: Вложенные транзакции
Отправлено: lolobotik от Ноябрь 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

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


Название: Re: Вложенные транзакции
Отправлено: lolobotik от Ноябрь 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"). При такой работе с транзакциями всё отрабатывает так, как я хотел.