diff options
65 files changed, 1789 insertions, 524 deletions
diff --git a/.bzr-mysql/default.conf b/.bzr-mysql/default.conf index 772f1d15b92..43d479a1043 100644 --- a/.bzr-mysql/default.conf +++ b/.bzr-mysql/default.conf @@ -1,4 +1,4 @@ [MYSQL] -post_commit_to = "MYSQL_COMMITS_SECURITY_WW@ORACLE.COM" -post_push_to = "MYSQL_COMMITS_SECURITY_WW@ORACLE.COM" -tree_name = "mysql-5.5-security" +post_commit_to = "commits@lists.mysql.com" +post_push_to = "commits@lists.mysql.com" +tree_name = "mysql-5.5" @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=5 MYSQL_VERSION_MINOR=5 -MYSQL_VERSION_PATCH=18 +MYSQL_VERSION_PATCH=19 MYSQL_VERSION_EXTRA= diff --git a/client/mysql_plugin.c b/client/mysql_plugin.c index 55f5ea0827e..fb2a031bb8e 100644 --- a/client/mysql_plugin.c +++ b/client/mysql_plugin.c @@ -929,19 +929,19 @@ static int check_access() opt_datadir); goto exit; } - if ((error= my_access(opt_plugin_ini, F_OK))) + if (opt_plugin_ini && (error= my_access(opt_plugin_ini, F_OK))) { fprintf(stderr, "ERROR: Cannot access plugin config file at '%s'.\n", opt_plugin_ini); goto exit; } - if ((error= my_access(opt_mysqld, F_OK))) + if (opt_mysqld && (error= my_access(opt_mysqld, F_OK))) { fprintf(stderr, "ERROR: Cannot access mysqld path '%s'.\n", opt_mysqld); goto exit; } - if ((error= my_access(opt_my_print_defaults, F_OK))) + if (opt_my_print_defaults && (error= my_access(opt_my_print_defaults, F_OK))) { fprintf(stderr, "ERROR: Cannot access my-print-defaults path '%s'.\n", opt_my_print_defaults); @@ -967,7 +967,7 @@ static int find_tool(const char *tool_name, char *tool_path) int i= 0; const char *paths[]= { - opt_basedir, opt_mysqld, opt_my_print_defaults, "/usr", + opt_mysqld, opt_basedir, opt_my_print_defaults, "/usr", "/usr/local/mysql", "/usr/sbin", "/usr/share", "/extra", "/extra/debug", "/extra/release", "/bin", "/usr/bin", "/mysql/bin" }; @@ -1124,7 +1124,7 @@ static int dump_bootstrap_file(char *bootstrap_file) error= 1; goto exit; } - printf("# Query: %s", query_str); + printf("# Query: %s\n", query_str); exit: if (file) diff --git a/client/mysql_upgrade.c b/client/mysql_upgrade.c index fb4be716df2..684d20c29a8 100644 --- a/client/mysql_upgrade.c +++ b/client/mysql_upgrade.c @@ -286,6 +286,8 @@ get_one_option(int optid, const struct my_option *opt, case 'v': /* --verbose */ case 'f': /* --force */ + case 's': /* --upgrade-system-tables */ + case OPT_WRITE_BINLOG: /* --write-binlog */ add_option= FALSE; break; diff --git a/client/mysqltest.cc b/client/mysqltest.cc index f0184e5297b..1bd5d5941e5 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -502,6 +502,31 @@ struct st_command *curr_command= 0; char builtin_echo[FN_REFLEN]; +struct st_replace_regex +{ +DYNAMIC_ARRAY regex_arr; /* stores a list of st_regex subsitutions */ + +/* +Temporary storage areas for substitutions. To reduce unnessary copying +and memory freeing/allocation, we pre-allocate two buffers, and alternate +their use, one for input/one for output, the roles changing on the next +st_regex substition. At the end of substitutions buf points to the +one containing the final result. +*/ +char* buf; +char* even_buf; +char* odd_buf; +int even_buf_len; +int odd_buf_len; +}; + +struct st_replace_regex *glob_replace_regex= 0; + +struct st_replace; +struct st_replace *glob_replace= 0; +void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, +const char *from, int len); + static void cleanup_and_exit(int exit_code) __attribute__((noreturn)); void die(const char *fmt, ...) @@ -531,6 +556,7 @@ void str_to_file2(const char *fname, char *str, int size, my_bool append); void fix_win_paths(const char *val, int len); const char *get_errname_from_code (uint error_code); +int multi_reg_replace(struct st_replace_regex* r,char* val); #ifdef __WIN__ void free_tmp_sh_file(); @@ -2432,7 +2458,23 @@ void var_query_set(VAR *var, const char *query, const char** query_end) if (row[i]) { /* Add column to tab separated string */ - dynstr_append_mem(&result, row[i], lengths[i]); + char *val= row[i]; + int len= lengths[i]; + + if (glob_replace_regex) + { + /* Regex replace */ + if (!multi_reg_replace(glob_replace_regex, (char*)val)) + { + val= glob_replace_regex->buf; + len= strlen(val); + } + } + + if (glob_replace) + replace_strings_append(glob_replace, &result, val, len); + else + dynstr_append_mem(&result, val, len); } dynstr_append_mem(&result, "\t", 1); } @@ -3344,8 +3386,9 @@ void do_copy_file(struct st_command *command) ' '); DBUG_PRINT("info", ("Copy %s to %s", ds_from_file.str, ds_to_file.str)); + /* MY_HOLD_ORIGINAL_MODES prevents attempts to chown the file */ error= (my_copy(ds_from_file.str, ds_to_file.str, - MYF(MY_DONT_OVERWRITE_FILE)) != 0); + MYF(MY_DONT_OVERWRITE_FILE | MY_HOLD_ORIGINAL_MODES)) != 0); handle_command_error(command, error); dynstr_free(&ds_from_file); dynstr_free(&ds_to_file); @@ -9119,16 +9162,11 @@ typedef struct st_pointer_array { /* when using array-strings */ uint array_allocs,max_count,length,max_length; } POINTER_ARRAY; -struct st_replace; struct st_replace *init_replace(char * *from, char * *to, uint count, char * word_end_chars); int insert_pointer_name(reg1 POINTER_ARRAY *pa,char * name); -void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, - const char *from, int len); void free_pointer_array(POINTER_ARRAY *pa); -struct st_replace *glob_replace; - /* Get arguments for replace. The syntax is: replace from to [from to ...] @@ -9272,26 +9310,6 @@ struct st_regex int icase; /* true if the match is case insensitive */ }; -struct st_replace_regex -{ - DYNAMIC_ARRAY regex_arr; /* stores a list of st_regex subsitutions */ - - /* - Temporary storage areas for substitutions. To reduce unnessary copying - and memory freeing/allocation, we pre-allocate two buffers, and alternate - their use, one for input/one for output, the roles changing on the next - st_regex substition. At the end of substitutions buf points to the - one containing the final result. - */ - char* buf; - char* even_buf; - char* odd_buf; - int even_buf_len; - int odd_buf_len; -}; - -struct st_replace_regex *glob_replace_regex= 0; - int reg_replace(char** buf_p, int* buf_len_p, char *pattern, char *replace, char *string, int icase); @@ -9490,7 +9508,13 @@ void do_get_replace_regex(struct st_command *command) { char *expr= command->first_argument; free_replace_regex(); - if (!(glob_replace_regex=init_replace_regex(expr))) + /* Allow variable for the *entire* list of replacements */ + if (*expr == '$') + { + VAR *val= var_get(expr, NULL, 0, 1); + expr= val ? val->str_val : NULL; + } + if (expr && *expr && !(glob_replace_regex=init_replace_regex(expr))) die("Could not init replace_regex"); command->last_argument= command->end; } diff --git a/mysql-test/extra/binlog_tests/ctype_ucs_binlog.test b/mysql-test/extra/binlog_tests/ctype_ucs_binlog.test index b240109f6e6..733ad05b0be 100644 --- a/mysql-test/extra/binlog_tests/ctype_ucs_binlog.test +++ b/mysql-test/extra/binlog_tests/ctype_ucs_binlog.test @@ -15,7 +15,6 @@ source include/show_binlog_events.inc; # absolutely need variables names to be quoted and strings to be # escaped). flush logs; ---replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR let $MYSQLD_DATADIR= `select @@datadir`; --exec $MYSQL_BINLOG --short-form $MYSQLD_DATADIR/master-bin.000001 drop table t2; diff --git a/mysql-test/r/alter_table-big.result b/mysql-test/r/alter_table-big.result index d6b936bd5d7..33af60938a1 100644 --- a/mysql-test/r/alter_table-big.result +++ b/mysql-test/r/alter_table-big.result @@ -1,57 +1,77 @@ drop table if exists t1, t2; +set debug_sync='RESET'; create table t1 (n1 int, n2 int, n3 int, key (n1, n2, n3), key (n2, n3, n1), key (n3, n1, n2)); create table t2 (i int); alter table t1 disable keys; -insert into t1 values (RAND()*1000, RAND()*1000, RAND()*1000); +insert into t1 values (1, 2, 3); reset master; -set session debug="+d,sleep_alter_enable_indexes"; +set debug_sync='alter_table_enable_indexes SIGNAL parked WAIT_FOR go'; alter table t1 enable keys;; +set debug_sync='now WAIT_FOR parked'; insert into t2 values (1); -insert into t1 values (1, 1, 1); -set session debug="-d,sleep_alter_enable_indexes"; +insert into t1 values (1, 1, 1);; +set debug_sync='now SIGNAL go'; show binlog events from <binlog_start>; Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN master-bin.000001 # Query # # use `test`; insert into t2 values (1) +master-bin.000001 # Query # # COMMIT master-bin.000001 # Query # # use `test`; alter table t1 enable keys +master-bin.000001 # Query # # BEGIN master-bin.000001 # Query # # use `test`; insert into t1 values (1, 1, 1) +master-bin.000001 # Query # # COMMIT drop tables t1, t2; +set debug_sync='RESET'; End of 5.0 tests drop table if exists t1, t2, t3; create table t1 (i int); reset master; -set session debug="+d,sleep_alter_before_main_binlog"; +set debug_sync='alter_table_before_main_binlog SIGNAL parked WAIT_FOR go'; alter table t1 change i c char(10) default 'Test1';; -insert into t1 values (); +set debug_sync='now WAIT_FOR parked'; +insert into t1 values ();; +set debug_sync='now SIGNAL go'; select * from t1; c Test1 +set debug_sync='alter_table_before_main_binlog SIGNAL parked WAIT_FOR go'; alter table t1 change c vc varchar(100) default 'Test2';; -rename table t1 to t2; +set debug_sync='now WAIT_FOR parked'; +rename table t1 to t2;; +set debug_sync='now SIGNAL go'; drop table t2; create table t1 (i int); +set debug_sync='alter_table_before_main_binlog SIGNAL parked WAIT_FOR go'; alter table t1 change i c char(10) default 'Test3', rename to t2;; -insert into t2 values (); +set debug_sync='now WAIT_FOR parked'; +insert into t2 values();; +set debug_sync='now SIGNAL go'; select * from t2; c Test3 alter table t2 change c vc varchar(100) default 'Test2', rename to t1;; rename table t1 to t3; drop table t3; -set session debug="-d,sleep_alter_before_main_binlog"; +set debug_sync='alter_table_before_main_binlog SIGNAL parked WAIT_FOR go'; +set debug_sync='RESET'; show binlog events from <binlog_start>; Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 # Query # # use `test`; alter table t1 change i c char(10) default 'Test1' +master-bin.000001 # Query # # BEGIN master-bin.000001 # Query # # use `test`; insert into t1 values () +master-bin.000001 # Query # # COMMIT master-bin.000001 # Query # # use `test`; alter table t1 change c vc varchar(100) default 'Test2' master-bin.000001 # Query # # use `test`; rename table t1 to t2 -master-bin.000001 # Query # # use `test`; drop table t2 +master-bin.000001 # Query # # use `test`; DROP TABLE `t2` /* generated by server */ master-bin.000001 # Query # # use `test`; create table t1 (i int) master-bin.000001 # Query # # use `test`; alter table t1 change i c char(10) default 'Test3', rename to t2 -master-bin.000001 # Query # # use `test`; insert into t2 values () +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Query # # use `test`; insert into t2 values() +master-bin.000001 # Query # # COMMIT master-bin.000001 # Query # # use `test`; alter table t2 change c vc varchar(100) default 'Test2', rename to t1 master-bin.000001 # Query # # use `test`; rename table t1 to t3 -master-bin.000001 # Query # # use `test`; drop table t3 +master-bin.000001 # Query # # use `test`; DROP TABLE `t3` /* generated by server */ End of 5.1 tests diff --git a/mysql-test/r/mysql_upgrade.result b/mysql-test/r/mysql_upgrade.result index e36b4781ddf..8ce3f426375 100644 --- a/mysql-test/r/mysql_upgrade.result +++ b/mysql-test/r/mysql_upgrade.result @@ -194,3 +194,36 @@ GRANT ALL PRIVILEGES ON `roelt`.`test2` TO 'user3'@'%' DROP USER 'user3'@'%'; End of 5.1 tests The --upgrade-system-tables option was used, databases won't be touched. +# +# Bug#11827359 60223: MYSQL_UPGRADE PROBLEM WITH OPTION +# SKIP-WRITE-BINLOG +# +# Droping the previously created mysql_upgrade_info file.. +# Running mysql_upgrade with --skip-write-binlog.. +mtr.global_suppressions OK +mtr.test_suppressions OK +mysql.columns_priv OK +mysql.db OK +mysql.event OK +mysql.func OK +mysql.general_log OK +mysql.help_category OK +mysql.help_keyword OK +mysql.help_relation OK +mysql.help_topic OK +mysql.host OK +mysql.ndb_binlog_index OK +mysql.plugin OK +mysql.proc OK +mysql.procs_priv OK +mysql.proxies_priv OK +mysql.servers OK +mysql.slow_log OK +mysql.tables_priv OK +mysql.time_zone OK +mysql.time_zone_leap_second OK +mysql.time_zone_name OK +mysql.time_zone_transition OK +mysql.time_zone_transition_type OK +mysql.user OK +End of tests diff --git a/mysql-test/r/mysqld--help-notwin.result b/mysql-test/r/mysqld--help-notwin.result index b4faa833c1b..cb727984ec0 100644 --- a/mysql-test/r/mysqld--help-notwin.result +++ b/mysql-test/r/mysqld--help-notwin.result @@ -336,6 +336,8 @@ The following options may be given as the first argument: After this many write locks, allow some read locks to run in between --memlock Lock mysqld in memory. + --metadata-locks-cache-size=# + Size of unused metadata locks cache --min-examined-row-limit=# Don't write queries to slow log that examine fewer rows than that @@ -844,6 +846,7 @@ max-tmp-tables 32 max-user-connections 0 max-write-lock-count 18446744073709551615 memlock FALSE +metadata-locks-cache-size 1024 min-examined-row-limit 0 multi-range-count 256 myisam-block-size 1024 diff --git a/mysql-test/r/mysqld--help-win.result b/mysql-test/r/mysqld--help-win.result index 361d30620f7..38235e1a093 100644 --- a/mysql-test/r/mysqld--help-win.result +++ b/mysql-test/r/mysqld--help-win.result @@ -335,6 +335,8 @@ The following options may be given as the first argument: After this many write locks, allow some read locks to run in between --memlock Lock mysqld in memory. + --metadata-locks-cache-size=# + Size of unused metadata locks cache --min-examined-row-limit=# Don't write queries to slow log that examine fewer rows than that @@ -847,6 +849,7 @@ max-tmp-tables 32 max-user-connections 0 max-write-lock-count 18446744073709551615 memlock FALSE +metadata-locks-cache-size 1024 min-examined-row-limit 0 multi-range-count 256 myisam-block-size 1024 diff --git a/mysql-test/r/mysqltest.result b/mysql-test/r/mysqltest.result index a0456c65f53..82069ebedaf 100644 --- a/mysql-test/r/mysqltest.result +++ b/mysql-test/r/mysqltest.result @@ -549,6 +549,7 @@ mysqltest: At line 1: Wrong column number to replace_column in 'replace_column 1 select "LONG_STRING" as x; x LONG_STRING +dog mysqltest: At line 1: Invalid integer argument "10!" mysqltest: At line 1: Invalid integer argument "a" mysqltest: At line 1: Missing required argument 'connection name' to command 'connect' @@ -662,6 +663,11 @@ a D 1 1 1 4 drop table t1; +y +txt +b is b and more is more +txt +a is a and less is more create table t2 ( a char(10)); garbage; ERROR 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 'garbage' at line 1 diff --git a/mysql-test/r/query_cache.result b/mysql-test/r/query_cache.result index f209e401764..8132f3e9e0a 100644 --- a/mysql-test/r/query_cache.result +++ b/mysql-test/r/query_cache.result @@ -1583,6 +1583,58 @@ show status like 'Qcache_free_blocks'; Variable_name Value Qcache_free_blocks 0 Restore default values. +drop database if exists db1; +drop database if exists db2; +set GLOBAL query_cache_size=15*1024*1024; +create database db1; +use db1; +create table t1(c1 int)engine=myisam; +insert into t1(c1) values (1); +select * from db1.t1 f; +c1 +1 +show status like 'Qcache_queries_in_cache'; +Variable_name Value +Qcache_queries_in_cache 1 +create database db2; +rename table db1.t1 to db2.t2; +drop database db1; +show status like 'Qcache_queries_in_cache'; +Variable_name Value +Qcache_queries_in_cache 0 +drop database db2; +set global query_cache_size=default; +drop database if exists db1; +drop database if exists db3; +set GLOBAL query_cache_size=15*1024*1024; +create database db1; +create database db3; +use db1; +create table t1(c1 int) engine=myisam; +use db3; +create table t1(c1 int) engine=myisam; +use db1; +insert into t1(c1) values (1); +use test; +select * from db1.t1; +c1 +1 +select c1+1 from db1.t1; +c1+1 +2 +select * from db3.t1; +c1 +show status like 'Qcache_queries_in_cache'; +Variable_name Value +Qcache_queries_in_cache 3 +create database db2; +rename table db1.t1 to db2.t2; +drop database db1; +show status like 'Qcache_queries_in_cache'; +Variable_name Value +Qcache_queries_in_cache 1 +drop database db2; +drop database db3; set GLOBAL query_cache_type=default; set GLOBAL query_cache_limit=default; set GLOBAL query_cache_min_res_unit=default; diff --git a/mysql-test/suite/innodb/r/innodb-lock.result b/mysql-test/suite/innodb/r/innodb-lock.result index 41f308788a2..439a8d6513c 100644 --- a/mysql-test/suite/innodb/r/innodb-lock.result +++ b/mysql-test/suite/innodb/r/innodb-lock.result @@ -89,3 +89,20 @@ commit; # Connection 'con1'. commit; drop table t1; +# +#Bug#12842206 INNODB LOCKING REGRESSION FOR INSERT IGNORE +#fixed by re-fixing Bug#7975 +#aka Bug#11759688 52020: InnoDB can still deadlock on just INSERT... +# +CREATE TABLE t1 (a INT PRIMARY KEY, b INT NOT NULL) ENGINE=InnoDB; +INSERT INTO t1 VALUES(3,1); +BEGIN; +INSERT IGNORE INTO t1 VALUES(3,14); +BEGIN; +INSERT IGNORE INTO t1 VALUES(3,23); +SELECT * FROM t1 FOR UPDATE; +COMMIT; +a b +3 1 +COMMIT; +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/innodb_replace.result b/mysql-test/suite/innodb/r/innodb_replace.result new file mode 100644 index 00000000000..30009b8ddc9 --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_replace.result @@ -0,0 +1,77 @@ +# +#Bug#11759688 52020: InnoDB can still deadlock +#on just INSERT...ON DUPLICATE KEY +#a.k.a. Bug#7975 deadlock without any locking, simple select and update +# +CREATE TABLE t1 (a INT PRIMARY KEY, b INT NOT NULL) ENGINE=InnoDB; +INSERT INTO t1 VALUES(3,1); +BEGIN; +SET DEBUG_SYNC='write_row_noreplace SIGNAL insert1 WAIT_FOR select1'; +INSERT INTO t1 VALUES(3,2); +SET DEBUG_SYNC='now WAIT_FOR insert1'; +SELECT * FROM t1 LOCK IN SHARE MODE; +a b +3 1 +SELECT * FROM t1 FOR UPDATE; +SET DEBUG_SYNC='now SIGNAL select1'; +ERROR 23000: Duplicate entry '3' for key 'PRIMARY' +INSERT INTO t1 VALUES(3,3) ON DUPLICATE KEY UPDATE b=b+10; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +COMMIT; +SET DEBUG_SYNC='write_row_replace SIGNAL insert2 WAIT_FOR select2'; +REPLACE INTO t1 VALUES(3,4); +SET DEBUG_SYNC='now WAIT_FOR insert2'; +SELECT * FROM t1; +a b +3 11 +SELECT * FROM t1 LOCK IN SHARE MODE; +SET DEBUG_SYNC='now SIGNAL select2'; +SET DEBUG_SYNC='write_row_replace SIGNAL insert3 WAIT_FOR select3'; +INSERT INTO t1 VALUES(3,5) ON DUPLICATE KEY UPDATE b=b+20; +a b +3 4 +SET DEBUG_SYNC='now WAIT_FOR insert3'; +SELECT b FROM t1 LOCK IN SHARE MODE; +SET DEBUG_SYNC='now SIGNAL select3'; +b +24 +SET DEBUG_SYNC='write_row_noreplace SIGNAL insert4 WAIT_FOR select4'; +LOAD DATA INFILE '../../std_data/loaddata5.dat' INTO TABLE t1 FIELDS TERMINATED BY '' ENCLOSED BY '' (a, b); +SET DEBUG_SYNC='now WAIT_FOR insert4'; +SELECT b FROM t1 WHERE a=3 LOCK IN SHARE MODE; +b +24 +SELECT b FROM t1 WHERE a=3 FOR UPDATE; +SET DEBUG_SYNC='now SIGNAL select4'; +b +24 +ERROR 23000: Duplicate entry '3' for key 'PRIMARY' +SET DEBUG_SYNC='write_row_noreplace SIGNAL insert5 WAIT_FOR select5'; +LOAD DATA INFILE '../../std_data/loaddata5.dat' IGNORE INTO TABLE t1 FIELDS TERMINATED BY '' ENCLOSED BY '' (a, b); +SET DEBUG_SYNC='now WAIT_FOR insert5'; +SELECT * FROM t1; +a b +3 24 +SELECT * FROM t1 WHERE a=3 LOCK IN SHARE MODE; +a b +3 24 +SELECT * FROM t1 WHERE a=3 FOR UPDATE; +SET DEBUG_SYNC='now SIGNAL select5'; +a b +3 24 +SET DEBUG_SYNC='write_row_replace SIGNAL insert6 WAIT_FOR select6'; +LOAD DATA INFILE '../../std_data/loaddata5.dat' REPLACE INTO TABLE t1 FIELDS TERMINATED BY '' ENCLOSED BY '' (a, b); +SET DEBUG_SYNC='now WAIT_FOR insert6'; +SELECT * FROM t1; +a b +1 2 +3 24 +5 6 +SELECT a,b FROM t1 LOCK IN SHARE MODE; +SET DEBUG_SYNC='now SIGNAL select6'; +a b +1 2 +3 4 +5 6 +SET DEBUG_SYNC='RESET'; +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/innodb-lock.test b/mysql-test/suite/innodb/t/innodb-lock.test index 96f555e3ecc..4d8d3d542d5 100644 --- a/mysql-test/suite/innodb/t/innodb-lock.test +++ b/mysql-test/suite/innodb/t/innodb-lock.test @@ -127,3 +127,44 @@ commit; drop table t1; # End of 4.1 tests + +--echo # +--echo #Bug#12842206 INNODB LOCKING REGRESSION FOR INSERT IGNORE +--echo #fixed by re-fixing Bug#7975 +--echo #aka Bug#11759688 52020: InnoDB can still deadlock on just INSERT... +--echo # + +connection default; +CREATE TABLE t1 (a INT PRIMARY KEY, b INT NOT NULL) ENGINE=InnoDB; + +INSERT INTO t1 VALUES(3,1); + +BEGIN; +# this used to wrongly acquire an X lock; now it takes an S lock +INSERT IGNORE INTO t1 VALUES(3,14); + +connection con1; +BEGIN; +# this used to wrongly acquire an X lock; now it takes an S lock +INSERT IGNORE INTO t1 VALUES(3,23); +--send +SELECT * FROM t1 FOR UPDATE; + +connection con2; +# Check that the above SELECT is blocked +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = 'Sending data' and + info = 'SELECT * FROM t1 FOR UPDATE'; +--source include/wait_condition.inc + +connection default; +COMMIT; +connection con1; +reap; +COMMIT; +disconnect con1; +disconnect con2; + +connection default; +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/innodb_replace.test b/mysql-test/suite/innodb/t/innodb_replace.test new file mode 100644 index 00000000000..a35f423c85e --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_replace.test @@ -0,0 +1,186 @@ +--source include/have_innodb.inc +--source include/have_debug_sync.inc + +--echo # +--echo #Bug#11759688 52020: InnoDB can still deadlock +--echo #on just INSERT...ON DUPLICATE KEY +--echo #a.k.a. Bug#7975 deadlock without any locking, simple select and update +--echo # + +CREATE TABLE t1 (a INT PRIMARY KEY, b INT NOT NULL) ENGINE=InnoDB; + +INSERT INTO t1 VALUES(3,1); + +connect (con1,localhost,root,,); +connect (con2,localhost,root,,); +connection con1; + +BEGIN; +# normal INSERT of a duplicate should only S-lock the existing record (3,1) +SET DEBUG_SYNC='write_row_noreplace SIGNAL insert1 WAIT_FOR select1'; +--send +INSERT INTO t1 VALUES(3,2); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR insert1'; +# this should S-lock (3,1); no conflict +SELECT * FROM t1 LOCK IN SHARE MODE; +# this should X-lock (3,1), conflicting with con1 +--send +SELECT * FROM t1 FOR UPDATE; + +connection con2; +# Check that the above SELECT is blocked +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = 'Sending data' and + info = 'SELECT * FROM t1 FOR UPDATE'; +--source include/wait_condition.inc +SET DEBUG_SYNC='now SIGNAL select1'; + +connection con1; +--error ER_DUP_ENTRY +reap; +# We are still holding an S-lock on (3,1) after the failed INSERT. +# The following will upgrade it to an X-lock, causing a deadlock. +# InnoDB should resolve the deadlock by aborting the blocked SELECT. +INSERT INTO t1 VALUES(3,3) ON DUPLICATE KEY UPDATE b=b+10; + +connection default; +--error ER_LOCK_DEADLOCK +reap; +connection con1; +COMMIT; + +SET DEBUG_SYNC='write_row_replace SIGNAL insert2 WAIT_FOR select2'; +--send +REPLACE INTO t1 VALUES(3,4); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR insert2'; +SELECT * FROM t1; +--send +SELECT * FROM t1 LOCK IN SHARE MODE; + +connection con2; +# Check that the above SELECT is blocked because of X lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = 'Sending data' and + info = 'SELECT * FROM t1 LOCK IN SHARE MODE'; +--source include/wait_condition.inc +SET DEBUG_SYNC='now SIGNAL select2'; + +connection con1; +reap; + +SET DEBUG_SYNC='write_row_replace SIGNAL insert3 WAIT_FOR select3'; +--send +INSERT INTO t1 VALUES(3,5) ON DUPLICATE KEY UPDATE b=b+20; + +connection default; +reap; +SET DEBUG_SYNC='now WAIT_FOR insert3'; +--send +SELECT b FROM t1 LOCK IN SHARE MODE; + +connection con2; +# Check that the above SELECT is blocked because of X lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = 'Sending data' and + info = 'SELECT b FROM t1 LOCK IN SHARE MODE'; +--source include/wait_condition.inc +SET DEBUG_SYNC='now SIGNAL select3'; + +connection default; +reap; + +connection con1; +reap; +SET DEBUG_SYNC='write_row_noreplace SIGNAL insert4 WAIT_FOR select4'; +--send +LOAD DATA INFILE '../../std_data/loaddata5.dat' INTO TABLE t1 FIELDS TERMINATED BY '' ENCLOSED BY '' (a, b); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR insert4'; +# this should S-lock (3,1); no conflict +SELECT b FROM t1 WHERE a=3 LOCK IN SHARE MODE; +# this should X-lock (3,1), conflicting with con1 +--send +SELECT b FROM t1 WHERE a=3 FOR UPDATE; + +connection con2; +# Check that the above SELECT is blocked +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = 'statistics' and + info = 'SELECT b FROM t1 WHERE a=3 FOR UPDATE'; +--source include/wait_condition.inc +SET DEBUG_SYNC='now SIGNAL select4'; + +connection default; +reap; + +connection con1; +--error ER_DUP_ENTRY +reap; +SET DEBUG_SYNC='write_row_noreplace SIGNAL insert5 WAIT_FOR select5'; +--send +LOAD DATA INFILE '../../std_data/loaddata5.dat' IGNORE INTO TABLE t1 FIELDS TERMINATED BY '' ENCLOSED BY '' (a, b); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR insert5'; +SELECT * FROM t1; +# this should S-lock; no conflict +SELECT * FROM t1 WHERE a=3 LOCK IN SHARE MODE; +# this should X-lock, conflicting with the S-lock of the IGNORE in con1 +--send +SELECT * FROM t1 WHERE a=3 FOR UPDATE; + +connection con2; +# Check that the above SELECT is blocked +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = 'statistics' and + info = 'SELECT * FROM t1 WHERE a=3 FOR UPDATE'; +--source include/wait_condition.inc +SET DEBUG_SYNC='now SIGNAL select5'; + +connection con1; +reap; +connection default; +reap; + +connection con1; +SET DEBUG_SYNC='write_row_replace SIGNAL insert6 WAIT_FOR select6'; +--send +LOAD DATA INFILE '../../std_data/loaddata5.dat' REPLACE INTO TABLE t1 FIELDS TERMINATED BY '' ENCLOSED BY '' (a, b); + +connection default; +SET DEBUG_SYNC='now WAIT_FOR insert6'; +SELECT * FROM t1; +# this should conflict with the X-lock acquired by the REPLACE +--send +SELECT a,b FROM t1 LOCK IN SHARE MODE; + +connection con2; +# Check that the above SELECT is blocked +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = 'Sending data' and + info = 'SELECT a,b FROM t1 LOCK IN SHARE MODE'; +--source include/wait_condition.inc +SET DEBUG_SYNC='now SIGNAL select6'; + +connection con1; +reap; +connection default; +reap; + +disconnect con1; +disconnect con2; + +connection default; +SET DEBUG_SYNC='RESET'; +DROP TABLE t1; diff --git a/mysql-test/suite/rpl/r/rpl_cant_read_event_incident.result b/mysql-test/suite/rpl/r/rpl_cant_read_event_incident.result index 1bee6f2ec1a..c1b2c6e3195 100644 --- a/mysql-test/suite/rpl/r/rpl_cant_read_event_incident.result +++ b/mysql-test/suite/rpl/r/rpl_cant_read_event_incident.result @@ -11,7 +11,7 @@ reset slave; start slave; include/wait_for_slave_param.inc [Last_IO_Errno] Last_IO_Errno = '1236' -Last_IO_Error = 'Got fatal error 1236 from master when reading data from binary log: 'binlog truncated in the middle of event; consider out of disk space on master; the last event was read from 'master-bin.000001' at 316, the last byte read was read from 'master-bin.000001' at 335.'' +Last_IO_Error = 'Got fatal error 1236 from master when reading data from binary log: 'binlog truncated in the middle of event; consider out of disk space on master; the last event was read from './master-bin.000001' at 316, the last byte read was read from './master-bin.000001' at 335.'' reset master; stop slave; reset slave; diff --git a/mysql-test/suite/rpl/r/rpl_log_pos.result b/mysql-test/suite/rpl/r/rpl_log_pos.result index 5e9c096d773..42b88711f03 100644 --- a/mysql-test/suite/rpl/r/rpl_log_pos.result +++ b/mysql-test/suite/rpl/r/rpl_log_pos.result @@ -9,7 +9,6 @@ change master to master_log_pos=MASTER_LOG_POS; Read_Master_Log_Pos = '75' start slave; include/wait_for_slave_io_error.inc [errno=1236] -Last_IO_Error = 'Got fatal error 1236 from master when reading data from binary log: 'log event entry exceeded max_allowed_packet; Increase max_allowed_packet on master; the last event was read from 'master-bin.000001' at 75, the last byte read was read from 'master-bin.000001' at 94.'' include/stop_slave_sql.inc show master status; File Position Binlog_Do_DB Binlog_Ignore_DB diff --git a/mysql-test/suite/rpl/r/rpl_manual_change_index_file.result b/mysql-test/suite/rpl/r/rpl_manual_change_index_file.result index 41101da98f8..a2dc5d402a7 100644 --- a/mysql-test/suite/rpl/r/rpl_manual_change_index_file.result +++ b/mysql-test/suite/rpl/r/rpl_manual_change_index_file.result @@ -5,7 +5,6 @@ CREATE TABLE t1(c1 INT); FLUSH LOGS; call mtr.add_suppression('Got fatal error 1236 from master when reading data from binary log: .*could not find next log'); include/wait_for_slave_io_error.inc [errno=1236] -Last_IO_Error = 'Got fatal error 1236 from master when reading data from binary log: 'could not find next log; the last event was read from 'master-bin.000002' at 237, the last byte read was read from 'master-bin.000002' at 237.'' CREATE TABLE t2(c1 INT); FLUSH LOGS; CREATE TABLE t3(c1 INT); diff --git a/mysql-test/suite/rpl/r/rpl_packet.result b/mysql-test/suite/rpl/r/rpl_packet.result index a11fc16123d..27f85cac7de 100644 --- a/mysql-test/suite/rpl/r/rpl_packet.result +++ b/mysql-test/suite/rpl/r/rpl_packet.result @@ -37,7 +37,6 @@ DROP TABLE t1; CREATE TABLE t1 (f1 int PRIMARY KEY, f2 LONGTEXT, f3 LONGTEXT) ENGINE=MyISAM; INSERT INTO t1(f1, f2, f3) VALUES(1, REPEAT('a', @@global.max_allowed_packet), REPEAT('b', @@global.max_allowed_packet)); include/wait_for_slave_io_error.inc [errno=1236] -Last_IO_Error = 'Got fatal error 1236 from master when reading data from binary log: 'log event entry exceeded max_allowed_packet; Increase max_allowed_packet on master; the last event was read from 'master-bin.000001' at 463, the last byte read was read from 'master-bin.000001' at 482.'' STOP SLAVE; RESET SLAVE; RESET MASTER; diff --git a/mysql-test/suite/rpl/r/rpl_rotate_purge_deadlock.result b/mysql-test/suite/rpl/r/rpl_rotate_purge_deadlock.result new file mode 100644 index 00000000000..34024f89617 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_rotate_purge_deadlock.result @@ -0,0 +1,30 @@ +include/master-slave.inc +[connection master] +show binary logs; +Log_name File_size +master-bin.000001 # +create table t1 (f text) engine=innodb; +SET DEBUG_SYNC = 'at_purge_logs_before_date WAIT_FOR rotated'; +insert into t1 set f=repeat('a', 4096); +*** there must be two logs in the list *** +show binary logs; +Log_name File_size +master-bin.000001 # +master-bin.000002 # +insert into t1 set f=repeat('b', 4096); +*** there must be three logs in the list *** +show binary logs; +Log_name File_size +master-bin.000001 # +master-bin.000002 # +master-bin.000003 # +SET DEBUG_SYNC = 'now SIGNAL rotated'; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'at_purge_logs_before_date WAIT_FOR rotated'; +insert into t1 set f=repeat('b', 4096); +SET DEBUG_SYNC = 'now SIGNAL rotated'; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'RESET'; +drop table t1; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_row_find_row_debug.result b/mysql-test/suite/rpl/r/rpl_row_find_row_debug.result new file mode 100644 index 00000000000..295c053563d --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_find_row_debug.result @@ -0,0 +1,18 @@ +include/master-slave.inc +[connection master] +include/stop_slave.inc +SET GLOBAL log_warnings = 2; +SET GLOBAL debug="d,inject_long_find_row_note"; +include/start_slave.inc +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1), (2); +UPDATE t1 SET c1= 1000 WHERE c1=2; +DELETE FROM t1; +DROP TABLE t1; +# Check if any note related to long DELETE_ROWS and UPDATE_ROWS appears in the error log +Occurrences: update=1, delete=1 +include/stop_slave.inc +SET GLOBAL debug = ''; +SET GLOBAL log_warnings = 1; +include/start_slave.inc +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_cant_read_event_incident.test b/mysql-test/suite/rpl/t/rpl_cant_read_event_incident.test index 71445be55e6..5e88b163d99 100644 --- a/mysql-test/suite/rpl/t/rpl_cant_read_event_incident.test +++ b/mysql-test/suite/rpl/t/rpl_cant_read_event_incident.test @@ -14,6 +14,11 @@ --source include/master-slave.inc --source include/have_binlog_format_mixed.inc +# +# Bug#13050593 swallows `\' from Last_IO_Error +# todo: uncomment the filter once the bug is fixed. +# +--source include/not_windows.inc call mtr.add_suppression("Error in Log_event::read_log_event()"); diff --git a/mysql-test/suite/rpl/t/rpl_log_pos.test b/mysql-test/suite/rpl/t/rpl_log_pos.test index 484ffa52a44..882a05712f2 100644 --- a/mysql-test/suite/rpl/t/rpl_log_pos.test +++ b/mysql-test/suite/rpl/t/rpl_log_pos.test @@ -22,7 +22,12 @@ let $status_items= Read_Master_Log_Pos; source include/show_slave_status.inc; start slave; let $slave_io_errno= 1236; -let $show_slave_io_error= 1; +# +# Win and Unix path is printed differently: BUG#13055685. So +# show_slave_io_error is made 0 until the bug fixes provide necessary +# facilities +# +let $show_slave_io_error= 0; source include/wait_for_slave_io_error.inc; source include/stop_slave_sql.inc; diff --git a/mysql-test/suite/rpl/t/rpl_manual_change_index_file.test b/mysql-test/suite/rpl/t/rpl_manual_change_index_file.test index b0d3b23b4e1..9b648de8486 100644 --- a/mysql-test/suite/rpl/t/rpl_manual_change_index_file.test +++ b/mysql-test/suite/rpl/t/rpl_manual_change_index_file.test @@ -60,7 +60,12 @@ call mtr.add_suppression('Got fatal error 1236 from master when reading data fro connection slave; # 1236 = ER_MASTER_FATAL_ERROR_READING_BINLOG --let $slave_io_errno= 1236 ---let $show_slave_io_error= 1 +# +# Win and Unix path is printed differently: BUG#13055685. So +# show_slave_io_error is made 0 until the bug fixes provide necessary +# facilities +# +--let $show_slave_io_error= 0 --source include/wait_for_slave_io_error.inc connection master; diff --git a/mysql-test/suite/rpl/t/rpl_packet.test b/mysql-test/suite/rpl/t/rpl_packet.test index 7e9a35883a3..2b8a1fd8310 100644 --- a/mysql-test/suite/rpl/t/rpl_packet.test +++ b/mysql-test/suite/rpl/t/rpl_packet.test @@ -125,7 +125,12 @@ connection slave; # The slave I/O thread must stop after receiving # 1236=ER_MASTER_FATAL_ERROR_READING_BINLOG error message from master. --let $slave_io_errno= 1236 ---let $show_slave_io_error= 1 +# +# Win and Unix path is printed differently: BUG#13055685. So +# show_slave_io_error is made 0 until the bug fixes provide necessary +# facilities +# +--let $show_slave_io_error= 0 --source include/wait_for_slave_io_error.inc # Remove the bad binlog and clear error status on slave. diff --git a/mysql-test/suite/rpl/t/rpl_rotate_purge_deadlock-master.opt b/mysql-test/suite/rpl/t/rpl_rotate_purge_deadlock-master.opt new file mode 100644 index 00000000000..f71317fc399 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_rotate_purge_deadlock-master.opt @@ -0,0 +1 @@ +--max-binlog-size=4k --expire-logs-days=1 diff --git a/mysql-test/suite/rpl/t/rpl_rotate_purge_deadlock.test b/mysql-test/suite/rpl/t/rpl_rotate_purge_deadlock.test new file mode 100644 index 00000000000..429612c405f --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_rotate_purge_deadlock.test @@ -0,0 +1,92 @@ +# +# Bug#11763573 - 56299: MUTEX DEADLOCK WITH COM_BINLOG_DUMP, BINLOG PURGE, AND PROCESSLIST/KILL +# +source include/master-slave.inc; +source include/have_debug_sync.inc; +source include/have_binlog_format_row.inc; +source include/have_innodb.inc; + +# +# Testing that execution of two concurrent INSERTing connections both +# triggering the binlog rotation is correct even though their execution +# is interleaved. +# The test makes the first connection to complete the rotation part +# and yields control to the second connection that rotates as well and +# gets first on purging. And the fact of interleaving does not create +# any issue. +# + +connection master; +source include/show_binary_logs.inc; +create table t1 (f text) engine=innodb; +SET DEBUG_SYNC = 'at_purge_logs_before_date WAIT_FOR rotated'; +send insert into t1 set f=repeat('a', 4096); + +connection master1; + +let $wait_condition= + SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE STATE like "debug sync point: at_purge_logs_before_date%"; +--source include/wait_condition.inc + +--echo *** there must be two logs in the list *** +source include/show_binary_logs.inc; + +insert into t1 set f=repeat('b', 4096); + +--echo *** there must be three logs in the list *** +source include/show_binary_logs.inc; + +SET DEBUG_SYNC = 'now SIGNAL rotated'; +SET DEBUG_SYNC = 'RESET'; + +# the first connection finally completes its INSERT +connection master; +reap; +SET DEBUG_SYNC = 'RESET'; + +sync_slave_with_master; + + +# +# Testing the reported deadlock involving DUMP, KILL and INSERT threads +# + +connection master; +SET DEBUG_SYNC = 'at_purge_logs_before_date WAIT_FOR rotated'; +send insert into t1 set f=repeat('b', 4096); + +connection master1; + +# make sure INSERT reaches waiting point +let $wait_condition= + SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE STATE like "debug sync point: at_purge_logs_before_date%"; +--source include/wait_condition.inc + +# find and kill DUMP thread +let $_tid= `select id from information_schema.processlist where command = 'Binlog Dump' limit 1`; +--disable_query_log +eval kill query $_tid; +--enable_query_log + +# +# Now the proof is that the new DUMP thread has executed +# a critical section of the deadlock without any regression and is UP +# +let $wait_condition= + SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE command = 'Binlog Dump' and STATE like "Master has sent all binlog to slave%"; +--source include/wait_condition.inc + +SET DEBUG_SYNC = 'now SIGNAL rotated'; +SET DEBUG_SYNC = 'RESET'; + +connection master; +reap; +SET DEBUG_SYNC = 'RESET'; +drop table t1; + +sync_slave_with_master; + +--source include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_row_find_row_debug.test b/mysql-test/suite/rpl/t/rpl_row_find_row_debug.test new file mode 100644 index 00000000000..a8d9e878ba2 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_find_row_debug.test @@ -0,0 +1,62 @@ +# +# Bug#11760927: 53375: RBR + NO PK => HIGH LOAD ON SLAVE (TABLE SCAN/CPU) => SLAVE FAILURE +# +--source include/master-slave.inc +--source include/have_binlog_format_row.inc +--source include/have_debug.inc + +# SETUP +# - setup log_warnings and debug +--connection slave +--source include/stop_slave.inc +--let $debug_save= `SELECT @@GLOBAL.debug` +--let $log_warnings_save= `SELECT @@GLOBAL.log_warnings` + +SET GLOBAL log_warnings = 2; + +let $log_error_= `SELECT @@GLOBAL.log_error`; +if(!$log_error_) +{ + # MySQL Server on windows is started with --console and thus + # does not know the location of its .err log, use default location + let $log_error_ = $MYSQLTEST_VARDIR/log/mysqld.2.err; +} + +# Assign env variable LOG_ERROR +let LOG_ERROR=$log_error_; + +# force printing the notes to the error log +SET GLOBAL debug="d,inject_long_find_row_note"; +--source include/start_slave.inc + +# test +--connection master +CREATE TABLE t1 (c1 INT); +--sync_slave_with_master +--connection master + +INSERT INTO t1 VALUES (1), (2); +UPDATE t1 SET c1= 1000 WHERE c1=2; +DELETE FROM t1; +DROP TABLE t1; +--sync_slave_with_master + +--echo # Check if any note related to long DELETE_ROWS and UPDATE_ROWS appears in the error log +perl; + use strict; + my $log_error= $ENV{'LOG_ERROR'} or die "LOG_ERROR not set"; + open(FILE, "$log_error") or die("Unable to open $log_error: $!\n"); + my $upd_count = () = grep(/The slave is applying a ROW event on behalf of an UPDATE statement on table t1 and is currently taking a considerable amount/g,<FILE>); + seek(FILE, 0, 0) or die "Can't seek to beginning of file: $!"; + my $del_count = () = grep(/The slave is applying a ROW event on behalf of a DELETE statement on table t1 and is currently taking a considerable amount/g,<FILE>); + print "Occurrences: update=$upd_count, delete=$del_count\n"; + close(FILE); +EOF + +# cleanup +--source include/stop_slave.inc +--eval SET GLOBAL debug = '$debug_save' +--eval SET GLOBAL log_warnings = $log_warnings_save +--source include/start_slave.inc + +--source include/rpl_end.inc diff --git a/mysql-test/suite/sys_vars/r/metadata_locks_cache_size_basic.result b/mysql-test/suite/sys_vars/r/metadata_locks_cache_size_basic.result new file mode 100644 index 00000000000..a62b6011739 --- /dev/null +++ b/mysql-test/suite/sys_vars/r/metadata_locks_cache_size_basic.result @@ -0,0 +1,21 @@ +# +# Check that the paremeter is correctly set by start-up +# option (.opt file sets it to 256 while default is 1024). +select @@global.metadata_locks_cache_size = 256; +@@global.metadata_locks_cache_size = 256 +1 +# +# Check that variable is read only +# +set @@global.metadata_locks_cache_size= 1024; +ERROR HY000: Variable 'metadata_locks_cache_size' is a read only variable +select @@global.metadata_locks_cache_size = 256; +@@global.metadata_locks_cache_size = 256 +1 +# +# And only GLOBAL +# +select @@session.metadata_locks_cache_size; +ERROR HY000: Variable 'metadata_locks_cache_size' is a GLOBAL variable +set @@session.metadata_locks_cache_size= 1024; +ERROR HY000: Variable 'metadata_locks_cache_size' is a read only variable diff --git a/mysql-test/suite/sys_vars/t/metadata_locks_cache_size_basic-master.opt b/mysql-test/suite/sys_vars/t/metadata_locks_cache_size_basic-master.opt new file mode 100644 index 00000000000..77e019cd3d4 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/metadata_locks_cache_size_basic-master.opt @@ -0,0 +1 @@ +--metadata-locks-cache-size=256 diff --git a/mysql-test/suite/sys_vars/t/metadata_locks_cache_size_basic.test b/mysql-test/suite/sys_vars/t/metadata_locks_cache_size_basic.test new file mode 100644 index 00000000000..31b033579c6 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/metadata_locks_cache_size_basic.test @@ -0,0 +1,25 @@ +# +# Basic test coverage for --metadata-locks-cache-size startup +# parameter and corresponding read-only global @@metadata_locks_cache_size +# variable. +# + +--echo # +--echo # Check that the paremeter is correctly set by start-up +--echo # option (.opt file sets it to 256 while default is 1024). +select @@global.metadata_locks_cache_size = 256; + +--echo # +--echo # Check that variable is read only +--echo # +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +set @@global.metadata_locks_cache_size= 1024; +select @@global.metadata_locks_cache_size = 256; + +--echo # +--echo # And only GLOBAL +--echo # +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +select @@session.metadata_locks_cache_size; +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +set @@session.metadata_locks_cache_size= 1024; diff --git a/mysql-test/t/alter_table-big.test b/mysql-test/t/alter_table-big.test index e007a3a55e0..b010815955f 100644 --- a/mysql-test/t/alter_table-big.test +++ b/mysql-test/t/alter_table-big.test @@ -18,7 +18,10 @@ --disable_warnings drop table if exists t1, t2; --enable_warnings +set debug_sync='RESET'; + connect (addconroot, localhost, root,,); +connect (addconroot2, localhost, root,,); connection default; create table t1 (n1 int, n2 int, n3 int, key (n1, n2, n3), @@ -26,38 +29,45 @@ create table t1 (n1 int, n2 int, n3 int, key (n3, n1, n2)); create table t2 (i int); -# Starting from 5.1 we have runtime settable @@debug variable, -# which can be used for introducing delays at certain points of -# statement execution, so we don't need many rows in 't1' to make -# this test repeatable. alter table t1 disable keys; ---disable_warnings -insert into t1 values (RAND()*1000, RAND()*1000, RAND()*1000); ---enable_warnings +insert into t1 values (1, 2, 3); # Later we use binlog to check the order in which statements are # executed so let us reset it first. reset master; -set session debug="+d,sleep_alter_enable_indexes"; +set debug_sync='alter_table_enable_indexes SIGNAL parked WAIT_FOR go'; --send alter table t1 enable keys; connection addconroot; ---sleep 2 +# Wait until ALTER TABLE acquires metadata lock. +set debug_sync='now WAIT_FOR parked'; # This statement should not be blocked by in-flight ALTER and therefore # should be executed and written to binlog before ALTER TABLE ... ENABLE KEYS # finishes. insert into t2 values (1); # And this should wait until the end of ALTER TABLE ... ENABLE KEYS. -insert into t1 values (1, 1, 1); +--send insert into t1 values (1, 1, 1); +connection addconroot2; +# Wait until the above INSERT INTO t1 is blocked due to ALTER +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table metadata lock" and + info = "insert into t1 values (1, 1, 1)"; +--source include/wait_condition.inc +# Resume ALTER execution. +set debug_sync='now SIGNAL go'; connection default; --reap -set session debug="-d,sleep_alter_enable_indexes"; +connection addconroot; +--reap +connection default; # Check that statements were executed/binlogged in correct order. source include/show_binlog_events.inc; # Clean up drop tables t1, t2; disconnect addconroot; - +disconnect addconroot2; +set debug_sync='RESET'; --echo End of 5.0 tests @@ -72,48 +82,92 @@ disconnect addconroot; --disable_warnings drop table if exists t1, t2, t3; --enable_warnings +connect (addconroot, localhost, root,,); +connect (addconroot2, localhost, root,,); +connection default; create table t1 (i int); # We are going to check that statements are logged in correct order reset master; -set session debug="+d,sleep_alter_before_main_binlog"; +set debug_sync='alter_table_before_main_binlog SIGNAL parked WAIT_FOR go'; --send alter table t1 change i c char(10) default 'Test1'; -connect (addconroot, localhost, root,,); connection addconroot; ---sleep 2 -insert into t1 values (); -select * from t1; +# Wait until ALTER TABLE acquires metadata lock. +set debug_sync='now WAIT_FOR parked'; +--send insert into t1 values (); +connection addconroot2; +# Wait until the above INSERT INTO t1 is blocked due to ALTER +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table metadata lock" and + info = "insert into t1 values ()"; +--source include/wait_condition.inc +# Resume ALTER execution. +set debug_sync='now SIGNAL go'; connection default; --reap +connection addconroot; +--reap +connection default; +select * from t1; +set debug_sync='alter_table_before_main_binlog SIGNAL parked WAIT_FOR go'; --send alter table t1 change c vc varchar(100) default 'Test2'; connection addconroot; ---sleep 2 -rename table t1 to t2; +# Wait until ALTER TABLE acquires metadata lock. +set debug_sync='now WAIT_FOR parked'; +--send rename table t1 to t2; +connection addconroot2; +# Wait until the above RENAME TABLE is blocked due to ALTER +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table metadata lock" and + info = "rename table t1 to t2"; +--source include/wait_condition.inc +# Resume ALTER execution. +set debug_sync='now SIGNAL go'; connection default; --reap +connection addconroot; +--reap +connection default; drop table t2; # And now tests for ALTER TABLE with RENAME clause. In this # case target table name should be properly locked as well. create table t1 (i int); +set debug_sync='alter_table_before_main_binlog SIGNAL parked WAIT_FOR go'; --send alter table t1 change i c char(10) default 'Test3', rename to t2; connection addconroot; ---sleep 2 -insert into t2 values (); -select * from t2; +# Wait until ALTER TABLE acquires metadata lock. +set debug_sync='now WAIT_FOR parked'; +--send insert into t2 values(); +connection addconroot2; +# Wait until the above INSERT INTO t2 is blocked due to ALTER +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table metadata lock" and + info = "insert into t2 values()"; +--source include/wait_condition.inc +# Resume ALTER execution. +set debug_sync='now SIGNAL go'; connection default; --reap +connection addconroot; +--reap +connection default; +select * from t2; --send alter table t2 change c vc varchar(100) default 'Test2', rename to t1; connection addconroot; ---sleep 2 -rename table t1 to t3; connection default; --reap +rename table t1 to t3; + disconnect addconroot; +disconnect addconroot2; drop table t3; -set session debug="-d,sleep_alter_before_main_binlog"; +set debug_sync='alter_table_before_main_binlog SIGNAL parked WAIT_FOR go'; +set debug_sync='RESET'; # Check that all statements were logged in correct order source include/show_binlog_events.inc; --echo End of 5.1 tests - diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def index 08db85a3f45..cb1de2cb0ff 100644 --- a/mysql-test/t/disabled.def +++ b/mysql-test/t/disabled.def @@ -12,7 +12,6 @@ lowercase_table3 : Bug#11762269 2010-06-30 alik main.lowercase_table3 on Mac OSX read_many_rows_innodb : Bug#11748886 2010-11-15 mattiasj report already exists sum_distinct-big : Bug#11764126 2010-11-15 mattiasj was not tested -alter_table-big : Bug#11748731 2010-11-15 mattiasj was not tested create-big : Bug#11748731 2010-11-15 mattiasj was not tested archive-big : Bug#11817185 2011-03-10 Anitha Disabled since this leads to timeout on Solaris Sparc log_tables-big : Bug#11756699 2010-11-15 mattiasj report already exists diff --git a/mysql-test/t/mysql_plugin.test b/mysql-test/t/mysql_plugin.test index 897e22ddd62..c5968df85f8 100644 --- a/mysql-test/t/mysql_plugin.test +++ b/mysql-test/t/mysql_plugin.test @@ -142,8 +142,11 @@ EOF --echo # Simulate loading a plugin libary with multiple entry points. --echo # This will test the DISABLE to ensure all rows are removed. --echo # +--replace_regex /\.dll/.so/ eval INSERT INTO mysql.plugin VALUES ('wicky', '$DAEMONEXAMPLE'); +--replace_regex /\.dll/.so/ eval INSERT INTO mysql.plugin VALUES ('wacky', '$DAEMONEXAMPLE'); +--replace_regex /\.dll/.so/ eval INSERT INTO mysql.plugin VALUES ('wonky', '$DAEMONEXAMPLE'); --echo # diff --git a/mysql-test/t/mysql_upgrade.test b/mysql-test/t/mysql_upgrade.test index 3c6cc45da38..05b8c81a797 100644 --- a/mysql-test/t/mysql_upgrade.test +++ b/mysql-test/t/mysql_upgrade.test @@ -102,5 +102,24 @@ DROP USER 'user3'@'%'; # Test the --upgrade-system-tables option # --replace_result $MYSQLTEST_VARDIR var ---exec $MYSQL_UPGRADE --skip-verbose --upgrade-system-tables +--exec $MYSQL_UPGRADE --skip-verbose --force --upgrade-system-tables +--echo # +--echo # Bug#11827359 60223: MYSQL_UPGRADE PROBLEM WITH OPTION +--echo # SKIP-WRITE-BINLOG +--echo # + +let $MYSQLD_DATADIR= `select @@datadir`; + +--echo # Droping the previously created mysql_upgrade_info file.. +--remove_file $MYSQLD_DATADIR/mysql_upgrade_info + +--echo # Running mysql_upgrade with --skip-write-binlog.. +--replace_result $MYSQLTEST_VARDIR var +--exec $MYSQL_UPGRADE --skip-verbose --skip-write-binlog + +# mysql_upgrade must have created mysql_upgrade_info file, +# so the following command should never fail. +--remove_file $MYSQLD_DATADIR/mysql_upgrade_info + +--echo End of tests diff --git a/mysql-test/t/mysqltest.test b/mysql-test/t/mysqltest.test index 11ab79458d7..86ee7ccc1fa 100644 --- a/mysql-test/t/mysqltest.test +++ b/mysql-test/t/mysqltest.test @@ -1745,6 +1745,12 @@ let $long_rep= $long_rep,$long_rep; --replace_result $long_rep LONG_STRING eval select "$long_rep" as x; +# Test replace within `` + +--replace_result cat dog +--let $animal= `select "cat" as pet` +--echo $animal + # ---------------------------------------------------------------------------- # Test sync_with_master # ---------------------------------------------------------------------------- @@ -2065,6 +2071,23 @@ insert into t1 values (2,4); select * from t1; drop table t1; +# Test usage with `` + +--replace_regex /x/y/ +--let $result= `select "x" as col` +--echo $result + +# Test usage with a variable as pattern list + +--disable_query_log +--let $patt= /a /b / /less/more/ +--replace_regex $patt +select "a is a and less is more" as txt; +--let $patt= +--replace_regex $patt +select "a is a and less is more" as txt; +--enable_query_log + #------------------------------------------------------------------------- # BUG #11754855 : Passing variable to --error #------------------------------------------------------------------------- diff --git a/mysql-test/t/query_cache.test b/mysql-test/t/query_cache.test index 51e362d4916..49a8817fe6b 100644 --- a/mysql-test/t/query_cache.test +++ b/mysql-test/t/query_cache.test @@ -1193,47 +1193,48 @@ set global query_cache_type=0; show status like 'Qcache_free_blocks'; --echo Restore default values. -# Bug#28211 RENAME DATABASE and query cache don't play nicely together -# -# TODO: enable these tests when RENAME DATABASE is implemented. -# --disable_warnings -# drop database if exists db1; -# drop database if exists db2; -# --enable_warnings -# set GLOBAL query_cache_size=15*1024*1024; -# create database db1; -# use db1; -# create table t1(c1 int)engine=myisam; -# insert into t1(c1) values (1); -# select * from db1.t1 f; -# show status like 'Qcache_queries_in_cache'; -# rename schema db1 to db2; -# show status like 'Qcache_queries_in_cache'; -# drop database db2; -# set global query_cache_size=default; -# -# --disable_warnings -# drop database if exists db1; -# drop database if exists db3; -# --enable_warnings -# set GLOBAL query_cache_size=15*1024*1024; -# create database db1; -# create database db3; -# use db1; -# create table t1(c1 int) engine=myisam; -# use db3; -# create table t1(c1 int) engine=myisam; -# use db1; -# insert into t1(c1) values (1); -# use mysql; -# select * from db1.t1; -# select c1+1 from db1.t1; -# select * from db3.t1; -# show status like 'Qcache_queries_in_cache'; -# rename schema db1 to db2; -# show status like 'Qcache_queries_in_cache'; -# drop database db2; -# drop database db3; + --disable_warnings + drop database if exists db1; + drop database if exists db2; + --enable_warnings + set GLOBAL query_cache_size=15*1024*1024; + create database db1; + use db1; + create table t1(c1 int)engine=myisam; + insert into t1(c1) values (1); + select * from db1.t1 f; + show status like 'Qcache_queries_in_cache'; + create database db2; + rename table db1.t1 to db2.t2; + drop database db1; + show status like 'Qcache_queries_in_cache'; + drop database db2; + set global query_cache_size=default; + + --disable_warnings + drop database if exists db1; + drop database if exists db3; + --enable_warnings + set GLOBAL query_cache_size=15*1024*1024; + create database db1; + create database db3; + use db1; + create table t1(c1 int) engine=myisam; + use db3; + create table t1(c1 int) engine=myisam; + use db1; + insert into t1(c1) values (1); + use test; + select * from db1.t1; + select c1+1 from db1.t1; + select * from db3.t1; + show status like 'Qcache_queries_in_cache'; + create database db2; + rename table db1.t1 to db2.t2; + drop database db1; + show status like 'Qcache_queries_in_cache'; + drop database db2; + drop database db3; set GLOBAL query_cache_type=default; set GLOBAL query_cache_limit=default; diff --git a/sql/item_func.cc b/sql/item_func.cc index 7257b411ec4..0cd6ff6357e 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2328,25 +2328,31 @@ double my_double_round(double value, longlong dec, bool dec_unsigned, /* tmp2 is here to avoid return the value with 80 bit precision This will fix that the test round(0.1,1) = round(0.1,1) is true + Tagging with volatile is no guarantee, it may still be optimized away... */ volatile double tmp2; tmp=(abs_dec < array_elements(log_10) ? log_10[abs_dec] : pow(10.0,(double) abs_dec)); + // Pre-compute these, to avoid optimizing away e.g. 'floor(v/tmp) * tmp'. + volatile double value_div_tmp= value / tmp; + volatile double value_mul_tmp= value * tmp; + if (dec_negative && my_isinf(tmp)) - tmp2= 0; - else if (!dec_negative && my_isinf(value * tmp)) + tmp2= 0.0; + else if (!dec_negative && my_isinf(value_mul_tmp)) tmp2= value; else if (truncate) { - if (value >= 0) - tmp2= dec < 0 ? floor(value/tmp)*tmp : floor(value*tmp)/tmp; + if (value >= 0.0) + tmp2= dec < 0 ? floor(value_div_tmp) * tmp : floor(value_mul_tmp) / tmp; else - tmp2= dec < 0 ? ceil(value/tmp)*tmp : ceil(value*tmp)/tmp; + tmp2= dec < 0 ? ceil(value_div_tmp) * tmp : ceil(value_mul_tmp) / tmp; } else - tmp2=dec < 0 ? rint(value/tmp)*tmp : rint(value*tmp)/tmp; + tmp2=dec < 0 ? rint(value_div_tmp) * tmp : rint(value_mul_tmp) / tmp; + return tmp2; } diff --git a/sql/log.cc b/sql/log.cc index c6b41447d6a..77ba7aa6283 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -49,7 +49,7 @@ #include "sql_plugin.h" #include "rpl_handler.h" - +#include "debug_sync.h" /* max size of the log message */ #define MAX_LOG_BUFFER_SIZE 1024 #define MAX_TIME_SIZE 32 @@ -5002,19 +5002,29 @@ err: { bool synced; if ((error= flush_and_sync(&synced))) - goto unlock; - - if ((error= RUN_HOOK(binlog_storage, after_flush, + { + mysql_mutex_unlock(&LOCK_log); + } + else if ((error= RUN_HOOK(binlog_storage, after_flush, (thd, log_file_name, file->pos_in_file, synced)))) { sql_print_error("Failed to run 'after_flush' hooks"); - goto unlock; + mysql_mutex_unlock(&LOCK_log); + } + else + { + bool check_purge; + signal_update(); + error= rotate(false, &check_purge); + mysql_mutex_unlock(&LOCK_log); + if (!error && check_purge) + purge(); } - signal_update(); - rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); } -unlock: - mysql_mutex_unlock(&LOCK_log); + else + { + mysql_mutex_unlock(&LOCK_log); + } } if (error) @@ -5100,25 +5110,29 @@ bool general_log_write(THD *thd, enum enum_server_command command, } /** + The method executes rotation when LOCK_log is already acquired + by the caller. + + @param force_rotate caller can request the log rotation + @param check_purge is set to true if rotation took place + @note If rotation fails, for instance the server was unable to create a new log file, we still try to write an incident event to the current log. @retval - nonzero - error + nonzero - error in rotating routine. */ -int MYSQL_BIN_LOG::rotate_and_purge(uint flags) +int MYSQL_BIN_LOG::rotate(bool force_rotate, bool* check_purge) { int error= 0; - DBUG_ENTER("MYSQL_BIN_LOG::rotate_and_purge"); -#ifdef HAVE_REPLICATION - bool check_purge= false; -#endif - if (!(flags & RP_LOCK_LOG_IS_ALREADY_LOCKED)) - mysql_mutex_lock(&LOCK_log); - if ((flags & RP_FORCE_ROTATE) || - (my_b_tell(&log_file) >= (my_off_t) max_size)) + DBUG_ENTER("MYSQL_BIN_LOG::rotate"); + + //todo: fix the macro def and restore safe_mutex_assert_owner(&LOCK_log); + *check_purge= false; + + if (force_rotate || (my_b_tell(&log_file) >= (my_off_t) max_size)) { if ((error= new_file_without_locking())) /** @@ -5133,24 +5147,59 @@ int MYSQL_BIN_LOG::rotate_and_purge(uint flags) if (!write_incident(current_thd, FALSE)) flush_and_sync(0); -#ifdef HAVE_REPLICATION - check_purge= true; -#endif + *check_purge= true; } - if (!(flags & RP_LOCK_LOG_IS_ALREADY_LOCKED)) - mysql_mutex_unlock(&LOCK_log); + DBUG_RETURN(error); +} + +/** + The method executes logs purging routine. + + @retval + nonzero - error in rotating routine. +*/ +void MYSQL_BIN_LOG::purge() +{ #ifdef HAVE_REPLICATION - /* - NOTE: Run purge_logs wo/ holding LOCK_log - as it otherwise will deadlock in ndbcluster_binlog_index_purge_file - */ - if (!error && check_purge && expire_logs_days) + if (expire_logs_days) { + DEBUG_SYNC(current_thd, "at_purge_logs_before_date"); time_t purge_time= my_time(0) - expire_logs_days*24*60*60; if (purge_time >= 0) + { purge_logs_before_date(purge_time); + } } #endif +} + +/** + The method is a shortcut of @c rotate() and @c purge(). + LOCK_log is acquired prior to rotate and is released after it. + + @param force_rotate caller can request the log rotation + + @retval + nonzero - error in rotating routine. +*/ +int MYSQL_BIN_LOG::rotate_and_purge(bool force_rotate) +{ + int error= 0; + DBUG_ENTER("MYSQL_BIN_LOG::rotate_and_purge"); + bool check_purge= false; + + //todo: fix the macro def and restore safe_mutex_assert_not_owner(&LOCK_log); + mysql_mutex_lock(&LOCK_log); + error= rotate(force_rotate, &check_purge); + /* + NOTE: Run purge_logs wo/ holding LOCK_log because it does not need + the mutex. Otherwise causes various deadlocks. + */ + mysql_mutex_unlock(&LOCK_log); + + if (!error && check_purge) + purge(); + DBUG_RETURN(error); } @@ -5352,10 +5401,17 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd, bool lock) { if (!error && !(error= flush_and_sync(0))) { + bool check_purge= false; signal_update(); - error= rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED); + error= rotate(false, &check_purge); + mysql_mutex_unlock(&LOCK_log); + if (!error && check_purge) + purge(); + } + else + { + mysql_mutex_unlock(&LOCK_log); } - mysql_mutex_unlock(&LOCK_log); } DBUG_RETURN(error); } @@ -5388,11 +5444,13 @@ bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event, bool incident) { DBUG_ENTER("MYSQL_BIN_LOG::write(THD *, IO_CACHE *, Log_event *)"); - mysql_mutex_lock(&LOCK_log); DBUG_ASSERT(is_open()); if (likely(is_open())) // Should always be true { + bool check_purge; + + mysql_mutex_lock(&LOCK_log); /* We only bother to write to the binary log if there is anything to write. @@ -5460,12 +5518,17 @@ bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event, mysql_mutex_lock(&LOCK_prep_xids); prepared_xids++; mysql_mutex_unlock(&LOCK_prep_xids); + mysql_mutex_unlock(&LOCK_log); } else - if (rotate_and_purge(RP_LOCK_LOG_IS_ALREADY_LOCKED)) + { + if (rotate(false, &check_purge)) goto err; + mysql_mutex_unlock(&LOCK_log); + if (check_purge) + purge(); + } } - mysql_mutex_unlock(&LOCK_log); DBUG_RETURN(0); diff --git a/sql/log.h b/sql/log.h index 0f0f81dd402..481bd545960 100644 --- a/sql/log.h +++ b/sql/log.h @@ -455,7 +455,9 @@ public: void make_log_name(char* buf, const char* log_ident); bool is_active(const char* log_file_name); int update_log_index(LOG_INFO* linfo, bool need_update_threads); - int rotate_and_purge(uint flags); + int rotate(bool force_rotate, bool* check_purge); + void purge(); + int rotate_and_purge(bool force_rotate); /** Flush binlog cache and synchronize to disk. diff --git a/sql/log_event.cc b/sql/log_event.cc index b29bcfdb536..0003a621cbb 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -7761,6 +7761,12 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) // row processing loop + /* + set the initial time of this ROWS statement if it was not done + before in some other ROWS event. + */ + const_cast<Relay_log_info*>(rli)->set_row_stmt_start_timestamp(); + while (error == 0 && m_curr_row < m_rows_end) { /* in_use can have been set to NULL in close_tables_for_reopen */ @@ -9252,6 +9258,51 @@ record_compare_exit: return result; } +/* + Check if we are already spending too much time on this statement. + if we are, warn user that it might be because table does not have + a PK, but only if the warning was not printed before for this STMT. + + @param type The event type code. + @param table_name The name of the table that the slave is + operating. + @param is_index_scan States whether the slave is doing an index scan + or not. + @param rli The relay metadata info. +*/ +static inline +void issue_long_find_row_warning(Log_event_type type, + const char *table_name, + bool is_index_scan, + const Relay_log_info *rli) +{ + if ((global_system_variables.log_warnings > 1 && + !const_cast<Relay_log_info*>(rli)->is_long_find_row_note_printed())) + { + time_t now= my_time(0); + time_t stmt_ts= const_cast<Relay_log_info*>(rli)->get_row_stmt_start_timestamp(); + + DBUG_EXECUTE_IF("inject_long_find_row_note", + stmt_ts-=(LONG_FIND_ROW_THRESHOLD*2);); + + long delta= (long) (now - stmt_ts); + + if (delta > LONG_FIND_ROW_THRESHOLD) + { + const_cast<Relay_log_info*>(rli)->set_long_find_row_note_printed(); + const char* evt_type= type == DELETE_ROWS_EVENT ? " DELETE" : "n UPDATE"; + const char* scan_type= is_index_scan ? "scanning an index" : "scanning the table"; + + sql_print_information("The slave is applying a ROW event on behalf of a%s statement " + "on table %s and is currently taking a considerable amount " + "of time (%ld seconds). This is due to the fact that it is %s " + "while looking up records to be processed. Consider adding a " + "primary key (or unique key) to the table to improve " + "performance.", evt_type, table_name, delta, scan_type); + } + } +} + /** Locate the current row in event's table. @@ -9287,6 +9338,7 @@ int Rows_log_event::find_row(const Relay_log_info *rli) TABLE *table= m_table; int error= 0; + bool is_table_scan= false, is_index_scan= false; /* rpl_row_tabledefs.test specifies that @@ -9452,6 +9504,8 @@ int Rows_log_event::find_row(const Relay_log_info *rli) } } + is_index_scan=true; + /* In case key is not unique, we still have to iterate over records found and find the one which is identical to the row given. A copy of the @@ -9508,6 +9562,8 @@ int Rows_log_event::find_row(const Relay_log_info *rli) goto err; } + is_table_scan= true; + /* Continue until we find the right record or have made a full loop */ do { @@ -9561,10 +9617,18 @@ int Rows_log_event::find_row(const Relay_log_info *rli) goto err; } ok: + if (is_table_scan || is_index_scan) + issue_long_find_row_warning(get_type_code(), m_table->alias, + is_index_scan, rli); + table->default_column_bitmaps(); DBUG_RETURN(0); err: + if (is_table_scan || is_index_scan) + issue_long_find_row_warning(get_type_code(), m_table->alias, + is_index_scan, rli); + table->default_column_bitmaps(); DBUG_RETURN(error); } diff --git a/sql/log_event.h b/sql/log_event.h index 07656ebaa72..d65f2d2c85d 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -53,6 +53,7 @@ class String; #define PREFIX_SQL_LOAD "SQL_LOAD-" +#define LONG_FIND_ROW_THRESHOLD 60 /* seconds */ /** Either assert or return an error. diff --git a/sql/mdl.cc b/sql/mdl.cc index 046ac25703a..81d6c69dca4 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -91,6 +91,10 @@ const char *MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]= static bool mdl_initialized= 0; +class MDL_object_lock; +class MDL_object_lock_cache_adapter; + + /** A collection of all MDL locks. A singleton, there is only one instance of the map in the server. @@ -111,6 +115,25 @@ private: HASH m_locks; /* Protects access to m_locks hash. */ mysql_mutex_t m_mutex; + /** + Cache of (unused) MDL_lock objects available for re-use. + + On some systems (e.g. Windows XP) constructing/destructing + MDL_lock objects can be fairly expensive. We use this cache + to avoid these costs in scenarios in which they can have + significant negative effect on performance. For example, when + there is only one thread constantly executing statements in + auto-commit mode and thus constantly causing creation/ + destruction of MDL_lock objects for the tables it uses. + + Note that this cache contains only MDL_object_lock objects. + + Protected by m_mutex mutex. + */ + typedef I_P_List<MDL_object_lock, MDL_object_lock_cache_adapter, + I_P_List_counter> + Lock_cache; + Lock_cache m_unused_locks_cache; /** Pre-allocated MDL_lock object for GLOBAL namespace. */ MDL_lock *m_global_lock; /** Pre-allocated MDL_lock object for COMMIT namespace. */ @@ -379,7 +402,8 @@ public: : key(key_arg), m_ref_usage(0), m_ref_release(0), - m_is_destroyed(FALSE) + m_is_destroyed(FALSE), + m_version(0) { mysql_prlock_init(key_MDL_lock_rwlock, &m_rwlock); } @@ -414,6 +438,22 @@ public: uint m_ref_usage; uint m_ref_release; bool m_is_destroyed; + /** + We use the same idea and an additional version counter to support + caching of unused MDL_lock object for further re-use. + This counter is incremented while holding both MDL_map::m_mutex and + MDL_lock::m_rwlock locks each time when a MDL_lock is moved from + the hash to the unused objects list (or destroyed). + A thread, which has found a MDL_lock object for the key in the hash + and then released the MDL_map::m_mutex before acquiring the + MDL_lock::m_rwlock, can determine that this object was moved to the + unused objects list (or destroyed) while it held no locks by comparing + the version value which it read while holding the MDL_map::m_mutex + with the value read after acquiring the MDL_lock::m_rwlock. + Note that since it takes several years to overflow this counter such + theoretically possible overflows should not have any practical effects. + */ + ulonglong m_version; }; @@ -462,6 +502,26 @@ public: : MDL_lock(key_arg) { } + /** + Reset unused MDL_object_lock object to represent the lock context for a + different object. + */ + void reset(const MDL_key *new_key) + { + /* We need to change only object's key. */ + key.mdl_key_init(new_key); + /* m_granted and m_waiting should be already in the empty/initial state. */ + DBUG_ASSERT(is_empty()); + /* Object should not be marked as destroyed. */ + DBUG_ASSERT(! m_is_destroyed); + /* + Values of the rest of the fields should be preserved between old and + new versions of the object. E.g., m_version and m_ref_usage/release + should be kept intact to properly handle possible remaining references + to the old version of the object. + */ + } + virtual const bitmap_t *incompatible_granted_types_bitmap() const { return m_granted_incompatible; @@ -479,10 +539,29 @@ public: private: static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; static const bitmap_t m_waiting_incompatible[MDL_TYPE_END]; + +public: + /** Members for linking the object into the list of unused objects. */ + MDL_object_lock *next_in_cache, **prev_in_cache; +}; + + +/** + Helper class for linking MDL_object_lock objects into the unused objects list. +*/ +class MDL_object_lock_cache_adapter : + public I_P_List_adapter<MDL_object_lock, &MDL_object_lock::next_in_cache, + &MDL_object_lock::prev_in_cache> +{ }; static MDL_map mdl_locks; +/** + Start-up parameter for the maximum size of the unused MDL_lock objects cache. +*/ +ulong mdl_locks_cache_size; + extern "C" { @@ -565,6 +644,10 @@ void MDL_map::destroy() my_hash_free(&m_locks); MDL_lock::destroy(m_global_lock); MDL_lock::destroy(m_commit_lock); + + MDL_object_lock *lock; + while ((lock= m_unused_locks_cache.pop_front())) + MDL_lock::destroy(lock); } @@ -614,11 +697,49 @@ retry: mdl_key->ptr(), mdl_key->length()))) { - lock= MDL_lock::create(mdl_key); + MDL_object_lock *unused_lock= NULL; + + /* + No lock object found so we need to create a new one + or reuse an existing unused object. + */ + if (mdl_key->mdl_namespace() != MDL_key::SCHEMA && + m_unused_locks_cache.elements()) + { + /* + We need a MDL_object_lock type of object and the unused objects + cache has some. Get the first object from the cache and set a new + key for it. + */ + DBUG_ASSERT(mdl_key->mdl_namespace() != MDL_key::GLOBAL && + mdl_key->mdl_namespace() != MDL_key::COMMIT); + + unused_lock= m_unused_locks_cache.pop_front(); + unused_lock->reset(mdl_key); + + lock= unused_lock; + } + else + { + lock= MDL_lock::create(mdl_key); + } + if (!lock || my_hash_insert(&m_locks, (uchar*)lock)) { + if (unused_lock) + { + /* + Note that we can't easily destroy an object from cache here as it + still might be referenced by other threads. So we simply put it + back into the cache. + */ + m_unused_locks_cache.push_front(unused_lock); + } + else + { + MDL_lock::destroy(lock); + } mysql_mutex_unlock(&m_mutex); - MDL_lock::destroy(lock); return NULL; } } @@ -633,7 +754,7 @@ retry: /** Release mdl_locks.m_mutex mutex and lock MDL_lock::m_rwlock for lock object from the hash. Handle situation when object was released - while the held no mutex. + while we held no locks. @retval FALSE - Success. @retval TRUE - Object was released while we held no mutex, caller @@ -642,6 +763,8 @@ retry: bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) { + ulonglong version; + DBUG_ASSERT(! lock->m_is_destroyed); mysql_mutex_assert_owner(&m_mutex); @@ -651,26 +774,50 @@ bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) m_is_destroyed is FALSE. */ lock->m_ref_usage++; + /* Read value of the version counter under protection of m_mutex lock. */ + version= lock->m_version; mysql_mutex_unlock(&m_mutex); mysql_prlock_wrlock(&lock->m_rwlock); lock->m_ref_release++; - if (unlikely(lock->m_is_destroyed)) + + if (unlikely(lock->m_version != version)) { /* - Object was released while we held no mutex, we need to - release it if no others hold references to it, while our own - reference count ensured that the object as such haven't got - its memory released yet. We can also safely compare - m_ref_usage and m_ref_release since the object is no longer - present in the hash so no one will be able to find it and - increment m_ref_usage anymore. + If the current value of version differs from one that was read while + we held m_mutex mutex, this MDL_lock object was moved to the unused + objects list or destroyed while we held no locks. + We should retry our search. But first we should destroy the MDL_lock + object if necessary. */ - uint ref_usage= lock->m_ref_usage; - uint ref_release= lock->m_ref_release; - mysql_prlock_unlock(&lock->m_rwlock); - if (ref_usage == ref_release) - MDL_lock::destroy(lock); + if (unlikely(lock->m_is_destroyed)) + { + /* + Object was released while we held no locks, we need to + release it if no others hold references to it, while our own + reference count ensured that the object as such haven't got + its memory released yet. We can also safely compare + m_ref_usage and m_ref_release since the object is no longer + present in the hash (or unused objects list) so no one will + be able to find it and increment m_ref_usage anymore. + */ + uint ref_usage= lock->m_ref_usage; + uint ref_release= lock->m_ref_release; + mysql_prlock_unlock(&lock->m_rwlock); + if (ref_usage == ref_release) + MDL_lock::destroy(lock); + } + else + { + /* + Object was not destroyed but its version has changed. + This means that it was moved to the unused objects list + (and even might be already re-used). So now it might + correspond to a different key, therefore we should simply + retry our search. + */ + mysql_prlock_unlock(&lock->m_rwlock); + } return TRUE; } return FALSE; @@ -685,8 +832,6 @@ bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) void MDL_map::remove(MDL_lock *lock) { - uint ref_usage, ref_release; - if (lock->key.mdl_namespace() == MDL_key::GLOBAL || lock->key.mdl_namespace() == MDL_key::COMMIT) { @@ -698,31 +843,65 @@ void MDL_map::remove(MDL_lock *lock) return; } - /* - Destroy the MDL_lock object, but ensure that anyone that is - holding a reference to the object is not remaining, if so he - has the responsibility to release it. - - Setting of m_is_destroyed to TRUE while holding _both_ - mdl_locks.m_mutex and MDL_lock::m_rwlock mutexes transfers the - protection of m_ref_usage from mdl_locks.m_mutex to - MDL_lock::m_rwlock while removal of object from the hash makes - it read-only. Therefore whoever acquires MDL_lock::m_rwlock next - will see most up to date version of m_ref_usage. - - This means that when m_is_destroyed is TRUE and we hold the - MDL_lock::m_rwlock we can safely read the m_ref_usage - member. - */ mysql_mutex_lock(&m_mutex); my_hash_delete(&m_locks, (uchar*) lock); - lock->m_is_destroyed= TRUE; - ref_usage= lock->m_ref_usage; - ref_release= lock->m_ref_release; - mysql_prlock_unlock(&lock->m_rwlock); - mysql_mutex_unlock(&m_mutex); - if (ref_usage == ref_release) - MDL_lock::destroy(lock); + /* + To let threads holding references to the MDL_lock object know that it was + moved to the list of unused objects or destroyed, we increment the version + counter under protection of both MDL_map::m_mutex and MDL_lock::m_rwlock + locks. This allows us to read the version value while having either one + of those locks. + */ + lock->m_version++; + + if ((lock->key.mdl_namespace() != MDL_key::SCHEMA) && + (m_unused_locks_cache.elements() < mdl_locks_cache_size)) + { + /* + This is an object of MDL_object_lock type and the cache of unused + objects has not reached its maximum size yet. So instead of destroying + object we move it to the list of unused objects to allow its later + re-use with possibly different key. Any threads holding references to + this object (owning MDL_map::m_mutex or MDL_lock::m_rwlock) will notice + this thanks to the fact that we have changed the MDL_lock::m_version + counter. + */ + DBUG_ASSERT(lock->key.mdl_namespace() != MDL_key::GLOBAL && + lock->key.mdl_namespace() != MDL_key::COMMIT); + + m_unused_locks_cache.push_front((MDL_object_lock*)lock); + mysql_mutex_unlock(&m_mutex); + mysql_prlock_unlock(&lock->m_rwlock); + } + else + { + /* + Destroy the MDL_lock object, but ensure that anyone that is + holding a reference to the object is not remaining, if so he + has the responsibility to release it. + + Setting of m_is_destroyed to TRUE while holding _both_ + mdl_locks.m_mutex and MDL_lock::m_rwlock mutexes transfers the + protection of m_ref_usage from mdl_locks.m_mutex to + MDL_lock::m_rwlock while removal of the object from the hash + (and cache of unused objects) makes it read-only. Therefore + whoever acquires MDL_lock::m_rwlock next will see the most up + to date version of m_ref_usage. + + This means that when m_is_destroyed is TRUE and we hold the + MDL_lock::m_rwlock we can safely read the m_ref_usage + member. + */ + uint ref_usage, ref_release; + + lock->m_is_destroyed= TRUE; + ref_usage= lock->m_ref_usage; + ref_release= lock->m_ref_release; + mysql_mutex_unlock(&m_mutex); + mysql_prlock_unlock(&lock->m_rwlock); + if (ref_usage == ref_release) + MDL_lock::destroy(lock); + } } @@ -820,9 +999,6 @@ void MDL_request::init(const MDL_key *key_arg, Auxiliary functions needed for creation/destruction of MDL_lock objects. @note Also chooses an MDL_lock descendant appropriate for object namespace. - - @todo This naive implementation should be replaced with one that saves - on memory allocation by reusing released objects. */ inline MDL_lock *MDL_lock::create(const MDL_key *mdl_key) diff --git a/sql/mdl.h b/sql/mdl.h index fb2de45b831..5c1af23f5e2 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -851,4 +851,12 @@ extern "C" void thd_exit_cond(MYSQL_THD thd, const char *old_msg); extern mysql_mutex_t LOCK_open; #endif + +/* + Start-up parameter for the maximum size of the unused MDL_lock objects cache + and a constant for its default value. +*/ +extern ulong mdl_locks_cache_size; +static const ulong MDL_LOCKS_CACHE_SIZE_DEFAULT = 1024; + #endif diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index d4e3e0fd7b4..c5fdc521f11 100644 --- a/sql/rpl_injector.cc +++ b/sql/rpl_injector.cc @@ -237,7 +237,7 @@ int injector::record_incident(THD *thd, Incident incident) Incident_log_event ev(thd, incident); if (int error= mysql_bin_log.write(&ev)) return error; - return mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE); + return mysql_bin_log.rotate_and_purge(true); } int injector::record_incident(THD *thd, Incident incident, LEX_STRING const message) @@ -245,5 +245,5 @@ int injector::record_incident(THD *thd, Incident incident, LEX_STRING const mess Incident_log_event ev(thd, incident, message); if (int error= mysql_bin_log.write(&ev)) return error; - return mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE); + return mysql_bin_log.rotate_and_purge(true); } diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index e1c764a4248..3371406ba65 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -52,7 +52,8 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery) inited(0), abort_slave(0), slave_running(0), until_condition(UNTIL_NONE), until_log_pos(0), retried_trans(0), tables_to_lock(0), tables_to_lock_count(0), - last_event_start_time(0), m_flags(0) + last_event_start_time(0), m_flags(0), row_stmt_start_timestamp(0), + long_find_row_note_printed(false) { DBUG_ENTER("Relay_log_info::Relay_log_info"); @@ -1245,6 +1246,15 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) */ thd->variables.option_bits&= ~OPTION_NO_FOREIGN_KEY_CHECKS; thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS; + + /* + Reset state related to long_find_row notes in the error log: + - timestamp + - flag that decides whether the slave prints or not + */ + reset_row_stmt_start_timestamp(); + unset_long_find_row_note_printed(); + DBUG_VOID_RETURN; } diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index fb62279c4c1..c8a0f549e0f 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -452,8 +452,49 @@ public: (m_flags & (1UL << IN_STMT)); } + time_t get_row_stmt_start_timestamp() + { + return row_stmt_start_timestamp; + } + + time_t set_row_stmt_start_timestamp() + { + if (row_stmt_start_timestamp == 0) + row_stmt_start_timestamp= my_time(0); + + return row_stmt_start_timestamp; + } + + void reset_row_stmt_start_timestamp() + { + row_stmt_start_timestamp= 0; + } + + void set_long_find_row_note_printed() + { + long_find_row_note_printed= true; + } + + void unset_long_find_row_note_printed() + { + long_find_row_note_printed= false; + } + + bool is_long_find_row_note_printed() + { + return long_find_row_note_printed; + } + private: + uint32 m_flags; + + /* + Runtime state for printing a note when slave is taking + too long while processing a row event. + */ + time_t row_stmt_start_timestamp; + bool long_find_row_note_printed; }; diff --git a/sql/sql_class.h b/sql/sql_class.h index 9f13908d31c..36091546ff4 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -161,9 +161,6 @@ typedef struct st_user_var_events bool unsigned_flag; } BINLOG_USER_VAR_EVENT; -#define RP_LOCK_LOG_IS_ALREADY_LOCKED 1 -#define RP_FORCE_ROTATE 2 - /* The COPY_INFO structure is used by INSERT/REPLACE code. The schema of the row counting by the INSERT/INSERT ... ON DUPLICATE KEY @@ -413,8 +410,9 @@ typedef struct system_variables */ ulong dynamic_variables_version; char* dynamic_variables_ptr; - uint dynamic_variables_head; /* largest valid variable offset */ - uint dynamic_variables_size; /* how many bytes are in use */ + uint dynamic_variables_head; /* largest valid variable offset */ + uint dynamic_variables_size; /* how many bytes are in use */ + LIST *dynamic_variables_allocs; /* memory hunks for PLUGIN_VAR_MEMALLOC */ ulonglong max_heap_table_size; ulonglong tmp_table_size; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index eaf7f4c895b..c061a3f472c 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -77,6 +77,8 @@ #include "transaction.h" #include "sql_audit.h" +#include "debug_sync.h" + #ifndef EMBEDDED_LIBRARY static bool delayed_get_table(THD *thd, MDL_request *grl_protection_request, TABLE_LIST *table_list); @@ -1541,6 +1543,8 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) error= HA_ERR_FOUND_DUPP_KEY; /* Database can't find key */ goto err; } + DEBUG_SYNC(thd, "write_row_replace"); + /* Read all columns for the row we are going to replace */ table->use_all_columns(); /* @@ -1729,6 +1733,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) } else if ((error=table->file->ha_write_row(table->record[0]))) { + DEBUG_SYNC(thd, "write_row_noreplace"); if (!info->ignore || table->file->is_fatal_error(error, HA_CHECK_DUP)) goto err; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3b5b5df788e..32ccb8f2c5f 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2888,7 +2888,7 @@ end_with_restore_list: { Incident_log_event ev(thd, incident); (void) mysql_bin_log.write(&ev); /* error is ignored */ - if (mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE)) + if (mysql_bin_log.rotate_and_purge(true)) { res= 1; break; diff --git a/sql/sql_plist.h b/sql/sql_plist.h index ca1d15f3015..2b6f1067321 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -128,6 +128,15 @@ public: } inline T* front() { return m_first; } inline const T *front() const { return m_first; } + inline T* pop_front() + { + T *result= front(); + + if (result) + remove(result); + + return result; + } void swap(I_P_List<T, B, C> &rhs) { swap_variables(T *, m_first, rhs.m_first); diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 5f5e73091ff..2b801b65a09 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -255,6 +255,13 @@ static bool register_builtin(struct st_mysql_plugin *, struct st_plugin_int *, static void unlock_variables(THD *thd, struct system_variables *vars); static void cleanup_variables(THD *thd, struct system_variables *vars); static void plugin_vars_free_values(sys_var *vars); +static bool plugin_var_memalloc_session_update(THD *thd, + struct st_mysql_sys_var *var, + char **dest, const char *value); +static bool plugin_var_memalloc_global_update(THD *thd, + struct st_mysql_sys_var *var, + char **dest, const char *value); +static void plugin_var_memalloc_free(struct system_variables *vars); static void restore_pluginvar_names(sys_var *first); static void plugin_opt_set_limits(struct my_option *, const struct st_mysql_sys_var *); @@ -2300,13 +2307,7 @@ static void update_func_longlong(THD *thd, struct st_mysql_sys_var *var, static void update_func_str(THD *thd, struct st_mysql_sys_var *var, void *tgt, const void *save) { - char *old= *(char **) tgt; - *(char **)tgt= *(char **) save; - if (var->flags & PLUGIN_VAR_MEMALLOC) - { - *(char **)tgt= my_strdup(*(char **) save, MYF(0)); - my_free(old); - } + *(char **) tgt= *(char **) save; } @@ -2565,11 +2566,13 @@ static uchar *intern_sys_var_ptr(THD* thd, int offset, bool global_lock) if ((pi->plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR && pi->plugin_var->flags & PLUGIN_VAR_MEMALLOC) { - char **pp= (char**) (thd->variables.dynamic_variables_ptr + - *(int*)(pi->plugin_var + 1)); - if ((*pp= *(char**) (global_system_variables.dynamic_variables_ptr + - *(int*)(pi->plugin_var + 1)))) - *pp= my_strdup(*pp, MYF(MY_WME|MY_FAE)); + int varoff= *(int *) (pi->plugin_var + 1); + char **thdvar= (char **) (thd->variables. + dynamic_variables_ptr + varoff); + char **sysvar= (char **) (global_system_variables. + dynamic_variables_ptr + varoff); + *thdvar= NULL; + plugin_var_memalloc_session_update(thd, NULL, thdvar, *sysvar); } } @@ -2675,33 +2678,8 @@ static void unlock_variables(THD *thd, struct system_variables *vars) */ static void cleanup_variables(THD *thd, struct system_variables *vars) { - st_bookmark *v; - sys_var_pluginvar *pivar; - sys_var *var; - int flags; - uint idx; - - mysql_rwlock_rdlock(&LOCK_system_variables_hash); - for (idx= 0; idx < bookmark_hash.records; idx++) - { - v= (st_bookmark*) my_hash_element(&bookmark_hash, idx); - if (v->version > vars->dynamic_variables_version || - !(var= intern_find_sys_var(v->key + 1, v->name_len)) || - !(pivar= var->cast_pluginvar()) || - v->key[0] != (pivar->plugin_var->flags & PLUGIN_VAR_TYPEMASK)) - continue; - - flags= pivar->plugin_var->flags; - - if ((flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR && - flags & PLUGIN_VAR_THDLOCAL && flags & PLUGIN_VAR_MEMALLOC) - { - char **ptr= (char**) pivar->real_value_ptr(thd, OPT_SESSION); - my_free(*ptr); - *ptr= NULL; - } - } - mysql_rwlock_unlock(&LOCK_system_variables_hash); + if (thd) + plugin_var_memalloc_free(&thd->variables); DBUG_ASSERT(vars->table_plugin == NULL); @@ -2796,6 +2774,136 @@ static SHOW_TYPE pluginvar_show_type(st_mysql_sys_var *plugin_var) } +/** + Set value for thread local variable with PLUGIN_VAR_MEMALLOC flag. + + @param[in] thd Thread context. + @param[in] var Plugin variable. + @param[in,out] dest Destination memory pointer. + @param[in] value '\0'-terminated new value. + + Most plugin variable values are stored on dynamic_variables_ptr. + Releasing memory occupied by these values is as simple as freeing + dynamic_variables_ptr. + + An exception to the rule are PLUGIN_VAR_MEMALLOC variables, which + are stored on individual memory hunks. All of these hunks has to + be freed when it comes to cleanup. + + It may happen that a plugin was uninstalled and descriptors of + it's variables are lost. In this case it is impossible to locate + corresponding values. + + In addition to allocating and setting variable value, new element + is added to dynamic_variables_allocs list. When thread is done, it + has to call plugin_var_memalloc_free() to release memory used by + PLUGIN_VAR_MEMALLOC variables. + + If var is NULL, variable update function is not called. This is + needed when we take snapshot of system variables during thread + initialization. + + @note List element and variable value are stored on the same memory + hunk. List element is followed by variable value. + + @return Completion status + @retval false Success + @retval true Failure +*/ + +static bool plugin_var_memalloc_session_update(THD *thd, + struct st_mysql_sys_var *var, + char **dest, const char *value) + +{ + LIST *old_element= NULL; + struct system_variables *vars= &thd->variables; + DBUG_ENTER("plugin_var_memalloc_session_update"); + + if (value) + { + size_t length= strlen(value) + 1; + LIST *element; + if (!(element= (LIST *) my_malloc(sizeof(LIST) + length, MYF(MY_WME)))) + DBUG_RETURN(true); + memcpy(element + 1, value, length); + value= (const char *) (element + 1); + vars->dynamic_variables_allocs= list_add(vars->dynamic_variables_allocs, + element); + } + + if (*dest) + old_element= (LIST *) (*dest - sizeof(LIST)); + + if (var) + var->update(thd, var, (void **) dest, (const void *) &value); + else + *dest= (char *) value; + + if (old_element) + { + vars->dynamic_variables_allocs= list_delete(vars->dynamic_variables_allocs, + old_element); + my_free(old_element); + } + DBUG_RETURN(false); +} + + +/** + Free all elements allocated by plugin_var_memalloc_session_update(). + + @param[in] vars system variables structure + + @see plugin_var_memalloc_session_update +*/ + +static void plugin_var_memalloc_free(struct system_variables *vars) +{ + LIST *next, *root; + DBUG_ENTER("plugin_var_memalloc_free"); + for (root= vars->dynamic_variables_allocs; root; root= next) + { + next= root->next; + my_free(root); + } + vars->dynamic_variables_allocs= NULL; + DBUG_VOID_RETURN; +} + + +/** + Set value for global variable with PLUGIN_VAR_MEMALLOC flag. + + @param[in] thd Thread context. + @param[in] var Plugin variable. + @param[in,out] dest Destination memory pointer. + @param[in] value '\0'-terminated new value. + + @return Completion status + @retval false Success + @retval true Failure +*/ + +static bool plugin_var_memalloc_global_update(THD *thd, + struct st_mysql_sys_var *var, + char **dest, const char *value) +{ + char *old_value= *dest; + DBUG_ENTER("plugin_var_memalloc_global_update"); + + if (value && !(value= my_strdup(value, MYF(MY_WME)))) + DBUG_RETURN(true); + + var->update(thd, var, (void **) dest, (const void *) &value); + + if (old_value) + my_free(old_value); + + DBUG_RETURN(false); +} + + bool sys_var_pluginvar::check_update_type(Item_result type) { switch (plugin_var->flags & PLUGIN_VAR_TYPEMASK) { @@ -2880,6 +2988,7 @@ bool sys_var_pluginvar::do_check(THD *thd, set_var *var) bool sys_var_pluginvar::session_update(THD *thd, set_var *var) { + bool rc= false; DBUG_ASSERT(!is_readonly()); DBUG_ASSERT(plugin_var->flags & PLUGIN_VAR_THDLOCAL); DBUG_ASSERT(thd == current_thd); @@ -2889,13 +2998,20 @@ bool sys_var_pluginvar::session_update(THD *thd, set_var *var) const void *src= var->value ? (void*)&var->save_result : (void*)real_value_ptr(thd, OPT_GLOBAL); mysql_mutex_unlock(&LOCK_global_system_variables); - plugin_var->update(thd, plugin_var, tgt, src); - return false; + if ((plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR && + plugin_var->flags & PLUGIN_VAR_MEMALLOC) + rc= plugin_var_memalloc_session_update(thd, plugin_var, (char **) tgt, + *(const char **) src); + else + plugin_var->update(thd, plugin_var, tgt, src); + + return rc; } bool sys_var_pluginvar::global_update(THD *thd, set_var *var) { + bool rc= false; DBUG_ASSERT(!is_readonly()); mysql_mutex_assert_owner(&LOCK_global_system_variables); @@ -2952,9 +3068,14 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var) } } - plugin_var->update(thd, plugin_var, tgt, src); + if ((plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR && + plugin_var->flags & PLUGIN_VAR_MEMALLOC) + rc= plugin_var_memalloc_global_update(thd, plugin_var, (char **) tgt, + *(const char **) src); + else + plugin_var->update(thd, plugin_var, tgt, src); - return false; + return rc; } diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc index ae4c3999838..b0665a9ea6b 100644 --- a/sql/sql_reload.cc +++ b/sql/sql_reload.cc @@ -148,7 +148,7 @@ bool reload_acl_and_cache(THD *thd, unsigned long options, tmp_write_to_binlog= 0; if (mysql_bin_log.is_open()) { - if (mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE)) + if (mysql_bin_log.rotate_and_purge(true)) *write_to_binlog= -1; } } diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 5d6d8bb13e3..00c85d8eb43 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1038,12 +1038,9 @@ err: detailing the fatal error message with coordinates of the last position read. */ - char b_start[FN_REFLEN], b_end[FN_REFLEN]; - fn_format(b_start, coord->file_name, "", "", MY_REPLACE_DIR); - fn_format(b_end, log_file_name, "", "", MY_REPLACE_DIR); my_snprintf(error_text, sizeof(error_text), fmt, errmsg, - b_start, (llstr(coord->pos, llbuff1), llbuff1), - b_end, (llstr(my_b_tell(&log), llbuff2), llbuff2)); + coord->file_name, (llstr(coord->pos, llbuff1), llbuff1), + log_file_name, (llstr(my_b_tell(&log), llbuff2), llbuff2)); } else strcpy(error_text, errmsg); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 9a68420e5fc..5363737998b 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -5958,6 +5958,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, case ENABLE: if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto err; + DEBUG_SYNC(thd,"alter_table_enable_indexes"); DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000);); error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); break; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index da8e61da52e..7a04015dc93 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1156,6 +1156,12 @@ static Sys_var_ulonglong Sys_max_heap_table_size( VALID_RANGE(16384, (ulonglong)~(intptr)0), DEFAULT(16*1024*1024), BLOCK_SIZE(1024)); +static Sys_var_ulong Sys_metadata_locks_cache_size( + "metadata_locks_cache_size", "Size of unused metadata locks cache", + READ_ONLY GLOBAL_VAR(mdl_locks_cache_size), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1, 1024*1024), DEFAULT(MDL_LOCKS_CACHE_SIZE_DEFAULT), + BLOCK_SIZE(1)); + static Sys_var_ulong Sys_pseudo_thread_id( "pseudo_thread_id", "This variable is for internal server use", diff --git a/storage/innobase/btr/btr0pcur.c b/storage/innobase/btr/btr0pcur.c index cbb0d21a7ed..57d9752649f 100644 --- a/storage/innobase/btr/btr0pcur.c +++ b/storage/innobase/btr/btr0pcur.c @@ -247,6 +247,8 @@ btr_pcur_restore_position_func( cursor->rel_pos == BTR_PCUR_BEFORE_FIRST_IN_TREE, index, latch_mode, btr_pcur_get_btr_cur(cursor), mtr); + cursor->latch_mode = latch_mode; + cursor->pos_state = BTR_PCUR_IS_POSITIONED; cursor->block_when_stored = btr_pcur_get_block(cursor); return(FALSE); diff --git a/storage/innobase/buf/buf0buf.c b/storage/innobase/buf/buf0buf.c index 30f37a2f89a..1fcc2107f18 100644 --- a/storage/innobase/buf/buf0buf.c +++ b/storage/innobase/buf/buf0buf.c @@ -839,6 +839,16 @@ pfs_register_buffer_block( rwlock->pfs_psi = (PSI_server) ? PSI_server->init_rwlock(buf_block_lock_key, rwlock) : NULL; + +# ifdef UNIV_SYNC_DEBUG + rwlock = &block->debug_latch; + ut_a(!rwlock->pfs_psi); + rwlock->pfs_psi = (PSI_server) + ? PSI_server->init_rwlock(buf_block_debug_latch_key, + rwlock) + : NULL; +# endif /* UNIV_SYNC_DEBUG */ + # endif /* UNIV_PFS_RWLOCK */ block++; } @@ -895,17 +905,24 @@ buf_block_init( mutex_create(PFS_NOT_INSTRUMENTED, &block->mutex, SYNC_BUF_BLOCK); rw_lock_create(PFS_NOT_INSTRUMENTED, &block->lock, SYNC_LEVEL_VARYING); + +# ifdef UNIV_SYNC_DEBUG + rw_lock_create(PFS_NOT_INSTRUMENTED, + &block->debug_latch, SYNC_NO_ORDER_CHECK); +# endif /* UNIV_SYNC_DEBUG */ + #else /* PFS_SKIP_BUFFER_MUTEX_RWLOCK || PFS_GROUP_BUFFER_SYNC */ mutex_create(buffer_block_mutex_key, &block->mutex, SYNC_BUF_BLOCK); rw_lock_create(buf_block_lock_key, &block->lock, SYNC_LEVEL_VARYING); + +# ifdef UNIV_SYNC_DEBUG + rw_lock_create(buf_block_debug_latch_key, + &block->debug_latch, SYNC_NO_ORDER_CHECK); +# endif /* UNIV_SYNC_DEBUG */ #endif /* PFS_SKIP_BUFFER_MUTEX_RWLOCK || PFS_GROUP_BUFFER_SYNC */ ut_ad(rw_lock_validate(&(block->lock))); -#ifdef UNIV_SYNC_DEBUG - rw_lock_create(buf_block_debug_latch_key, - &block->debug_latch, SYNC_NO_ORDER_CHECK); -#endif /* UNIV_SYNC_DEBUG */ } /********************************************************************//** diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index f6156471380..4e01f7e1cf4 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -5063,8 +5063,7 @@ no_commit: switch (sql_command) { case SQLCOM_LOAD: - if ((trx->duplicates - & (TRX_DUP_IGNORE | TRX_DUP_REPLACE))) { + if (trx->duplicates) { goto set_max_autoinc; } @@ -5340,8 +5339,7 @@ ha_innobase::update_row( && table->next_number_field && new_row == table->record[0] && thd_sql_command(user_thd) == SQLCOM_INSERT - && (trx->duplicates & (TRX_DUP_IGNORE | TRX_DUP_REPLACE)) - == TRX_DUP_IGNORE) { + && trx->duplicates) { ulonglong auto_inc; ulonglong col_max_value; @@ -5701,6 +5699,7 @@ ha_innobase::index_read( (byte*) key_ptr, (ulint) key_len, prebuilt->trx); + DBUG_ASSERT(prebuilt->search_tuple->n_fields > 0); } else { /* We position the cursor to the last or the first entry in the index */ @@ -5807,13 +5806,13 @@ ha_innobase::innobase_get_index( table. Only print message if the index translation table exists */ if (share->idx_trans_tbl.index_mapping) { - sql_print_error("InnoDB could not find " - "index %s key no %u for " - "table %s through its " - "index translation table", - key ? key->name : "NULL", - keynr, - prebuilt->table->name); + sql_print_warning("InnoDB could not find " + "index %s key no %u for " + "table %s through its " + "index translation table", + key ? key->name : "NULL", + keynr, + prebuilt->table->name); } index = dict_table_get_index_on_name(prebuilt->table, @@ -7528,6 +7527,7 @@ ha_innobase::records_in_range( mem_heap_t* heap; DBUG_ENTER("records_in_range"); + DBUG_ASSERT(min_key || max_key); ut_a(prebuilt->trx == thd_to_trx(ha_thd())); @@ -7577,6 +7577,9 @@ ha_innobase::records_in_range( (const uchar*) 0), (ulint) (min_key ? min_key->length : 0), prebuilt->trx); + DBUG_ASSERT(min_key + ? range_start->n_fields > 0 + : range_start->n_fields == 0); row_sel_convert_mysql_key_to_innobase( range_end, (byte*) key_val_buff2, @@ -7585,6 +7588,9 @@ ha_innobase::records_in_range( (const uchar*) 0), (ulint) (max_key ? max_key->length : 0), prebuilt->trx); + DBUG_ASSERT(max_key + ? range_end->n_fields > 0 + : range_end->n_fields == 0); mode1 = convert_search_mode_to_innobase(min_key ? min_key->flag : HA_READ_KEY_EXACT); @@ -8286,7 +8292,10 @@ ha_innobase::check( putc('\n', stderr); #endif - if (!btr_validate_index(index, prebuilt->trx)) { + /* If this is an index being created, break */ + if (*index->name == TEMP_INDEX_PREFIX) { + break; + } else if (!btr_validate_index(index, prebuilt->trx)) { is_ok = FALSE; innobase_format_name( @@ -8835,6 +8844,7 @@ ha_innobase::extra( break; case HA_EXTRA_RESET_STATE: reset_template(prebuilt); + thd_to_trx(ha_thd())->duplicates = 0; break; case HA_EXTRA_NO_KEYREAD: prebuilt->read_just_key = 0; @@ -8852,19 +8862,18 @@ ha_innobase::extra( parameters below. We must not invoke update_thd() either, because the calling threads may change. CAREFUL HERE, OR MEMORY CORRUPTION MAY OCCUR! */ - case HA_EXTRA_IGNORE_DUP_KEY: + case HA_EXTRA_INSERT_WITH_UPDATE: thd_to_trx(ha_thd())->duplicates |= TRX_DUP_IGNORE; break; + case HA_EXTRA_NO_IGNORE_DUP_KEY: + thd_to_trx(ha_thd())->duplicates &= ~TRX_DUP_IGNORE; + break; case HA_EXTRA_WRITE_CAN_REPLACE: thd_to_trx(ha_thd())->duplicates |= TRX_DUP_REPLACE; break; case HA_EXTRA_WRITE_CANNOT_REPLACE: thd_to_trx(ha_thd())->duplicates &= ~TRX_DUP_REPLACE; break; - case HA_EXTRA_NO_IGNORE_DUP_KEY: - thd_to_trx(ha_thd())->duplicates &= - ~(TRX_DUP_IGNORE | TRX_DUP_REPLACE); - break; default:/* Do nothing */ ; } diff --git a/storage/innobase/ibuf/ibuf0ibuf.c b/storage/innobase/ibuf/ibuf0ibuf.c index 0676a7be0f7..47ec1365cb8 100644 --- a/storage/innobase/ibuf/ibuf0ibuf.c +++ b/storage/innobase/ibuf/ibuf0ibuf.c @@ -254,11 +254,20 @@ ibuf_count_check( list of the ibuf */ /* @} */ +#define IBUF_REC_FIELD_SPACE 0 /*!< in the pre-4.1 format, + the page number. later, the space_id */ +#define IBUF_REC_FIELD_MARKER 1 /*!< starting with 4.1, a marker + consisting of 1 byte that is 0 */ +#define IBUF_REC_FIELD_PAGE 2 /*!< starting with 4.1, the + page number */ +#define IBUF_REC_FIELD_METADATA 3 /* the metadata field */ +#define IBUF_REC_FIELD_USER 4 /* first user field */ + /* Various constants for checking the type of an ibuf record and extracting data from it. For details, see the description of the record format at the top of this file. */ -/** @name Format of the fourth column of an insert buffer record +/** @name Format of the IBUF_REC_FIELD_METADATA of an insert buffer record The fourth column in the MySQL 5.5 format contains an operation type, counter, and some flags. */ /* @{ */ @@ -1233,13 +1242,13 @@ ibuf_rec_get_page_no_func( ut_ad(ibuf_inside(mtr)); ut_ad(rec_get_n_fields_old(rec) > 2); - field = rec_get_nth_field_old(rec, 1, &len); + field = rec_get_nth_field_old(rec, IBUF_REC_FIELD_MARKER, &len); if (len == 1) { /* This is of the >= 4.1.x record format */ ut_a(trx_sys_multiple_tablespace_format); - field = rec_get_nth_field_old(rec, 2, &len); + field = rec_get_nth_field_old(rec, IBUF_REC_FIELD_PAGE, &len); } else { ut_a(trx_doublewrite_must_reset_space_ids); ut_a(!trx_sys_multiple_tablespace_format); @@ -1279,13 +1288,13 @@ ibuf_rec_get_space_func( ut_ad(ibuf_inside(mtr)); ut_ad(rec_get_n_fields_old(rec) > 2); - field = rec_get_nth_field_old(rec, 1, &len); + field = rec_get_nth_field_old(rec, IBUF_REC_FIELD_MARKER, &len); if (len == 1) { /* This is of the >= 4.1.x record format */ ut_a(trx_sys_multiple_tablespace_format); - field = rec_get_nth_field_old(rec, 0, &len); + field = rec_get_nth_field_old(rec, IBUF_REC_FIELD_SPACE, &len); ut_a(len == 4); return(mach_read_from_4(field)); @@ -1335,9 +1344,9 @@ ibuf_rec_get_info_func( || mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX)); ut_ad(ibuf_inside(mtr)); fields = rec_get_n_fields_old(rec); - ut_a(fields > 4); + ut_a(fields > IBUF_REC_FIELD_USER); - types = rec_get_nth_field_old(rec, 3, &len); + types = rec_get_nth_field_old(rec, IBUF_REC_FIELD_METADATA, &len); info_len_local = len % DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE; @@ -1363,7 +1372,8 @@ ibuf_rec_get_info_func( ut_a(op_local < IBUF_OP_COUNT); ut_a((len - info_len_local) == - (fields - 4) * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE); + (fields - IBUF_REC_FIELD_USER) + * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE); if (op) { *op = op_local; @@ -1407,7 +1417,7 @@ ibuf_rec_get_op_type_func( ut_ad(ibuf_inside(mtr)); ut_ad(rec_get_n_fields_old(rec) > 2); - (void) rec_get_nth_field_old(rec, 1, &len); + (void) rec_get_nth_field_old(rec, IBUF_REC_FIELD_MARKER, &len); if (len > 1) { /* This is a < 4.1.x format record */ @@ -1436,12 +1446,12 @@ ibuf_rec_get_counter( const byte* ptr; ulint len; - if (rec_get_n_fields_old(rec) < 4) { + if (rec_get_n_fields_old(rec) <= IBUF_REC_FIELD_METADATA) { return(ULINT_UNDEFINED); } - ptr = rec_get_nth_field_old(rec, 3, &len); + ptr = rec_get_nth_field_old(rec, IBUF_REC_FIELD_METADATA, &len); if (len >= 2) { @@ -1666,7 +1676,7 @@ ibuf_build_entry_from_ibuf_rec_func( || mtr_memo_contains_page(mtr, ibuf_rec, MTR_MEMO_PAGE_S_FIX)); ut_ad(ibuf_inside(mtr)); - data = rec_get_nth_field_old(ibuf_rec, 1, &len); + data = rec_get_nth_field_old(ibuf_rec, IBUF_REC_FIELD_MARKER, &len); if (len > 1) { /* This a < 4.1.x format record */ @@ -1678,13 +1688,13 @@ ibuf_build_entry_from_ibuf_rec_func( ut_a(trx_sys_multiple_tablespace_format); ut_a(*data == 0); - ut_a(rec_get_n_fields_old(ibuf_rec) > 4); + ut_a(rec_get_n_fields_old(ibuf_rec) > IBUF_REC_FIELD_USER); - n_fields = rec_get_n_fields_old(ibuf_rec) - 4; + n_fields = rec_get_n_fields_old(ibuf_rec) - IBUF_REC_FIELD_USER; tuple = dtuple_create(heap, n_fields); - types = rec_get_nth_field_old(ibuf_rec, 3, &len); + types = rec_get_nth_field_old(ibuf_rec, IBUF_REC_FIELD_METADATA, &len); ibuf_rec_get_info(mtr, ibuf_rec, NULL, &comp, &info_len, NULL); @@ -1698,7 +1708,8 @@ ibuf_build_entry_from_ibuf_rec_func( for (i = 0; i < n_fields; i++) { field = dtuple_get_nth_field(tuple, i); - data = rec_get_nth_field_old(ibuf_rec, i + 4, &len); + data = rec_get_nth_field_old( + ibuf_rec, i + IBUF_REC_FIELD_USER, &len); dfield_set_data(field, data, len); @@ -1745,7 +1756,7 @@ ibuf_rec_get_size( field_offset = 2; types_offset = DATA_ORDER_NULL_TYPE_BUF_SIZE; } else { - field_offset = 4; + field_offset = IBUF_REC_FIELD_USER; types_offset = DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE; } @@ -1806,7 +1817,7 @@ ibuf_rec_get_volume_func( ut_ad(ibuf_inside(mtr)); ut_ad(rec_get_n_fields_old(ibuf_rec) > 2); - data = rec_get_nth_field_old(ibuf_rec, 1, &len); + data = rec_get_nth_field_old(ibuf_rec, IBUF_REC_FIELD_MARKER, &len); pre_4_1 = (len > 1); if (pre_4_1) { @@ -1829,7 +1840,8 @@ ibuf_rec_get_volume_func( ut_a(trx_sys_multiple_tablespace_format); ut_a(*data == 0); - types = rec_get_nth_field_old(ibuf_rec, 3, &len); + types = rec_get_nth_field_old( + ibuf_rec, IBUF_REC_FIELD_METADATA, &len); ibuf_rec_get_info(mtr, ibuf_rec, &op, &comp, &info_len, NULL); @@ -1859,7 +1871,8 @@ ibuf_rec_get_volume_func( } types += info_len; - n_fields = rec_get_n_fields_old(ibuf_rec) - 4; + n_fields = rec_get_n_fields_old(ibuf_rec) + - IBUF_REC_FIELD_USER; } data_size = ibuf_rec_get_size(ibuf_rec, types, n_fields, pre_4_1, comp); @@ -1914,11 +1927,11 @@ ibuf_entry_build( n_fields = dtuple_get_n_fields(entry); - tuple = dtuple_create(heap, n_fields + 4); + tuple = dtuple_create(heap, n_fields + IBUF_REC_FIELD_USER); /* 1) Space Id */ - field = dtuple_get_nth_field(tuple, 0); + field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_SPACE); buf = mem_heap_alloc(heap, 4); @@ -1928,7 +1941,7 @@ ibuf_entry_build( /* 2) Marker byte */ - field = dtuple_get_nth_field(tuple, 1); + field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_MARKER); buf = mem_heap_alloc(heap, 1); @@ -1940,7 +1953,7 @@ ibuf_entry_build( /* 3) Page number */ - field = dtuple_get_nth_field(tuple, 2); + field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_PAGE); buf = mem_heap_alloc(heap, 4); @@ -1988,10 +2001,7 @@ ibuf_entry_build( ulint fixed_len; const dict_field_t* ifield; - /* We add 4 below because we have the 4 extra fields at the - start of an ibuf record */ - - field = dtuple_get_nth_field(tuple, i + 4); + field = dtuple_get_nth_field(tuple, i + IBUF_REC_FIELD_USER); entry_field = dtuple_get_nth_field(entry, i); dfield_copy(field, entry_field); @@ -2024,13 +2034,13 @@ ibuf_entry_build( /* 4) Type info, part #2 */ - field = dtuple_get_nth_field(tuple, 3); + field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_METADATA); dfield_set_data(field, type_info, ti - type_info); /* Set all the types in the new tuple binary */ - dtuple_set_types_binary(tuple, n_fields + 4); + dtuple_set_types_binary(tuple, n_fields + IBUF_REC_FIELD_USER); return(tuple); } @@ -2090,11 +2100,11 @@ ibuf_new_search_tuple_build( ut_a(trx_sys_multiple_tablespace_format); - tuple = dtuple_create(heap, 3); + tuple = dtuple_create(heap, IBUF_REC_FIELD_METADATA); /* Store the space id in tuple */ - field = dtuple_get_nth_field(tuple, 0); + field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_SPACE); buf = mem_heap_alloc(heap, 4); @@ -2104,7 +2114,7 @@ ibuf_new_search_tuple_build( /* Store the new format record marker byte */ - field = dtuple_get_nth_field(tuple, 1); + field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_MARKER); buf = mem_heap_alloc(heap, 1); @@ -2114,7 +2124,7 @@ ibuf_new_search_tuple_build( /* Store the page number in tuple */ - field = dtuple_get_nth_field(tuple, 2); + field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_PAGE); buf = mem_heap_alloc(heap, 4); @@ -2122,7 +2132,7 @@ ibuf_new_search_tuple_build( dfield_set_data(field, buf, 4); - dtuple_set_types_binary(tuple, 3); + dtuple_set_types_binary(tuple, IBUF_REC_FIELD_METADATA); return(tuple); } @@ -2789,8 +2799,10 @@ ibuf_get_volume_buffered_hash( ulint fold; ulint bitmask; - len = ibuf_rec_get_size(rec, types, rec_get_n_fields_old(rec) - 4, - FALSE, comp); + len = ibuf_rec_get_size( + rec, types, + rec_get_n_fields_old(rec) - IBUF_REC_FIELD_USER, + FALSE, comp); fold = ut_fold_binary(data, len); hash += (fold / (CHAR_BIT * sizeof *hash)) % size; @@ -2842,8 +2854,8 @@ ibuf_get_volume_buffered_count_func( ut_ad(ibuf_inside(mtr)); n_fields = rec_get_n_fields_old(rec); - ut_ad(n_fields > 4); - n_fields -= 4; + ut_ad(n_fields > IBUF_REC_FIELD_USER); + n_fields -= IBUF_REC_FIELD_USER; rec_get_nth_field_offs_old(rec, 1, &len); /* This function is only invoked when buffering new @@ -2852,7 +2864,7 @@ ibuf_get_volume_buffered_count_func( ut_a(len == 1); ut_ad(trx_sys_multiple_tablespace_format); - types = rec_get_nth_field_old(rec, 3, &len); + types = rec_get_nth_field_old(rec, IBUF_REC_FIELD_METADATA, &len); switch (UNIV_EXPECT(len % DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE, IBUF_REC_INFO_SIZE)) { @@ -3164,7 +3176,7 @@ ibuf_update_max_tablespace_id(void) } else { rec = btr_pcur_get_rec(&pcur); - field = rec_get_nth_field_old(rec, 0, &len); + field = rec_get_nth_field_old(rec, IBUF_REC_FIELD_SPACE, &len); ut_a(len == 4); @@ -3186,10 +3198,12 @@ ibuf_update_max_tablespace_id(void) ibuf_get_entry_counter_low_func(rec,space,page_no) #endif /****************************************************************//** -Helper function for ibuf_set_entry_counter. Checks if rec is for (space, -page_no), and if so, reads counter value from it and returns that + 1. -Otherwise, returns 0. -@return new counter value, or 0 */ +Helper function for ibuf_get_entry_counter_func. Checks if rec is for +(space, page_no), and if so, reads counter value from it and returns +that + 1. +@retval ULINT_UNDEFINED if the record does not contain any counter +@retval 0 if the record is not for (space, page_no) +@retval 1 + previous counter value, otherwise */ static ulint ibuf_get_entry_counter_low_func( @@ -3210,7 +3224,7 @@ ibuf_get_entry_counter_low_func( || mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX)); ut_ad(rec_get_n_fields_old(rec) > 2); - field = rec_get_nth_field_old(rec, 1, &len); + field = rec_get_nth_field_old(rec, IBUF_REC_FIELD_MARKER, &len); if (UNIV_UNLIKELY(len != 1)) { /* pre-4.1 format */ @@ -3223,7 +3237,7 @@ ibuf_get_entry_counter_low_func( ut_a(trx_sys_multiple_tablespace_format); /* Check the tablespace identifier. */ - field = rec_get_nth_field_old(rec, 0, &len); + field = rec_get_nth_field_old(rec, IBUF_REC_FIELD_SPACE, &len); ut_a(len == 4); if (mach_read_from_4(field) != space) { @@ -3232,7 +3246,7 @@ ibuf_get_entry_counter_low_func( } /* Check the page offset. */ - field = rec_get_nth_field_old(rec, 2, &len); + field = rec_get_nth_field_old(rec, IBUF_REC_FIELD_PAGE, &len); ut_a(len == 4); if (mach_read_from_4(field) != page_no) { @@ -3241,7 +3255,7 @@ ibuf_get_entry_counter_low_func( } /* Check if the record contains a counter field. */ - field = rec_get_nth_field_old(rec, 3, &len); + field = rec_get_nth_field_old(rec, IBUF_REC_FIELD_METADATA, &len); switch (len % DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE) { default: @@ -3257,147 +3271,61 @@ ibuf_get_entry_counter_low_func( } } +#ifdef UNIV_DEBUG +# define ibuf_get_entry_counter(space,page_no,rec,mtr,exact_leaf) \ + ibuf_get_entry_counter_func(space,page_no,rec,mtr,exact_leaf) +#else /* UNIV_DEBUG */ +# define ibuf_get_entry_counter(space,page_no,rec,mtr,exact_leaf) \ + ibuf_get_entry_counter_func(space,page_no,rec,exact_leaf) +#endif + /****************************************************************//** -Set the counter field in entry to the correct value based on the current +Calculate the counter field for an entry based on the current last record in ibuf for (space, page_no). -@return FALSE if we should abort this insertion to ibuf */ +@return the counter field, or ULINT_UNDEFINED +if we should abort this insertion to ibuf */ static -ibool -ibuf_set_entry_counter( -/*===================*/ - dtuple_t* entry, /*!< in/out: entry to patch */ +ulint +ibuf_get_entry_counter_func( +/*========================*/ ulint space, /*!< in: space id of entry */ ulint page_no, /*!< in: page number of entry */ - btr_pcur_t* pcur, /*!< in: pcur positioned on the record - found by btr_pcur_open(.., entry, - PAGE_CUR_LE, ..., pcur, ...) */ - ibool is_optimistic, /*!< in: is this an optimistic insert */ - mtr_t* mtr) /*!< in: mtr */ + const rec_t* rec, /*!< in: the record preceding the + insertion point */ +#ifdef UNIV_DEBUG + mtr_t* mtr, /*!< in: mini-transaction */ +#endif /* UNIV_DEBUG */ + ibool only_leaf) /*!< in: TRUE if this is the only + leaf page that can contain entries + for (space,page_no), that is, there + was no exact match for (space,page_no) + in the node pointer */ { - dfield_t* field; - byte* data; - ulint counter = 0; - - /* pcur points to either a user rec or to a page's infimum record. */ ut_ad(ibuf_inside(mtr)); - ut_ad(mtr_memo_contains(mtr, btr_pcur_get_block(pcur), - MTR_MEMO_PAGE_X_FIX)); - ut_ad(page_validate(btr_pcur_get_page(pcur), ibuf->index)); - - if (btr_pcur_is_on_user_rec(pcur)) { - - counter = ibuf_get_entry_counter_low( - mtr, btr_pcur_get_rec(pcur), space, page_no); - - if (UNIV_UNLIKELY(counter == ULINT_UNDEFINED)) { - /* The record lacks a counter field. - Such old records must be merged before - new records can be buffered. */ - - return(FALSE); - } - } else if (btr_pcur_is_before_first_in_tree(pcur, mtr)) { - /* Ibuf tree is either completely empty, or the insert - position is at the very first record of a non-empty tree. In - either case we have no previous records for (space, - page_no). */ - - counter = 0; - } else if (btr_pcur_is_before_first_on_page(pcur)) { - btr_cur_t* cursor = btr_pcur_get_btr_cur(pcur); - - if (cursor->low_match < 3) { - /* If low_match < 3, we know that the father node - pointer did not contain the searched for (space, - page_no), which means that the search ended on the - right page regardless of the counter value, and - since we're at the infimum record, there are no - existing records. */ - - counter = 0; - } else { - rec_t* rec; - const page_t* page; - buf_block_t* block; - page_t* prev_page; - ulint prev_page_no; - - ut_a(cursor->ibuf_cnt != ULINT_UNDEFINED); - - page = btr_pcur_get_page(pcur); - prev_page_no = btr_page_get_prev(page, mtr); - - ut_a(prev_page_no != FIL_NULL); - - block = buf_page_get( - IBUF_SPACE_ID, 0, prev_page_no, - RW_X_LATCH, mtr); + ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_X_FIX)); + ut_ad(page_validate(page_align(rec), ibuf->index)); - buf_block_dbg_add_level(block, SYNC_IBUF_TREE_NODE); - - prev_page = buf_block_get_frame(block); - - rec = page_rec_get_prev( - page_get_supremum_rec(prev_page)); - - ut_ad(page_rec_is_user_rec(rec)); - - counter = ibuf_get_entry_counter_low( - mtr, rec, space, page_no); - - if (UNIV_UNLIKELY(counter == ULINT_UNDEFINED)) { - /* The record lacks a counter field. - Such old records must be merged before - new records can be buffered. */ - - return(FALSE); - } - - if (counter < cursor->ibuf_cnt) { - /* Search ended on the wrong page. */ - - if (is_optimistic) { - /* In an optimistic insert, we can - shift the insert position to the left - page, since it only needs an X-latch - on the page itself, which the - original search acquired for us. */ - - btr_cur_position( - ibuf->index, rec, block, - btr_pcur_get_btr_cur(pcur)); - } else { - /* We can't shift the insert - position to the left page in a - pessimistic insert since it would - require an X-latch on the left - page's left page, so we have to - abort. */ - - return(FALSE); - } - } else { - /* The counter field in the father node is - the same as we would insert; we don't know - whether the insert should go to this page or - the left page (the later fields can differ), - so refuse the insert. */ - - return(FALSE); - } - } + if (page_rec_is_supremum(rec)) { + /* This is just for safety. The record should be a + page infimum or a user record. */ + ut_ad(0); + return(ULINT_UNDEFINED); + } else if (!page_rec_is_infimum(rec)) { + return(ibuf_get_entry_counter_low(mtr, rec, space, page_no)); + } else if (only_leaf + || fil_page_get_prev(page_align(rec)) == FIL_NULL) { + /* The parent node pointer did not contain the + searched for (space, page_no), which means that the + search ended on the correct page regardless of the + counter value, and since we're at the infimum record, + there are no existing records. */ + return(0); } else { - /* The cursor is not positioned at or before a user record. */ - return(FALSE); + /* We used to read the previous page here. It would + break the latching order, because the caller has + buffer-fixed an insert buffer bitmap page. */ + return(ULINT_UNDEFINED); } - - /* Patch counter value in already built entry. */ - field = dtuple_get_nth_field(entry, 3); - data = dfield_get_data(field); - - mach_write_to_2(data + IBUF_REC_OFFSET_COUNTER, counter); - - return(TRUE); } /*********************************************************************//** @@ -3604,16 +3532,27 @@ fail_exit: } } - /* Patch correct counter value to the entry to insert. This can - change the insert position, which can result in the need to abort in - some cases. */ - if (!no_counter - && !ibuf_set_entry_counter(ibuf_entry, space, page_no, &pcur, - mode == BTR_MODIFY_PREV, &mtr)) { + if (!no_counter) { + /* Patch correct counter value to the entry to + insert. This can change the insert position, which can + result in the need to abort in some cases. */ + ulint counter = ibuf_get_entry_counter( + space, page_no, btr_pcur_get_rec(&pcur), &mtr, + btr_pcur_get_btr_cur(&pcur)->low_match + < IBUF_REC_FIELD_METADATA); + dfield_t* field; + + if (counter == ULINT_UNDEFINED) { bitmap_fail: - ibuf_mtr_commit(&bitmap_mtr); + ibuf_mtr_commit(&bitmap_mtr); + goto fail_exit; + } - goto fail_exit; + field = dtuple_get_nth_field( + ibuf_entry, IBUF_REC_FIELD_METADATA); + mach_write_to_2( + (byte*) dfield_get_data(field) + + IBUF_REC_OFFSET_COUNTER, counter); } /* Set the bitmap bit denoting that the insert buffer contains diff --git a/storage/innobase/include/btr0pcur.h b/storage/innobase/include/btr0pcur.h index f605c476844..140f94466db 100644 --- a/storage/innobase/include/btr0pcur.h +++ b/storage/innobase/include/btr0pcur.h @@ -263,14 +263,6 @@ btr_pcur_commit_specify_mtr( /*========================*/ btr_pcur_t* pcur, /*!< in: persistent cursor */ mtr_t* mtr); /*!< in: mtr to commit */ -/**************************************************************//** -Tests if a cursor is detached: that is the latch mode is BTR_NO_LATCHES. -@return TRUE if detached */ -UNIV_INLINE -ibool -btr_pcur_is_detached( -/*=================*/ - btr_pcur_t* pcur); /*!< in: persistent cursor */ /*********************************************************//** Moves the persistent cursor to the next record in the tree. If no records are left, the cursor stays 'after last in tree'. diff --git a/storage/innobase/include/btr0pcur.ic b/storage/innobase/include/btr0pcur.ic index d86601e5a32..054ce753c7d 100644 --- a/storage/innobase/include/btr0pcur.ic +++ b/storage/innobase/include/btr0pcur.ic @@ -389,38 +389,6 @@ btr_pcur_commit_specify_mtr( } /**************************************************************//** -Sets the pcur latch mode to BTR_NO_LATCHES. */ -UNIV_INLINE -void -btr_pcur_detach( -/*============*/ - btr_pcur_t* pcur) /*!< in: persistent cursor */ -{ - ut_a(pcur->pos_state == BTR_PCUR_IS_POSITIONED); - - pcur->latch_mode = BTR_NO_LATCHES; - - pcur->pos_state = BTR_PCUR_WAS_POSITIONED; -} - -/**************************************************************//** -Tests if a cursor is detached: that is the latch mode is BTR_NO_LATCHES. -@return TRUE if detached */ -UNIV_INLINE -ibool -btr_pcur_is_detached( -/*=================*/ - btr_pcur_t* pcur) /*!< in: persistent cursor */ -{ - if (pcur->latch_mode == BTR_NO_LATCHES) { - - return(TRUE); - } - - return(FALSE); -} - -/**************************************************************//** Sets the old_rec_buf field to NULL. */ UNIV_INLINE void diff --git a/storage/innobase/row/row0ins.c b/storage/innobase/row/row0ins.c index 6d34e3217af..6e311ce2e80 100644 --- a/storage/innobase/row/row0ins.c +++ b/storage/innobase/row/row0ins.c @@ -1655,7 +1655,7 @@ row_ins_scan_sec_index_for_duplicate( ulint n_fields_cmp; btr_pcur_t pcur; ulint err = DB_SUCCESS; - unsigned allow_duplicates; + ulint allow_duplicates; mtr_t mtr; mem_heap_t* heap = NULL; ulint offsets_[REC_OFFS_NORMAL_SIZE]; @@ -1686,7 +1686,7 @@ row_ins_scan_sec_index_for_duplicate( btr_pcur_open(index, entry, PAGE_CUR_GE, BTR_SEARCH_LEAF, &pcur, &mtr); - allow_duplicates = thr_get_trx(thr)->duplicates & TRX_DUP_IGNORE; + allow_duplicates = thr_get_trx(thr)->duplicates; /* Scan index records and check if there is a duplicate */ @@ -1820,7 +1820,7 @@ row_ins_duplicate_error_in_clust( sure that in roll-forward we get the same duplicate errors as in original execution */ - if (trx->duplicates & TRX_DUP_IGNORE) { + if (trx->duplicates) { /* If the SQL-query will update or replace duplicate key we will take X-lock for @@ -1864,7 +1864,7 @@ row_ins_duplicate_error_in_clust( offsets = rec_get_offsets(rec, cursor->index, offsets, ULINT_UNDEFINED, &heap); - if (trx->duplicates & TRX_DUP_IGNORE) { + if (trx->duplicates) { /* If the SQL-query will update or replace duplicate key we will take X-lock for diff --git a/storage/innobase/row/row0mysql.c b/storage/innobase/row/row0mysql.c index d0bedd69842..d06411e09f0 100644 --- a/storage/innobase/row/row0mysql.c +++ b/storage/innobase/row/row0mysql.c @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 2000, 2010, Innobase Oy. All Rights Reserved. +Copyright (c) 2000, 2011, Oracle and/or its affiliates. All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -3995,6 +3995,7 @@ end: trx->error_state = DB_SUCCESS; trx_general_rollback_for_mysql(trx, NULL); trx->error_state = DB_SUCCESS; + err = DB_ERROR; goto funct_exit; } |