Is this MySQL trigger thread-safe? - mysql

I have the same question as described here and also I think the answer https://stackoverflow.com/a/22343265/297487 is a good solution but I have another question about this answer.
Is the following trigger (copied from answer) thread-safe? I mean if two concurrent record inserted to table, does "priority" column (as describe in question) have consistent value (The same value as id)?
delimiter //
drop trigger if exists bi_table_name //
create trigger bi_table_name before insert on table_name
for each row begin
set #auto_id := ( SELECT AUTO_INCREMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='table_name'
AND TABLE_SCHEMA=DATABASE() );
set new.priority= #auto_id;
end;
//
delimiter ;
How do we interpret "for each row" clause?
Suppose that MySQL wants insert two concurrent rows. How does this trigger works?
One interpretation is as follows:
MySQL locks table and before insert one of the rows (one of concurrent record) MySQL trigger starts and gets current AUTO_INCREMENT value and sets it to "priority" column and then inserts a record. After that MySQL starts inserting another record and then the same situation applies for new record.
Another interpretation might be as follows:
When two concurrent records are inserted to MySQL, MySQL locks the table and then before inserting two concurrent records a trigger starts and "for each row" clause iterate between two record and set "priority" column value to the same value and then insert two concurrent record in database. In this situation the trigger does not work as expected.
Which one of the above interpretation is correct?
Update :
I have the following table :
CREATE TABLE `t_file` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_persian_ci NOT NULL,
`p_name` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `p_name_2` (`p_name`)
) ENGINE=InnoDB AUTO_INCREMENT=206284 DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci;
I want to insert value of p_name the same as id when row inserted.
The trigger that sets value of p_name is as follows(copied from your code)
delimiter $$
drop trigger if exists file_p_name $$
create trigger file_p_name before insert on t_file
for each row begin
set #id := ( SELECT AUTO_INCREMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='t_file'
AND TABLE_SCHEMA=DATABASE() );
set new.p_name= #id;
end;
$$
delimiter ;
In our application, i surround the code that inserts int_file table with try catch,in almost always everything is OK,but sometimes(in concurrent insert to t_file table), i see the following exception in our application's log:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'387456\' for key \'p_name_2\''
It seems the trigger does not work as expected or maybe i am wrong!!!

The answer referred in your question was posted by me some time back.
While in "For Each Row", The NEW is the corresponding new row, being inserted, in context. And the set new.xxx is only applicable for that row and but not 'for all rows' in batch. So there won't be any collision and the question of failure should not arise.
I have also answered a similar question
How does “for each row” work in triggers in mysql?. Please go through the examples given in the answer.
Refer to Documentation:
MySQL: Triggers

Related

Calculate time difference between two columns into another column AFTER\BEFORE INSERT in table

