How can I rewrite my transaction to undo changes since the beginning whenever an error condition occurs before the end?
I see this question has already been asked and I followed the link provided in that answer. It's not clear to me what the next step is. I am hoping someone can illustrate the solution with an example applied to this transaction:
BEGIN TRANSACTION;
INSERT INTO Employee(ID, Name) VALUES(1, 'John');
...
<some failure happens here>
...
END TRANSACTION;
In this case, I would like the insert into the Employee table to be undone because a failure occurs before END TRANSACTION. How do I write rules that enforce this? This is for sqlite version 3.9.2.
Execute a ROLLBACK TRANSACTION query, or use the ON CONFLICT ROLLBACK conflict resolution on the failable queries to let SQLite roll back the transaction for you.
Related
Assume an employees table like the one in Oracle's HR sample schema. It includes the following:
CREATE TABLE employees
(
employee_id NUMBER(6) NOT NULL PRIMARY KEY,
...
manager_id NUMBER(6)
);
ALTER TABLE employees
ADD CONSTRAINT employee_manager_fk
FOREIGN KEY (manager_id)
REFERENCES employees(employee_id);
I want to prevent a reciprocal relationship: if the employee with id 110 reports to the employee with id 110, I don't want to allow employee 110 to report to employee 100. I want to prevent the following:
employee_id manager_id
100 110
110 100
I don't think this is doable with a constraint, because it would require a subquery. So, I think a trigger is necessary. This is only a concern on an UPDATE as it's impossible to create this situation with a DELETE or INSERT. I've created the following trigger, which does the job:
CREATE OR REPLACE TRIGGER employees_bu_trigger
BEFORE
UPDATE OF manager_id
ON employees
FOR EACH ROW
DECLARE
l_managers_manager_id employees.manager_id%TYPE;
reciprocal_managers EXCEPTION;
-- This prevents ORA-04091: table C##HR.EMPLOYEES
-- is mutating, trigger/function may not see it
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
SELECT manager_id
INTO l_managers_manager_id
FROM employees
WHERE employee_id = :new.manager_id;
IF l_managers_manager_id = :new.employee_id THEN
RAISE reciprocal_managers;
END IF;
EXCEPTION
WHEN reciprocal_managers THEN
-- re-raise the error to be caught by calling code
RAISE_APPLICATION_ERROR(-20102,
'Employees cannot manage each other.');
END;
But I'm worried about the PRAGMA AUTONOMOUS_TRANSACTION; line. That's needed to prevent the ORA-04091 error, but it makes me think there might be some further underlying problem here.
Is this trigger okay or will it have unforeseen negative side effects?
The danger of the PRAGMA AUTONOMOUS_TRANSACTION is that it runs in a separate transaction. So if your active transaction just added a entry and then before committing you do the update, this trigger will not see the inserted record and will allow records that violate your rule. This problem also occurs with other concurrent users (i.e. if user a makes an update in a transaction and user b makes an update the check will pass for both of them, but once they both commit the error state can exist even though both checks passed.)
Also while the example provided does not have this problem (as Oracle's documentation states that A writer never blocks a reader.), there is a danger of deadlocks if you do any writes in the trigger.
It may be possible to refactor your check to be a AFTER trigger without the FOR EACH ROW.
This will eliminate the danger of deadlocks, and of the test passing in the single user case. BUT it will not eliminate the multiuser issue.
Jon Heller's comment on multilevel loops may also be relevant to our specific problem, but any changes to the checks do not impact the issues you were specifically asking about.
In a plsql block all the statements will get roll backed if any of the statement through error in DML?
No. You have the power to rollback any specific set of statements when an exception or error comes. You can use
SAVEPOINT savepoint_name;
ROLLBACK TO savepoint_name;
Create a SAVEPOINT before the block till which you want to rollback. SAVEPOINT names and marks the current point in the processing of a transaction. Savepoints let you roll back part of a transaction instead of the whole transaction. The number of active savepoints for each session is unlimited.
Example :
BEGIN
-- other DML statements
SAVEPOINT do_insert;
INSERT INTO emp_name VALUES (emp_id, emp_lastname, emp_salary);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
ROLLBACK TO do_insert;
DBMS_OUTPUT.PUT_LINE('Insert has been rolled back');
END;
SAVEPOINT
Not by itself.
With autocommit on the individual sql statement forms a transaction and is committed/rolled back on completion. A block forms an aggregation of statements. If you have one of the statements of a block failing any stateents before (within the block) still have been executed (and committed).
If you want a group of statements to behave transactional you need to use "BEGIN [TRANSACTION]...COMMIT/ROLLBACK" and do proper error handling to your needs.
I have started SQlite 2-days ago, and today i tried to learn Transactions in Sqlite3. But i am unable to even run the simplest ever transaction.
Begin;
Insert into newTable(Name,Age) values ("Adnan Ahamd KHan",24)
Insert into tbl2 (Name, FID) values ("Adnan",(Select MAx(ID) from newTable))
END Transaction;
Error Displayed is
cannot start a transaction within a transaction: Begin;
Here we go,
I found the answer to my Questio. Actually i am using DBBrowser for SQlite. And upto my knowledge, you have to commit every statement in DBBrowser for SQlite.
I First created Tables, established Relationship, and without committing them all, i then tried to start that Transaction, that's why it was saying
cannot start a transaction within a transaction: Begin;
What i did, first issued a single
commit
to commit the statements which created tables, and then started Transaction. And it worked fine.
Thanks
I looked into the code and found BEGIN TRANSACTION; starting line and Commit; ending line. I removed both (remove the first and last line which says BEGIN TRANSACTION and Commit/ END TRANSACTION) of them and it works now.
The following failed for me:
BEGIN TRANSACTION;
....
END TRANSACTION;
AND
BEGIN TRANSACTION;
....
Commit;
I need to modify all the content in a table. So I wrap the modifications inside a transaction to ensure either all the operations succeed, or none do. I start the modifications with a DELETE statement, followed by INSERTs. What I’ve discovered is even if an INSERT fails, the DELETE has still takes place, and the database is not rolled back to the pre-transaction state.
I’ve created an example to demonstrate this issue. Put the following commands into a script called EXAMPLE.SQL
CREATE TABLE A(id INT PRIMARY KEY, val TEXT);
INSERT INTO A VALUES(1, “hello”);
BEGIN;
DELETE FROM A;
INSERT INTO A VALUES(1, “goodbye”);
INSERT INTO A VALUES(1, “world”);
COMMIT;
SELECT * FROM A;
If you run the script: “sqlite3 a.db < EXAMPLE.SQL”, you will see:
SQL error near line 10: column id is not unique
1|goodbye
What’s surprising is that the SELECT statement results did not show ‘1|hello’.
It would appear the DELETE was successful, and the first INSERT was successful. But when the second INSERT failed (as it was intended to)….it did not ROLLBACK the database.
Is this a sqlite error? Or an error in my understanding of what is supposed to happen?
Thanks
It works as it should.
COMMIT commits all operations in the transaction. The one involving world had problems so it was not included in the transaction.
To cancel the transaction, use ROLLBACK, not COMMIT. There is no automatic ROLLBACK unless you specify it as conflict resolution with e.g. INSERT OR ROLLBACK INTO ....
And use ' single quotes instead of “ for string literals.
This documentation shows the error types that lead to an automatic rollback:
SQLITE_FULL: database or disk full
SQLITE_IOERR: disk I/O error
SQLITE_BUSY: database in use by another process
SQLITE_NOMEM: out or memory
SQLITE_INTERRUPT: processing interrupted by application request
For other error types you will need to catch the error and rollback, more on this is covered in this SO question.
I am running an END TRANSACTION on my database and occasionally I get error
#1 that "cannot commit - no transaction is active"
Is there a way to determine if a transaction is active before trying a commit? I have been tracking my "BEGIN TRANSACTIONS" by hand but I feel there is a better way.
I am using the C API
You might want to check this:
http://www.sqlite.org/c3ref/get_autocommit.html
According to the page, if you are in a transaction, sqlite3_get_autocommit() will return 0.
The interface you're looking for is actually implemented in this next version of sqlite as you can see in the notes: https://sqlite.org/draft/c3ref/txn_state.html
Determine the transaction state of a database
int sqlite3_txn_state(sqlite3*,const char *zSchema);
The sqlite3_txn_state(D,S) interface returns the current transaction state of schema S in database connection D. If S is NULL, then the highest transaction state of any schema on databse connection D is returned. Transaction states are (in order of lowest to highest):
SQLITE_TXN_NONE
SQLITE_TXN_READ
SQLITE_TXN_WRITE
If the S argument to sqlite3_txn_state(D,S) is not the name of a valid schema, then -1 is returned.
That's weird. I thought sqlite was always in a transaction, either explicitly created by you or implicitly created by sqlite:
http://www.sqlite.org/lang_transaction.html
So I suppose the error means that it's not in a transaction that you initiated ... and if that's what you need to know, it seems OK for sqlite to expect you to keep up with it. Not terribly convenient of course, but I guess that's the cost of a simple API. =/
In SQLite, transactions created using BEGIN TRANSACTION ... END TRANSACTION do not nest.
For nested transactions you need to use the SAVEPOINT and RELEASE commands.
See http://www.sqlite.org/lang_transaction.html
and http://www.sqlite.org/lang_savepoint.html