How to rollback all statements inside a MySQL Transaction? - mysql

I need to update a specific column of a table (bigtable) containing ids of another table (FK constraint to oldsmalltable) to point to ids on another table (FK constraint to newsmalltable). Basically this is what I am doing:
DELIMITER //
CREATE PROCEDURE updatebigtable ()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING ROLLBACK;
START TRANSACTION;
ALTER TABLE bigtable DROP FOREIGN KEY bigtable_ibfk_1,
MODIFY smalltable_id SMALLINT ;
UPDATE bigtable SET smalltable_id=CASE smalltable_id
WHEN 1 THEN 1592
WHEN 2 THEN 1593
WHEN 3 THEN 1602
...
ELSE 0
END;
ALTER TABLE bigtable ADD CONSTRAINT bigtable_ibfk_1
FOREIGN KEY(smalltable_id) REFERENCES newsmalltable(id);
COMMIT;
END//
DELIMITER ;
CALL updatebigtable();
DROP PROCEDURE updatebigtable;
I need to ensure that if by some reason the new Foreign Key constraint fails (e.g. with columns with different types, the error would occur on the last alter table statement), the UPDATE and the first ALTER TABLE should be rolled back as well, i.e. they should remain as they were initially.
According to MySQL documentation, by using START TRANSACTION the autocommit mode is disabled for that transaction, which will not allow:
that as soon as you execute a statement that updates (modifies) a table, MySQL stores the update on disk to make it permanent.
I only found this question as minimally related to mine:
How can I use transactions in my MySQL stored procedure?
If that error I mentioned occurs inside the transaction, the previous statements were already executed and the updates were "permanently done on disk"...
I also tried to place SET autocommit=0; before creating the procedure but the behavior is still the same... Am I missing something? Or is this the expected behavior of a MySQL transaction rollback?
If it makes any difference, I am using MySQL v.5.6.17.

ALTER TABLE statements always cause an implicit commit (section 13.3.3 from MySQL docs, thanks wchiquito), which means that even if they're inside a START TRANSACTION; ... COMMIT; block, there will be as many commits as the number of alters done inside that block.
Locking the table is not an option as well since (from problems with ALTER TABLE):
If you use ALTER TABLE on a transactional table or if you are using Windows, ALTER TABLE unlocks the table if you had done a LOCK TABLE on it. This is done because InnoDB and these operating systems cannot drop a table that is in use.
The only option left for avoiding unwanted reads/writes while the alter and update statements are being executed is emulating all the steps of an ALTER TABLE:
Create a new table named A-xxx with the requested structural changes.
Copy all rows from the original table to A-xxx.
Rename the original table to B-xxx.
Rename A-xxx to your original table name.
Delete B-xxx.
This way the updates can be done in the new table (after step 2) and the only time the bigtable is unavailable is while doing step 3 and 4 (renaming).

Use a TRY CATCH block
BEGIN TRAN before BEGIN TRY and ROLLBACK TRAN inside CATCH block

Related

Automatically drop a temporary table when the transaction is over

I need to create a temporary table and perform a few SELECTs on it. After that I won't need that temp table. But since I'm using a connection pool, the temp table will stay there and will even interfere with the next transaction that happens to acquire the same connection.
My question is, is there a way to automatically clean-up the table when my current transaction is over?
Obviously, I could do that manually, but in order to do it properly, the code won't be that simple:
<START TX>
DROP TEMPORARY TABLE IF EXISTS some_tt;
CREATE TEMPORARY TABLE some_tt AS ...;
...
<use some_tt here>
...
DROP TEMPORARY TABLE IF EXISTS some_tt;
<END TX>
In order to release resources, we should drop the table as soon as we don't need it anymore (before the tx ends). But since we cannot guarantee the drop will happen & succeed, I believe we should also execute a drop right before the CREATE TEMPORARY TABLE statement, just in case.
A TEMPORARY table is visible only within the current session, and is dropped automatically when the session is closed. However, temporary tables are not affected by transactions and are not deleted when a transaction ends or rollbacks. In addition, the temporary table is visible to parallel transactions within the session.