i have a DB for managing an airport. I want to try to calculate the time difference between columns ArrivingDate and DepartingDate (both are DATETIME type) into a third column called Flight_time (TIME type) after any INSERT in the table FLIGHT_SCHEDULES.
I tried to create a trigger for doing that, but with no success. I have already read some stuff on the internet about my error but couldn't find something which solve my dilemma.
This is the table:
CREATE TABLE FLIGHT_SCHEDULES(
id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
Flight INT(10) UNSIGNED NOT NULL,
Airplane INT(10) UNSIGNED NOT NULL,
DepartingDate DATETIME NOT NULL,
ArrivingDate DATETIME NOT NULL,
Flight_time TIME DEFAULT '00:00:00',
CONSTRAINT flight_unique UNIQUE (Flight),
CONSTRAINT fk_scheduled_flight_id FOREIGN KEY (Flight) REFERENCES FLIGHTS(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_scheduled_airplane_id FOREIGN KEY (Airplane) REFERENCES AIRPLANES(id) ON UPDATE CASCADE ON DELETE CASCADE
);
This is the trigger I created:
delimiter //
CREATE TRIGGER calculate_flightTime2 BEFORE INSERT ON FLIGHT_SCHEDULES FOR EACH ROW
BEGIN
UPDATE FLIGHT_SCHEDULES
SET NEW.Flight_time = TIMEDIFF(new.ArrivingDate, new.DepartingDate);
END //
I can create the trigger with no problems, but the I try to insert a new row in the table I receive the following error message:
Error Code: 1442. Can't update table 'flight_schedules' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. 0.000 sec
Note that I tried to create an AFTER trigger too, but the result was the same.
It is a restriction of mysql so you don t end up in an endless loop.
Try something like this.
CREATE TRIGGER calculate_flightTime2
BEFORE INSERT
ON FLIGHT_SCHEDULES
FOR EACH ROW
BEGIN
IF NEW.Flight_time = '00:00:00' THEN
SET NEW.Flight_time = TIMEDIFF(new.ArrivingDate, new.DepartingDate);
END IF;
END
Usually it's not a good idea to materialize values that can be calculated from others. This bears the risk of inconsistencies. What happens if you change the arriving time for example?
So it's probably best if you drop the column Flight_time.
ALTER TABLE FLIGHT_SCHEDULES
DROP Flight_time;
For convenience you can then create a view, that includes the calculated value.
CREATE VIEW FLIGHT_SCHEDULES_WITH_FLIGHT_TIME
AS
SELECT *,
timediff(ArrivingDate, DepartingDate) Flight_time
FROM FLIGHT_SCHEDULES;
In MySQL 8+ you could alternatively use a generated column. That's a safe way as the DBMS guarantees consistency like that.
ALTER TABLE FLIGHT_SCHEDULES
ADD Flight_time time AS timediff(ArrivingDate, DepartingDate);
But if you insist on using a trigger, you don't need an UPDATE to set a value in the new pseudo record. Just an assignment is enough.
...
SET NEW.Flight_time = TIMEDIFF(new.ArrivingDate, new.DepartingDate);
...

How can I update MySQL table row's when 'BEFORE UPDATE' trigger is firing?

I created a table
Databases Name - mytrigger;
Table name - employee_audit
use mytrigger;
create table employee_audit(
id int auto_increment primary key,
employeeNumber int not null,
lastName varchar(50) not null,
changee datetime default null,
action varchar(50) default null
);
After that, I created one update trigger
My trigger name is
before_employees_update
DELIMITER $$
create trigger before_employees_update
before update on employee_audit
for each row
begin
insert into employee_audit
set action ='update',
employeeNumber = OLD.employeeNumber,
lastName = OLD.lastName,
changee = now();
end$$
DELIMITER ;
After that, I inserted values in table using this command ->
insert into employee_audit values(1,112,'prakash','2015-11-12 15:36:20' ,' ');
After that, I want to update my table row where id =1
update employee_audit set lastName = 'Sharma' where employeeNumber =112;
But it is not executed give an error
ERROR 1442 (HY000): Can't update table 'employee_audit' in stored
function/trigger because it is already used by statement which invoked
this stored function/trigger.
When I searched on Google I found a lot of Question with the same error. But not able to fix my problem. what is the reason I'm not able to update my row?
What i suggest,you can create one log table like employee_audit_LOG .
And on every insert or update in main table you can make new entry in this table or update existing record.
Also you can add updated_timestamp column to that LOG table which maintain when did specific record get updated.
The error itself tells you the answer. This is because, you can't use the same table on which trigger is being executed. You need to store your audit logs into some different table.

MySQL trigger (replacing assertion) does not work

Can you help me with this problem? I have two tables in a MySQL database:
ServiceProvider(SPID, Name, ... etc.)
hasTag(SPID, TagID)
Each service provider is supposed to have at least one tag, and a maximum of five tags. The max-constraint is not a problem, but the min-constraint refuses to work properly. I first tried to implement this via assertions, but then I found out, that MySQL does not support assertions. Thus, I wrote the following trigger:
delimiter |
CREATE TRIGGER MinTags BEFORE INSERT
ON ServiceProvider
FOR EACH ROW BEGIN
IF EXISTS (SELECT SPID FROM ServiceProvider
WHERE NOT EXISTS (SELECT DISTINCT SPID FROM hasTag))
THEN INSERT INTO stop_action VALUES(1, 'Assert Failure');
END IF;
END;
|
delimiter ;
The insert of 'Assert Failure' into the stop_action table is only to create a constraint violation, so that the DB would abort the action.
Now, normally, when I insert any value into the ServiceProvider table, without inserting anything in to the hasTag table, I should get an error, right? But, somehow it doesn't work ... I can insert anything I want into the ServiceProvider table, without receiving any kind of error. Do you know, what is wrong with my code?
How about denormalising a tad:
ALTER TABLE ServiceProvider
ADD COLUMN TagID1 BIGINT UNSIGNED NOT NULL,
ADD COLUMN TagID2 BIGINT UNSIGNED NULL,
ADD COLUMN TagID3 BIGINT UNSIGNED NULL,
ADD COLUMN TagID4 BIGINT UNSIGNED NULL,
ADD COLUMN TagID5 BIGINT UNSIGNED NULL;
Include foreign key constraints, if appropriate.
As written, this trigger does not even use the values from the row to be inserted.
The syntax to get the value of the SPID column is:
NEW.SPID
Also, consider using the SIGNAL statement to raise an error.
If you want to use ASSERT in SQL, this post may help:
SQL Scripts - Does the equivalent of a #define exist?

MySQL Trigger To Update New Row

We are in the process of migrating between 2 systems and need to have 2 fields for one of our database tables that always stay in sync. Here is the table structure:
CREATE TABLE `example` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`object_id` int(11) NOT NULL DEFAULT '0',
`value` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `object_id` (`object_id`)
);
Every time one of the systems inserts a new row we need to have object_id set to id. We can't use 'before insert' since the id column is an auto_increment column so it's value is NULL before insert and due to the limitations of the MySQL 'after insert' on triggers I can't do the following:
CREATE TRIGGER insert_example
AFTER INSERT ON example
FOR EACH ROW
SET NEW.object_id = NEW.id;
I can't update the code for either system so I need a way to accomplish this on the database side. Both systems are going to be inserting new rows. How can I accomplish this?
Using a trigger which fires before the insert should do the job
CREATE TRIGGER insert_example
BEFORE INSERT ON example
FOR EACH ROW
SET NEW.object_id = NEW.id;
EDIT:
As the OP pointed out NEW.id won't work with auto-increment; one could use the following trigger (use at own risk):
CREATE TRIGGER insert_example
BEFORE INSERT ON example
FOR EACH ROW
SET NEW.object_id = (
SELECT AUTO_INCREMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'example'
);
But I'd rather re-think this somewhat strange requirement - why do you need the pk value twice in the table?
Is there any reason you cant use a BEFORE INSERT trigger?
I've always seen AFTER INSERT triggers as a method to manipulate other tables rather than the table for which the trigger was executed on.
Rule of thumb, manipulate table the trigger is running on = BEFORE INSERT, manipulate other tables AFTER INSERT :)
I think your trigger will never create in the first place because you can't refer NEW.column_name in an AFTER INSERT trigger.
Try doing this in a BEFORE INSERT trigger (PLEASE IGNORE THIS FIX AS IT WILL NOT WORK):
CREATE TRIGGER `insert_example` BEFORE INSERT ON `t`
FOR EACH ROW
SET NEW.`object_id` = NEW.`id`;
Please change the table and column names as per your schema.
Hope this helps.

