MySQL transaction and trigger - mysql

I quickly browsed MySQL manual but didn't find the exact information about my question. Here is my question: if I have a InnoDB table A with two triggers triggered by 'AFTER INSERT ON A' and 'AFTER UPDATE ON A'. More specifically, For example: one trigger is defined as:
CREATE TRIGGER test_trigger AFTER INSERT ON A
FOR EACH ROW
BEGIN
INSERT INTO B SELECT * FROM A WHERE A.col1 = NEW.col1
END;
You can ignore the query between BEGIN AND END, basically I mean this trigger will insert several rows into table B which is also a InnoDB table.
Now, if I started a transaction and then insert many rows, say: 10K rows, into table A. If there is no trigger associated with table A, all these inserts are atomic, that's for sure. Now, if table A is associated with several insert/update triggers which insert/update many rows to table B and/or table C etc.. will all these inserts and/or updates are still all atomic?
I think it's still atomic, but it's kind of difficult to test and I can't find any explanations in the Manual. Anyone can confirm this?

And by atomic, you mean if one statement in the trigger fails, the whole statement fails. Yes -- the trigger is done in the context of statement's transaction. And no, of course, if there is no transaction, then there is no transaction context.
For transactional tables, failure of a statement should cause rollback of all changes performed by the statement. Failure of a trigger causes the statement to fail, so trigger failure also causes rollback. For nontransactional tables, such rollback cannot be done, so although the statement fails, any changes performed prior to the point of the error remain in effect.
And you aren't allowed to start a transaction in the trigger.

They should be atomic, if done in a transaction. The MySQL page on triggers has the appropriate information.
http://dev.mysql.com/doc/refman/5.0/en/triggers.html

Related

SQL UPDATES/INSERTS are not getting blocked during a transaction

I have a table where it is constantly being sent UPDATES/INSERTS from other processes, and I am trying to perform a transaction to my SQL table where I rotate the table and move some of the last values into the new table that I just created:
BEGIN TRANSACTION;
CREATE TABLE temp LIKE sales;
RENAME TABLE sales TO sales_05_04_19, temp TO sales
INSERT INTO sales SELECT * FROM sales_05_04_19 WHERE time > 1556953200000;
COMMIT;
But it does not appear to be blocking these UPDATES/INSERTS and some seem to actually make it through to the newly created sales table before the transaction's INSERT occurs. This causes me to get the error on the transaction insert:
(1062, "Duplicate entry '1' for key 'PRIMARY'")
I thought that this transaction would block the UPDATES/INSERTS until it commits, but that doesn't seem to be the case here.
So I feel that I would need to acquire a lock. How would I go about doing this (if that is the right approach to fixing this)?
If you want to prevent updates on a table while you do this kind of thing, you'll need to LOCK TABLES rather than use a transaction. I suggest creating your new table, locking both it and the old one, doing the rename switcheroo, doing your insert, then releasing the locks. There is no need for the transaction. Transactions avoid inconsistency, but they do not guarantee order, and DDL statements like create and rename table are not transaction-safe in any case.

How to read innodb row which is created by after insert trigger in mysql?

Let me come to the point directly. I have two tables named A and B. In table A I have a trigger(AFTER INSERT) which is responsible to insert a row in table B with NEW.ID in it. In table B there is also a trigger(AFTER INSERT) which calls php external script using the mysql UDF and in the UDF I pass the NEW.table_A_ID. Now the script tries to access the newly inserted row in table A.
The problem: I am unable to read the newly inserted row in table A. I can read the n-1th row but not the nth row. By default the mysql autocommit is 1 and engine Im using is INNODB. What is the workaround to this problem?
In the context of an InnoDB transaction, the row inserted into table A is not yet committed when the AFTER INSERT trigger is fired. The INSERT statement is not "complete" until after the trigger finishes.
The transaction isolation level of a second database session will determine whether that session can see uncommitted data or not. Default transaction isolation level is REPEATABLE READ.
InnoDB also provides a (more strict) SERIALIZABLE isolation level, an (Oracle-like) READ COMMITTED isolation level, and a READ UNCOMMITTED isolation level.
Note that AUTOCOMMIT doesn't really matter in the scenario you describe; the "commit" action will only occur AFTER the INSERT statement returns, and that won't happen until the AFTER INSERT trigger completes.
If you inserted rows into a MyISAM table in the trigger, then those rows would be visible to another database session. (This is one way to achieve an Oracle-like AUTONOMOUS TRANSACTION in MySQL.)

How do I handle errors so a locked table will unlock in a procedure?