Drop a trigger from within the trigger body

I want to run a trigger once and only once the first time a condition is satisfied.
To do this I would like to drop the trigger from within the body of the trigger itself. I have two questions: 1) is there a better way than this and 2) will anything weird happen if I drop the trigger inside the trigger body?
This is what I have so far. For context: There's another process running moving things to done and in a particular case it does not write the result so in that case I want to run a script such that when they're all done I want this trigger to read some values another table and then remove the trigger itself so that it doesn't run every single time stuff gets done normally.
CREATE TRIGGER some_trigger AFTER UPDATE ON table_name FOR EACH ROW
SELECT CASE WHEN ((SELECT count(*) FROM table_name WHERE status!='done') = 0)
THEN BEGIN
UPDATE table_name SET result = (SELECT other.result FROM table_name, other WHERE other.id = table_name.id);
DROP TRIGGER some_trigger;
END;
ELSE BEGIN END;
END CASE;
EDIT: also a third question, what does "FOR EACH ROW" mean? I only want the trigger to run once, not once per row. Looking at the docs it seems like "FOR EACH ROW" is not optional.
DROP TRIGGER cannot be performed within a Trigger.
To explain why, firstly, DROP TRIGGER causes an implicit commit, and secondly, commits cannot occur within triggers. Details below:
DROP TRIGGER causes an implicit commit
See (https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html):
The statements listed in this section (and any synonyms for them) implicitly end any transaction active in the current session, as if you had done a COMMIT before executing the statement.
...
Data definition language (DDL) statements that define or modify database objects. ALTER EVENT, ALTER FUNCTION, ALTER PROCEDURE, ALTER SERVER, ALTER TABLE, ALTER VIEW, CREATE DATABASE, CREATE EVENT, CREATE FUNCTION, CREATE INDEX, CREATE PROCEDURE, CREATE ROLE, CREATE SERVER, CREATE SPATIAL REFERENCE SYSTEM, CREATE TABLE, CREATE TRIGGER, CREATE VIEW, DROP DATABASE, DROP EVENT, DROP FUNCTION, DROP INDEX, DROP PROCEDURE, DROP ROLE, DROP SERVER, DROP SPATIAL REFERENCE SYSTEM, DROP TABLE, DROP TRIGGER, DROP VIEW, INSTALL PLUGIN, RENAME TABLE, TRUNCATE TABLE, UNINSTALL PLUGIN.
Commits cannot occur within a trigger:
See (https://dev.mysql.com/doc/refman/8.0/en/trigger-syntax.html):
The trigger cannot use statements that explicitly or implicitly begin or end a transaction, such as START TRANSACTION, COMMIT, or ROLLBACK. (ROLLBACK to SAVEPOINT is permitted because it does not end a transaction.).

Truncate table in mysql

I want to make trigger and this is code
DELIMITER $$
CREATE TRIGGER tax_year_update AFTER UPDATE ON const_data
FOR EACH ROW
BEGIN
IF NEW.tax_year <> OLD.tax_year THEN
TRUNCATE family_income;
TRUNCATE student_income;
END IF;
END$$
DELIMITER;
It causes this error
1422 - Explicit or implicit commit is not allowed in stored function or trigger.
Any suggestions why it doesn't work?
Truncate implicitly commits the transaction which is not allowed inside the trigger. Also TRUNCATE TABLE is DDL statament. You need to better use DELETE instead of TRUNCATE.
From the source:
Depending on version and storage engine, TRUNCATE can cause the table
to be dropped and recreated. This provides a much more efficient way
of deleting all rows from a table, but it does perform an implicit
COMMIT. You might want to use DELETE instead of TRUNCATE.
So you can try
DELETE FROM family_income;
DELETE FROM student_income;
instead of
TRUNCATE family_income;
TRUNCATE student_income;
While you truncating the table and if it records relates to other tables. It won't be truncating.
For that first you've to remove all the records from table in which you want to truncate.
Then go to options and set auto-increment with 1.
Enjoy :)

creating a trigger to lock and unlock a record

