Make pt-osc compatible with AFTER triggers

Description

Currently pt-online-schema-change cannot run if table already has AFTER triggers. But it can, if add an option which instruct the tool to do following (see pseudo-code):

The workflow to add triggers would be as follows (from our own experiences doing this with trigger-based tools):

  • lock the table in question (standard mysql lock tables write)

  • pull the existing trigger code, call that block 1

  • log trigger code into temporary file to avoid trigger definition loss in case of pt-osc failure

  • generate the trigger code for pt-osc, call that block 2

  • drop the existing trigger

  • create a new trigger with block 1 and block 2 appended, wrap them in /* BLOCK 1 START */ comments or similar if that makes it easier to parse out

  • repeat for other events (cover all of ins,upd,del)

  • unlock table
    To remove triggers, the reverse:

  • lock the table with write lock

  • pull the trigger code and parse out block 1

  • drop the trigger

  • create the trigger with only code from block 1

  • repeat for other event

  • unlock table

Do not make this option default and add a warning about database can be in inconsistent state if the tool or MySQL Server is killed during execution.

Launchpad: [https://bugs.launchpad.net/percona-toolkit/+bug/1491133

]

Acceptance Criteria for params:

Scenario The --preserve-triggers feature needs to delete the old table and triggers and recreate them into for new table. Since it is not possible to have duplicated trigger names, the old table and triggers MUST be deleted and recreated Feature: --preserve and --no-drop-old-table params When --preserve-triggers param is specified And --no-drop-old-table was specified Then Exit with an error message Feature: --preserve triggers and --no-swap-tables params When --preserve-triggers param is specified And --no-swap-tables was specified Then exit with an error message Feature: --preserve triggers and --no-drop-triggers params When --preserve-triggers param is specified And --no-drop-triggers was specified Then exit with an error message

 

Environment

None

Smart Checklist

Activity

Sveta Smirnova July 28, 2017 at 6:57 PM

Works fine for me:

// Session 1 mysql> create table t1(id int not null auto_increment primary key, f1 int); Query OK, 0 rows affected (0.01 sec) mysql> insert into t1(f1) values(1),(2),(3),(4),(5),(6),(7),(8),(9),(0); Query OK, 10 rows affected (0.01 sec) Records: 10  Duplicates: 0  Warnings: 0 mysql> create trigger t1ai for each row \c mysql> create table t1_log(f1 int, f1_old int); Query OK, 0 rows affected (0.01 sec) mysql> create trigger t1ai for each row insert into t1_log values(NULL, NEW.f1); ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'for each row insert into t1_log values(NULL, NEW.f1)' at line 1 mysql> create trigger t1ai after insert on t1 for each row insert into t1_log values(NULL, NEW.f1); Query OK, 0 rows affected (0.02 sec) mysql> create table t1_log2(f1 int, f1_old int); Query OK, 0 rows affected (0.01 sec) mysql> create trigger t1bi before insert on t1 for each row insert into t1_log2 values(NULL, NEW.f1); Query OK, 0 rows affected (0.02 sec) mysql> create trigger t1au after insert on t1 for each row insert into t1_log values(OLD.f1, NEW.f1); ERROR 1363 (HY000): There is no OLD row in on INSERT trigger mysql> create trigger t1au after update on t1 for each row insert into t1_log values(OLD.f1, NEW.f1); Query OK, 0 rows affected (0.02 sec) mysql> create trigger t1bu before update on t1 for each row insert into t1_log2 values(OLD.f1, NEW.f1); Query OK, 0 rows affected (0.01 sec) mysql> create trigger t1ad after delete on t1 for each row insert into t1_log values(OLD.f1, NULL); Query OK, 0 rows affected (0.02 sec) mysql> create trigger t1bd before delete on t1 for each row insert into t1_log2 values(OLD.f1, NULL); Query OK, 0 rows affected (0.02 sec) mysql> insert into t1(f1) values(10); Query OK, 1 row affected (0.00 sec) mysql> select * from t1; +----+------+ | id | f1   | +----+------+ |  1 |    1 | |  2 |    2 | |  3 |    3 | |  4 |    4 | |  5 |    5 | |  6 |    6 | |  7 |    7 | |  8 |    8 | |  9 |    9 | | 10 |    0 | | 11 |   10 | +----+------+ 11 rows in set (0.00 sec) mysql> update t1 set f1=20 where id=11; Query OK, 1 row affected (0.00 sec) Rows matched: 1  Changed: 1  Warnings: 0 mysql> delete from t1 where id=11; Query OK, 1 row affected (0.01 sec) mysql> select * from t1_LOG; ERROR 1146 (42S02): Table 'test.t1_LOG' doesn't exist mysql> select * from t1_log; +------+--------+ | f1   | f1_old | +------+--------+ | NULL |     10 | |   10 |     20 | |   20 |   NULL | +------+--------+ 3 rows in set (0.00 sec) mysql> select * from t1_log2; +------+--------+ | f1   | f1_old | +------+--------+ | NULL |     10 | |   10 |     20 | |   20 |   NULL | +------+--------+ 3 rows in set (0.00 sec) // Session 2 sveta@delly:~/src/percona-toolkit$ ~/build/percona-toolkit/bin/pt-online-schema-change --preserve-triggers --alter="add column f2 int" h=127.0.0.1,P=13000,u=root,D=test,t=t1 --execute No slaves found.  See --recursion-method if host delly has slaves. Not checking slave lag because no slaves were found and --check-slave-lag was not specified. Operation, tries, wait:   analyze_table, 10, 1   copy_rows, 10, 0.25   create_triggers, 10, 1   drop_triggers, 10, 1   swap_tables, 10, 1   update_foreign_keys, 10, 1 Altering `test`.`t1`... Creating new table... Created new table test._t1_new OK. Altering new table... Altered `test`.`_t1_new` OK. 2017-07-28T21:54:12 Creating triggers... 2017-07-28T21:54:12 Created triggers OK. 2017-07-28T21:54:12 Copying approximately 10 rows... 2017-07-28T21:54:12 Copied rows OK. 2017-07-28T21:54:12 Adding original triggers to new table. 2017-07-28T21:54:13 Analyzing new table... 2017-07-28T21:54:13 Swapping tables... 2017-07-28T21:54:13 Swapped original and new tables OK. 2017-07-28T21:54:13 Dropping old table... 2017-07-28T21:54:13 Dropped old table `test`.`_t1_old` OK. 2017-07-28T21:54:13 Dropping triggers... 2017-07-28T21:54:13 Dropped triggers OK. Successfully altered `test`.`t1`. // Session 1 mysql> show triggers; +---------+--------+-------+--------------------------------------------+--------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+ | Trigger | Event  | Table | Statement                                  | Timing | Created                | sql_mode                                                                                                                                  | Definer        | character_set_client | collation_connection | Database Collation | +---------+--------+-------+--------------------------------------------+--------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+ | t1bi    | INSERT | t1    | insert into t1_log2 values(NULL, NEW.f1)   | BEFORE | 2017-07-28 21:54:13.04 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@localhost | utf8                 | utf8_general_ci      | latin1_swedish_ci  | | t1ai    | INSERT | t1    | insert into t1_log values(NULL, NEW.f1)    | AFTER  | 2017-07-28 21:54:12.94 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@localhost | utf8                 | utf8_general_ci      | latin1_swedish_ci  | | t1bu    | UPDATE | t1    | insert into t1_log2 values(OLD.f1, NEW.f1) | BEFORE | 2017-07-28 21:54:13.00 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@localhost | utf8                 | utf8_general_ci      | latin1_swedish_ci  | | t1au    | UPDATE | t1    | insert into t1_log values(OLD.f1, NEW.f1)  | AFTER  | 2017-07-28 21:54:12.92 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@localhost | utf8                 | utf8_general_ci      | latin1_swedish_ci  | | t1bd    | DELETE | t1    | insert into t1_log2 values(OLD.f1, NULL)   | BEFORE | 2017-07-28 21:54:12.96 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@localhost | utf8                 | utf8_general_ci      | latin1_swedish_ci  | | t1ad    | DELETE | t1    | insert into t1_log values(OLD.f1, NULL)    | AFTER  | 2017-07-28 21:54:12.89 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@localhost | utf8                 | utf8_general_ci      | latin1_swedish_ci  | +---------+--------+-------+--------------------------------------------+--------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+ 6 rows in set (0.01 sec) mysql> mysql> show create table t1; +-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table                                                                                                                                                                                        | +-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | t1    | CREATE TABLE `t1` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `f1` int(11) DEFAULT NULL,   `f2` int(11) DEFAULT NULL,   PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=12 DEFAULT CHARSET=latin1 | +-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.01 sec)

