From 07ba5560da805c766da68811692272dd9eebdaf7 Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Fri, 26 Aug 2016 11:00:44 +0530 Subject: [PATCH] Bug #20989615 INNODB AUTO_INCREMENT PRODUCES SAME VALUE TWICE Problem: ======= Autoincrement value gives duplicate values because of the following reasons. (1) In InnoDB handler function, current autoincrement value is not changed based on newly set auto_increment_increment or auto_increment_offset variable. (2) Handler function does the rounding logic and changes the current autoincrement value and InnoDB doesn't aware of the change in current autoincrement value. Solution: ======== Fix the problem(1), InnoDB always respect the auto_increment_increment and auto_increment_offset value in case of current autoincrement value. By fixing the problem (2), handler layer won't change any current autoincrement value. Reviewed-by: Jimmy Yang RB: 13748 --- .../suite/innodb/r/autoinc_debug.result | 95 +++++++++++++++++++ mysql-test/suite/innodb/t/autoinc_debug.test | 91 ++++++++++++++++++ storage/innobase/handler/ha_innodb.cc | 32 +++++++ storage/innobase/row/row0mysql.c | 2 + storage/xtradb/handler/ha_innodb.cc | 31 ++++++ storage/xtradb/row/row0mysql.c | 2 + 6 files changed, 253 insertions(+) create mode 100644 mysql-test/suite/innodb/r/autoinc_debug.result create mode 100644 mysql-test/suite/innodb/t/autoinc_debug.test diff --git a/mysql-test/suite/innodb/r/autoinc_debug.result b/mysql-test/suite/innodb/r/autoinc_debug.result new file mode 100644 index 00000000000..505e9f4dfc9 --- /dev/null +++ b/mysql-test/suite/innodb/r/autoinc_debug.result @@ -0,0 +1,95 @@ +CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY)ENGINE=INNODB; +# SETTING auto_increment_increment IN CONNECTION DEFAULT +SET AUTO_INCREMENT_INCREMENT = 1; +INSERT INTO t1 VALUES(NULL); +SELECT * FROM t1; +id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 +# SETTING auto_increment_increment IN CONNECTION1 +SET AUTO_INCREMENT_INCREMENT = 2; +SET DEBUG_SYNC= 'ib_after_row_insert SIGNAL opened WAIT_FOR flushed1'; +INSERT INTO t1 VALUES(NULL); +SET AUTO_INCREMENT_INCREMENT = 2; +SET DEBUG_SYNC= 'now WAIT_FOR opened'; +SET DEBUG_SYNC= 'ib_after_row_insert_step SIGNAL flushed1 WAIT_FOR opened1'; +insert into t1 values(NULL); +# CONNECTION default +SELECT * FROM t1; +id +1 +3 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1 +SET DEBUG_SYNC= 'now SIGNAL opened1'; +# CONNECTION con1 +SELECT * FROM t1; +id +1 +3 +5 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1 +DROP TABLE t1; +CREATE TABLE t1(id INT AUTO_INCREMENT PRIMARY KEY)ENGINE=INNODB; +# SETTING auto_increment_increment IN CONNECTION DEFAULT +SET AUTO_INCREMENT_INCREMENT = 1; +INSERT INTO t1 VALUES(NULL); +SELECT * FROM t1; +id +1 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 +SET DEBUG_SYNC = 'now SIGNAL flushed'; +# SETTING auto_increment_increment in connection1 +SET AUTO_INCREMENT_INCREMENT = 2; +SET DEBUG_SYNC= 'now WAIT_FOR flushed'; +SET DEBUG_SYNC= 'ib_after_row_insert SIGNAL opened WAIT_FOR flushed1'; +INSERT INTO t1 values(NULL); +# CONNECTION DEFAULT +SET DEBUG_SYNC= 'now WAIT_FOR opened'; +SET DEBUG_SYNC= 'ib_after_row_insert_step SIGNAL flushed1 WAIT_FOR opened1'; +INSERT INTO t1 VALUES(NULL); +# CONNECTION con1 +SELECT * FROM t1; +id +1 +3 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 +SET DEBUG_SYNC= 'now SIGNAL opened1'; +# CONNECTION default +SELECT * FROM t1; +id +1 +3 +5 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/autoinc_debug.test b/mysql-test/suite/innodb/t/autoinc_debug.test new file mode 100644 index 00000000000..c3f9dbffecf --- /dev/null +++ b/mysql-test/suite/innodb/t/autoinc_debug.test @@ -0,0 +1,91 @@ +--source include/have_innodb.inc +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/not_embedded.inc + +# Two parallel connection with autoinc column after restart. + +CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY)ENGINE=INNODB; + +--echo # SETTING auto_increment_increment IN CONNECTION DEFAULT +SET AUTO_INCREMENT_INCREMENT = 1; +INSERT INTO t1 VALUES(NULL); +SELECT * FROM t1; +SHOW CREATE TABLE t1; + +--source include/restart_mysqld.inc + +--echo # SETTING auto_increment_increment IN CONNECTION1 +SET AUTO_INCREMENT_INCREMENT = 2; + +SET DEBUG_SYNC= 'ib_after_row_insert SIGNAL opened WAIT_FOR flushed1'; + +SEND INSERT INTO t1 VALUES(NULL); + +connect(con1, localhost, root,,); +SET AUTO_INCREMENT_INCREMENT = 2; +SET DEBUG_SYNC= 'now WAIT_FOR opened'; +SET DEBUG_SYNC= 'ib_after_row_insert_step SIGNAL flushed1 WAIT_FOR opened1'; +send insert into t1 values(NULL); + +--echo # CONNECTION default +connection default; +reap; +SELECT * FROM t1; +SHOW CREATE TABLE t1; +SET DEBUG_SYNC= 'now SIGNAL opened1'; + +--echo # CONNECTION con1 +connection con1; +reap; +SELECT * FROM t1; +SHOW CREATE TABLE t1; +connection default; +disconnect con1; + +DROP TABLE t1; + +# Two parallel connection with autoinc column without restart. + +CREATE TABLE t1(id INT AUTO_INCREMENT PRIMARY KEY)ENGINE=INNODB; + +--echo # SETTING auto_increment_increment IN CONNECTION DEFAULT +SET AUTO_INCREMENT_INCREMENT = 1; +INSERT INTO t1 VALUES(NULL); +SELECT * FROM t1; +SHOW CREATE TABLE t1; +SET DEBUG_SYNC = 'now SIGNAL flushed'; + +connect(con1, localhost, root,,); + +--echo # SETTING auto_increment_increment in connection1 +SET AUTO_INCREMENT_INCREMENT = 2; + +SET DEBUG_SYNC= 'now WAIT_FOR flushed'; +SET DEBUG_SYNC= 'ib_after_row_insert SIGNAL opened WAIT_FOR flushed1'; + +send INSERT INTO t1 values(NULL); + +--echo # CONNECTION DEFAULT +connection default; + +SET DEBUG_SYNC= 'now WAIT_FOR opened'; +SET DEBUG_SYNC= 'ib_after_row_insert_step SIGNAL flushed1 WAIT_FOR opened1'; + +send INSERT INTO t1 VALUES(NULL); + +--echo # CONNECTION con1 +connection con1; +reap; +SELECT * FROM t1; +SHOW CREATE TABLE t1; +SET DEBUG_SYNC= 'now SIGNAL opened1'; + +--echo # CONNECTION default +connection default; +reap; +SELECT * FROM t1; +SHOW CREATE TABLE t1; +disconnect con1; +SET DEBUG_SYNC= 'RESET'; +DROP TABLE t1; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 287eae5681c..8b48552eed4 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -5350,6 +5350,7 @@ no_commit: innodb_srv_conc_enter_innodb(prebuilt->trx); error = row_insert_for_mysql((byte*) record, prebuilt); + DEBUG_SYNC(user_thd, "ib_after_row_insert"); /* Handle duplicate key errors */ if (auto_inc_used) { @@ -10461,6 +10462,37 @@ ha_innobase::get_auto_increment( ulonglong col_max_value = innobase_get_int_col_max_value( table->next_number_field); + /** The following logic is needed to avoid duplicate key error + for autoincrement column. + + (1) InnoDB gives the current autoincrement value with respect + to increment and offset value. + + (2) Basically it does compute_next_insert_id() logic inside InnoDB + to avoid the current auto increment value changed by handler layer. + + (3) It is restricted only for insert operations. */ + + if (increment > 1 && thd_sql_command(user_thd) != SQLCOM_ALTER_TABLE + && autoinc < col_max_value) { + + ulint prev_auto_inc = autoinc; + + autoinc = ((autoinc - 1) + increment - offset)/ increment; + + autoinc = autoinc * increment + offset; + + /* If autoinc exceeds the col_max_value then reset + to old autoinc value. Because in case of non-strict + sql mode, boundary value is not considered as error. */ + + if (autoinc >= col_max_value) { + autoinc = prev_auto_inc; + } + + ut_ad(autoinc > 0); + } + /* Called for the first time ? */ if (trx->n_autoinc_rows == 0) { diff --git a/storage/innobase/row/row0mysql.c b/storage/innobase/row/row0mysql.c index 2c39ec4587f..ad07ff7709d 100644 --- a/storage/innobase/row/row0mysql.c +++ b/storage/innobase/row/row0mysql.c @@ -1241,6 +1241,8 @@ run_again: row_ins_step(thr); + DEBUG_SYNC_C("ib_after_row_insert_step"); + err = trx->error_state; if (err != DB_SUCCESS) { diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index d2079585b94..d148ab8c8d8 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -11731,6 +11731,37 @@ ha_innobase::get_auto_increment( ulonglong col_max_value = innobase_get_int_col_max_value( table->next_number_field); + /** The following logic is needed to avoid duplicate key error + for autoincrement column. + + (1) InnoDB gives the current autoincrement value with respect + to increment and offset value. + + (2) Basically it does compute_next_insert_id() logic inside InnoDB + to avoid the current auto increment value changed by handler layer. + + (3) It is restricted only for insert operations. */ + + if (increment > 1 && thd_sql_command(user_thd) != SQLCOM_ALTER_TABLE + && autoinc < col_max_value) { + + ulint prev_auto_inc = autoinc; + + autoinc = ((autoinc - 1) + increment - offset)/ increment; + + autoinc = autoinc * increment + offset; + + /* If autoinc exceeds the col_max_value then reset + to old autoinc value. Because in case of non-strict + sql mode, boundary value is not considered as error. */ + + if (autoinc >= col_max_value) { + autoinc = prev_auto_inc; + } + + ut_ad(autoinc > 0); + } + /* Called for the first time ? */ if (trx->n_autoinc_rows == 0) { diff --git a/storage/xtradb/row/row0mysql.c b/storage/xtradb/row/row0mysql.c index 278a287325e..cb156ff6e46 100644 --- a/storage/xtradb/row/row0mysql.c +++ b/storage/xtradb/row/row0mysql.c @@ -1254,6 +1254,8 @@ run_again: row_ins_step(thr); + DEBUG_SYNC_C("ib_after_row_insert_step"); + err = trx->error_state; if (err != DB_SUCCESS) {