I am creating a php script for a MySQL database whereby I call a MySQL trigger..
The trigger should affect a table which is effectively an invoice:
So when I update a field called 'date_invoiced' from its NULL default to a valid date it then locks the whole record from being updated unless you have permission via your MySQL logon to change it back to its default NULL, (effectively 're-opening' the invoice)
No idea how to do this, any help would be great
You can't put a lock on a row. I suggest you use a TRIGGER on update, which makes the update fail if date_invoiced is NOT NULL. Unless username is 'superman'.
I think that you can code what you want following this example.
DELIMITER ||
CREATE TRIGGER upd_lock
BEFORE UPDATE
ON table_name
FOR EACH ROW
BEGIN
IF OLD.date_invoiced IS NOT NULL AND USER() NOT LIKE '\'superman\'#%' THEN
SIGNAL SQLSTATE VALUE '45000' SET MESSAGE_TEXT = '[upd_lock] - Record is locked';
END IF;
END;
||
DELIMITER ;
Adding triggers is essential for the development of complex MySQL databases that retain enforced referential integrity. Foreign keys cannot handle complex cases that perhaps involve more than one column (such as an item_id and item_type_id scenario).
SUPER is required when creating or dropping trigger only when binary logging is turned on.
The reason appears to be related to replication issues (MySQL 5.0 documentation).
RTM.and RTM
Read this link to ... & this threads Applying column permissions for a table over a trigger , Can't create MySQL trigger with TRIGGER privilege on 5.1.32

Sqlserver standard trigger order

I have database partially created and edited by an external CRM where certain tables have multiple (at most 2) after triggers on them. This is due to 1 trigger being auto generated by the CRM (over which I have limited control) and the other containing my code.
The CRM trigger generates the primary key for the datarow inserted. My trigger needs to access that primary key in order to write it to another table as foreign key. I use
Select #id=max(id) from mytable
since Scope_Identity did not produce the desired result somehow.
This worked until I let the CRM recreate the table and its own trigger. The maximum id of that table selected by my trigger seemed to always be actual_id - 1.
When I altered my trigger using the same code it has always had the procedure worked again.
My question is:
Does SQL server (I am using SQL Server 2008) set its trigger order by creation time?
And:
Is
sp_settriggerorder #triggername='mycustomtrigger', #order='Last', #stmttype='INSERT'
going to change this permanently or do I have to call that procedure again, every time the CRM recreates its trigger? (using DROP and CREATE, not ALTER)
Hope the answers to that will help someone looking at the same issue.
Regards
It's not documented, but I believe that the LAST setting will stay with a trigger, provided it is not modified. (Contrariwise, it is documented that a trigger will lose this setting if it is modified). However, it seems to work:
create table T (ID int not null)
go
create trigger T_T1 on T
after insert
as
RAISERROR('T1',10,1) WITH NOWAIT
go
create trigger T_T2 on T
after insert
as
RAISERROR('T2',10,1) WITH NOWAIT
go
create trigger T_T3 on T
after insert
as
RAISERROR('T3',10,1) WITH NOWAIT
go
insert into T(ID) values (1)
go
sp_settriggerorder 'T_T2','Last','INSERT'
go
insert into T(ID) values (2)
go
drop trigger T_T1
go
create trigger T_T1 on T
after insert
as
RAISERROR('T1',10,1) WITH NOWAIT
go
insert into T(ID) values (3)
Results:
T1
T2
T3
(1 row(s) affected)
T1
T3
T2
(1 row(s) affected)
T3
T1
T2
(1 row(s) affected)
As to your first question, however:
Does SQL server (I am using SQL Server 2008) set its trigger order by creation time?
It also appears to, but I would not rely on that. sp_settriggerorder is the only place where any ordering is documented.
Finally, as mentioned in my comment, I wouldn't rely on your current Select #id=max(id) from mytable method - it could be broken for a number of reasons, but the most important is that a trigger is fired once per method, and may fire in response to multiple rows, so you ought to write triggers to use the inserted pseudo-table instead (and expect it to contain 0, 1 or multiple rows).