Trigger on slave only - mysql

I am trying to run a trigger on slave for RBR (https://mariadb.com/kb/en/library/running-triggers-on-the-slave-for-row-based-events/).
I have created a table on master like this:
CREATE TABLE `t1` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`mobile` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
On slave, I had this trigger:
CREATE TRIGGER t1_obfus AFTER INSERT ON `t1`
FOR EACH ROW
UPDATE `t1`
SET `mobile` = LEFT(MD5(NEW.`mobile`), 20);
which did not work. I got the following error on SHOW SLAVE STATUS:
Last_SQL_Error: Could not execute Write_rows_v1 event on table d1.t1; Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger., Error_code: 1442; handler error HA_ERR_GENERIC; the event's master log mariadb-bin.000004, end_log_pos 454
Then I modified the trigger to:
CREATE TRIGGER t1_obfus BEFORE INSERT ON `t1`
FOR EACH ROW
UPDATE `t1`
SET NEW.`mobile` = LEFT(MD5(NEW.`mobile`), 20)
WHERE id = NEW.id;
but it still did not work. Then I created a new table on slave:
CREATE TABLE t1_2 (
id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
mobile VARCHAR(20)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
and modified my trigger to:
CREATE TRIGGER t1_obfus BEFORE INSERT ON `t1_2`
FOR EACH ROW
INSERT INTO `t1_2` (id, mobile)
VALUES (NEW.id,LEFT(MD5(NEW.`mobile`), 20));
Now the replication started working but there is no data in the table t1_2. How do I fix this?

If you use row-based replication, triggers on the slave won't work.
As described in the official documentation:
Blockquote
If you want triggers to execute on both the master and the slave—perhaps because you have different triggers on the master and slave—you must use statement-based replication. However, to enable slave-side triggers, it is not necessary to use statement-based replication exclusively. It is sufficient to switch to statement-based replication only for those statements where you want this effect, and to use row-based replication the rest of the time.

Related

SQLSTATE[40001]: Serialization failure: 1213 Deadlock issue caused by INSERT trigger on concurrent access

I found a SQL deadlock issue which occurs when the function is executed concurrently by two users. I have a PHP function which executes several database insert queries which enclose in a transaction. And one of the inserts fires a trigger as well. See my table schema and code sample below.
main_table
CREATE TABLE `main_table` (
`id` INT NOT NULL AUTO_INCREMENT,
`action_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
history_table
CREATE TABLE `history_table` (
`id` INT NOT NULL AUTO_INCREMENT,
`action_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`audit_id` INT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
audit_table
CREATE TABLE `audit_table` (
`id` INT NOT NULL AUTO_INCREMENT,
`action_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I have a trigger on main_table, defined as follows. What it does is that selecting the max id from audit_table and insert a record to the history_table.
CREATE TRIGGER watch_insert_main_table
AFTER INSERT ON main_table
FOR EACH ROW BEGIN
DECLARE max_id INT;
SET max_id = (SELECT MAX(`id`) FROM `audit_table`) + 1;
INSERT INTO `history_table` SET audit_id=max_id;
END;
Following is the function which is executed by two users concurrently. insertRecord funtion simply inserts a record to the given table.
try {
$dbConnection->beginTransaction();
$this->insertRecord('main_table');
$this->insertRecord('audit_table');
$dbConnection->commit();
} catch (Exception $e) {
$dbConnection->rollback();
}
I get the following dead lock error when the function is called for the second time (concurrently).
40001 - SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction
If I do either of the following changes, this issue won't occur.
Remove the enclosed transaction in the PHP code
Remove $this->insertRecord('audit_table'); line from the PHP code
Modify the trigger so that it doesn't have a select statement from the audit_table
I want to know the root cause of this issue. Is another transaction being starting from the MySQL trigger when it is fired? How are the transactions and locks working inside a trigger?
I found that the following two questions are also related to a similar issue.
MySQL Deadlock with an an insert that raises a trigger
Deadlock caused by INSERT trigger - thread looking for lock it already has?
See if this simplification helps:
CREATE TRIGGER watch_insert_main_table
AFTER INSERT ON main_table
FOR EACH ROW BEGIN
INSERT INTO `history_table` (audit_id)
SELECT MAX(`id`)+1 FROM `audit_table`;
END;
Notice how it combines your two statements into a single statement. This may avoid letting another connection get in there and grab the same id (or something to that effect).
The following may be related (provided by OP): This happens due to a known issue in MySQL. Setting a variable from a select acquires a lock when using read uncommitted isolation level. This thread has more information.
Are you using isolation mode Read Uncommitted? If so, why?

Duplication values in autoincremented field on replication

That's a strange issue.
There are source MySQL DB (MASTER) and its replic (SLAVE). It's a (as you understand its a MASTER-SLAVE) statement-bases replication, because I need triggers to run on the SLAVE side.
The original triggers were replaced by new ones.
Every table has 3 trigers: on INSERT, on UPDATE and on DELETE.
Trigers were generated by a single pattern and differ only by params.
Every trigger does a single INSERT query to a table (CHANGES) on the SLAVE.
This table is not replicated and exists only on the SLAVE.
There is an autoincremented column (ID - bigint) in this table.
None of the triggers set or modify values of the column ID. The DB sets a default values for it.
It's about 20 inserts executed on CHANGES per minute.
I see errors with duplicated values.
How it's possible?
Let's again:
An INSERT / UPDATE / DELETE query is executed on MASTER.
This change is replicated to SLAVE.
A trigger is called and inserts a row to the CHANGES.
Duplicated values error is generated.
And as I said before, none of the triggers set or modify value of the autoincremented field (ID). And only triggers work with table CHANGES.
I understand that two or more triggers can be called together, and try to do INSERT together, but I think DB should easy solve this. Or that's a very bad DB.
UPD:
CREATE TABLE `CHANGES` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`field1` ENUM(...) NOT NULL,
`field2` BOOL NOT NULL DEFAULT FALSE,
`field3` VARCHAR(64) NOT NULL,
`field4` VARCHAR(255) NOT NULL,
`field5` TEXT,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE TRIGGER `tr_TABLE_insert` AFTER INSERT ON `TABLE`
FOR EACH ROW BEGIN
INSERT INTO `CHANGES` (`field1`, `field3`, `field4`, `field5`)
VALUES ("value1", "value3", "value4", "value5");
END
UPD 2:
I found a temporary and more dirty method - added BEFORE INSERT trigger for CHANGES to set ID manually.
It works. But I still can't figure out why the native AUTO_INCREMENT mechanism is generating duplicated ids.

Warning Unsafe statement written to the binary log using statement

I've created a log system based on trigger.
Every time a row is inserted or updated the trigger store a new row in another table.
The trigger works fine but after some time I found this message in logs:
[Warning] Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTO_INCREMENT column. Inserted values cannot be logged correctly. Statement: update `gl_item` set `is_shown` = '0', `updated_at` = '2016-03-21 16:56:28' where `list_id` = '1' and `is_shown` = '1'
I've already red some post related to this issue i like:
- MySQL Replication & Triggers
But I don't understand the nature of the problem.
What this warning mean?
I don't have to insert into auto increment column with triggers?
Which is the best way to create a log system in order to avoid this warning?
Update
Output of SHOW CREATE TABLE, this is the table where the trigger will enter THE rows.
gl_item_log | CREATE TABLE `gl_item_log` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Item log unique id',
`item_id` bigint(20) unsigned DEFAULT NULL ,
`updated_by` bigint(20) unsigned DEFAULT NULL ,
`switch_shown` tinyint(4) DEFAULT NULL ,
`switch_checked` tinyint(4) DEFAULT NULL ,
`logged_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`logged_at_microtime` decimal(6,6) unsigned NOT NULL ,
`logged_at_microtime_int` mediumint(8) unsigned NOT NULL DEFAULT '0' ,
PRIMARY KEY (`id`),
KEY `gl_item_log_updated_by_foreign` (`updated_by`),
KEY `gl_item_log_item_id_updated_by_switch_shown_switch_checked_index`
(`item_id`,`updated_by`,`switch_shown`,`switch_checked`),
CONSTRAINT `gl_item_log_item_id_foreign`
FOREIGN KEY (`item_id`) REFERENCES `gl_item` (`id`),
CONSTRAINT `gl_item_log_updated_by_foreign`
FOREIGN KEY (`updated_by`) REFERENCES `gl_general_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Could be a good idea drop the id column with the auto increment field in order to have logs entries without a unique identifier?
Thanks
What this warning mean?
According to the MySQL documentation:
A statement invoking a trigger (or function) that causes an update to an AUTO_INCREMENT column is not replicated correctly using statement-based replication. MySQL 5.7 marks such statements as unsafe. (Bug #45677)
With statement-based replication, the exact SQL which is run on your master database is also run on your slave(s). When your trigger is fired, if it exists on every one of your databases, it is run on each database and inserts into your log. This can be a tricky situation for your databases to remain in sync.
I don't have to insert into auto increment column with triggers?
Correct. It's never necessary to insert your own values into an auto-increment column.
Which is the best way to create a log system in order to avoid this warning?
First, either keep the trigger on every database and turn off replication for your log table, or have the trigger only on your master database and let replication copy the log table inserts to the other databases.
To work around this specific warning, configure your log table to have no auto-increment column. Your trigger can then insert into it and it shouldn't cause any replication warnings.
Another option is to switch to row-based replication. Then the trigger will only be fired automatically on the master and the auto-increment values will always replicate without issue.

How to create trigger to insert data to a database on another server

I have 2 mysql databases on different hosts, want trigger after insert data to one database it inserted to another . I'm new in mysql , in sql server I can create linked server and do it . But how to do in mysql ?
Both databases have similar table
CREATE TABLE `tsttbl` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`age` smallint(6) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
For you to be able to proceed with this, you must first ensure that both servers have identical configurations. And to accomplish what you are wanting to do, it is possible to use the FEDERATED storage engine on both servers, in conjunction with triggers, to allow each server to update the other server's database.
You need to create the local table that is federated to the user table on the other server. So, if a record already exists on the other server, but not here, we want the insert on the other server to throw an error that prevents us from creating the record here... as opposed to creating a record here with what would be a conflicting ID;
CREATE TABLE remote_user (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`age` smallint(6) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
CONNECTION='mysql://username:pass#the_other_host:port/schema/user';
Then you can create your trigger;
DELIMITER $$
CREATE TRIGGER user_bi BEFORE INSERT ON user FOR EACH ROW
BEGIN
INSERT INTO remote_user (ID,name, age) VALUES (NEW.ID,NEW.name, NEW.Age);
END $$
CREATE TRIGGER user_bu BEFORE UPDATE ON user FOR EACH ROW
BEGIN
UPDATE remote_user
SET ID= NEW.ID,
name= NEW.name
age = NEW.Age
WHERE ID = OLD.ID;
END $$
CREATE TRIGGER user_bd BEFORE DELETE ON user FOR EACH ROW
BEGIN
DELETE FROM remote_user
WHERE ID= OLD.ID;
END $$
DELIMITER ;

Do you need triggers on a mysql database slave?

Say you are running two mysql servers: one a master, the other the slave. The master has triggers set that update columns with the COUNT of the number of rows in other tables. For instance, you have a news table and a comments table. News contains an INT column called "total_comments" which is incremented via trigger every time a new row is put into "comments." Does the slave need this trigger as well (to keep "news.total_comments" up to date) or will it get be told to update the appropriate "news.total_comments" directly?
From the docs http://dev.mysql.com/doc/refman/5.0/en/faqs-triggers.html:
22.5.4: How are actions carried out through triggers on a master
replicated to a slave? First, the
triggers that exist on a master must
be re-created on the slave server.
Once this is done, the replication
flow works as any other standard DML
statement that participates in
replication. For example, consider a
table EMP that has an AFTER insert
trigger, which exists on a master
MySQL server. The same EMP table and
AFTER insert trigger exist on the
slave server as well. The replication
flow would be: An INSERT statement is
made to EMP. The AFTER trigger on EMP
activates. The INSERT statement is
written to the binary log. The
replication slave picks up the INSERT
statement to EMP and executes it. The
AFTER trigger on EMP that exists on
the slave activates.
And
22.5.4 Actions carried out through triggers on a master are not
replicated to a slave server.
Thus, you DO need the triggers on the slave.
It depends on the replication you're using. If you use statement based replication, then you must use matching triggers in the master and the slave. If you use row-based replication, then you must not include the triggers on the slave.
You can have the action of requests made by triggers in the binary log with federated tables (MySQL5) by adding the same table with a local connection.
---------------------------------------------------------------------------------------
-- EXEMPLE :
-- We want install a replication of the table test_table that will be managed by Trg_Update triggers.
---------------------------------------------------------------------------------------
Create database TEST;
USE TEST;
CREATE TABLE test_trigger (
id INT(20) NOT NULL AUTO_INCREMENT,
name VARCHAR(32) NOT NULL DEFAULT '',
PRIMARY KEY (id),
INDEX name (name),
) ;
DELIMITER |
CREATE TRIGGER Trg_Update AFTER INSERT ON test_trigger
FOR EACH ROW BEGIN
INSERT INTO federated_table (name, other) values (NEW.name, ‘test trigger on federated table -> OK’)
END|
DELIMITER ;
CREATE TABLE test_table (
id INT(20) NOT NULL AUTO_INCREMENT,
name VARCHAR(32) NOT NULL DEFAULT '',
other VARCHAR(32) NOT NULL DEFAULT '',
PRIMARY KEY (id),
INDEX name (name),
INDEX other_key (other)
) ;
CREATE TABLE federated_table (
id INT(20) NOT NULL AUTO_INCREMENT,
name VARCHAR(32) NOT NULL DEFAULT '',
other VARCHAR(32) NOT NULL DEFAULT '',
PRIMARY KEY (id),
INDEX name (name),
INDEX other_key (other)
)
ENGINE=FEDERATED
CONNECTION='mysql://root#localhost/TEST/test_table';
---------------------------------------------------------------------------------------