MySql autoincrement column increases by 10 problem

I am a user of a some host company which serves my MySql database. Due to their replication problem, the autoincrement values increses by 10, which seems to be a common problem.
My question is how can I simulate (safely) autoincrement feature so that the column have an consecutive ID?
My idea was to implement some sequence mechanism to solve my problem, but I do not know if it is a best option. I had found such a code snipset over the web:
DELIMITER ;;
DROP TABLE IF EXISTS `sequence`;;
CREATE TABLE `sequence` (
`name` CHAR(16) NOT NULL,
`value` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;;
DROP FUNCTION IF EXISTS `nextval`;
CREATE FUNCTION `nextval`(thename CHAR(16) CHARSET latin1)
RETURNS BIGINT UNSIGNED
MODIFIES SQL DATA
SQL SECURITY DEFINER
BEGIN
INSERT INTO `sequence`
SET `name`=thename,
`value`=(#val:=##auto_increment_offset)+##auto_increment_increment
ON DUPLICATE KEY
UPDATE `value`=(#val:=`value`)+##auto_increment_increment;
RETURN #val;
END ;;
DELIMITER ;
which seems quite all correct. My second question is if this solution is concurrent-safe? Of course INSERT statement is, but what about ON DUPLICATE KEY update?
Thanks!
Why do you need to have it in the first place?
Even with auto_increment_increment == 1 you are not guaranteed, that the autoincrement field in the table will have consecutive values (what if the rows are deleted, hmm?).
With autoincrement you are simply guaranteed by the db engine, that the field will be unique, nothing else, really.
EDIT: I want to reiterate: In my opinion, it is not a good idea to assume things like concurrent values of an autoincrement column, because it is going to bite you later.
EDIT2: Anyway, this can be "solved" by an "on insert" trigger
create trigger "sequence_b_ins" before insert on `sequence`
for each row
begin
NEW.id = select max(id)+1 from `sequence`;
end
Or something along these lines (sorry, not tested)
Another option would be to use a stored proc to do the insert and have it either select max id from your table or keep another table with the current id being used and update as id's are used.