Здравствуйте.
Ситуация следующая. Есть 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 не поддерживают такой функционал? Или я упустил что-то где-то в настройках (сервера/драйвера)?
С проверкой возвращаемых значений операций открытия/закрытия транзакций пример выглядит страшнее, но ничего не поделать:
bool ok;
ok = db.transaction(); qDebug() << "Start transaction" << " result = " << ok << "; errorText: " << db.lastError().text();
PrintTranCount(); //0
query->exec("INSERT INTO TestOnly (Value) VALUES('1')");
PrintTranCount(); //1
ok = db.transaction(); qDebug() << "Start transaction" << " result = " << ok << "; errorText: " << db.lastError().text();
PrintTranCount(); //2
query->exec("INSERT INTO TestOnly (Value) VALUES('2')");
PrintTranCount(); //3
ok = db.commit(); qDebug() << "Commit transaction" << " result = " << ok << "; errorText: " << db.lastError().text();
PrintTranCount(); //4
query->exec("INSERT INTO TestOnly (Value) VALUES('3')");
PrintTranCount(); //5
ok = db.rollback(); qDebug() << "Rollback transaction" << " result = " << ok << "; errorText: " << db.lastError().text();
PrintTranCount(); //6
Результат:
Start transaction result = true ; errorText: " "
Transaction Count 0: 0
Transaction Count 1: 1
Start transaction result = true ; errorText: " "
Transaction Count 2: 1
Transaction Count 3: 1
Commit transaction result = true ; errorText: " "
Transaction Count 4: 0
Transaction Count 5: 0
Rollback transaction result = true ; errorText: " "
Transaction Count 6: 0
Иными словами, вторая db.transaction() не инкриминирует счётчик совсем и транзакция полностью завершается после первого COMMIT'а. Буду благодарен за любые подсказки по этому поводу.
Вывод значения счётчика транзакций:
void PrintTranCount()
{
static int Num = 0;
query->exec("SELECT @@TRANCOUNT");query->first();
qDebug() << "Transaction Count "<< Num << ": " <
Ответ
Краткий ответ
Вызов метода QSqlDatabase::transaction() не начинает транзакцию в базе, а лишь включает режим "ручной режим фиксации". Для того чтобы начать явную транзакцию, нужно отослать базе непосредственно команду "begin tran".
Пояснения
Проблема заключалась в том, что я принимал желаемое поведение драйвера за действительное и поленился залезть в исходники. Спасибо @teran за то, что "ткнул носом" куда надо.
Описание функции bool QSqlDatabase::transaction() гласит:
Begins a transaction on the database if the driver supports
transactions. Returns true if the operation succeeded. Otherwise it
returns false.
Из этого описания я сделал ложный вывод о том, что драйвер отправляет базе команду открытия транзакции, что, мягко говоря, не соотносится с действительностью.
Из исходников:
bool QSqlDatabase::transaction()
{
if (!d->driver->hasFeature(QSqlDriver::Transactions))
return false;
return d->driver->beginTransaction();
}
Функция "спрашивает" драйвер о поддержке транзакций, вызывая hasFeature(QSqlDriver::Transactions). Если получает положительный ответ - вызывает метод драйвера beginTransaction().
bool QODBCDriver::beginTransaction()
{
Q_D(QODBCDriver);
//---------------Проверяем открыта ли база-----------------
if (!isOpen())
{
qWarning("QODBCDriver::beginTransaction: Database not open");
return false;
}
//------Инициализация переменной-параметра значением,-----
//------отключающим автоматический commit операций--------
SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
//-------Устанавливаем этот параметр и проверяем на ошибки-----------------
SQLRETURN r = SQLSetConnectAttr(d->hDbc,
SQL_ATTR_AUTOCOMMIT,
(SQLPOINTER)size_t(ac),
sizeof(ac));
if (r != SQL_SUCCESS) {
setLastError(qMakeError(tr("Unable to disable autocommit"),
QSqlError::TransactionError, d));
return false;
}
return true;
}
В функции beginTransaction(), в свою очередь, единственное производимое действие - отключение автоматического подтверждения изменений в базе. Никаких "begin tran".
В результате, с отключённым автокоммитом база данных при выполнении каждой инструкции начинает неявную транзакцию. Это (а не сама функция transaction(), как я думал) и приводит к установке счётчика транзакций @TRANCOUNT в единицу. Нет никакой вложенности транзакций в данном случае, она одна и завершается сразу после первого же COMMIT'а.
Функция commit() (как и rollback()) содержит в себе вызов QODBCDriver::endTrans(), который вновь отключает режим неявных транзакций. Это отключение объясняет, почему в моём примере в результирующей таблице была и третья строка (та, что перед ROLLBACK'ом). COMMIT перед отправкой команды вставки этой строки завершил неявную транзакцию и отключил ручной режим фиксации, так что INSERT был автоматически зафиксирован в базе, а rollback() отработал вхолостую.
Комментариев нет:
Отправить комментарий