Carlos Salguero July 17, 2017 at 6:13 PM

Since we still have time before releasing this, I'll ask some DBAs to also test it.

Carlos Salguero July 6, 2017 at 4:02 AM

I have these tests:

ok 136 - Basic --preserve-triggers exit 0 ok 137 - Basic --preserve-triggers tables ok 138 - Basic --preserve-triggers rows ok 139 - Basic --preserve-triggers triggers still exist ok 140 - Basic --preserve-triggers MAX(pk_col) ok 141 - Basic --preserve-triggers AUTO_INCREMENT=20000 ok 142 - Basic --preserve-triggers foo added ok 143 - --preserve-triggers: after triggers exit 0 ok 144 - --preserve-triggers: after triggers tables ok 145 - --preserve-triggers: after triggers rows ok 146 - --preserve-triggers: after triggers triggers still exist ok 147 - --preserve-triggers: after triggers MAX(pk_col) ok 148 - --preserve-triggers: after triggers AUTO_INCREMENT=9 ok 149 - --preserve-triggers: after triggers foo3 added ok 150 - --preserve-triggers cannot drop column used by trigger ok 151 - --preserve-triggers: message if try to drop a field used by triggers ok 152 - --preserve-triggers --no-swap-tables exit status ok 153 - --preserve-triggers --no-drop-old-table exit status ok 154 - --preserve-triggers --no-drop-old-table original & new tables still exists ok 155 - --preserve-triggers cannot be used --no-drop-triggers

Anything missing?

 

Kenny Gryp July 4, 2017 at 8:53 PM

: on your updated acceptance criteria.

 

Why would --no-drop-old-table not work? We can keep the old table and only remove the trigger.

As extra feature I would also maybe print out  the existing trigger definition that was removed, so when the old table remains and needs to be brought back, the user can manually copy paste the trigger definition and put it back.

Vadim Tkachenko June 27, 2017 at 10:17 PM

I agree to make this only 5.7 feature. It is very complicated and problematic to support 5.6

Done

Details

Assignee

Reporter

Priority

Affects versions

Fix versions

Smart Checklist

Created March 3, 2017 at 2:55 AM
Updated December 12, 2017 at 12:47 PM
Resolved July 17, 2017 at 6:13 PM

Flag notifications