I want to have a stored procedure that does the following:
Locks a table
Checks for a value in it
Updates same table based on that value
Unlocks the table
If an error occurs between 1 and 4, will the table be unlocked? Or do I need to capture the error somehow? (how?)
Is there a better way to do this?
You can't lock a table within a stored procedure in MySQL.
SQL Statements Not Permitted in Stored Routines
Stored routines cannot contain arbitrary SQL statements. The following statements are not permitted:
The locking statements LOCK TABLES and UNLOCK TABLES.
— http://dev.mysql.com/doc/refman/5.6/en/stored-program-restrictions.html
If you are using InnoDB, then you can accomplish your purpose by locking the rows of interest using locking reads with SELECT ... FOR UPDATE. When you hit an error and roll back the transaction, the rows are unlocked automatically.
I wrote about this in detail in this recent answer where the question involved avoiding conflicting inserts but the underlying concept is the same whether you know the row you want already exists, or whether it might or might not exist.
Have you considered using transactions with a try-catch block? See this:
BEGIN TRAN
SAVE TRAN S1 -- Savepoint so any rollbacks will only affect this transaction
BEGIN TRY
/* Do your work in here */
END TRY
BEGIN CATCH
ROLLBACK TRAN S1 -- rollback just this transaction
SET #ErrorMessage = ERROR_MESSAGE()
SET #Severity = ERROR_SEVERITY()
SET #State = ERROR_STATE()
RAISERROR(#ErrorMessage, #Severity, #State) -- re-throw error if needed
END CATCH

InnoDb transactions with create statements

Are sql statements such as CREATE TABLE tbl_name ..... allowed in transactions.
For example:
begin;
CREATE TABLE .......;
sdfghjk;
rollback;
The table is still created despite a statement in the transaction failing, and a rollback at the end. Is there a way to prevent the table from being created if a statement in the transaction fails?
DDL statements are allowed within transactions, but are not generally impacted by the transactions. From the MySQL Documentation on what can and cannot be rolled back:
Some statements cannot be rolled back. In general, these include data
definition language (DDL) statements, such as those that create or
drop databases, those that create, drop, or alter tables or stored
routines.
You should design your transactions not to include such statements. If
you issue a statement early in a transaction that cannot be rolled
back, and then another statement later fails, the full effect of the
transaction cannot be rolled back in such cases by issuing a ROLLBACK
statement.
Source
If you still need to use table you can do create temporary table..... It doesn't commit transaction but will be deleted when connection will be closed.

MySQL pause index rebuild on bulk INSERT without TRANSACTION

I have a lot of data to INSERT LOW_PRIORITY into a table. As the index is rebuilt every time a row is inserted, this takes a long time. I know I could use transactions, but this is a case where I don't want the whole set to fail if just one row fails.
Is there any way to get MySQL to stop rebuilding indices on a specific table until I tell it that it can resume?
Ideally, I would like to insert 1,000 rows or so, set the index do its thing, and then insert the next 1,000 rows.
I cannot use INSERT DELAYED as my table type is InnoDB. Otherwise, INSERT DELAYED would be perfect for me.
Not that it matters, but I am using PHP/PDO to access MySQL. Any advice you could give would be appreciated. Thanks!
ALTER TABLE tableName DISABLE KEYS
// perform inserts
ALTER TABLE tableName ENABLE KEYS
This disables updating of all non-unique indexes. The disadvantage is that those indexes won't be used for select queries as well.
You can however use multi-inserts (INSERT INTO table(...) VALUES(...),(...),(...) which will also update indexes in batches.
AFAIK, for those that use InnoDB tables, if you don't want indexes to be rebuilt after each INSERT, you must use transactions.
For example, for inserting a batch of 1000 rows, use the following SQL:
SET autocommit=0;
//Insert the rows one after the other, or using multi values inserts
COMMIT;
By disabling autocommit, a transaction will be started at the first INSERT. Then, the rows are inserted one after the other and at the end, the transaction is committed and the indexes are rebuilt.
If an error occurs during execution of one of the INSERT, the transaction is not rolled back but an error is reported to the client which has the choice of rolling back or continuing. Therefore, if you don't want the entire batch to be rolled back if one INSERT fails, you can log the INSERTs that failed and continue inserting the rows, and finally commit the transaction at the end.
However, take into account that wrapping the INSERTs in a transaction means you will not be able to see the inserted rows until the transaction is committed. It is possible to set the transaction isolation level for the SELECT to READ_UNCOMMITTED but as I've tested it, the rows are not visible when the SELECT happens very close to the INSERT. See my post.