From d2d03e923eebe1565ed7208cad393926eece4480 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Fri, 23 Oct 2009 13:04:55 +0200 Subject: [PATCH 001/212] Bug #48209 Confusing message "Test has succeeded" after test fails due to warnings There's no need for --verbose for the mysqltest in check-warnings, remove it --- mysql-test/mysql-test-run.pl | 1 - 1 file changed, 1 deletion(-) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 4d8d178e092..11ad4a56eb7 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -3739,7 +3739,6 @@ sub start_check_warnings ($$) { mtr_add_arg($args, "--skip-safemalloc"); mtr_add_arg($args, "--test-file=%s", "include/check-warnings.test"); - mtr_add_arg($args, "--verbose"); if ( $opt_embedded_server ) { From c22826bfad57cb216905bca37315cdbe3c8d8349 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Mon, 26 Oct 2009 14:28:51 +0100 Subject: [PATCH 002/212] Bug #48240 "Test suite timeout" and "Too many tests failed" is masked from status page Prepend "Completed", "Timeout" or "Too many failed" to summary line --- mysql-test/lib/mtr_report.pm | 7 +++++-- mysql-test/mysql-test-run.pl | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mysql-test/lib/mtr_report.pm b/mysql-test/lib/mtr_report.pm index 937e19111fb..81885c35a44 100644 --- a/mysql-test/lib/mtr_report.pm +++ b/mysql-test/lib/mtr_report.pm @@ -222,8 +222,8 @@ sub mtr_report_test ($) { } -sub mtr_report_stats ($;$) { - my ($tests, $dont_error)= @_; +sub mtr_report_stats ($$;$) { + my ($prefix, $tests, $dont_error)= @_; # ---------------------------------------------------------------------- # Find out how we where doing @@ -328,6 +328,9 @@ sub mtr_report_stats ($;$) { } } + # Print summary line prefix + print "$prefix: "; + # Print a list of testcases that failed if ( $tot_failed != 0 ) { diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 4d8d178e092..762fa75ed93 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -411,7 +411,7 @@ sub main { $opt_gcov_msg, $opt_gcov_err); } - mtr_report_stats($completed); + mtr_report_stats("Completed", $completed); exit(0); } @@ -533,7 +533,7 @@ sub run_test_server ($$$) { $num_failed_test >= $opt_max_test_fail) { $suite_timeout_proc->kill(); push(@$completed, $result); - mtr_report_stats($completed, 1); + mtr_report_stats("Too many failed", $completed, 1); mtr_report("Too many tests($num_failed_test) failed!", "Terminating..."); return undef; @@ -665,7 +665,7 @@ sub run_test_server ($$$) { # ---------------------------------------------------- if ( ! $suite_timeout_proc->wait_one(0) ) { - mtr_report_stats($completed, 1); + mtr_report_stats("Timeout", $completed, 1); mtr_report("Test suite timeout! Terminating..."); return undef; } From afb8b414daa9391e90147a82cd77e64b394e1621 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Wed, 4 Nov 2009 13:42:22 +0100 Subject: [PATCH 003/212] Bug #47663 mtr --parallel has weird output Some output is written, some is not Finally concluded it's a Perl bug: after running with parallel threads for a while, print suddenly ignores all but the first argument. Workaround: concatenate all the arguments into one, except in output that only comes before we start running tests --- mysql-test/lib/My/SafeProcess.pm | 6 +++--- mysql-test/lib/mtr_report.pm | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/mysql-test/lib/My/SafeProcess.pm b/mysql-test/lib/My/SafeProcess.pm index 7e102b628ca..ee7ed2471c7 100644 --- a/mysql-test/lib/My/SafeProcess.pm +++ b/mysql-test/lib/My/SafeProcess.pm @@ -338,12 +338,12 @@ sub start_kill { $ret= system($safe_kill, $winpid) >> 8; if ($ret == 3){ - print "Couldn't open the winpid: $winpid ", + print "Couldn't open the winpid: $winpid ". "for pid: $pid, try one more time\n"; sleep(1); $winpid= _winpid($pid); $ret= system($safe_kill, $winpid) >> 8; - print "Couldn't open the winpid: $winpid ", + print "Couldn't open the winpid: $winpid ". "for pid: $pid, continue and see what happens...\n"; } } @@ -594,7 +594,7 @@ sub self2str { sub _verbose { return unless $_verbose; - print STDERR " ## ", @_, "\n"; + print STDERR " ## ". @_. "\n"; } diff --git a/mysql-test/lib/mtr_report.pm b/mysql-test/lib/mtr_report.pm index 81885c35a44..1c4b940bbee 100644 --- a/mysql-test/lib/mtr_report.pm +++ b/mysql-test/lib/mtr_report.pm @@ -69,7 +69,7 @@ sub _mtr_report_test_name ($) { $tname.= " '$tinfo->{combination}'" if defined $tinfo->{combination}; - print _name(), _timestamp(); + print _name(). _timestamp(); printf "%-40s ", $tname; my $worker = $tinfo->{worker}; printf "w$worker " if $worker; @@ -390,13 +390,13 @@ sub mtr_report_stats ($$;$) { ############################################################################## sub mtr_print_line () { - print '-' x 60, "\n"; + print '-' x 60 . "\n"; } sub mtr_print_thick_line { my $char= shift || '='; - print $char x 78, "\n"; + print $char x 78 . "\n"; } @@ -454,7 +454,7 @@ sub _timestamp { # Always print message to screen sub mtr_print (@) { - print _name(), join(" ", @_), "\n"; + print _name(). join(" ", @_). "\n"; } @@ -462,22 +462,22 @@ sub mtr_print (@) { sub mtr_report (@) { if (defined $verbose) { - print _name(), join(" ", @_), "\n"; + print _name(). join(" ", @_). "\n"; } } # Print warning to screen sub mtr_warning (@) { - print STDERR _name(), _timestamp(), - "mysql-test-run: WARNING: ", join(" ", @_), "\n"; + print STDERR _name(). _timestamp(). + "mysql-test-run: WARNING: ". join(" ", @_). "\n"; } # Print error to screen and then exit sub mtr_error (@) { - print STDERR _name(), _timestamp(), - "mysql-test-run: *** ERROR: ", join(" ", @_), "\n"; + print STDERR _name(). _timestamp(). + "mysql-test-run: *** ERROR: ". join(" ", @_). "\n"; if (IS_WINDOWS) { POSIX::_exit(1); @@ -492,8 +492,8 @@ sub mtr_error (@) { sub mtr_debug (@) { if ( $verbose > 2 ) { - print STDERR _name(), - _timestamp(), "####: ", join(" ", @_), "\n"; + print STDERR _name(). + _timestamp(). "####: ". join(" ", @_). "\n"; } } @@ -501,8 +501,8 @@ sub mtr_debug (@) { sub mtr_verbose (@) { if ( $verbose ) { - print STDERR _name(), _timestamp(), - "> ",join(" ", @_),"\n"; + print STDERR _name(). _timestamp(). + "> ".join(" ", @_)."\n"; } } @@ -512,8 +512,8 @@ sub mtr_verbose_restart (@) { my $proc= $server->{proc}; if ( $verbose_restart ) { - print STDERR _name(),_timestamp(), - "> Restart $proc - ",join(" ", @args),"\n"; + print STDERR _name()._timestamp(). + "> Restart $proc - ".join(" ", @args)."\n"; } } From b229781c2d58603cb39b9e4cee2ba1c20f9d183f Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Wed, 11 Nov 2009 12:46:19 +0100 Subject: [PATCH 004/212] Bug #48671 mysqltest fails on 'perl' in file sourced inside 'while' Actually, fails on 'perl' in any while Fixed essentially the same way as for append_file --- client/mysqltest.cc | 87 +++++++++++++++++++---------------- mysql-test/r/mysqltest.result | 2 + mysql-test/t/mysqltest.test | 14 ++++++ 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 8de2f0c79b0..cfcabb5a5ab 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -3632,49 +3632,58 @@ void do_perl(struct st_command *command) sizeof(perl_args)/sizeof(struct command_arg), ' '); - /* If no delimiter was provided, use EOF */ - if (ds_delimiter.length == 0) - dynstr_set(&ds_delimiter, "EOF"); - - init_dynamic_string(&ds_script, "", 1024, 1024); - read_until_delimiter(&ds_script, &ds_delimiter); - - DBUG_PRINT("info", ("Executing perl: %s", ds_script.str)); - - /* Create temporary file name */ - if ((fd= create_temp_file(temp_file_path, getenv("MYSQLTEST_VARDIR"), - "tmp", O_CREAT | O_SHARE | O_RDWR, - MYF(MY_WME))) < 0) - die("Failed to create temporary file for perl command"); - my_close(fd, MYF(0)); - - str_to_file(temp_file_path, ds_script.str, ds_script.length); - - /* Format the "perl " command */ - my_snprintf(buf, sizeof(buf), "perl %s", temp_file_path); - - if (!(res_file= popen(buf, "r")) && command->abort_on_error) - die("popen(\"%s\", \"r\") failed", buf); - - while (fgets(buf, sizeof(buf), res_file)) + ds_script= command->content; + /* If it hasn't been done already by a loop iteration, fill it in */ + if (! ds_script.str) { - if (disable_result_log) - { - buf[strlen(buf)-1]=0; - DBUG_PRINT("exec_result",("%s", buf)); - } - else - { - replace_dynstr_append(&ds_res, buf); - } + /* If no delimiter was provided, use EOF */ + if (ds_delimiter.length == 0) + dynstr_set(&ds_delimiter, "EOF"); + + init_dynamic_string(&ds_script, "", 1024, 1024); + read_until_delimiter(&ds_script, &ds_delimiter); + command->content= ds_script; } - error= pclose(res_file); - /* Remove the temporary file */ - my_delete(temp_file_path, MYF(0)); + /* This function could be called even if "false", so check before doing */ + if (cur_block->ok) + { + DBUG_PRINT("info", ("Executing perl: %s", ds_script.str)); - handle_command_error(command, WEXITSTATUS(error)); - dynstr_free(&ds_script); + /* Create temporary file name */ + if ((fd= create_temp_file(temp_file_path, getenv("MYSQLTEST_VARDIR"), + "tmp", O_CREAT | O_SHARE | O_RDWR, + MYF(MY_WME))) < 0) + die("Failed to create temporary file for perl command"); + my_close(fd, MYF(0)); + + str_to_file(temp_file_path, ds_script.str, ds_script.length); + + /* Format the "perl " command */ + my_snprintf(buf, sizeof(buf), "perl %s", temp_file_path); + + if (!(res_file= popen(buf, "r")) && command->abort_on_error) + die("popen(\"%s\", \"r\") failed", buf); + + while (fgets(buf, sizeof(buf), res_file)) + { + if (disable_result_log) + { + buf[strlen(buf)-1]=0; + DBUG_PRINT("exec_result",("%s", buf)); + } + else + { + replace_dynstr_append(&ds_res, buf); + } + } + error= pclose(res_file); + + /* Remove the temporary file */ + my_delete(temp_file_path, MYF(0)); + + handle_command_error(command, WEXITSTATUS(error)); + } dynstr_free(&ds_delimiter); DBUG_VOID_RETURN; } diff --git a/mysql-test/r/mysqltest.result b/mysql-test/r/mysqltest.result index 2e3a9489593..671f88cb00a 100644 --- a/mysql-test/r/mysqltest.result +++ b/mysql-test/r/mysqltest.result @@ -553,6 +553,8 @@ hello mysqltest: At line 1: Max delimiter length(16) exceeded hello hello +val is 5 +val is 5 mysqltest: At line 1: test of die Some output create table t1( a int, b char(255), c timestamp); diff --git a/mysql-test/t/mysqltest.test b/mysql-test/t/mysqltest.test index bcf33aa8c27..b4ea9202df7 100644 --- a/mysql-test/t/mysqltest.test +++ b/mysql-test/t/mysqltest.test @@ -1905,6 +1905,20 @@ perl; print "hello\n"; EOF +# Test perl within while, also with if being false first iteration +let $outer= 3; +let $ifval= 0; +while ($outer) { + if ($ifval) { + perl UNTIL; + my $val= 5; + print "val is $val\n"; +UNTIL + } + inc $ifval; + dec $outer; +} + # ---------------------------------------------------------------------------- # test for die # ---------------------------------------------------------------------------- From f1b4be2a21c55ca69ba99c27f4fd8f66cfa7e4f8 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 12 Nov 2009 11:04:01 +0100 Subject: [PATCH 005/212] Bug #43418 MTR2: does not notice a memory leak occuring at shutdown of mysqld w/ --valgrind Checking is done before server is terminated Adds post processing of server logs if --valgrind Also had to remove --quiet option to valgrind --- mysql-test/include/mtr_warnings.sql | 8 ++++ mysql-test/mysql-test-run.pl | 71 +++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/mysql-test/include/mtr_warnings.sql b/mysql-test/include/mtr_warnings.sql index 57e7cb97d48..0176c1800e8 100644 --- a/mysql-test/include/mtr_warnings.sql +++ b/mysql-test/include/mtr_warnings.sql @@ -175,6 +175,14 @@ INSERT INTO global_suppressions VALUES ("Can't find file: '.\\\\test\\\\\\?{8}.frm'"), ("Slave: Unknown table 't1' Error_code: 1051"), + /* Messages from valgrind */ + ("==[0-9]*== Memcheck,"), + ("==[0-9]*== Copyright"), + ("==[0-9]*== Using"), + ("==[0-9]*== For more details"), + /* This comes with innodb plugin tests */ + ("==[0-9]*== Warning: set address range perms: large range .* .defined."), + ("THE_LAST_SUPPRESSION")|| diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index c91a74570bd..4ac3beff0a6 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -226,6 +226,7 @@ my @default_valgrind_args= ("--show-reachable=yes"); my @valgrind_args; my $opt_valgrind_path; my $opt_callgrind; +my %mysqld_logs; my $opt_debug_sync_timeout= 300; # Default timeout for WAIT_FOR actions. our $opt_warnings= 1; @@ -741,6 +742,9 @@ sub run_worker ($) { elsif ($line eq 'BYE'){ mtr_report("Server said BYE"); stop_all_servers($opt_shutdown_timeout); + if ($opt_valgrind_mysqld) { + valgrind_exit_reports(); + } exit(0); } else { @@ -1340,8 +1344,7 @@ sub command_line_setup { push(@valgrind_args, @default_valgrind_args) unless @valgrind_args; - # Make valgrind run in quiet mode so it only print errors - push(@valgrind_args, "--quiet" ); + # Don't add --quiet; you will loose the summary reports. mtr_report("Running valgrind with options \"", join(" ", @valgrind_args), "\""); @@ -3690,7 +3693,7 @@ sub extract_warning_lines ($$) { ( qr/^Warning:|mysqld: Warning|\[Warning\]/, qr/^Error:|\[ERROR\]/, - qr/^==\d*==/, # valgrind errors + qr/^==\d+==\s+\S/, # valgrind errors qr/InnoDB: Warning|InnoDB: Error/, qr/^safe_mutex:|allocated at line/, qr/missing DBUG_RETURN/, @@ -4296,6 +4299,8 @@ sub mysqld_start ($$) { # see the exact location where valgrind complains $output= "$opt_vardir/log/".$mysqld->name().".trace"; } + # Remember this log file for valgrind error report search + $mysqld_logs{$output}= 1 if $opt_valgrind; if ( defined $exe ) { @@ -5148,6 +5153,66 @@ sub valgrind_arguments { } } +# +# Search server logs for valgrind reports printed at mysqld termination +# + +sub valgrind_exit_reports() { + foreach my $log_file (keys %mysqld_logs) + { + my @culprits= (); + my $valgrind_rep= ""; + my $found_report= 0; + my $err_in_report= 0; + + my $LOGF = IO::File->new($log_file) + or mtr_error("Could not open file '$log_file' for reading: $!"); + + while ( my $line = <$LOGF> ) + { + if ($line =~ /^CURRENT_TEST: (.+)$/) + { + my $testname= $1; + # If we have a report, report it if needed and start new list of tests + if ($found_report) + { + if ($err_in_report) + { + mtr_print ("Valgrind report from $log_file after tests:\n", + @culprits); + mtr_print_line(); + print ("$valgrind_rep\n"); + $err_in_report= 0; + } + # Make ready to collect new report + @culprits= (); + $found_report= 0; + $valgrind_rep= ""; + } + push (@culprits, $testname); + next; + } + # This line marks the start of a valgrind report + $found_report= 1 if $line =~ /ERROR SUMMARY:/; + + if ($found_report) { + $line=~ s/^==\d+== //; + $valgrind_rep .= $line; + $err_in_report= 1 if $line =~ /ERROR SUMMARY: [1-9]/; + $err_in_report= 1 if $line =~ /definitely lost: [1-9]/; + $err_in_report= 1 if $line =~ /possibly lost: [1-9]/; + } + } + + $LOGF= undef; + + if ($err_in_report) { + mtr_print ("Valgrind report from $log_file after tests:\n", @culprits); + mtr_print_line(); + print ("$valgrind_rep\n"); + } + } +} # # Usage From 858d260bd3d7037c920a50c9ea568544058ced7a Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Mon, 16 Nov 2009 11:18:16 +0100 Subject: [PATCH 006/212] Bug #48795 Valgrind summary from tests where server has been restarted cause test failure Seen in a few tests after 43418 Add code in extract_warning_lines() to skip this part --- mysql-test/mysql-test-run.pl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 4ac3beff0a6..4501afa8ee7 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -3700,9 +3700,17 @@ sub extract_warning_lines ($$) { qr/Attempting backtrace/, qr/Assertion .* failed/, ); + my $skip_valgrind= 0; foreach my $line ( @lines ) { + if ($opt_valgrind_mysqld) { + # Skip valgrind summary from tests where server has been restarted + # Should this contain memory leaks, the final report will find it + $skip_valgrind= 1 if $line =~ /^==\d+== ERROR SUMMARY:/; + $skip_valgrind= 0 unless $line =~ /^==\d+==/; + next if $skip_valgrind; + } foreach my $pat ( @patterns ) { if ( $line =~ /$pat/ ) From 052688855ee98ba031aa74773399f136752c6685 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Mon, 16 Nov 2009 14:46:33 +0100 Subject: [PATCH 007/212] minor fix of a valgrind suppress pattern --- mysql-test/include/mtr_warnings.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/include/mtr_warnings.sql b/mysql-test/include/mtr_warnings.sql index 0176c1800e8..7502470979e 100644 --- a/mysql-test/include/mtr_warnings.sql +++ b/mysql-test/include/mtr_warnings.sql @@ -181,7 +181,7 @@ INSERT INTO global_suppressions VALUES ("==[0-9]*== Using"), ("==[0-9]*== For more details"), /* This comes with innodb plugin tests */ - ("==[0-9]*== Warning: set address range perms: large range .* .defined."), + ("==[0-9]*== Warning: set address range perms: large range"), ("THE_LAST_SUPPRESSION")|| From 830a2b1bbd9ce1e4b189c0057f2e4c4f02d0ad3f Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Tue, 17 Nov 2009 09:36:09 +0100 Subject: [PATCH 008/212] mtr ignoring of skip-im incorrectly removed by backport of WL#4085 --- mysql-test/mysql-test-run.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 6a6be3b6f1e..aeda46e6c5b 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -824,6 +824,7 @@ sub command_line_setup { 'combination=s' => \@opt_combinations, 'skip-combinations' => \&collect_option, 'experimental=s' => \$opt_experimental, + 'skip-im' => \&ignore_option, # Specify ports 'build-thread|mtr-build-thread=i' => \$opt_build_thread, From 242f3f8cddca4c29bc3e88496e11ae8175a7b67b Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Tue, 17 Nov 2009 12:13:22 +0100 Subject: [PATCH 009/212] Bug #48806 mysql-test-run.pl --help should work even in the absence of binaries Searches for my_safe_process binary too early Put this into a sub() and call it after examining options (incl. --help) --- mysql-test/lib/My/SafeProcess.pm | 34 ++++++++++++++++++-------------- mysql-test/mysql-test-run.pl | 3 +++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/mysql-test/lib/My/SafeProcess.pm b/mysql-test/lib/My/SafeProcess.pm index ee7ed2471c7..a620a7c6c72 100644 --- a/mysql-test/lib/My/SafeProcess.pm +++ b/mysql-test/lib/My/SafeProcess.pm @@ -81,24 +81,28 @@ sub is_child { } -# Find the safe process binary or script my @safe_process_cmd; my $safe_kill; -if (IS_WIN32PERL or IS_CYGWIN){ - # Use my_safe_process.exe - my $exe= my_find_bin(".", ["lib/My/SafeProcess", "My/SafeProcess"], - "my_safe_process"); - push(@safe_process_cmd, $exe); - # Use my_safe_kill.exe - $safe_kill= my_find_bin(".", "lib/My/SafeProcess", "my_safe_kill"); -} -else -{ - # Use my_safe_process - my $exe= my_find_bin(".", ["lib/My/SafeProcess", "My/SafeProcess"], - "my_safe_process"); - push(@safe_process_cmd, $exe); +# Find the safe process binary or script +sub find_bin { + if (IS_WIN32PERL or IS_CYGWIN) + { + # Use my_safe_process.exe + my $exe= my_find_bin(".", ["lib/My/SafeProcess", "My/SafeProcess"], + "my_safe_process"); + push(@safe_process_cmd, $exe); + + # Use my_safe_kill.exe + $safe_kill= my_find_bin(".", "lib/My/SafeProcess", "my_safe_kill"); + } + else + { + # Use my_safe_process + my $exe= my_find_bin(".", ["lib/My/SafeProcess", "My/SafeProcess"], + "my_safe_process"); + push(@safe_process_cmd, $exe); + } } diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 4501afa8ee7..1184d994627 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -266,6 +266,9 @@ sub main { command_line_setup(); + # --help will not reach here, so now it's safe to assume we have binaries + My::SafeProcess::find_bin(); + if ( $opt_gcov ) { gcov_prepare($basedir); } From eb2ad909d42e7c14a81d3f6fa4d0b7438c3c06d4 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Wed, 18 Nov 2009 10:27:43 +0100 Subject: [PATCH 010/212] Bug #48808 mysql-test-run.pl --debugger=devenv does not work Wrong argument order, fixed --- mysql-test/mysql-test-run.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 4501afa8ee7..d07b976a26d 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -5086,9 +5086,9 @@ sub debugger_arguments { { # vc[express] /debugexe exe arg1 .. argn - # Add /debugexe and name of the exe before args - unshift(@$$args, "/debugexe"); + # Add name of the exe and /debugexe before args unshift(@$$args, "$$exe"); + unshift(@$$args, "/debugexe"); # Set exe to debuggername $$exe= $debugger; From 9ea158edf001fcb00361ea7695d44686ea2a36cd Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 19 Nov 2009 09:58:50 +0100 Subject: [PATCH 011/212] Bug #48367 MTR should react to server dying, rather than to resulting failure of mysqltest For some reason it usually picks up mysqltest Wait .1s and then see if a server has died Change from first commit: label on separate line --- mysql-test/mysql-test-run.pl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 4501afa8ee7..e8da2c9b5c2 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -3495,6 +3495,14 @@ sub run_testcase ($) { run_on_all($tinfo, "analyze-$analyze"); } + # Wait a bit and see if a server died, if so report that instead + mtr_milli_sleep(100); + my $srvproc= My::SafeProcess::check_any(); + if ($srvproc && grep($srvproc eq $_, started(all_servers()))) { + $proc= $srvproc; + goto SRVDIED; + } + # Test case failure reported by mysqltest report_failure_and_restart($tinfo); } @@ -3520,6 +3528,7 @@ sub run_testcase ($) { # ---------------------------------------------------- # Check if it was an expected crash # ---------------------------------------------------- + SRVDIED: my $check_crash = check_expected_crash_and_restart($proc); if ($check_crash) { From ea4618e1d1a580e2ff2bdc6f854fa60a9e2bfdaa Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 19 Nov 2009 10:10:21 +0100 Subject: [PATCH 012/212] Bug #35543 mysqlbinlog.cc does not properly work with tmp files mtr patch ported to v2 --- mysql-test/mysql-test-run.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 4501afa8ee7..bd11ffde5fe 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -2511,6 +2511,7 @@ sub create_config_file_for_extern { # binlog reads from [client] and [mysqlbinlog] [mysqlbinlog] character-sets-dir= $path_charsetsdir +local-load= $opt_tmpdir # mysql_fix_privilege_tables.sh don't read from [client] [mysql_fix_privilege_tables] From 154ca3d925d3ea3296689316e46d7e1f5b7076ba Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 19 Nov 2009 13:19:11 +0100 Subject: [PATCH 013/212] Bug #48683 mysql-stress-test fails with "Value ... invalid for option abort-on-error" Add =1 to --abort-on-error argument --- mysql-test/lib/mtr_stress.pl | 2 +- mysql-test/lib/v1/mtr_stress.pl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-test/lib/mtr_stress.pl b/mysql-test/lib/mtr_stress.pl index cd5c7b0dbb7..702bc178ae5 100644 --- a/mysql-test/lib/mtr_stress.pl +++ b/mysql-test/lib/mtr_stress.pl @@ -150,7 +150,7 @@ sub run_stress_test () mtr_add_arg($args, "--verbose"); mtr_add_arg($args, "--cleanup"); mtr_add_arg($args, "--log-error-details"); - mtr_add_arg($args, "--abort-on-error"); + mtr_add_arg($args, "--abort-on-error=1"); if ( $::opt_stress_init_file ) { diff --git a/mysql-test/lib/v1/mtr_stress.pl b/mysql-test/lib/v1/mtr_stress.pl index 93b06b32c5f..40800c9729b 100644 --- a/mysql-test/lib/v1/mtr_stress.pl +++ b/mysql-test/lib/v1/mtr_stress.pl @@ -150,7 +150,7 @@ sub run_stress_test () mtr_add_arg($args, "--verbose"); mtr_add_arg($args, "--cleanup"); mtr_add_arg($args, "--log-error-details"); - mtr_add_arg($args, "--abort-on-error"); + mtr_add_arg($args, "--abort-on-error=1"); if ( $::opt_stress_init_file ) { From e1fbb32e76d11c743fd6852e976a7c1451c5a2fe Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Tue, 24 Nov 2009 09:12:48 +0100 Subject: [PATCH 014/212] Bug #47978 timer : expired after 90 seconds Problems occur after killing threads on Windows Get rid of the timeout threads, implement simple timer in wait_any_timeout() --- mysql-test/lib/My/SafeProcess.pm | 91 ++++++++++++-------------------- mysql-test/lib/mtr_misc.pl | 13 ++++- mysql-test/mysql-test-run.pl | 79 ++++++++++----------------- 3 files changed, 72 insertions(+), 111 deletions(-) diff --git a/mysql-test/lib/My/SafeProcess.pm b/mysql-test/lib/My/SafeProcess.pm index a620a7c6c72..bfcad910a16 100644 --- a/mysql-test/lib/My/SafeProcess.pm +++ b/mysql-test/lib/My/SafeProcess.pm @@ -187,63 +187,6 @@ sub run { return $proc->exit_status(); } -# -# Start a process that returns after "duration" seconds -# or when it's parent process does not exist anymore -# -sub timer { - my $class= shift; - my $duration= shift or croak "duration required"; - my $parent_pid= $$; - - my $pid= My::SafeProcess::Base::_safe_fork(); - if ($pid){ - # Parent - my $proc= bless - ({ - SAFE_PID => $pid, - SAFE_NAME => "timer", - PARENT => $$, - }, $class); - - # Put the new process in list of running - $running{$pid}= $proc; - return $proc; - } - - # Child, install signal handlers and sleep for "duration" - $SIG{INT}= 'IGNORE'; - - $SIG{TERM}= sub { - #print STDERR "timer $$: woken up, exiting!\n"; - exit(0); - }; - - $0= "safe_timer($duration)"; - - if (IS_WIN32PERL){ - # Just a thread in same process - sleep($duration); - print STDERR "timer $$: expired after $duration seconds\n"; - exit(0); - } - - my $count_down= $duration; - while($count_down--){ - - # Check that parent is still alive - if (kill(0, $parent_pid) == 0){ - #print STDERR "timer $$: parent gone, exiting!\n"; - exit(0); - } - - sleep(1); - } - print STDERR "timer $$: expired after $duration seconds\n"; - exit(0); -} - - # # Shutdown process nicely, and wait for shutdown_timeout seconds # If processes hasn't shutdown, kill them hard and wait for return @@ -541,6 +484,40 @@ sub wait_any { } +# +# Wait for any process to exit, or a timeout +# +# Returns a reference to the SafeProcess that +# exited or a pseudo-process with $proc->{timeout} == 1 +# + +sub wait_any_timeout { + my $class= shift; + my $timeout= shift; + my $proc; + my $millis=10; + + do { + ::mtr_milli_sleep($millis); + # Slowly increse interval up to max. 1 second + $millis++ if $millis < 1000; + # Return a "fake" process for timeout + if (::has_expired($timeout)) { + $proc= bless + ({ + SAFE_PID => 0, + SAFE_NAME => "timer", + timeout => 1, + }, $class); + } else { + $proc= check_any(); + } + } while (! $proc); + + return $proc; +} + + # # Wait for all processes to exit # diff --git a/mysql-test/lib/mtr_misc.pl b/mysql-test/lib/mtr_misc.pl index 658eb270535..97eb693b52e 100644 --- a/mysql-test/lib/mtr_misc.pl +++ b/mysql-test/lib/mtr_misc.pl @@ -30,7 +30,9 @@ sub mtr_script_exists(@); sub mtr_file_exists(@); sub mtr_exe_exists(@); sub mtr_exe_maybe_exists(@); - +sub mtr_milli_sleep($); +sub start_timer($); +sub has_expired($); ############################################################################## # @@ -167,11 +169,18 @@ sub mtr_exe_exists (@) { } -sub mtr_milli_sleep { +sub mtr_milli_sleep ($) { die "usage: mtr_milli_sleep(milliseconds)" unless @_ == 1; my ($millis)= @_; select(undef, undef, undef, ($millis/1000)); } +# Simple functions to start and check timers (have to be actively polled) +# Timer can be "killed" by setting it to 0 + +sub start_timer ($) { return time + $_[0]; } + +sub has_expired ($) { return $_[0] && time gt $_[0]; } + 1; diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index ae9674bbde5..2147b4ae7a7 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -439,7 +439,7 @@ sub run_test_server ($$$) { my $result; my $exe_mysqld= find_mysqld($basedir) || ""; # Used as hint to CoreDump - my $suite_timeout_proc= My::SafeProcess->timer(suite_timeout()); + my $suite_timeout= start_timer(suite_timeout()); my $s= IO::Select->new(); $s->add($server); @@ -460,7 +460,6 @@ sub run_test_server ($$$) { mtr_verbose("Child closed socket"); $s->remove($sock); if (--$childs == 0){ - $suite_timeout_proc->kill(); return $completed; } next; @@ -529,13 +528,11 @@ sub run_test_server ($$$) { if ( !$opt_force ) { # Test has failed, force is off - $suite_timeout_proc->kill(); push(@$completed, $result); return $completed; } elsif ($opt_max_test_fail > 0 and $num_failed_test >= $opt_max_test_fail) { - $suite_timeout_proc->kill(); push(@$completed, $result); mtr_report_stats("Too many failed", $completed, 1); mtr_report("Too many tests($num_failed_test) failed!", @@ -667,7 +664,7 @@ sub run_test_server ($$$) { # ---------------------------------------------------- # Check if test suite timer expired # ---------------------------------------------------- - if ( ! $suite_timeout_proc->wait_one(0) ) + if ( has_expired($suite_timeout) ) { mtr_report_stats("Timeout", $completed, 1); mtr_report("Test suite timeout! Terminating..."); @@ -2944,11 +2941,11 @@ sub check_testcase($$) # Return immediately if no check proceess was started return 0 unless ( keys %started ); - my $timeout_proc= My::SafeProcess->timer(check_timeout()); + my $timeout= start_timer(check_timeout()); while (1){ my $result; - my $proc= My::SafeProcess->wait_any(); + my $proc= My::SafeProcess->wait_any_timeout($timeout); mtr_report("Got $proc"); if ( delete $started{$proc->pid()} ) { @@ -2972,9 +2969,6 @@ sub check_testcase($$) if ( keys(%started) == 0){ # All checks completed - - $timeout_proc->kill(); - return 0; } # Wait for next process to exit @@ -3015,10 +3009,9 @@ test case was executed:\n"; } } - elsif ( $proc eq $timeout_proc ) { - $tinfo->{comment}.= "Timeout $timeout_proc for ". - "'check-testcase' expired after ".check_timeout(). - " seconds"; + elsif ( $proc->{timeout} ) { + $tinfo->{comment}.= "Timeout for 'check-testcase' expired after " + .check_timeout()." seconds"; $result= 4; } else { @@ -3033,8 +3026,6 @@ test case was executed:\n"; # Kill any check processes still running map($_->kill(), values(%started)); - $timeout_proc->kill(); - return $result; } @@ -3106,11 +3097,11 @@ sub run_on_all($$) # Return immediately if no check proceess was started return 0 unless ( keys %started ); - my $timeout_proc= My::SafeProcess->timer(check_timeout()); + my $timeout= start_timer(check_timeout()); while (1){ my $result; - my $proc= My::SafeProcess->wait_any(); + my $proc= My::SafeProcess->wait_any_timeout($timeout); mtr_report("Got $proc"); if ( delete $started{$proc->pid()} ) { @@ -3129,17 +3120,15 @@ sub run_on_all($$) if ( keys(%started) == 0){ # All completed - $timeout_proc->kill(); return 0; } # Wait for next process to exit next; } - elsif ( $proc eq $timeout_proc ) { - $tinfo->{comment}.= "Timeout $timeout_proc for '$run' ". - "expired after ". check_timeout(). - " seconds"; + elsif ($proc->{timeout}) { + $tinfo->{comment}.= "Timeout for '$run' expired after " + .check_timeout()." seconds"; } else { # Unknown process returned, most likley a crash, abort everything @@ -3151,8 +3140,6 @@ sub run_on_all($$) # Kill any check processes still running map($_->kill(), values(%started)); - $timeout_proc->kill(); - return 1; } mtr_error("INTERNAL_ERROR: run_on_all"); @@ -3382,7 +3369,7 @@ sub run_testcase ($) { } } - my $test_timeout_proc= My::SafeProcess->timer(testcase_timeout()); + my $test_timeout= start_timer(testcase_timeout()); do_before_run_mysqltest($tinfo); @@ -3390,9 +3377,6 @@ sub run_testcase ($) { # Failed to record state of server or server crashed report_failure_and_restart($tinfo); - # Stop the test case timer - $test_timeout_proc->kill(); - return 1; } @@ -3410,20 +3394,20 @@ sub run_testcase ($) { if ($proc) { mtr_verbose ("Found exited process $proc"); - # If that was the timeout, cancel waiting - if ( $proc eq $test_timeout_proc ) - { - $keep_waiting_proc = 0; - } } else { $proc = $keep_waiting_proc; + # Also check if timer has expired, if so cancel waiting + if ( has_expired($test_timeout) ) + { + $keep_waiting_proc = 0; + } } } - else + if (! $keep_waiting_proc) { - $proc= My::SafeProcess->wait_any(); + $proc= My::SafeProcess->wait_any_timeout($test_timeout); } # Will be restored if we need to keep waiting @@ -3440,9 +3424,6 @@ sub run_testcase ($) { # ---------------------------------------------------- if ($proc eq $test) { - # Stop the test case timer - $test_timeout_proc->kill(); - my $res= $test->exit_status(); if ($res == 0 and $opt_warnings and check_warnings($tinfo) ) @@ -3545,7 +3526,7 @@ sub run_testcase ($) { # ---------------------------------------------------- # Stop the test case timer # ---------------------------------------------------- - $test_timeout_proc->kill(); + $test_timeout= 0; # ---------------------------------------------------- # Check if it was a server that died @@ -3584,7 +3565,7 @@ sub run_testcase ($) { # ---------------------------------------------------- # Check if testcase timer expired # ---------------------------------------------------- - if ( $proc eq $test_timeout_proc ) + if ( $proc->{timeout} ) { my $log_file_name= $opt_vardir."/log/".$tinfo->{shortname}.".log"; $tinfo->{comment}= @@ -3825,11 +3806,11 @@ sub check_warnings ($) { # Return immediately if no check proceess was started return 0 unless ( keys %started ); - my $timeout_proc= My::SafeProcess->timer(check_timeout()); + my $timeout= start_timer(check_timeout()); while (1){ my $result= 0; - my $proc= My::SafeProcess->wait_any(); + my $proc= My::SafeProcess->wait_any_timeout($timeout); mtr_report("Got $proc"); if ( delete $started{$proc->pid()} ) { @@ -3858,9 +3839,6 @@ sub check_warnings ($) { if ( keys(%started) == 0){ # All checks completed - - $timeout_proc->kill(); - return $result; } # Wait for next process to exit @@ -3877,10 +3855,9 @@ sub check_warnings ($) { $result= 2; } } - elsif ( $proc eq $timeout_proc ) { - $tinfo->{comment}.= "Timeout $timeout_proc for ". - "'check warnings' expired after ".check_timeout(). - " seconds"; + elsif ( $proc->{timeout} ) { + $tinfo->{comment}.= "Timeout for 'check warnings' expired after " + .check_timeout()." seconds"; $result= 4; } else { @@ -3894,8 +3871,6 @@ sub check_warnings ($) { # Kill any check processes still running map($_->kill(), values(%started)); - $timeout_proc->kill(); - return $result; } From 69c540588a627bad4875373953b2923d840a2c25 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 26 Nov 2009 11:16:06 +0100 Subject: [PATCH 015/212] Bug #48250 mysqtest_embedded can lock destroyed mutex As suggested, replaced relevant uses of my_fopen with fopen (and close) Tested on HPUX where it was reproducable with test innodb_bug30919 --- client/mysqltest.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index cfcabb5a5ab..3cd87bd3236 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -1119,7 +1119,7 @@ void close_files() if (cur_file->file && cur_file->file != stdin) { DBUG_PRINT("info", ("closing file: %s", cur_file->file_name)); - my_fclose(cur_file->file, MYF(0)); + fclose(cur_file->file); } my_free((uchar*) cur_file->file_name, MYF(MY_ALLOW_ZERO_PTR)); cur_file->file_name= 0; @@ -2441,7 +2441,7 @@ int open_file(const char *name) if (cur_file == file_stack_end) die("Source directives are nesting too deep"); cur_file++; - if (!(cur_file->file = my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(0)))) + if (!(cur_file->file = fopen(buff, "rb"))) { cur_file--; die("Could not open '%s' for reading, errno: %d", buff, errno); @@ -5286,7 +5286,7 @@ int read_line(char *buf, int size) found_eof: if (cur_file->file != stdin) { - my_fclose(cur_file->file, MYF(0)); + fclose(cur_file->file); cur_file->file= 0; } my_free((uchar*) cur_file->file_name, MYF(MY_ALLOW_ZERO_PTR)); @@ -5865,7 +5865,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), fn_format(buff, argument, "", "", MY_UNPACK_FILENAME); DBUG_ASSERT(cur_file == file_stack && cur_file->file == 0); if (!(cur_file->file= - my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(0)))) + fopen(buff, "rb"))) die("Could not open '%s' for reading, errno: %d", buff, errno); cur_file->file_name= my_strdup(buff, MYF(MY_FAE)); cur_file->lineno= 1; From 22d80703efc8b1f548bce62dd2e785a83a3100ba Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 26 Nov 2009 11:23:30 +0100 Subject: [PATCH 016/212] Bug #49096 mtr: --mem option should be ignored on Windows Ignored w/message in v1 and v2 --- mysql-test/lib/v1/mysql-test-run.pl | 5 +++++ mysql-test/mysql-test-run.pl | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/mysql-test/lib/v1/mysql-test-run.pl b/mysql-test/lib/v1/mysql-test-run.pl index 86ad5c485c1..9630c65ade4 100755 --- a/mysql-test/lib/v1/mysql-test-run.pl +++ b/mysql-test/lib/v1/mysql-test-run.pl @@ -905,6 +905,11 @@ sub command_line_setup () { mtr_report("Using default engine '$used_default_engine'") if defined $used_default_engine; + if ($glob_win32 and defined $opt_mem) { + mtr_report("--mem not supported on Windows, ignored"); + $opt_mem= undef; + } + # -------------------------------------------------------------------------- # Check if we should speed up tests by trying to run on tmpfs # -------------------------------------------------------------------------- diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 2147b4ae7a7..e7f8e90dc44 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -1080,6 +1080,11 @@ sub command_line_setup { } } + if (IS_WINDOWS and defined $opt_mem) { + mtr_report("--mem not supported on Windows, ignored"); + $opt_mem= undef; + } + # -------------------------------------------------------------------------- # Check if we should speed up tests by trying to run on tmpfs # -------------------------------------------------------------------------- From 50ab925e046f197daa97057158fda532f6fe1c81 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 26 Nov 2009 11:34:16 +0100 Subject: [PATCH 017/212] Bug #48918 MTR uses an un-initialized value in comparison mysqld->{proc} not defined for an embedded server Check only if {proc} defined --- mysql-test/mysql-test-run.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index e7f8e90dc44..7d2426459d0 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -3893,7 +3893,7 @@ sub check_expected_crash_and_restart { foreach my $mysqld ( mysqlds() ) { - next unless ( $mysqld->{proc} eq $proc ); + next unless ( $mysqld->{proc} and $mysqld->{proc} eq $proc ); # Check if crash expected by looking at the .expect file # in var/tmp From eff3780dd8f0e31d06b94f69ae18d2c55240100b Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Mon, 30 Nov 2009 18:55:03 +0300 Subject: [PATCH 018/212] Initial import of WL#3726 "DDL locking for all metadata objects". Backport of: ------------------------------------------------------------ revno: 2630.4.1 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Fri 2008-05-23 17:54:03 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. ------------------------------------------------------------ This is the first patch in series. It transforms the metadata locking subsystem to use a dedicated module (mdl.h,cc). No significant changes in the locking protocol. The import passes the test suite with the exception of deprecated/removed 6.0 features, and MERGE tables. The latter are subject to a fix by WL#4144. Unfortunately, the original changeset comments got lost in a merge, thus this import has its own (largely insufficient) comments. This patch fixes Bug#25144 "replication / binlog with view breaks". Warning: this patch introduces an incompatible change: Under LOCK TABLES, it's no longer possible to FLUSH a table that was not locked for WRITE. Under LOCK TABLES, it's no longer possible to DROP a table or VIEW that was not locked for WRITE. ****** Backport of: ------------------------------------------------------------ revno: 2630.4.2 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Sat 2008-05-24 14:03:45 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. ****** Backport of: ------------------------------------------------------------ revno: 2630.4.3 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Sat 2008-05-24 14:08:51 +0400 message: WL#3726 "DDL locking for all metadata objects" Fixed failing Windows builds by adding mdl.cc to the lists of files needed to build server/libmysqld on Windows. ****** Backport of: ------------------------------------------------------------ revno: 2630.4.4 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Sat 2008-05-24 21:57:58 +0400 message: WL#3726 "DDL locking for all metadata objects". Fix for assert failures in kill.test which occured when one tried to kill ALTER TABLE statement on merge table while it was waiting in wait_while_table_is_used() for other connections to close this table. These assert failures stemmed from the fact that cleanup code in this case assumed that temporary table representing new version of table was open with adding to THD::temporary_tables list while code which were opening this temporary table wasn't always fulfilling this. This patch changes code that opens new version of table to always do this linking in. It also streamlines cleanup process for cases when error occurs while we have new version of table open. ****** WL#3726 "DDL locking for all metadata objects" Add libmysqld/mdl.cc to .bzrignore. ****** Backport of: ------------------------------------------------------------ revno: 2630.4.6 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Sun 2008-05-25 00:33:22 +0400 message: WL#3726 "DDL locking for all metadata objects". Addition to the fix of assert failures in kill.test caused by changes for this worklog. Make sure we close the new table only once. .bzrignore: Add libmysqld/mdl.cc libmysqld/CMakeLists.txt: Added mdl.cc to the list of files needed for building of libmysqld. libmysqld/Makefile.am: Added files implementing new meta-data locking subsystem to the server. mysql-test/include/handler.inc: Use separate connection for waiting while threads performing DDL operations conflicting with open HANDLER tables reach blocked state. This is required because now we check and close tables open by HANDLER statements in this connection conflicting with DDL in another each time open_tables() is called and thus select from I_S which is used for waiting will unblock DDL operations if issued from connection with open HANDLERs. mysql-test/r/create.result: Adjusted test case after change in implementation of CREATE TABLE ... SELECT. We no longer have special check in open_table() which catches the case when we select from the table created. Instead we rely on unique_table() call which happens after opening and locking all tables. mysql-test/r/flush.result: FLUSH TABLES WITH READ LOCK can no longer happen under LOCK TABLES. Updated test accordingly. mysql-test/r/flush_table.result: Under LOCK TABLES we no longer allow to do FLUSH TABLES for tables locked for read. Updated test accordingly. mysql-test/r/handler_innodb.result: Use separate connection for waiting while threads performing DDL operations conflicting with open HANDLER tables reach blocked state. This is required because now we check and close tables open by HANDLER statements in this connection conflicting with DDL in another each time open_tables() is called and thus select from I_S which is used for waiting will unblock DDL operations if issued from connection with open HANDLERs. mysql-test/r/handler_myisam.result: Use separate connection for waiting while threads performing DDL operations conflicting with open HANDLER tables reach blocked state. This is required because now we check and close tables open by HANDLER statements in this connection conflicting with DDL in another each time open_tables() is called and thus select from I_S which is used for waiting will unblock DDL operations if issued from connection with open HANDLERs. mysql-test/r/information_schema.result: Additional test for WL#3726 "DDL locking for all metadata objects". Check that we use high-priority metadata lock requests when filling I_S tables. Rearrange tests to match 6.0 better (fewer merge conflicts). mysql-test/r/kill.result: Added tests checking that DDL and DML statements waiting for metadata locks can be interrupted by KILL command. mysql-test/r/lock.result: One no longer is allowed to do DROP VIEW under LOCK TABLES even if this view is locked by LOCK TABLES. The problem is that in such situation write locks on view are not mutually exclusive so upgrading metadata lock which is required for dropping of view will lead to deadlock. mysql-test/r/partition_column_prune.result: Update results (same results in 6.0), WL#3726 mysql-test/r/partition_pruning.result: Update results (same results in 6.0), WL#3726 mysql-test/r/ps_ddl.result: We no longer invalidate prepared CREATE TABLE ... SELECT statement if target table changes. This is OK since it is not strictly necessary. The first change is wrong, is caused by FLUSH TABLE now flushing all unused tables. This is a regression that Dmitri fixed in 6.0 in a follow up patch. mysql-test/r/sp.result: Under LOCK TABLES we no longer allow accessing views which were not explicitly locked. To access view we need to obtain metadata lock on it and doing this under LOCK TABLES may lead to deadlocks. mysql-test/r/view.result: One no longer is allowed to do DROP VIEW under LOCK TABLES even if this view is locked by LOCK TABLES. The problem is that in such situation even "write locks" on view are not mutually exclusive so upgrading metadata lock which is required for dropping of view will lead to deadlock mysql-test/r/view_grant.result: ALTER VIEW implementation was changed to open a view only after checking that user which does alter has appropriate privileges on it. This means that in case when user's privileges are insufficient for this we won't check that new view definer is the same as original one or user performing alter has SUPER privilege. Adjusted test case accordingly. mysql-test/r/view_multi.result: Added test case for bug#25144 "replication / binlog with view breaks". mysql-test/suite/rpl/t/disabled.def: Disable test for deprecated features (they don't work with new MDL). mysql-test/t/create.test: Adjusted test case after change in implementation of CREATE TABLE ... SELECT. We no longer have special check in open_table() which catches the case when we select from the table created. Instead we rely on unique_table() call which happens after opening and locking all tables. mysql-test/t/disabled.def: Disable merge.test, subject of WL#4144 mysql-test/t/flush.test: FLUSH TABLES WITH READ LOCK can no longer happen under LOCK TABLES. Updated test accordingly. mysql-test/t/flush_table.test: Under LOCK TABLES we no longer allow to do FLUSH TABLES for tables locked for read. Updated test accordingly. mysql-test/t/information_schema.test: Additional test for WL#3726 "DDL locking for all metadata objects". Check that we use high-priority metadata lock requests when filling I_S tables. Rearrange the results for easier merges with 6.0. mysql-test/t/kill.test: Added tests checking that DDL and DML statements waiting for metadata locks can be interrupted by KILL command. mysql-test/t/lock.test: One no longer is allowed to do DROP VIEW under LOCK TABLES even if this view is locked by LOCK TABLES. The problem is that in such situation write locks on view are not mutually exclusive so upgrading metadata lock which is required for dropping of view will lead to deadlock. mysql-test/t/lock_multi.test: Adjusted test case to the changes of status in various places caused by change in implementation FLUSH TABLES WITH READ LOCK, which is now takes global metadata lock before flushing tables and therefore waits on at these places. mysql-test/t/ps_ddl.test: We no longer invalidate prepared CREATE TABLE ... SELECT statement if target table changes. This is OK since it is not strictly necessary. The first change is wrong, is caused by FLUSH TABLE now flushing all unused tables. This is a regression that Dmitri fixed in 6.0 in a follow up patch. mysql-test/t/sp.test: Under LOCK TABLES we no longer allow accessing views which were not explicitly locked. To access view we need to obtain metadata lock on it and doing this under LOCK TABLES may lead to deadlocks. mysql-test/t/trigger_notembedded.test: Adjusted test case to the changes of status in various places caused by change in implementation FLUSH TABLES WITH READ LOCK, which is now takes global metadata lock before flushing tables and therefore waits on at these places. mysql-test/t/view.test: One no longer is allowed to do DROP VIEW under LOCK TABLES even if this view is locked by LOCK TABLES. The problem is that in such situation even "write locks" on view are not mutually exclusive so upgrading metadata lock which is required for dropping of view will lead to deadlock. mysql-test/t/view_grant.test: ALTER VIEW implementation was changed to open a view only after checking that user which does alter has appropriate privileges on it. This means that in case when user's privileges are insufficient for this we won't check that new view definer is the same as original one or user performing alter has SUPER privilege. Adjusted test case accordingly. mysql-test/t/view_multi.test: Added test case for bug#25144 "replication / binlog with view breaks". sql/CMakeLists.txt: Added mdl.cc to the list of files needed for building of server. sql/Makefile.am: Added files implementing new meta-data locking subsystem to the server. sql/event_db_repository.cc: Allocate metadata lock requests objects (MDL_LOCK) on execution memory root in cases when TABLE_LIST objects is also allocated there or on stack. sql/ha_ndbcluster.cc: Adjusted code to work nicely with new metadata locking subsystem. close_cached_tables() no longer has wait_for_placeholder argument. Instead of relying on this parameter and related behavior FLUSH TABLES WITH READ LOCK now takes global shared metadata lock. sql/ha_ndbcluster_binlog.cc: Adjusted code to work with new metadata locking subsystem. close_cached_tables() no longer has wait_for_placeholder argument. Instead of relying on this parameter and related behavior FLUSH TABLES WITH READ LOCK now takes global shared metadata lock. sql/handler.cc: update_frm_version(): Directly update TABLE_SHARE::mysql_version member instead of going through all TABLE instances for this table (old code was a legacy from pre-table-definition-cache days). sql/lock.cc: Use new metadata locking subsystem. Threw away most of functions related to name locking as now one is supposed to use metadata locking API instead. In lock_global_read_lock() and unlock_global_read_lock() in order to avoid problems with global read lock sneaking in at the moment when we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES and when tables being reopened are protected only by metadata locks we also have to take global shared meta data lock. sql/log_event.cc: Adjusted code to work with new metadata locking subsystem. For tables open by slave thread for applying RBR events allocate memory for lock request object in the same chunk of memory as TABLE_LIST objects for them. In order to ensure that we keep these objects around until tables are open always close tables before calling Relay_log_info::clear_tables_to_lock(). Use new auxiliary Relay_log_info::slave_close_thread_tables() method to enforce this. sql/log_event_old.cc: Adjusted code to work with new metadata locking subsystem. Since for tables open by slave thread for applying RBR events memory for lock request object is allocated in the same chunk of memory as TABLE_LIST objects for them we have to ensure that we keep these objects around until tables are open. To ensure this we always close tables before calling Relay_log_info::clear_tables_to_lock(). To enfore this we use new auxiliary Relay_log_info::slave_close_thread_tables() method. sql/mdl.cc: Implemented new metadata locking subsystem and API described in WL3726 "DDL locking for all metadata objects". sql/mdl.h: Implemented new metadata locking subsystem and API described in WL3726 "DDL locking for all metadata objects". sql/mysql_priv.h: - close_thread_tables()/close_tables_for_reopen() now has one more argument which indicates that metadata locks should be released but not removed from the context in order to be used later in mdl_wait_for_locks() and tdc_wait_for_old_version(). - close_cached_table() routine is no longer public. - Thread waiting in wait_while_table_is_used() can be now killed so this function returns boolean to make caller aware of such situation. - We no longer have table cache as separate entity instead used and unused TABLE instances are linked to TABLE_SHARE objects in table definition cache. - Now third argument of open_table() is also used for requesting table repair or auto-discovery of table's new definition. So its type was changed from bool to enum. - Added tdc_open_view() function for opening view by getting its definition from disk (and table cache in future). - reopen_name_locked_table() no longer needs "link_in" argument as now we have exclusive metadata locks instead of dummy TABLE instances when this function is called. - find_locked_table() now takes head of list of TABLE instances instead of always scanning through THD::open_tables list. Also added find_write_locked_table() auxiliary. - reopen_tables(), close_cached_tables() no longer have mark_share_as_old and wait_for_placeholder arguments. Instead of relying on this parameters and related behavior FLUSH TABLES WITH READ LOCK now takes global shared metadata lock. - We no longer need drop_locked_tables() and abort_locked_tables(). - mysql_ha_rm_tables() now always assume that LOCK_open is not acquired by caller. - Added notify_thread_having_shared_lock() callback invoked by metadata locking subsystem when acquiring an exclusive lock, for each thread that has a conflicting shared metadata lock. - Introduced expel_table_from_cache() as replacement for remove_table_from_cache() (the main difference is that this new function assumes that caller follows metadata locking protocol and never waits). - Threw away most of functions related to name locking. One should use new metadata locking subsystem and API instead. sql/mysqld.cc: Got rid of call initializing/deinitializing table cache since now it is embedded into table definition cache. Added calls for initializing/ deinitializing metadata locking subsystem. sql/rpl_rli.cc: Introduced auxiliary Relay_log_info::slave_close_thread_tables() method which is used for enforcing that we always close tables open for RBR before deallocating TABLE_LIST elements and MDL_LOCK objects for them. sql/rpl_rli.h: Introduced auxiliary Relay_log_info::slave_close_thread_tables() method which is used for enforcing that we always close tables open for RBR before deallocating TABLE_LIST elements and MDL_LOCK objects for them. sql/set_var.cc: close_cached_tables() no longer has wait_for_placeholder argument. Instead of relying on this parameter and related behavior FLUSH TABLES WITH READ LOCK now takes global shared metadata lock. sql/sp_head.cc: For tables added to the statement's table list by prelocking algorithm we allocate these objects either on the same memory as corresponding table list elements or on THD::locked_tables_root (if we are building table list for LOCK TABLES). sql/sql_acl.cc: Allocate metadata lock requests objects (MDL_LOCK) on execution memory root in cases when we use stack TABLE_LIST objects to open tables. Got rid of redundant code by using unlock_locked_tables() function. sql/sql_base.cc: Changed code to use new MDL subsystem. Got rid of separate table cache. Now used and unused TABLE instances are linked to the TABLE_SHAREs in table definition cache. check_unused(): Adjusted code to the fact that we no longer have separate table cache. Removed dead code. table_def_free(): Free TABLE instances referenced from TABLE_SHARE objects before destroying table definition cache. get_table_share(): Added assert which ensures that noone will be able to access table (and its share) without acquiring some kind of metadata lock first. close_handle_and_leave_table_as_lock(): Adjusted code to the fact that TABLE instances now are linked to list in TABLE_SHARE. list_open_tables(): Changed this function to use table definition cache instead of table cache. free_cache_entry(): Unlink freed TABLE elements from the list of all TABLE instances for the table in TABLE_SHARE. kill_delayed_thread_for_table(): Added auxiliary for killing delayed insert threads for particular table. close_cached_tables(): Got rid of wait_for_refresh argument as we now rely on global shared metadata lock to prevent FLUSH WITH READ LOCK sneaking in when we are reopening tables. Heavily reworked this function to use new MDL code and not to rely on separate table cache entity. close_open_tables(): We no longer have separate table cache. close_thread_tables(): Release metadata locks after closing all tables. Added skip_mdl argument which allows us not to remove metadata lock requests from the context in case when we are going to use this requests later in mdl_wait_for_locks() and tdc_wait_for_old_versions(). close_thread_table()/close_table_for_reopen(): Since we no longer have separate table cache and all TABLE instances are linked to TABLE_SHARE objects in table definition cache we have to link/unlink TABLE object to/from appropriate lists in the share. name_lock_locked_table(): Moved redundant code to find_write_locked_table() function and adjusted code to the fact that wait_while_table_is_used() can now return with an error if our thread is killed. reopen_table_entry(): We no longer need "link_in" argument as with MDL we no longer call this function with dummy TABLE object pre-allocated and added to the THD::open_tables. Also now we add newly-open TABLE instance to the list of share's used TABLE instances. table_cache_insert_placeholder(): Got rid of name-locking legacy. lock_table_name_if_not_cached(): Moved to sql_table.cc the only place where it is used. It was also reimplemented using new MDL API. open_table(): - Reworked this function to use new MDL subsystem. - Changed code to deal with table definition cache directly instead of going through separate table cache. - Now third argument is also used for requesting table repair or auto-discovery of table's new definition. So its type was changed from bool to enum. find_locked_table()/find_write_locked_table(): Accept head of list of TABLE objects as first argument and use this list instead of always searching in THD::open_tables list. Also added auxiliary for finding write-locked locked tables. reopen_table(): Adjusted function to work with new MDL subsystem and to properly manuipulate with lists of used/unused TABLE instaces in TABLE_SHARE. reopen_tables(): Removed mark_share_as_old parameter. Instead of relying on it and related behavior FLUSH TABLES WITH READ LOCK now takes global shared metadata lock. Changed code after removing separate table cache. drop_locked_tables()/abort_locked_tables(): Got rid of functions which are no longer needed. unlock_locked_tables(): Moved this function from sql_parse.cc and changed it to release memory which was used for allocating metadata lock requests for tables open and locked by LOCK TABLES. tdc_open_view(): Intoduced function for opening a view by getting its definition from disk (and table cache in future). reopen_table_entry(): Introduced function for opening table definitions while holding exclusive metatadata lock on it. open_unireg_entry(): Got rid of this function. Most of its functionality is relocated to open_table() and open_table_fini() functions, and some of it to reopen_table_entry() and tdc_open_view(). Also code resposible for auto-repair and auto-discovery of tables was moved to separate function. open_table_entry_fini(): Introduced function which contains common actions which finalize process of TABLE object creation. auto_repair_table(): Moved code responsible for auto-repair of table being opened here. handle_failed_open_table_attempt() Moved code responsible for handling failing attempt to open table to one place (retry due to lock conflict/old version, auto-discovery and repair). open_tables(): - Flush open HANDLER tables if they have old version of if there is conflicting metadata lock against them (before this moment we had this code in open_table()). - When we open view which should be processed via derived table on the second execution of prepared statement or stored routine we still should call open_table() for it in order to obtain metadata lock on it and prepare its security context. - In cases when we discover that some special handling of failure to open table is needed call handle_failed_open_table_attempt() which handles all such scenarios. open_ltable(): Handling of various special scenarios of failure to open a table was moved to separate handle_failed_open_table_attempt() function. remove_db_from_cache(): Removed this function as it is no longer used. notify_thread_having_shared_lock(): Added callback which is invoked by MDL subsystem when acquiring an exclusive lock, for each thread that has a conflicting shared metadata lock. expel_table_from_cache(): Introduced function for removing unused TABLE instances. Unlike remove_table_from_cache() it relies on caller following MDL protocol and having appropriate locks when calling it and thus does not do any waiting if table is still in use. tdc_wait_for_old_version(): Added function which allows open_tables() to wait in cases when we discover that we should back-off due to presence of old version of table. abort_and_upgrade_lock(): Use new MDL calls. mysql_wait_completed_table(): Got rid of unused function. open_system_tables_for_read/for_update()/performance_schema_table(): Allocate MDL_LOCK objects on execution memory root in cases when TABLE_LIST objects for corresponding tables is allocated on stack. close_performance_schema_table(): Release metadata locks after closing tables. ****** Use I_P_List for free/used tables list in the table share. sql/sql_binlog.cc: Use Relay_log_info::slave_close_thread_tables() method to enforce that we always close tables open for RBR before deallocating TABLE_LIST elements and MDL_LOCK objects for them. sql/sql_class.cc: Added meta-data locking contexts as part of Open_tables_state context. Also introduced THD::locked_tables_root memory root which is to be used for allocating MDL_LOCK objects for tables in LOCK TABLES statement (end of lifetime for such objects is UNLOCK TABLES so we can't use statement or execution root for them). sql/sql_class.h: Added meta-data locking contexts as part of Open_tables_state context. Also introduced THD::locked_tables_root memory root which is to be used for allocating MDL_LOCK objects for tables in LOCK TABLES statement (end of lifetime for such objects is UNLOCK TABLES so we can't use statement or execution root for them). Note: handler_mdl_context and locked_tables_root and mdl_el_root will be removed by subsequent patches. sql/sql_db.cc: mysql_rm_db() does not really need to call remove_db_from_cache() as it drops each table in the database using mysql_rm_table_part2(), which performs all necessary operations on table (definition) cache. sql/sql_delete.cc: Use the new metadata locking API for TRUNCATE. sql/sql_handler.cc: Changed HANDLER implementation to use new metadata locking subsystem. Note that MDL_LOCK objects for HANDLER tables are allocated in the same chunk of heap memory as TABLE_LIST object for those tables. sql/sql_insert.cc: mysql_insert(): find_locked_table() now takes head of list of TABLE object as its argument instead of always scanning through THD::open_tables list. handle_delayed_insert(): Allocate metadata lock request object for table open by delayed insert thread on execution memroot. create_table_from_items(): We no longer allocate dummy TABLE objects for tables being created if they don't exist. As consequence reopen_name_locked_table() no longer has link_in argument. open_table() now has one more argument which is not relevant for temporary tables. sql/sql_parse.cc: - Moved unlock_locked_tables() routine to sql_base.cc and made available it in other files. Got rid of some redundant code by using this function. - Replaced boolean TABLE_LIST::create member with enum open_table_type member. - Use special memory root for allocating MDL_LOCK objects for tables open and locked by LOCK TABLES (these object should live till UNLOCK TABLES so we can't allocate them on statement nor execution memory root). Also properly set metadata lock upgradability attribure for those tables. - Under LOCK TABLES it is no longer allowed to flush tables which are not write-locked as this breaks metadata locking protocol and thus potentially might lead to deadlock. - Added auxiliary adjust_mdl_locks_upgradability() function. sql/sql_partition.cc: Adjusted code to the fact that reopen_tables() no longer has "mark_share_as_old" argument. Got rid of comments which are no longer true. sql/sql_plist.h: Added I_P_List template class for parametrized intrusive doubly linked lists and I_P_List_iterator for corresponding iterator. Unlike for I_List<> list elements of such list can participate in several lists. Unlike List<> such lists are doubly-linked and intrusive. sql/sql_plugin.cc: Allocate metadata lock requests objects (MDL_LOCK) on execution memory root in cases when we use stack TABLE_LIST objects to open tables. sql/sql_prepare.cc: Replaced boolean TABLE_LIST::create member with enum open_table_type member. This allows easily handle situation in which instead of opening the table we want only to take exclusive metadata lock on it. sql/sql_rename.cc: Use new metadata locking subsystem in implementation of RENAME TABLE. sql/sql_servers.cc: Allocate metadata lock requests objects (MDL_LOCK) on execution memory root in cases when we use stack TABLE_LIST objects to open tables. Got rid of redundant code by using unlock_locked_tables() function. sql/sql_show.cc: Acquire shared metadata lock when we are getting information for I_S table directly from TABLE_SHARE without doing full-blown table open. We use high priority lock request in this situation in order to avoid deadlocks. Also allocate metadata lock requests objects (MDL_LOCK) on execution memory root in cases when TABLE_LIST objects are also allocated there sql/sql_table.cc: mysql_rm_table(): Removed comment which is no longer relevant. mysql_rm_table_part2(): Now caller of mysql_ha_rm_tables() should not own LOCK_open. Adjusted code to use new metadata locking subsystem instead of name-locks. lock_table_name_if_not_cached(): Moved this function from sql_base.cc to this file and reimplemented it using metadata locking API. mysql_create_table(): Adjusted code to use new MDL API. wait_while_table_is_used(): Changed function to use new MDL subsystem. Made thread waiting in it killable (this also led to introduction of return value so caller can distinguish successful executions from situations when waiting was aborted). close_cached_tables(): Thread waiting in this function is killable now. As result it has return value for distinguishing between succes and failure. Got rid of redundant boradcast_refresh() call. prepare_for_repair(): Use MDL subsystem instead of name-locks. mysql_admin_table(): mysql_ha_rm_tables() now always assumes that caller doesn't own LOCK_open. mysql_repair_table(): We should mark all elements of table list as requiring upgradable metadata locks. mysql_create_table_like(): Use new MDL subsystem instead of name-locks. create_temporary_tables(): We don't need to obtain metadata locks when creating temporary table. mysql_fast_or_online_alter_table(): Thread waiting in wait_while_table_is_used() is now killable. mysql_alter_table(): Adjusted code to work with new MDL subsystem and to the fact that threads waiting in what_while_table_is_used() and close_cached_table() are now killable. sql/sql_test.cc: We no longer have separate table cache. TABLE instances are now associated with/linked to TABLE_SHARE objects in table definition cache. sql/sql_trigger.cc: Adjusted code to work with new metadata locking subsystem. Also reopen_tables() no longer has mark_share_as_old argument (Instead of relying on this parameter and related behavior FLUSH TABLES WITH READ LOCK now takes global shared metadata lock). sql/sql_udf.cc: Allocate metadata lock requests objects (MDL_LOCK) on execution memory root in cases when we use stack TABLE_LIST objects to open tables. sql/sql_update.cc: Adjusted code to work with new meta-data locking subsystem. sql/sql_view.cc: Added proper meta-data locking to implementations of CREATE/ALTER/DROP VIEW statements. Now we obtain exclusive meta-data lock on a view before creating/ changing/dropping it. This ensures that all concurrent statements that use this view will finish before our statement will proceed and therefore we will get correct order of statements in the binary log. Also ensure that TABLE_LIST::mdl_upgradable attribute is properly propagated for underlying tables of view. sql/table.cc: Added auxiliary alloc_mdl_locks() function for allocating metadata lock request objects for all elements of table list. sql/table.h: TABLE_SHARE: Got rid of unused members. Introduced members for storing lists of used and unused TABLE objects for this share. TABLE: Added members for linking TABLE objects into per-share lists of used and unused TABLE instances. Added member for holding pointer to metadata lock for this table. TABLE_LIST: Replaced boolean TABLE_LIST::create member with enum open_table_type member. This allows easily handle situation in which instead of opening the table we want only to take exclusive meta-data lock on it (we need this in order to handle ALTER VIEW and CREATE VIEW statements). Introduced new mdl_upgradable member for marking elements of table list for which we need to take upgradable shared metadata lock instead of plain shared metadata lock. Added pointer for holding pointer to MDL_LOCK for the table. Added auxiliary alloc_mdl_locks() function for allocating metadata lock requests objects for all elements of table list. Added auxiliary set_all_mdl_upgradable() function for marking all elements in table list as requiring upgradable metadata locks. storage/myisammrg/ha_myisammrg.cc: Allocate MDL_LOCK objects for underlying tables of MERGE table. To be reworked once Ingo pushes his patch for WL4144. --- .bzrignore | 1 + libmysqld/CMakeLists.txt | 1 + libmysqld/Makefile.am | 2 +- mysql-test/include/handler.inc | 33 +- mysql-test/r/create.result | 7 +- mysql-test/r/flush.result | 5 + mysql-test/r/flush_table.result | 17 +- mysql-test/r/handler_innodb.result | 2 + mysql-test/r/handler_myisam.result | 2 + mysql-test/r/information_schema.result | 77 +- mysql-test/r/kill.result | 103 + mysql-test/r/lock.result | 3 +- mysql-test/r/partition_column_prune.result | 6 +- mysql-test/r/partition_pruning.result | 2 +- mysql-test/r/ps_ddl.result | 4 +- mysql-test/r/sp.result | 6 +- mysql-test/r/view.result | 4 +- mysql-test/r/view_grant.result | 4 +- mysql-test/r/view_multi.result | 48 + mysql-test/suite/rpl/t/disabled.def | 3 + mysql-test/t/create.test | 8 +- mysql-test/t/disabled.def | 1 + mysql-test/t/flush.test | 14 +- mysql-test/t/flush_table.test | 29 +- mysql-test/t/information_schema.test | 99 +- mysql-test/t/kill.test | 226 ++ mysql-test/t/lock.test | 3 +- mysql-test/t/lock_multi.test | 34 +- mysql-test/t/ps_ddl.test | 4 +- mysql-test/t/sp.test | 4 +- mysql-test/t/trigger_notembedded.test | 2 +- mysql-test/t/view.test | 4 +- mysql-test/t/view_grant.test | 4 +- mysql-test/t/view_multi.test | 110 + sql/CMakeLists.txt | 2 +- sql/Makefile.am | 5 +- sql/event_db_repository.cc | 4 + sql/ha_ndbcluster.cc | 23 +- sql/ha_ndbcluster_binlog.cc | 33 +- sql/handler.cc | 9 +- sql/lock.cc | 382 +-- sql/log_event.cc | 22 +- sql/log_event_old.cc | 36 +- sql/mdl.cc | 1342 +++++++++++ sql/mdl.h | 260 ++ sql/mysql_priv.h | 53 +- sql/mysqld.cc | 5 +- sql/rpl_rli.cc | 21 +- sql/rpl_rli.h | 1 + sql/set_var.cc | 2 +- sql/sp_head.cc | 7 +- sql/sql_acl.cc | 16 +- sql/sql_base.cc | 2491 +++++++++++--------- sql/sql_binlog.cc | 2 +- sql/sql_class.cc | 20 +- sql/sql_class.h | 13 +- sql/sql_db.cc | 4 - sql/sql_delete.cc | 27 +- sql/sql_handler.cc | 136 +- sql/sql_insert.cc | 13 +- sql/sql_parse.cc | 118 +- sql/sql_partition.cc | 10 +- sql/sql_plist.h | 125 + sql/sql_plugin.cc | 4 + sql/sql_prepare.cc | 2 +- sql/sql_rename.cc | 17 +- sql/sql_servers.cc | 11 +- sql/sql_show.cc | 52 +- sql/sql_table.cc | 508 ++-- sql/sql_test.cc | 40 +- sql/sql_trigger.cc | 46 +- sql/sql_udf.cc | 3 + sql/sql_update.cc | 4 +- sql/sql_view.cc | 92 +- sql/table.cc | 20 + sql/table.h | 80 +- storage/myisammrg/ha_myisammrg.cc | 6 + 77 files changed, 4804 insertions(+), 2135 deletions(-) create mode 100644 mysql-test/r/view_multi.result create mode 100644 mysql-test/t/view_multi.test create mode 100644 sql/mdl.cc create mode 100644 sql/mdl.h create mode 100644 sql/sql_plist.h diff --git a/.bzrignore b/.bzrignore index 351417a4353..bc3ca7b0c24 100644 --- a/.bzrignore +++ b/.bzrignore @@ -3070,3 +3070,4 @@ libmysqld/rpl_handler.cc libmysqld/debug_sync.cc libmysqld/rpl_handler.cc dbug/tests +libmysqld/mdl.cc diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 65b8e12bc26..655082f0304 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -133,6 +133,7 @@ SET(LIBMYSQLD_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/partition_info.cc ../sql/sql_connect.cc ../sql/scheduler.cc ../sql/event_parse_data.cc ../sql/sql_signal.cc ../sql/rpl_handler.cc + ../sql/mdl.cc ${GEN_SOURCES} ${LIB_SOURCES}) diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index ec73741eaaf..3a7fa934778 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -79,7 +79,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ sql_tablespace.cc \ rpl_injector.cc my_user.c partition_info.cc \ sql_servers.cc event_parse_data.cc sql_signal.cc \ - rpl_handler.cc + rpl_handler.cc mdl.cc libmysqld_int_a_SOURCES= $(libmysqld_sources) nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources) diff --git a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc index 6e7f53ba9b2..8ff38c7e7a1 100644 --- a/mysql-test/include/handler.inc +++ b/mysql-test/include/handler.inc @@ -518,12 +518,15 @@ connect (flush,localhost,root,,); connection flush; --echo connection: flush --send flush tables; -connection default; ---echo connection: default +connect (waiter,localhost,root,,); +connection waiter; +--echo connection: waiter let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Flushing tables"; --source include/wait_condition.inc +connection default; +--echo connection: default handler t2 open; handler t2 read first; handler t1 read next; @@ -550,12 +553,14 @@ connect (flush,localhost,root,,); connection flush; --echo connection: flush --send rename table t1 to t2; -connection default; ---echo connection: default +connection waiter; +--echo connection: waiter let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "rename table t1 to t2"; --source include/wait_condition.inc +connection default; +--echo connection: default handler t2 open; handler t2 read first; --error ER_NO_SUCH_TABLE @@ -566,7 +571,13 @@ connection flush; reap; connection default; drop table t2; +connection flush; disconnect flush; +--source include/wait_until_disconnected.inc +connection waiter; +disconnect waiter; +--source include/wait_until_disconnected.inc +connection default; # # Bug#30882 Dropping a temporary table inside a stored function may cause a server crash @@ -699,19 +710,24 @@ handler t1 read a next; # Bug#41112: crash in mysql_ha_close_table/get_lock_data with alter table # +connect(con1,localhost,root,,); +connect(con2,localhost,root,,); + +connection default; --disable_warnings drop table if exists t1; --enable_warnings create table t1 (a int); insert into t1 values (1); handler t1 open; -connect(con1,localhost,root,,); +connection con1; send alter table t1 engine=memory; -connection default; +connection con2; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "rename result table" and info = "alter table t1 engine=memory"; + where state = "Waiting for table" and info = "alter table t1 engine=memory"; --source include/wait_condition.inc +connection default; --error ER_ILLEGAL_HA handler t1 read a next; handler t1 close; @@ -720,6 +736,9 @@ connection con1; drop table t1; disconnect con1; --source include/wait_until_disconnected.inc +connection con2; +disconnect con2; +--source include/wait_until_disconnected.inc connection default; # diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result index 471cc6e9a3d..cf424b8b058 100644 --- a/mysql-test/r/create.result +++ b/mysql-test/r/create.result @@ -785,7 +785,7 @@ drop table t1; create table t1 select * from t2; ERROR 42S02: Table 'test.t2' doesn't exist create table t1 select * from t1; -ERROR HY000: You can't specify target table 't1' for update in FROM clause +ERROR 42S02: Table 'test.t1' doesn't exist create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin); ERROR HY000: Illegal mix of collations (latin1_swedish_ci,EXPLICIT) and (latin1_bin,EXPLICIT) for operation 'coalesce' create table t1 (primary key(a)) select "b" as b; @@ -805,6 +805,11 @@ Note 1050 Table 't1' already exists select * from t1; i 1 +create table if not exists t1 select * from t1; +ERROR HY000: You can't specify target table 't1' for update in FROM clause +select * from t1; +i +1 create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin); ERROR HY000: Illegal mix of collations (latin1_swedish_ci,EXPLICIT) and (latin1_bin,EXPLICIT) for operation 'coalesce' select * from t1; diff --git a/mysql-test/r/flush.result b/mysql-test/r/flush.result index b978304f59d..2be426d3a4a 100644 --- a/mysql-test/r/flush.result +++ b/mysql-test/r/flush.result @@ -33,6 +33,9 @@ flush tables with read lock; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction lock table t1 read; flush tables with read lock; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +unlock tables; +flush tables with read lock; lock table t1 write; ERROR HY000: Can't execute the query because you have a conflicting read lock lock table t1 read; @@ -46,6 +49,7 @@ flush tables with read lock; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction lock table t1 read, t2 read, t3 read; flush tables with read lock; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction unlock tables; drop table t1, t2, t3; create table t1 (c1 int); @@ -69,6 +73,7 @@ ERROR HY000: Can't execute the given command because you have active locked tabl unlock tables; lock tables t1 read; flush tables with read lock; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction unlock tables; drop table t1, t2; set session low_priority_updates=default; diff --git a/mysql-test/r/flush_table.result b/mysql-test/r/flush_table.result index 8821bade6b4..2b0ee1cb205 100644 --- a/mysql-test/r/flush_table.result +++ b/mysql-test/r/flush_table.result @@ -3,21 +3,14 @@ create table t1 (a int not null auto_increment primary key); insert into t1 values(0); lock table t1 read; flush table t1; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +unlock tables; +lock table t1 write; +flush table t1; check table t1; Table Op Msg_type Msg_text test.t1 check status OK unlock tables; -lock table t1 read; -lock table t1 read; -flush table t1; -select * from t1; -a -1 -unlock tables; -select * from t1; -a -1 -unlock tables; lock table t1 write; lock table t1 read; flush table t1; @@ -26,7 +19,7 @@ a 1 unlock tables; unlock tables; -lock table t1 read; +lock table t1 write; lock table t1 write; flush table t1; select * from t1; diff --git a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result index 957fc30acef..5990b19062b 100644 --- a/mysql-test/r/handler_innodb.result +++ b/mysql-test/r/handler_innodb.result @@ -548,6 +548,7 @@ c1 1 connection: flush flush tables;; +connection: waiter connection: default handler t2 open; handler t2 read first; @@ -567,6 +568,7 @@ handler t1 read first; c1 connection: flush rename table t1 to t2;; +connection: waiter connection: default handler t2 open; handler t2 read first; diff --git a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result index 90a1bdfe6be..f7b0ff2e04e 100644 --- a/mysql-test/r/handler_myisam.result +++ b/mysql-test/r/handler_myisam.result @@ -547,6 +547,7 @@ c1 1 connection: flush flush tables;; +connection: waiter connection: default handler t2 open; handler t2 read first; @@ -566,6 +567,7 @@ handler t1 read first; c1 connection: flush rename table t1 to t2;; +connection: waiter connection: default handler t2 open; handler t2 read first; diff --git a/mysql-test/r/information_schema.result b/mysql-test/r/information_schema.result index 04234eb3cc4..a66e494dcd2 100644 --- a/mysql-test/r/information_schema.result +++ b/mysql-test/r/information_schema.result @@ -1644,6 +1644,57 @@ TEST_RESULT OK SET TIMESTAMP=DEFAULT; End of 5.1 tests. +# +# Additional test for WL#3726 "DDL locking for all metadata objects" +# To avoid possible deadlocks process of filling of I_S tables should +# use high-priority metadata lock requests when opening tables. +# Below we just test that we really use high-priority lock request +# since reproducing a deadlock will require much more complex test. +# +drop tables if exists t1, t2, t3; +create table t1 (i int); +create table t2 (j int primary key auto_increment); +# Switching to connection 'con3726_1' +lock table t2 read; +# Switching to connection 'con3726_2' +# RENAME below will be blocked by 'lock table t2 read' above but +# will add two pending requests for exclusive metadata locks. +rename table t2 to t3; +# Switching to connection 'default' +# These statements should not be blocked by pending lock requests +select table_name, column_name, data_type from information_schema.columns +where table_schema = 'test' and table_name in ('t1', 't2'); +table_name column_name data_type +t1 i int +t2 j int +select table_name, auto_increment from information_schema.tables +where table_schema = 'test' and table_name in ('t1', 't2'); +table_name auto_increment +t1 NULL +t2 1 +# Switching to connection 'con3726_1' +unlock tables; +# Switching to connection 'con3726_2' +# Switching to connection 'default' +drop tables t1, t3; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE KEY_COLUMN_USAGE ALL NULL NULL NULL NULL NULL Open_full_table; Scanned all databases +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE PARTITIONS ALL NULL TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 1 database +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS +WHERE CONSTRAINT_SCHEMA='test'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE REFERENTIAL_CONSTRAINTS ALL NULL CONSTRAINT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS +WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE TABLE_CONSTRAINTS ALL NULL TABLE_SCHEMA,TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 0 databases +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS +WHERE EVENT_OBJECT_SCHEMA='test'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE TRIGGERS ALL NULL EVENT_OBJECT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database create table information_schema.t1 (f1 INT); ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema' drop table information_schema.t1; @@ -1682,28 +1733,10 @@ ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_ LOCK TABLES t1 READ, information_schema.tables READ; ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema' DROP TABLE t1; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE KEY_COLUMN_USAGE ALL NULL NULL NULL NULL NULL Open_full_table; Scanned all databases -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1'; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE PARTITIONS ALL NULL TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 1 database -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS -WHERE CONSTRAINT_SCHEMA='test'; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE REFERENTIAL_CONSTRAINTS ALL NULL CONSTRAINT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS -WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test'; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE TABLE_CONSTRAINTS ALL NULL TABLE_SCHEMA,TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 0 databases -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS -WHERE EVENT_OBJECT_SCHEMA='test'; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE TRIGGERS ALL NULL EVENT_OBJECT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database SELECT * -FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE -LEFT JOIN INFORMATION_SCHEMA.COLUMNS -USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME) -WHERE COLUMNS.TABLE_SCHEMA = 'test' +FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE +LEFT JOIN INFORMATION_SCHEMA.COLUMNS +USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME) +WHERE COLUMNS.TABLE_SCHEMA = 'test' AND COLUMNS.TABLE_NAME = 't1'; TABLE_SCHEMA TABLE_NAME COLUMN_NAME CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_CATALOG ORDINAL_POSITION POSITION_IN_UNIQUE_CONSTRAINT REFERENCED_TABLE_SCHEMA REFERENCED_TABLE_NAME REFERENCED_COLUMN_NAME TABLE_CATALOG ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_SCALE CHARACTER_SET_NAME COLLATION_NAME COLUMN_TYPE COLUMN_KEY EXTRA PRIVILEGES COLUMN_COMMENT diff --git a/mysql-test/r/kill.result b/mysql-test/r/kill.result index 8b6830d4798..1f4f4bb32eb 100644 --- a/mysql-test/r/kill.result +++ b/mysql-test/r/kill.result @@ -138,4 +138,107 @@ KILL CONNECTION_ID(); # of close of the connection socket SELECT 1; Got one of the listed errors +# +# Additional test for WL#3726 "DDL locking for all metadata objects" +# Check that DDL and DML statements waiting for metadata locks can +# be killed. Note that we don't cover all situations here since it +# can be tricky to write test case for some of them (e.g. REPAIR or +# ALTER and other statements under LOCK TABLES). +# +drop tables if exists t1, t2, t3; +create table t1 (i int primary key); +# Test for RENAME TABLE +# Switching to connection 'blocker' +lock table t1 read; +# Switching to connection 'ddl' +rename table t1 to t2; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Test for DROP TABLE +drop table t1; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Test for CREATE TRIGGER +create trigger t1_bi before insert on t1 for each row set @a:=1; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# +# Tests for various kinds of ALTER TABLE +# +# Full-blown ALTER which should copy table +alter table t1 add column j int; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Two kinds of simple ALTER +alter table t1 rename to t2; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +alter table t1 disable keys; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Fast ALTER +alter table t1 alter column i set default 100; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Special case which is triggered only for MERGE tables. +# Switching to connection 'blocker' +unlock tables; +create table t2 (i int primary key) engine=merge union=(t1); +lock tables t2 read; +# Switching to connection 'ddl' +alter table t2 alter column i set default 100; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Test for DML waiting for meta-data lock +# Switching to connection 'blocker' +unlock tables; +drop table t2; +create table t2 (k int); +lock tables t1 read; +# Switching to connection 'ddl' +rename tables t1 to t3, t2 to t1; +# Switching to connection 'dml' +insert into t2 values (1); +# Switching to connection 'default' +kill query ID2; +# Switching to connection 'dml' +ERROR 70100: Query execution was interrupted +# Switching to connection 'blocker' +unlock tables; +# Switching to connection 'ddl' +# Test for DML waiting for tables to be flushed +# Switching to connection 'blocker' +lock tables t1 read; +# Switching to connection 'ddl' +# Let us mark locked table t1 as old +flush tables; +# Switching to connection 'dml' +select * from t1; +# Switching to connection 'default' +kill query ID2; +# Switching to connection 'dml' +ERROR 70100: Query execution was interrupted +# Switching to connection 'blocker' +unlock tables; +# Switching to connection 'ddl' +# Cleanup. +# Switching to connection 'default' +drop table t3; +drop table t1; set @@global.concurrent_insert= @old_concurrent_insert; diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index 1f8f6aa04ae..a60c5c8383f 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -128,13 +128,14 @@ select * from v_bug5719; 1 1 drop view v_bug5719; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction sic: did not left LOCK TABLES mode automatically select * from t1; ERROR HY000: Table 't1' was not locked with LOCK TABLES unlock tables; -create view v_bug5719 as select * from t1; +create or replace view v_bug5719 as select * from t1; lock tables v_bug5719 write; select * from v_bug5719; a diff --git a/mysql-test/r/partition_column_prune.result b/mysql-test/r/partition_column_prune.result index 82c49453d43..844429d24a6 100644 --- a/mysql-test/r/partition_column_prune.result +++ b/mysql-test/r/partition_column_prune.result @@ -56,11 +56,11 @@ insert into t1 values (2,5), (2,15), (2,25), insert into t1 select * from t1; explain partitions select * from t1 where a=2; id select_type table partitions type possible_keys key key_len ref rows Extra -1 SIMPLE t1 p01,p02,p03,p11 ALL NULL NULL NULL NULL 13 Using where +1 SIMPLE t1 p01,p02,p03,p11 ALL NULL NULL NULL NULL 8 Using where explain partitions select * from t1 where a=4; id select_type table partitions type possible_keys key key_len ref rows Extra -1 SIMPLE t1 p11,p12,p13,p21 ALL NULL NULL NULL NULL 16 Using where +1 SIMPLE t1 p11,p12,p13,p21 ALL NULL NULL NULL NULL 14 Using where explain partitions select * from t1 where a=2 and b < 22; id select_type table partitions type possible_keys key key_len ref rows Extra -1 SIMPLE t1 p01,p02,p03 ALL NULL NULL NULL NULL 16 Using where +1 SIMPLE t1 p01,p02,p03 ALL NULL NULL NULL NULL 14 Using where drop table t1; diff --git a/mysql-test/r/partition_pruning.result b/mysql-test/r/partition_pruning.result index d7790cd8075..4c22c25ddb9 100644 --- a/mysql-test/r/partition_pruning.result +++ b/mysql-test/r/partition_pruning.result @@ -1960,7 +1960,7 @@ id select_type table partitions type possible_keys key key_len ref rows Extra explain partitions select * from t1 X, t1 Y where X.a = Y.a and (X.a=1 or X.a=2); id select_type table partitions type possible_keys key key_len ref rows Extra -1 SIMPLE X p1,p2 ALL a NULL NULL NULL 4 Using where +1 SIMPLE X p1,p2 ALL a NULL NULL NULL 2 Using where 1 SIMPLE Y p1,p2 ref a a 4 test.X.a 2 drop table t1; create table t1 (a int) partition by hash(a) partitions 20; diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result index c7e8812320c..af0ffaf4ae5 100644 --- a/mysql-test/r/ps_ddl.result +++ b/mysql-test/r/ps_ddl.result @@ -850,7 +850,7 @@ flush table t1; execute stmt; f1() 6 -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); SUCCESS execute stmt; @@ -1706,7 +1706,7 @@ SUCCESS drop temporary table t2; execute stmt; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); SUCCESS drop table t2; diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index a73abf787d8..47d441ee182 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -1083,11 +1083,9 @@ select f0()| f0() 100 select * from v0| -f0() -100 +ERROR HY000: Table 'v0' was not locked with LOCK TABLES select *, f0() from v0, (select 123) as d1| -f0() 123 f0() -100 123 100 +ERROR HY000: Table 'v0' was not locked with LOCK TABLES select id, f3() from t1| ERROR HY000: Table 't1' was not locked with LOCK TABLES select f4()| diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index 2df2b0bafa6..5f16d88a0dc 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -1102,10 +1102,8 @@ select * from v1; a select * from t2; ERROR HY000: Table 't2' was not locked with LOCK TABLES -drop view v1; -drop table t1, t2; -ERROR HY000: Table 't1' was locked with a READ lock and can't be updated unlock tables; +drop view v1; drop table t1, t2; create table t1 (a int); create view v1 as select * from t1 where a < 2 with check option; diff --git a/mysql-test/r/view_grant.result b/mysql-test/r/view_grant.result index 65fbf2d87b6..0c74d8ed91b 100644 --- a/mysql-test/r/view_grant.result +++ b/mysql-test/r/view_grant.result @@ -779,9 +779,9 @@ GRANT CREATE VIEW ON db26813.v2 TO u26813@localhost; GRANT DROP, CREATE VIEW ON db26813.v3 TO u26813@localhost; GRANT SELECT ON db26813.t1 TO u26813@localhost; ALTER VIEW v1 AS SELECT f2 FROM t1; -ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation +ERROR 42000: CREATE VIEW command denied to user 'u26813'@'localhost' for table 'v1' ALTER VIEW v2 AS SELECT f2 FROM t1; -ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation +ERROR 42000: DROP command denied to user 'u26813'@'localhost' for table 'v2' ALTER VIEW v3 AS SELECT f2 FROM t1; ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation SHOW CREATE VIEW v3; diff --git a/mysql-test/r/view_multi.result b/mysql-test/r/view_multi.result new file mode 100644 index 00000000000..95a8d572be4 --- /dev/null +++ b/mysql-test/r/view_multi.result @@ -0,0 +1,48 @@ +reset master; +create table t1 (i int); +create table t2 (i int); +create view v1 as select * from t1; +select get_lock("lock_bg25144", 1); +get_lock("lock_bg25144", 1) +1 +insert into v1 values (get_lock("lock_bg25144", 100));; +drop view v1;; +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +select * from t1; +i +1 +create view v1 as select * from t1; +select get_lock("lock_bg25144", 1); +get_lock("lock_bg25144", 1) +1 +insert into v1 values (get_lock("lock_bg25144", 100));; +alter view v1 as select * from t2;; +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +select * from t1; +i +1 +1 +select * from t2; +i +show binlog events in 'master-bin.000001' from 107; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query 1 # use `test`; create table t1 (i int) +master-bin.000001 # Query 1 # use `test`; create table t2 (i int) +master-bin.000001 # Query 1 # use `test`; CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t1 +master-bin.000001 # Query 1 # use `test`; insert into v1 values (get_lock("lock_bg25144", 100)) +master-bin.000001 # Query 1 # use `test`; drop view v1 +master-bin.000001 # Query 1 # use `test`; CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t1 +master-bin.000001 # Query 1 # use `test`; insert into v1 values (get_lock("lock_bg25144", 100)) +master-bin.000001 # Query 1 # use `test`; ALTER ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t2 +drop table t1, t2; +drop view v1; diff --git a/mysql-test/suite/rpl/t/disabled.def b/mysql-test/suite/rpl/t/disabled.def index bed019f9c79..da962a7fb9d 100644 --- a/mysql-test/suite/rpl/t/disabled.def +++ b/mysql-test/suite/rpl/t/disabled.def @@ -13,3 +13,6 @@ rpl_get_master_version_and_clock: # Bug#46931 2009-10-17 joro rpl.rpl_get_master_version_and_clock fails rpl_cross_version : BUG#43913 2009-10-22 luis rpl_cross_version fails with symptom in described in bug report rpl_spec_variables : BUG#47661 2009-10-27 jasonh rpl_spec_variables fails on PB2 hpux +rpl_empty_master_crash : BUG#48048 +rpl_load_from_master : BUG#48048 +rpl_load_table_from_master : BUG#48048 diff --git a/mysql-test/t/create.test b/mysql-test/t/create.test index 9f3c3a88151..c07014bfc19 100644 --- a/mysql-test/t/create.test +++ b/mysql-test/t/create.test @@ -683,8 +683,8 @@ drop table t1; # Error during open_and_lock_tables() of tables --error ER_NO_SUCH_TABLE create table t1 select * from t2; -# Rather special error which also caught during open tables pahse ---error ER_UPDATE_TABLE_USED +# A special case which is also caught during open tables pahse +--error ER_NO_SUCH_TABLE create table t1 select * from t1; # Error which happens before select_create::prepare() --error ER_CANT_AGGREGATE_2COLLATIONS @@ -706,6 +706,10 @@ create table t1 (i int); create table t1 select 1 as i; create table if not exists t1 select 1 as i; select * from t1; +# Error which is detected after successfull table open. +--error ER_UPDATE_TABLE_USED +create table if not exists t1 select * from t1; +select * from t1; # Error before select_create::prepare() --error ER_CANT_AGGREGATE_2COLLATIONS create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin); diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def index ad7617b9403..bb2749e902b 100644 --- a/mysql-test/t/disabled.def +++ b/mysql-test/t/disabled.def @@ -15,3 +15,4 @@ partition_innodb_builtin : Bug#32430 2009-09-25 mattiasj Waiting for push of Inn partition_innodb_plugin : Bug#32430 2009-09-25 mattiasj Waiting for push of Innodb changes innodb-autoinc : Bug#48482 2009-11-02 svoj innodb-autoinc.test fails with results difference rpl_killed_ddl : Bug#45520: rpl_killed_ddl fails sporadically in pb2 +merge : WL#4144 diff --git a/mysql-test/t/flush.test b/mysql-test/t/flush.test index f27d4cf2fad..4172230a54d 100644 --- a/mysql-test/t/flush.test +++ b/mysql-test/t/flush.test @@ -68,10 +68,13 @@ drop table t1; create table t1 (c1 int); lock table t1 write; # Cannot get the global read lock with write locked tables. ---error 1192 +--error ER_LOCK_OR_ACTIVE_TRANSACTION flush tables with read lock; lock table t1 read; -# Can get the global read lock with read locked tables. +# Cannot get the global read lock with read locked tables. +--error ER_LOCK_OR_ACTIVE_TRANSACTION +flush tables with read lock; +unlock tables; flush tables with read lock; --error 1223 lock table t1 write; @@ -84,12 +87,12 @@ create table t2 (c1 int); create table t3 (c1 int); lock table t1 read, t2 read, t3 write; # Cannot get the global read lock with write locked tables. ---error 1192 +--error ER_LOCK_OR_ACTIVE_TRANSACTION flush tables with read lock; lock table t1 read, t2 read, t3 read; -# Can get the global read lock with read locked tables. +# Cannot get the global read lock with read locked tables. +--error ER_LOCK_OR_ACTIVE_TRANSACTION flush tables with read lock; -# Release all table locks and the global read lock. unlock tables; drop table t1, t2, t3; @@ -157,6 +160,7 @@ flush tables with read lock; unlock tables; lock tables t1 read; +--error ER_LOCK_OR_ACTIVE_TRANSACTION flush tables with read lock; unlock tables; diff --git a/mysql-test/t/flush_table.test b/mysql-test/t/flush_table.test index 50e7e91419a..e4fc1b0c39f 100644 --- a/mysql-test/t/flush_table.test +++ b/mysql-test/t/flush_table.test @@ -15,30 +15,21 @@ insert into t1 values(0); # Test for with read lock + flush lock table t1 read; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +flush table t1; +unlock tables; + +# Test for with write lock + flush + +lock table t1 write; flush table t1; check table t1; unlock tables; -# Test for with 2 read lock in different thread + flush - -lock table t1 read; -connect (locker,localhost,root,,test); -connection locker; -lock table t1 read; -connection default; -send flush table t1; -connection locker; ---sleep 2 -select * from t1; -unlock tables; -connection default; -reap; -select * from t1; -unlock tables; - # Test for with a write lock and a waiting read lock + flush lock table t1 write; +connect (locker,localhost,root,,test); connection locker; send lock table t1 read; connection default; @@ -51,9 +42,9 @@ reap; unlock tables; connection default; -# Test for with a read lock and a waiting write lock + flush +# Test for with a write lock and a waiting write lock + flush -lock table t1 read; +lock table t1 write; connection locker; send lock table t1 write; connection default; diff --git a/mysql-test/t/information_schema.test b/mysql-test/t/information_schema.test index 9da7cc1042d..c6cb28a4a30 100644 --- a/mysql-test/t/information_schema.test +++ b/mysql-test/t/information_schema.test @@ -544,6 +544,7 @@ AND table_name not like 'ndb%' AND table_name not like 'innodb_%' GROUP BY TABLE_SCHEMA; + # # TRIGGERS table test # @@ -914,8 +915,8 @@ DROP PROCEDURE p1; DROP USER mysql_bug20230@localhost; # -# Bug#2123 query with a simple non-correlated subquery over -# INFORMARTION_SCHEMA.TABLES +# Bug#21231 query with a simple non-correlated subquery over +# INFORMARTION_SCHEMA.TABLES # SELECT MAX(table_name) FROM information_schema.tables WHERE table_schema IN ('mysql', 'INFORMATION_SCHEMA', 'test'); @@ -1391,9 +1392,66 @@ SET TIMESTAMP=DEFAULT; --echo End of 5.1 tests. +--echo # +--echo # Additional test for WL#3726 "DDL locking for all metadata objects" +--echo # To avoid possible deadlocks process of filling of I_S tables should +--echo # use high-priority metadata lock requests when opening tables. +--echo # Below we just test that we really use high-priority lock request +--echo # since reproducing a deadlock will require much more complex test. +--echo # +--disable_warnings +drop tables if exists t1, t2, t3; +--enable_warnings +create table t1 (i int); +create table t2 (j int primary key auto_increment); +connect (con3726_1,localhost,root,,test); +--echo # Switching to connection 'con3726_1' +connection con3726_1; +lock table t2 read; +connect (con3726_2,localhost,root,,test); +--echo # Switching to connection 'con3726_2' +connection con3726_2; +--echo # RENAME below will be blocked by 'lock table t2 read' above but +--echo # will add two pending requests for exclusive metadata locks. +--send rename table t2 to t3 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info like "rename table t2 to t3"; +--source include/wait_condition.inc +--echo # These statements should not be blocked by pending lock requests +select table_name, column_name, data_type from information_schema.columns + where table_schema = 'test' and table_name in ('t1', 't2'); +select table_name, auto_increment from information_schema.tables + where table_schema = 'test' and table_name in ('t1', 't2'); +--echo # Switching to connection 'con3726_1' +connection con3726_1; +unlock tables; +--echo # Switching to connection 'con3726_2' +connection con3726_2; +--reap +--echo # Switching to connection 'default' +connection default; +disconnect con3726_1; +disconnect con3726_2; +drop tables t1, t3; + +# +# Bug#39270 I_S optimization algorithm does not work properly in some cases +# +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1'; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS + WHERE CONSTRAINT_SCHEMA='test'; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS + WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test'; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS + WHERE EVENT_OBJECT_SCHEMA='test'; + # # Bug#24062 Incorrect error msg after execute DROP TABLE IF EXISTS on information_schema -# +# --error ER_DBACCESS_DENIED_ERROR create table information_schema.t1 (f1 INT); --error ER_DBACCESS_DENIED_ERROR @@ -1429,27 +1487,18 @@ DROP TABLE t1, information_schema.tables; LOCK TABLES t1 READ, information_schema.tables READ; DROP TABLE t1; -# -# Bug#39270 I_S optimization algorithm does not work properly in some cases -# -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1'; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS - WHERE CONSTRAINT_SCHEMA='test'; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS - WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test'; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS - WHERE EVENT_OBJECT_SCHEMA='test'; - -# -# Bug #43834 Assertion in Natural_join_column::db_name() on an I_S query -# -SELECT * -FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE -LEFT JOIN INFORMATION_SCHEMA.COLUMNS -USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME) -WHERE COLUMNS.TABLE_SCHEMA = 'test' -AND COLUMNS.TABLE_NAME = 't1'; - # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc + +# +# Bug #43834 Assertion in Natural_join_column::db_name() on an I_S query +# + +SELECT * +FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE +LEFT JOIN INFORMATION_SCHEMA.COLUMNS +USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME) +WHERE COLUMNS.TABLE_SCHEMA = 'test' +AND COLUMNS.TABLE_NAME = 't1'; + + diff --git a/mysql-test/t/kill.test b/mysql-test/t/kill.test index 02b033df2e5..98cb9f64d98 100644 --- a/mysql-test/t/kill.test +++ b/mysql-test/t/kill.test @@ -329,6 +329,232 @@ KILL CONNECTION_ID(); SELECT 1; --connection default +--echo # +--echo # Additional test for WL#3726 "DDL locking for all metadata objects" +--echo # Check that DDL and DML statements waiting for metadata locks can +--echo # be killed. Note that we don't cover all situations here since it +--echo # can be tricky to write test case for some of them (e.g. REPAIR or +--echo # ALTER and other statements under LOCK TABLES). +--echo # +--disable_warnings +drop tables if exists t1, t2, t3; +--enable_warnings + +create table t1 (i int primary key); +connect (blocker, localhost, root, , ); +connect (dml, localhost, root, , ); +connect (ddl, localhost, root, , ); + +--echo # Test for RENAME TABLE +--echo # Switching to connection 'blocker' +connection blocker; +lock table t1 read; +--echo # Switching to connection 'ddl' +connection ddl; +let $ID= `select connection_id()`; +--send rename table t1 to t2 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # Test for DROP TABLE +--send drop table t1 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "drop table t1"; +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # Test for CREATE TRIGGER +--send create trigger t1_bi before insert on t1 for each row set @a:=1 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "create trigger t1_bi before insert on t1 for each row set @a:=1"; +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # +--echo # Tests for various kinds of ALTER TABLE +--echo # +--echo # Full-blown ALTER which should copy table +--send alter table t1 add column j int +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t1 add column j int"; +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # Two kinds of simple ALTER +--send alter table t1 rename to t2 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t1 rename to t2"; +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap +--send alter table t1 disable keys +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t1 disable keys"; +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap +--echo # Fast ALTER +--send alter table t1 alter column i set default 100 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t1 alter column i set default 100"; +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap +--echo # Special case which is triggered only for MERGE tables. +--echo # Switching to connection 'blocker' +connection blocker; +unlock tables; +create table t2 (i int primary key) engine=merge union=(t1); +lock tables t2 read; +--echo # Switching to connection 'ddl' +connection ddl; +--send alter table t2 alter column i set default 100 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t2 alter column i set default 100"; +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # Test for DML waiting for meta-data lock +--echo # Switching to connection 'blocker' +connection blocker; +unlock tables; +drop table t2; +create table t2 (k int); +lock tables t1 read; +--echo # Switching to connection 'ddl' +connection ddl; +# Let us add pending exclusive metadata lock on t2 +--send rename tables t1 to t3, t2 to t1 +--echo # Switching to connection 'dml' +connection dml; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "rename tables t1 to t3, t2 to t1"; +let $ID2= `select connection_id()`; +--send insert into t2 values (1) +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "insert into t2 values (1)"; +--replace_result $ID2 ID2 +eval kill query $ID2; +--echo # Switching to connection 'dml' +connection dml; +--error ER_QUERY_INTERRUPTED +--reap +--echo # Switching to connection 'blocker' +connection blocker; +unlock tables; +--echo # Switching to connection 'ddl' +connection ddl; +--reap + +--echo # Test for DML waiting for tables to be flushed +--echo # Switching to connection 'blocker' +connection blocker; +lock tables t1 read; +--echo # Switching to connection 'ddl' +connection ddl; +--echo # Let us mark locked table t1 as old +--send flush tables +--echo # Switching to connection 'dml' +connection dml; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Flushing tables" and + info = "flush tables"; +--send select * from t1 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "select * from t1"; +--replace_result $ID2 ID2 +eval kill query $ID2; +--echo # Switching to connection 'dml' +connection dml; +--error ER_QUERY_INTERRUPTED +--reap +--echo # Switching to connection 'blocker' +connection blocker; +unlock tables; +--echo # Switching to connection 'ddl' +connection ddl; +--reap + +--echo # Cleanup. +--echo # Switching to connection 'default' +connection default; +drop table t3; +drop table t1; + ########################################################################### # Restore global concurrent_insert value. Keep in the end of the test file. diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index 04994e3e48f..856ae020492 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -178,6 +178,7 @@ select * from t2; --error ER_TABLE_NOT_LOCKED select * from t3; select * from v_bug5719; +--error ER_LOCK_OR_ACTIVE_TRANSACTION drop view v_bug5719; --echo --echo sic: did not left LOCK TABLES mode automatically @@ -185,7 +186,7 @@ drop view v_bug5719; --error ER_TABLE_NOT_LOCKED select * from t1; unlock tables; -create view v_bug5719 as select * from t1; +create or replace view v_bug5719 as select * from t1; lock tables v_bug5719 write; select * from v_bug5719; --echo diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test index 75ee6d07723..58f46941920 100644 --- a/mysql-test/t/lock_multi.test +++ b/mysql-test/t/lock_multi.test @@ -164,7 +164,7 @@ connection locker; # Sleep a bit till the select of connection reader is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Waiting for table" and info = + where state = "Locked" and info = "SELECT user.Select_priv FROM user, db WHERE user.user = db.user LIMIT 1"; --source include/wait_condition.inc # Make test case independent from earlier grants. @@ -196,7 +196,7 @@ connection writer; # Sleep a bit till the flush of connection locker is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK"; + where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK"; --source include/wait_condition.inc # This must not block. CREATE TABLE t2 (c1 int); @@ -226,7 +226,7 @@ connection writer; # Sleep a bit till the flush of connection locker is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK"; + where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK"; --source include/wait_condition.inc --error ER_TABLE_NOT_LOCKED CREATE TABLE t2 AS SELECT * FROM t1; @@ -337,10 +337,10 @@ connection con2; send flush tables with read lock; connection con5; --echo # con5 -let $show_statement= SHOW PROCESSLIST; -let $field= State; -let $condition= = 'Flushing tables'; ---source include/wait_show_condition.inc +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "flush tables with read lock"; +--source include/wait_condition.inc --echo # global read lock is taken connection con3; --echo # con3 @@ -528,7 +528,7 @@ connection default; --echo connection: default let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc alter table t1 add column j int; connect (insert,localhost,root,,test,,); @@ -536,7 +536,7 @@ connection insert; --echo connection: insert let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc --send insert into t1 values (1,2); --echo connection: default @@ -586,18 +586,14 @@ connection default; --echo connection: default let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc flush tables; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc unlock tables; -let $wait_condition= - select count(*) = 0 from information_schema.processlist - where state = "Flushing tables"; ---source include/wait_condition.inc connection flush; --reap connection default; @@ -656,18 +652,14 @@ connection default; --echo connection: default let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc flush tables; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc drop table t1; -let $wait_condition= - select count(*) = 0 from information_schema.processlist - where state = "Flushing tables"; ---source include/wait_condition.inc connection flush; --reap connection default; diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test index fee235cd36c..6e771d44b3f 100644 --- a/mysql-test/t/ps_ddl.test +++ b/mysql-test/t/ps_ddl.test @@ -745,7 +745,7 @@ execute stmt; call p_verify_reprepare_count(1); flush table t1; execute stmt; -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); execute stmt; --echo # Test 18-c: dependent VIEW has changed @@ -1453,7 +1453,7 @@ execute stmt; call p_verify_reprepare_count(0); drop temporary table t2; execute stmt; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); drop table t2; execute stmt; call p_verify_reprepare_count(0); diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index b0342491a34..b477b5a3e24 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -1308,9 +1308,11 @@ select f3()| select id, f3() from t1 as t11 order by id| # Degenerate cases work too :) select f0()| +# But these should not (particularly views should be locked explicitly). +--error ER_TABLE_NOT_LOCKED select * from v0| +--error ER_TABLE_NOT_LOCKED select *, f0() from v0, (select 123) as d1| -# But these should not ! --error ER_TABLE_NOT_LOCKED select id, f3() from t1| --error ER_TABLE_NOT_LOCKED diff --git a/mysql-test/t/trigger_notembedded.test b/mysql-test/t/trigger_notembedded.test index 7a7e6c6bc85..8a570a7e87d 100644 --- a/mysql-test/t/trigger_notembedded.test +++ b/mysql-test/t/trigger_notembedded.test @@ -896,7 +896,7 @@ connection default; --echo connection: default let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc create trigger t1_bi before insert on t1 for each row begin end; unlock tables; diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test index abf8dac2870..c3ff58880c9 100644 --- a/mysql-test/t/view.test +++ b/mysql-test/t/view.test @@ -1016,10 +1016,8 @@ lock tables t1 read, v1 read; select * from v1; -- error ER_TABLE_NOT_LOCKED select * from t2; -drop view v1; ---error ER_TABLE_NOT_LOCKED_FOR_WRITE -drop table t1, t2; unlock tables; +drop view v1; drop table t1, t2; # diff --git a/mysql-test/t/view_grant.test b/mysql-test/t/view_grant.test index f01edb1e499..d94bcfc29ed 100644 --- a/mysql-test/t/view_grant.test +++ b/mysql-test/t/view_grant.test @@ -1047,9 +1047,9 @@ GRANT SELECT ON db26813.t1 TO u26813@localhost; connect (u1,localhost,u26813,,db26813); connection u1; ---error ER_SPECIFIC_ACCESS_DENIED_ERROR +--error ER_TABLEACCESS_DENIED_ERROR ALTER VIEW v1 AS SELECT f2 FROM t1; ---error ER_SPECIFIC_ACCESS_DENIED_ERROR +--error ER_TABLEACCESS_DENIED_ERROR ALTER VIEW v2 AS SELECT f2 FROM t1; --error ER_SPECIFIC_ACCESS_DENIED_ERROR ALTER VIEW v3 AS SELECT f2 FROM t1; diff --git a/mysql-test/t/view_multi.test b/mysql-test/t/view_multi.test new file mode 100644 index 00000000000..a61e7738095 --- /dev/null +++ b/mysql-test/t/view_multi.test @@ -0,0 +1,110 @@ +# +# QQ: Should we find a better place for this test? +# May be binlog or rpl suites ? +# +--source include/have_log_bin.inc +--source include/have_binlog_format_mixed_or_statement.inc + +# +# Bug #25144 "replication / binlog with view breaks". +# Statements that used views didn't ensure that view were not modified +# during their execution. Indeed this led to incorrect binary log with +# statement based logging. +# +--disable_parsing +drop table if not exists t1, t2; +drop view if exists v1; +--enable_parsing + +# We are going to use binary log later to check that statements are +# logged in proper order, so it is good idea to reset it here. +reset master; + +connect (addconn1,localhost,root,,); +connect (addconn2,localhost,root,,); +connection default; + +create table t1 (i int); +create table t2 (i int); +create view v1 as select * from t1; + +# First we try to concurrently execute statement that uses view +# and statement that drops it. We use "user" locks as means to +# suspend execution of first statement once it opens our view. +select get_lock("lock_bg25144", 1); + +connection addconn1; +--send insert into v1 values (get_lock("lock_bg25144", 100)); + +connection addconn2; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "User lock" and info like "insert into v1 %lock_bg25144%"; +--source include/wait_condition.inc +--send drop view v1; + +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "drop view v1"; +--source include/wait_condition.inc + +select release_lock("lock_bg25144"); + +connection addconn1; +--reap +select release_lock("lock_bg25144"); + +connection addconn2; +--reap + +connection default; +# Check that insertion through view did happen. +select * from t1; +# At the end of test we will check that statements were +# logged in proper order. + +# Now we will repeat the test by trying concurrently execute +# statement that uses a view and statement that alters it. +create view v1 as select * from t1; + +select get_lock("lock_bg25144", 1); + +connection addconn1; +--send insert into v1 values (get_lock("lock_bg25144", 100)); + +connection addconn2; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "User lock" and info like "insert into v1 %lock_bg25144%"; +--source include/wait_condition.inc +--send alter view v1 as select * from t2; + +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter view v1 as select * from t2"; +--source include/wait_condition.inc + +select release_lock("lock_bg25144"); + +connection addconn1; +--reap +select release_lock("lock_bg25144"); + +connection addconn2; +--reap + +connection default; + +# Second insertion should go to t1 as well. +select * from t1; +select * from t2; + +# Now let us check that statements were logged in proper order +--replace_column 2 # 5 # +show binlog events in 'master-bin.000001' from 107; + +drop table t1, t2; +drop view v1; diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 15c2d950ff9..a40cf04f37c 100755 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -76,7 +76,7 @@ SET (SQL_SOURCE rpl_rli.cc rpl_mi.cc sql_servers.cc sql_connect.cc scheduler.cc sql_profile.cc event_parse_data.cc - sql_signal.cc rpl_handler.cc + sql_signal.cc rpl_handler.cc mdl.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h ${PROJECT_SOURCE_DIR}/include/mysqld_error.h diff --git a/sql/Makefile.am b/sql/Makefile.am index 15ee0d588c4..95864dfbb1b 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -112,7 +112,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ event_data_objects.h event_scheduler.h \ sql_partition.h partition_info.h partition_element.h \ contributors.h sql_servers.h sql_signal.h records.h \ - sql_prepare.h rpl_handler.h replication.h + sql_prepare.h rpl_handler.h replication.h mdl.h \ + sql_plist.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ @@ -158,7 +159,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc \ sql_servers.cc event_parse_data.cc sql_signal.cc \ - rpl_handler.cc + rpl_handler.cc mdl.cc nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 8faab5023da..0cf16e3a8a4 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -555,6 +555,7 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, DBUG_ENTER("Event_db_repository::open_event_table"); tables.init_one_table("mysql", "event", lock_type); + alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1109,6 +1110,7 @@ Event_db_repository::check_system_tables(THD *thd) /* Check mysql.db */ tables.init_one_table("mysql", "db", TL_READ); + alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1126,6 +1128,7 @@ Event_db_repository::check_system_tables(THD *thd) } /* Check mysql.user */ tables.init_one_table("mysql", "user", TL_READ); + alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1146,6 +1149,7 @@ Event_db_repository::check_system_tables(THD *thd) } /* Check mysql.event */ tables.init_one_table("mysql", "event", TL_READ); + alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index b71a7f602ab..a7c4cbf5d20 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -565,7 +565,7 @@ int ha_ndbcluster::ndb_err(NdbTransaction *trans) bzero((char*) &table_list,sizeof(table_list)); table_list.db= m_dbname; table_list.alias= table_list.table_name= m_tabname; - close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE); break; } default: @@ -7276,6 +7276,20 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, } } + /* + ndbcluster_find_files() may be called from I_S code and ndbcluster_binlog + thread in situations when some tables are already open. This means that + code below will try to obtain exclusive metadata lock on some table + while holding shared meta-data lock on other tables. This might lead to + a deadlock, and therefore is disallowed by assertions of the metadata + locking subsystem. In order to temporarily make the code work, we must + reset and backup the open tables state, thus hide the existing locks + from MDL asserts. But in the essence this is violation of metadata + locking protocol which has to be closed ASAP. + */ + Open_tables_state open_tables_state_backup; + thd->reset_n_backup_open_tables_state(&open_tables_state_backup); + if (!global_read_lock) { // Delete old files @@ -7299,8 +7313,11 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, } } + thd->restore_backup_open_tables_state(&open_tables_state_backup); + + /* Lock mutex before creating .FRM files. */ pthread_mutex_lock(&LOCK_open); - // Create new files + /* Create new files. */ List_iterator_fast it2(create_list); while ((file_name_str=it2++)) { @@ -8215,7 +8232,7 @@ int handle_trailing_share(NDB_SHARE *share) table_list.db= share->db; table_list.alias= table_list.table_name= share->table_name; safe_mutex_assert_owner(&LOCK_open); - close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE); + close_cached_tables(thd, &table_list, TRUE, FALSE); pthread_mutex_lock(&ndbcluster_mutex); /* ndb_share reference temporary free */ diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index e34a22cf9f4..bf76960201d 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -140,6 +140,8 @@ static Uint64 *p_latest_trans_gci= 0; */ static TABLE *ndb_binlog_index= 0; static TABLE_LIST binlog_tables; +static MDL_LOCK binlog_mdl_lock; +static char binlog_mdlkey[MAX_DBKEY_LENGTH]; /* Helper functions @@ -282,7 +284,13 @@ static void run_query(THD *thd, char *buf, char *end, thd_ndb->m_error_code, (int) thd->is_error(), thd->is_slave_error); } + + /* + After executing statement we should unlock and close tables open + by it as well as release meta-data locks obtained by it. + */ close_thread_tables(thd); + /* XXX: this code is broken. mysql_parse()/mysql_reset_thd_for_next_command() can not be called from within a statement, and @@ -921,7 +929,7 @@ int ndbcluster_setup_binlog_table_shares(THD *thd) ndb_binlog_tables_inited= TRUE; if (ndb_extra_logging) sql_print_information("NDB Binlog: ndb tables writable"); - close_cached_tables(NULL, NULL, TRUE, FALSE, FALSE); + close_cached_tables(NULL, NULL, TRUE, FALSE); pthread_mutex_unlock(&LOCK_open); /* Signal injector thread that all is setup */ pthread_cond_signal(&injector_cond); @@ -1735,7 +1743,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, bzero((char*) &table_list,sizeof(table_list)); table_list.db= (char *)dbname; table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE); + close_cached_tables(thd, &table_list, TRUE, FALSE); if ((error= ndbcluster_binlog_open_table(thd, share, table_share, table, 1))) @@ -1841,7 +1849,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, bzero((char*) &table_list,sizeof(table_list)); table_list.db= (char *)dbname; table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE); /* ndb_share reference create free */ DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u", share->key, share->use_count)); @@ -1962,7 +1970,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, bzero((char*) &table_list,sizeof(table_list)); table_list.db= schema->db; table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE); } /* ndb_share reference temporary free */ if (share) @@ -2079,7 +2087,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, pthread_mutex_unlock(&ndb_schema_share_mutex); /* end protect ndb_schema_share */ - close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE); + close_cached_tables(NULL, NULL, FALSE, FALSE); // fall through case NDBEVENT::TE_ALTER: ndb_handle_schema_change(thd, ndb, pOp, tmp_share); @@ -2236,7 +2244,7 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd, bzero((char*) &table_list,sizeof(table_list)); table_list.db= schema->db; table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE); } if (schema_type != SOT_ALTER_TABLE) break; @@ -2323,18 +2331,21 @@ struct ndb_binlog_index_row { /* Open the ndb_binlog_index table */ -static int open_ndb_binlog_index(THD *thd, TABLE_LIST *tables, - TABLE **ndb_binlog_index) +static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index) { static char repdb[]= NDB_REP_DB; static char reptable[]= NDB_REP_TABLE; const char *save_proc_info= thd->proc_info; + TABLE_LIST *tables= &binlog_tables; bzero((char*) tables, sizeof(*tables)); tables->db= repdb; tables->alias= tables->table_name= reptable; tables->lock_type= TL_WRITE; thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE; + mdl_init_lock(&binlog_mdl_lock, binlog_mdlkey, 0, tables->db, + tables->table_name); + tables->mdl_lock= &binlog_mdl_lock; tables->required_type= FRMTYPE_TABLE; uint counter; thd->clear_error(); @@ -2374,7 +2385,7 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row) for ( ; ; ) /* loop for need_reopen */ { - if (!ndb_binlog_index && open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index)) + if (!ndb_binlog_index && open_ndb_binlog_index(thd, &ndb_binlog_index)) { error= -1; goto add_ndb_binlog_index_err; @@ -2385,7 +2396,7 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row) if (need_reopen) { TABLE_LIST *p_binlog_tables= &binlog_tables; - close_tables_for_reopen(thd, &p_binlog_tables); + close_tables_for_reopen(thd, &p_binlog_tables, FALSE); ndb_binlog_index= 0; continue; } @@ -3893,7 +3904,7 @@ restart: static char db[]= ""; thd->db= db; if (ndb_binlog_running) - open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index); + open_ndb_binlog_index(thd, &ndb_binlog_index); thd->db= db; } do_ndbcluster_binlog_close_connection= BCCC_running; diff --git a/sql/handler.cc b/sql/handler.cc index 17a92b00b4f..9c32171eefd 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2997,20 +2997,13 @@ static bool update_frm_version(TABLE *table) if ((file= my_open(path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0) { uchar version[4]; - char *key= table->s->table_cache_key.str; - uint key_length= table->s->table_cache_key.length; - TABLE *entry; - HASH_SEARCH_STATE state; int4store(version, MYSQL_VERSION_ID); if ((result= my_pwrite(file,(uchar*) version,4,51L,MYF_RW))) goto err; - for (entry=(TABLE*) my_hash_first(&open_cache,(uchar*) key,key_length, &state); - entry; - entry= (TABLE*) my_hash_next(&open_cache,(uchar*) key,key_length, &state)) - entry->s->mysql_version= MYSQL_VERSION_ID; + table->s->mysql_version= MYSQL_VERSION_ID; } err: if (file >= 0) diff --git a/sql/lock.cc b/sql/lock.cc index 56ae94ddc39..0c8c3095844 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -955,361 +955,56 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, *****************************************************************************/ /** - Lock and wait for the named lock. + Obtain exclusive metadata locks on the list of tables. - @param thd Thread handler - @param table_list Lock first table in this list + @param thd Thread handle + @param table_list List of tables to lock + @note This function assumes that no metadata locks were acquired + before calling it. Also it cannot be called while holding + LOCK_open mutex. Both these invariants are enforced by asserts + in mdl_acquire_exclusive_locks() functions. - @note - Works together with global read lock. - - @retval - 0 ok - @retval - 1 error -*/ - -int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list) -{ - int lock_retcode; - int error= -1; - DBUG_ENTER("lock_and_wait_for_table_name"); - - if (wait_if_global_read_lock(thd, 0, 1)) - DBUG_RETURN(1); - pthread_mutex_lock(&LOCK_open); - if ((lock_retcode = lock_table_name(thd, table_list, TRUE)) < 0) - goto end; - if (lock_retcode && wait_for_locked_table_names(thd, table_list)) - { - unlock_table_name(thd, table_list); - goto end; - } - error=0; - -end: - pthread_mutex_unlock(&LOCK_open); - start_waiting_global_read_lock(thd); - DBUG_RETURN(error); -} - - -/** - Put a not open table with an old refresh version in the table cache. - - @param thd Thread handler - @param table_list Lock first table in this list - @param check_in_use Do we need to check if table already in use by us - - @note - One must have a lock on LOCK_open! - - @warning - If you are going to update the table, you should use - lock_and_wait_for_table_name instead of this function as this works - together with 'FLUSH TABLES WITH READ LOCK' - - @note - This will force any other threads that uses the table to release it - as soon as possible. - - @return - < 0 error - @return - == 0 table locked - @return - > 0 table locked, but someone is using it -*/ - -int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use) -{ - TABLE *table; - char key[MAX_DBKEY_LENGTH]; - char *db= table_list->db; - uint key_length; - bool found_locked_table= FALSE; - HASH_SEARCH_STATE state; - DBUG_ENTER("lock_table_name"); - DBUG_PRINT("enter",("db: %s name: %s", db, table_list->table_name)); - - key_length= create_table_def_key(thd, key, table_list, 0); - - if (check_in_use) - { - /* Only insert the table if we haven't insert it already */ - for (table=(TABLE*) my_hash_first(&open_cache, (uchar*)key, - key_length, &state); - table ; - table = (TABLE*) my_hash_next(&open_cache,(uchar*) key, - key_length, &state)) - { - if (table->reginfo.lock_type < TL_WRITE) - { - if (table->in_use == thd) - found_locked_table= TRUE; - continue; - } - - if (table->in_use == thd) - { - DBUG_PRINT("info", ("Table is in use")); - table->s->version= 0; // Ensure no one can use this - table->locked_by_name= 1; - DBUG_RETURN(0); - } - } - } - - if (thd->locked_tables && thd->locked_tables->table_count && - ! find_temporary_table(thd, table_list->db, table_list->table_name)) - { - if (found_locked_table) - my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_list->alias); - else - my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_list->alias); - - DBUG_RETURN(-1); - } - - if (!(table= table_cache_insert_placeholder(thd, key, key_length))) - DBUG_RETURN(-1); - - table_list->table=table; - - /* Return 1 if table is in use */ - DBUG_RETURN(test(remove_table_from_cache(thd, db, table_list->table_name, - check_in_use ? RTFC_NO_FLAG : RTFC_WAIT_OTHER_THREAD_FLAG))); -} - - -void unlock_table_name(THD *thd, TABLE_LIST *table_list) -{ - if (table_list->table) - { - my_hash_delete(&open_cache, (uchar*) table_list->table); - broadcast_refresh(); - } -} - - -static bool locked_named_table(THD *thd, TABLE_LIST *table_list) -{ - for (; table_list ; table_list=table_list->next_local) - { - TABLE *table= table_list->table; - if (table) - { - TABLE *save_next= table->next; - bool result; - table->next= 0; - result= table_is_used(table_list->table, 0); - table->next= save_next; - if (result) - return 1; - } - } - return 0; // All tables are locked -} - - -bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list) -{ - bool result=0; - DBUG_ENTER("wait_for_locked_table_names"); - - safe_mutex_assert_owner(&LOCK_open); - - while (locked_named_table(thd,table_list)) - { - if (thd->killed) - { - result=1; - break; - } - wait_for_condition(thd, &LOCK_open, &COND_refresh); - pthread_mutex_lock(&LOCK_open); - } - DBUG_RETURN(result); -} - - -/** - Lock all tables in list with a name lock. - - REQUIREMENTS - - One must have a lock on LOCK_open when calling this - - @param thd Thread handle - @param table_list Names of tables to lock - - @note - If you are just locking one table, you should use - lock_and_wait_for_table_name(). - - @retval - 0 ok - @retval - 1 Fatal error (end of memory ?) + @retval FALSE Success. + @retval TRUE Failure (OOM or thread was killed). */ bool lock_table_names(THD *thd, TABLE_LIST *table_list) { - bool got_all_locks=1; TABLE_LIST *lock_table; + MDL_LOCK *mdl_lock; for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) { - int got_lock; - if ((got_lock=lock_table_name(thd,lock_table, TRUE)) < 0) - goto end; // Fatal error - if (got_lock) - got_all_locks=0; // Someone is using table + if (!(mdl_lock= mdl_alloc_lock(0, lock_table->db, lock_table->table_name, + thd->mem_root))) + goto end; + mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, mdl_lock); } - - /* If some table was in use, wait until we got the lock */ - if (!got_all_locks && wait_for_locked_table_names(thd, table_list)) - goto end; + if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + return 1; return 0; end: - unlock_table_names(thd, table_list, lock_table); + mdl_remove_all_locks(&thd->mdl_context); return 1; } /** - Unlock all tables in list with a name lock. + Release all metadata locks previously obtained by lock_table_names(). - @param thd Thread handle. - @param table_list Names of tables to lock. + @param thd Thread handle. - @note - This function needs to be protected by LOCK_open. If we're - under LOCK TABLES, this function does not work as advertised. Namely, - it does not exclude other threads from using this table and does not - put an exclusive name lock on this table into the table cache. - - @see lock_table_names - @see unlock_table_names - - @retval TRUE An error occured. - @retval FALSE Name lock successfully acquired. + @note Cannot be called while holding LOCK_open mutex. */ -bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list) -{ - if (lock_table_names(thd, table_list)) - return TRUE; - - /* - Upgrade the table name locks from semi-exclusive to exclusive locks. - */ - for (TABLE_LIST *table= table_list; table; table= table->next_global) - { - if (table->table) - table->table->open_placeholder= 1; - } - return FALSE; -} - - -/** - Test is 'table' is protected by an exclusive name lock. - - @param[in] thd The current thread handler - @param[in] table_list Table container containing the single table to be - tested - - @note Needs to be protected by LOCK_open mutex. - - @return Error status code - @retval TRUE Table is protected - @retval FALSE Table is not protected -*/ - -bool -is_table_name_exclusively_locked_by_this_thread(THD *thd, - TABLE_LIST *table_list) -{ - char key[MAX_DBKEY_LENGTH]; - uint key_length; - - key_length= create_table_def_key(thd, key, table_list, 0); - - return is_table_name_exclusively_locked_by_this_thread(thd, (uchar *)key, - key_length); -} - - -/** - Test is 'table key' is protected by an exclusive name lock. - - @param[in] thd The current thread handler. - @param[in] key - @param[in] key_length - - @note Needs to be protected by LOCK_open mutex - - @retval TRUE Table is protected - @retval FALSE Table is not protected - */ - -bool -is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key, - int key_length) -{ - HASH_SEARCH_STATE state; - TABLE *table; - - for (table= (TABLE*) my_hash_first(&open_cache, key, - key_length, &state); - table ; - table= (TABLE*) my_hash_next(&open_cache, key, - key_length, &state)) - { - if (table->in_use == thd && - table->open_placeholder == 1 && - table->s->version == 0) - return TRUE; - } - - return FALSE; -} - -/** - Unlock all tables in list with a name lock. - - @param - thd Thread handle - @param - table_list Names of tables to unlock - @param - last_table Don't unlock any tables after this one. - (default 0, which will unlock all tables) - - @note - One must have a lock on LOCK_open when calling this. - - @note - This function will broadcast refresh signals to inform other threads - that the name locks are removed. - - @retval - 0 ok - @retval - 1 Fatal error (end of memory ?) -*/ - -void unlock_table_names(THD *thd, TABLE_LIST *table_list, - TABLE_LIST *last_table) +void unlock_table_names(THD *thd) { DBUG_ENTER("unlock_table_names"); - for (TABLE_LIST *table= table_list; - table != last_table; - table= table->next_local) - unlock_table_name(thd,table); - broadcast_refresh(); + mdl_release_locks(&thd->mdl_context); + mdl_remove_all_locks(&thd->mdl_context); DBUG_VOID_RETURN; } @@ -1455,6 +1150,33 @@ bool lock_global_read_lock(THD *thd) thd->global_read_lock= GOT_GLOBAL_READ_LOCK; global_read_lock++; thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock + /* + When we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES, + tables being reopened are protected only by meta-data locks at + some point. To avoid sneaking in with our global read lock at + this moment we have to take global shared meta data lock. + + TODO: We should change this code to acquire global shared metadata + lock before acquiring global read lock. But in order to do + this we have to get rid of all those places in which + wait_if_global_read_lock() is called before acquiring + metadata locks first. Also long-term we should get rid of + redundancy between metadata locks, global read lock and DDL + blocker (see WL#4399 and WL#4400). + */ + if (mdl_acquire_global_shared_lock(&thd->mdl_context)) + { + /* Our thread was killed -- return back to initial state. */ + pthread_mutex_lock(&LOCK_global_read_lock); + if (!(--global_read_lock)) + { + DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock")); + pthread_cond_broadcast(&COND_global_read_lock); + } + pthread_mutex_unlock(&LOCK_global_read_lock); + thd->global_read_lock= 0; + DBUG_RETURN(1); + } } /* We DON'T set global_read_lock_blocks_commit now, it will be set after @@ -1476,6 +1198,8 @@ void unlock_global_read_lock(THD *thd) ("global_read_lock: %u global_read_lock_blocks_commit: %u", global_read_lock, global_read_lock_blocks_commit)); + mdl_release_global_shared_lock(&thd->mdl_context); + pthread_mutex_lock(&LOCK_global_read_lock); tmp= --global_read_lock; if (thd->global_read_lock == MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT) diff --git a/sql/log_event.cc b/sql/log_event.cc index 3d35dec4fb0..fd0e20d690d 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -3036,7 +3036,7 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, } else { - const_cast(rli)->clear_tables_to_lock(); + const_cast(rli)->slave_close_thread_tables(thd); } /* @@ -7238,8 +7238,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) */ DBUG_ASSERT(get_flags(STMT_END_F)); - const_cast(rli)->clear_tables_to_lock(); - close_thread_tables(thd); + const_cast(rli)->slave_close_thread_tables(thd); thd->clear_error(); DBUG_RETURN(0); } @@ -7322,7 +7321,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) "unexpected success or fatal error")); thd->is_slave_error= 1; } - const_cast(rli)->clear_tables_to_lock(); + const_cast(rli)->slave_close_thread_tables(thd); DBUG_RETURN(actual_error); } @@ -7347,7 +7346,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) mysql_unlock_tables(thd, thd->lock); thd->lock= 0; thd->is_slave_error= 1; - const_cast(rli)->clear_tables_to_lock(); + const_cast(rli)->slave_close_thread_tables(thd); DBUG_RETURN(ERR_BAD_TABLE_DEF); } } @@ -7530,12 +7529,6 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) } } // if (table) - /* - We need to delay this clear until here bacause unpack_current_row() uses - master-side table definitions stored in rli. - */ - if (rli->tables_to_lock && get_flags(STMT_END_F)) - const_cast(rli)->clear_tables_to_lock(); if (error) { @@ -8064,7 +8057,8 @@ Table_map_log_event::~Table_map_log_event() int Table_map_log_event::do_apply_event(Relay_log_info const *rli) { RPL_TABLE_LIST *table_list; - char *db_mem, *tname_mem; + char *db_mem, *tname_mem, *mdlkey; + MDL_LOCK *mdl_lock; size_t dummy_len; void *memory; DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)"); @@ -8079,6 +8073,8 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) &table_list, (uint) sizeof(RPL_TABLE_LIST), &db_mem, (uint) NAME_LEN + 1, &tname_mem, (uint) NAME_LEN + 1, + &mdl_lock, sizeof(MDL_LOCK), + &mdlkey, MAX_DBKEY_LENGTH, NullS))) DBUG_RETURN(HA_ERR_OUT_OF_MEM); @@ -8091,6 +8087,8 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) table_list->updating= 1; strmov(table_list->db, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len)); strmov(table_list->table_name, m_tblnam); + mdl_init_lock(mdl_lock, mdlkey, 0, table_list->db, table_list->table_name); + table_list->mdl_lock= mdl_lock; int error= 0; diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index fbcbb388236..030d51b3618 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -32,8 +32,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info */ DBUG_ASSERT(ev->get_flags(Old_rows_log_event::STMT_END_F)); - const_cast(rli)->clear_tables_to_lock(); - close_thread_tables(thd); + const_cast(rli)->slave_close_thread_tables(thd); thd->clear_error(); DBUG_RETURN(0); } @@ -91,7 +90,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info "unexpected success or fatal error")); thd->is_slave_error= 1; } - const_cast(rli)->clear_tables_to_lock(); + const_cast(rli)->slave_close_thread_tables(thd); DBUG_RETURN(actual_error); } @@ -109,10 +108,8 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info { if (ptr->m_tabledef.compatible_with(rli, ptr->table)) { - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; thd->is_slave_error= 1; - const_cast(rli)->clear_tables_to_lock(); + const_cast(rli)->slave_close_thread_tables(thd); DBUG_RETURN(Old_rows_log_event::ERR_BAD_TABLE_DEF); } } @@ -236,13 +233,6 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info } } - /* - We need to delay this clear until the table def is no longer needed. - The table def is needed in unpack_row(). - */ - if (rli->tables_to_lock && ev->get_flags(Old_rows_log_event::STMT_END_F)) - const_cast(rli)->clear_tables_to_lock(); - if (error) { /* error has occured during the transaction */ rli->report(ERROR_LEVEL, thd->stmt_da->sql_errno(), @@ -1440,8 +1430,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ DBUG_ASSERT(get_flags(STMT_END_F)); - const_cast(rli)->clear_tables_to_lock(); - close_thread_tables(thd); + const_cast(rli)->slave_close_thread_tables(thd); thd->clear_error(); DBUG_RETURN(0); } @@ -1496,7 +1485,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) "Error in %s event: when locking tables", get_type_str()); } - const_cast(rli)->clear_tables_to_lock(); + const_cast(rli)->slave_close_thread_tables(thd); DBUG_RETURN(error); } @@ -1515,7 +1504,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ thd->binlog_flush_pending_rows_event(false); TABLE_LIST *tables= rli->tables_to_lock; - close_tables_for_reopen(thd, &tables); + close_tables_for_reopen(thd, &tables, FALSE); uint tables_count= rli->tables_to_lock_count; if ((error= open_tables(thd, &tables, &tables_count, 0))) @@ -1533,7 +1522,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) "unexpected success or fatal error")); thd->is_slave_error= 1; } - const_cast(rli)->clear_tables_to_lock(); + const_cast(rli)->slave_close_thread_tables(thd); DBUG_RETURN(error); } } @@ -1552,10 +1541,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) { if (ptr->m_tabledef.compatible_with(rli, ptr->table)) { - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; thd->is_slave_error= 1; - const_cast(rli)->clear_tables_to_lock(); + const_cast(rli)->slave_close_thread_tables(thd); DBUG_RETURN(ERR_BAD_TABLE_DEF); } } @@ -1726,13 +1713,6 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) } } // if (table) - /* - We need to delay this clear until here bacause unpack_current_row() uses - master-side table definitions stored in rli. - */ - if (rli->tables_to_lock && get_flags(STMT_END_F)) - const_cast(rli)->clear_tables_to_lock(); - if (error) { /* error has occured during the transaction */ rli->report(ERROR_LEVEL, thd->net.last_errno, diff --git a/sql/mdl.cc b/sql/mdl.cc new file mode 100644 index 00000000000..00dcc12cdc8 --- /dev/null +++ b/sql/mdl.cc @@ -0,0 +1,1342 @@ +/* Copyright (C) 2007-2008 MySQL AB + + 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 Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + + +/* + TODO: Remove this dependency on mysql_priv.h. It's not + trivial step at the moment since currently we access to + some of THD members and use some of its methods here. +*/ +#include "mysql_priv.h" +#include "mdl.h" + + +/** + The lock context. Created internally for an acquired lock. + For a given name, there exists only one MDL_LOCK_DATA instance, + and it exists only when the lock has been granted. + Can be seen as an MDL subsystem's version of TABLE_SHARE. +*/ + +struct MDL_LOCK_DATA +{ + I_P_List active_shared; + /* + There can be several upgraders and active exclusive + belonging to the same context. + */ + I_P_List active_shared_waiting_upgrade; + I_P_List active_exclusive; + I_P_List waiting_exclusive; + uint users; + void *cached_object; + mdl_cached_object_release_hook cached_object_release_hook; + + MDL_LOCK_DATA() : cached_object(0), cached_object_release_hook(0) {} + + MDL_LOCK *get_key_owner() + { + return !active_shared.is_empty() ? + active_shared.head() : + (!active_shared_waiting_upgrade.is_empty() ? + active_shared_waiting_upgrade.head() : + (!active_exclusive.is_empty() ? + active_exclusive.head() : waiting_exclusive.head())); + } + + bool has_no_other_users() + { + return (users == 1); + } +}; + + +pthread_mutex_t LOCK_mdl; +pthread_cond_t COND_mdl; +HASH mdl_locks; +uint global_shared_locks_pending; +uint global_shared_locks_acquired; +uint global_intention_exclusive_locks_acquired; + + + +extern "C" uchar *mdl_locks_key(const uchar *record, size_t *length, + my_bool not_used __attribute__((unused))) +{ + MDL_LOCK_DATA *entry=(MDL_LOCK_DATA*) record; + *length= entry->get_key_owner()->key_length; + return (uchar*) entry->get_key_owner()->key; +} + + +/** + Initialize the metadata locking subsystem. + + This function is called at server startup. + + In particular, initializes the new global mutex and + the associated condition variable: LOCK_mdl and COND_mdl. + These locking primitives are implementation details of the MDL + subsystem and are private to it. + + Note, that even though the new implementation adds acquisition + of a new global mutex to the execution flow of almost every SQL + statement, the design capitalizes on that to later save on + look ups in the table definition cache. This leads to reduced + contention overall and on LOCK_open in particular. + Please see the description of mdl_acquire_shared_lock() for details. +*/ + +void mdl_init() +{ + pthread_mutex_init(&LOCK_mdl, NULL); + pthread_cond_init(&COND_mdl, NULL); + my_hash_init(&mdl_locks, &my_charset_bin, 16 /* FIXME */, 0, 0, + mdl_locks_key, 0, 0); + global_shared_locks_pending= global_shared_locks_acquired= 0; + global_intention_exclusive_locks_acquired= 0; +} + + +/** + Release resources of metadata locking subsystem. + + Destroys the global mutex and the condition variable. + Called at server shutdown. +*/ + +void mdl_destroy() +{ + DBUG_ASSERT(!mdl_locks.records); + pthread_mutex_destroy(&LOCK_mdl); + pthread_cond_destroy(&COND_mdl); + my_hash_free(&mdl_locks); +} + + +/** + Initialize a metadata locking context. + + This is to be called when a new server connection is created. +*/ + +void mdl_context_init(MDL_CONTEXT *context, THD *thd) +{ + context->locks.empty(); + context->thd= thd; + context->has_global_shared_lock= FALSE; +} + + +/** + Destroy metadata locking context. + + Assumes and asserts that there are no active or pending locks + associated with this context at the time of the destruction. + + Currently does nothing. Asserts that there are no pending + or satisfied lock requests. The pending locks must be released + prior to destruction. This is a new way to express the assertion + that all tables are closed before a connection is destroyed. +*/ + +void mdl_context_destroy(MDL_CONTEXT *context) +{ + DBUG_ASSERT(context->locks.is_empty()); + DBUG_ASSERT(!context->has_global_shared_lock); +} + + +/** + Backup and reset state of meta-data locking context. + + mdl_context_backup_and_reset(), mdl_context_restore() and + mdl_context_merge() are used by HANDLER implementation which + needs to open table for new HANDLER independently of already + open HANDLERs and add this table/metadata lock to the set of + tables open/metadata locks for HANDLERs afterwards. +*/ + +void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) +{ + backup->locks.empty(); + ctx->locks.swap(backup->locks); +} + + +/** + Restore state of meta-data locking context from backup. +*/ + +void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) +{ + DBUG_ASSERT(ctx->locks.is_empty()); + ctx->locks.swap(backup->locks); +} + + +/** + Merge meta-data locks from one context into another. +*/ + +void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) +{ + MDL_LOCK *l; + + DBUG_ASSERT(dst->thd == src->thd); + + if (!src->locks.is_empty()) + { + I_P_List_iterator it(src->locks); + while ((l= it++)) + { + DBUG_ASSERT(l->ctx); + l->ctx= dst; + dst->locks.push_front(l); + } + src->locks.empty(); + } +} + + +/** + Initialize a lock request. + + This is to be used for every lock request. + + Note that initialization and allocation are split + into two calls. This is to allow flexible memory management + of lock requests. Normally a lock request is stored + in statement memory (e.g. is a member of struct TABLE_LIST), + but we would also like to allow allocation of lock + requests in other memory roots, for example in the grant + subsystem, to lock privilege tables. + + The MDL subsystem does not own or manage memory of lock + requests. Instead it assumes that the life time of every lock + request encloses calls to mdl_acquire_shared_lock() and + mdl_release_locks(). + + @param mdl Pointer to an MDL_LOCK object to initialize + @param key_buff Pointer to the buffer for key for the lock request + (should be at least strlen(db) + strlen(name) + + 2 bytes, or, if the lengths are not known, MAX_DBNAME_LENGTH) + @param type Id of type of object to be locked + @param db Name of database to which the object belongs + @param name Name of of the object + + Stores the database name, object name and the type in the key + buffer. Initializes mdl_el to point to the key. + We can't simply initialize mdl_el with type, db and name + by-pointer because of the underlying HASH implementation + requires the key to be a contiguous buffer. + + The initialized lock request will have MDL_SHARED type and + normal priority. + + Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2 + Note that tables and views have the same lock type, since + they share the same name space in the SQL standard. +*/ + +void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db, + const char *name) +{ + int4store(key, type); + mdl->key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; + mdl->key= key; + mdl->type= MDL_SHARED; + mdl->state= MDL_PENDING; + mdl->prio= MDL_NORMAL_PRIO; + mdl->upgradable= FALSE; +#ifndef DBUG_OFF + mdl->ctx= 0; + mdl->lock_data= 0; +#endif +} + + +/** + Allocate and initialize one lock request. + + Same as mdl_init_lock(), but allocates the lock and the key buffer + on a memory root. Necessary to lock ad-hoc tables, e.g. + mysql.* tables of grant and data dictionary subsystems. + + @param type Id of type of object to be locked + @param db Name of database to which object belongs + @param name Name of of object + @param root MEM_ROOT on which object should be allocated + + @note The allocated lock request will have MDL_SHARED type and + normal priority. + + @retval 0 Error + @retval non-0 Pointer to an object representing a lock request +*/ + +MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name, + MEM_ROOT *root) +{ + MDL_LOCK *lock; + char *key; + + if (!multi_alloc_root(root, &lock, sizeof(MDL_LOCK), &key, + MAX_DBKEY_LENGTH, NULL)) + return NULL; + + mdl_init_lock(lock, key, type, db, name); + + return lock; +} + + +/** + Add a lock request to the list of lock requests of the context. + + The procedure to acquire metadata locks is: + - allocate and initialize lock requests (mdl_alloc_lock()) + - associate them with a context (mdl_add_lock()) + - call mdl_acquire_shared_lock()/mdl_release_lock() (maybe repeatedly). + + Associates a lock request with the given context. + + @param context The MDL context to associate the lock with. + There should be no more than one context per + connection, to avoid deadlocks. + @param lock The lock request to be added. +*/ + +void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock) +{ + DBUG_ENTER("mdl_add_lock"); + DBUG_ASSERT(lock->state == MDL_PENDING); + DBUG_ASSERT(!lock->ctx); + lock->ctx= context; + context->locks.push_front(lock); + DBUG_VOID_RETURN; +} + + +/** + Clear all lock requests in the context (clear the context). + + Disassociates lock requests from the context. + All granted locks must be released prior to calling this + function. + + In other words, the expected procedure to release locks is: + - mdl_release_locks(); + - mdl_remove_all_locks(); + + We could possibly merge mdl_remove_all_locks() and mdl_release_locks(), + but this function comes in handy when we need to back off: in that case + we release all the locks acquired so-far but do not free them, since + we know that the respective lock requests will be used again. + + Also resets lock requests back to their initial state (i.e. + sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO). + + @param context Context to be cleared. +*/ + +void mdl_remove_all_locks(MDL_CONTEXT *context) +{ + MDL_LOCK *l; + I_P_List_iterator it(context->locks); + while ((l= it++)) + { + /* Reset lock request back to its initial state. */ + l->type= MDL_SHARED; + l->prio= MDL_NORMAL_PRIO; + l->upgradable= FALSE; +#ifndef DBUG_OFF + l->ctx= 0; +#endif + } + context->locks.empty(); +} + + +/** + Auxiliary functions needed for creation/destruction of MDL_LOCK_DATA + objects. + + @todo This naive implementation should be replaced with one that saves + on memory allocation by reusing released objects. +*/ + +static MDL_LOCK_DATA* get_lock_data_object(void) +{ + return new MDL_LOCK_DATA(); +} + + +static void release_lock_data_object(MDL_LOCK_DATA *lock) +{ + delete lock; +} + + +/** + Try to acquire one shared lock. + + Unlike exclusive locks, shared locks are acquired one by + one. This is interface is chosen to simplify introduction of + the new locking API to the system. mdl_acquire_shared_lock() + is currently used from open_table(), and there we have only one + table to work with. + + In future we may consider allocating multiple shared locks at once. + + This function must be called after the lock is added to a context. + + @param lock [in] Lock request object for lock to be acquired + @param retry [out] Indicates that conflicting lock exists and another + attempt should be made after releasing all current + locks and waiting for conflicting lock go away + (using mdl_wait_for_locks()). + + @retval FALSE Success. + @retval TRUE Failure. Either error occured or conflicting lock exists. + In the latter case "retry" parameter is set to TRUE. +*/ + +bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry) +{ + MDL_LOCK_DATA *lock_data; + *retry= FALSE; + + DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_PENDING); + + safe_mutex_assert_not_owner(&LOCK_open); + + if (l->ctx->has_global_shared_lock && l->upgradable) + { + my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); + return TRUE; + } + + pthread_mutex_lock(&LOCK_mdl); + + if (l->upgradable && + (global_shared_locks_acquired || global_shared_locks_pending)) + { + pthread_mutex_unlock(&LOCK_mdl); + *retry= TRUE; + return TRUE; + } + + if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key, + l->key_length))) + { + lock_data= get_lock_data_object(); + lock_data->active_shared.push_front(l); + lock_data->users= 1; + my_hash_insert(&mdl_locks, (uchar*)lock_data); + l->state= MDL_ACQUIRED; + l->lock_data= lock_data; + if (l->upgradable) + global_intention_exclusive_locks_acquired++; + } + else + { + if ((lock_data->active_exclusive.is_empty() && + (l->prio == MDL_HIGH_PRIO || + lock_data->waiting_exclusive.is_empty() && + lock_data->active_shared_waiting_upgrade.is_empty())) || + (!lock_data->active_exclusive.is_empty() && + lock_data->active_exclusive.head()->ctx == l->ctx)) + { + /* + When exclusive lock comes from the same context we can satisfy our + shared lock. This is required for CREATE TABLE ... SELECT ... and + ALTER VIEW ... AS .... + */ + lock_data->active_shared.push_front(l); + lock_data->users++; + l->state= MDL_ACQUIRED; + l->lock_data= lock_data; + if (l->upgradable) + global_intention_exclusive_locks_acquired++; + } + else + *retry= TRUE; + } + pthread_mutex_unlock(&LOCK_mdl); + + return *retry; +} + + +static void release_lock(MDL_LOCK *l); + + +/** + Acquire exclusive locks. The context must contain the list of + locks to be acquired. There must be no granted locks in the + context. + + This is a replacement of lock_table_names(). It is used in + RENAME, DROP and other DDL SQL statements. + + @param context A context containing requests for exclusive locks + The context may not have other lock requests. + + @note In case of failure (for example, if our thread was killed) + resets lock requests back to their initial state (MDL_SHARED + and MDL_NORMAL_PRIO). + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) +{ + MDL_LOCK *l, *lh; + MDL_LOCK_DATA *lock_data; + bool signalled= FALSE; + const char *old_msg; + I_P_List_iterator it(context->locks); + THD *thd= context->thd; + + DBUG_ASSERT(thd == current_thd); + + safe_mutex_assert_not_owner(&LOCK_open); + + if (context->has_global_shared_lock) + { + my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); + return TRUE; + } + + pthread_mutex_lock(&LOCK_mdl); + + old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); + + while ((l= it++)) + { + DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING); + if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key, + l->key_length))) + { + lock_data= get_lock_data_object(); + lock_data->waiting_exclusive.push_front(l); + lock_data->users= 1; + my_hash_insert(&mdl_locks, (uchar*)lock_data); + l->lock_data= lock_data; + } + else + { + lock_data->waiting_exclusive.push_front(l); + lock_data->users++; + l->lock_data= lock_data; + } + } + + while (1) + { + it.rewind(); + while ((l= it++)) + { + lock_data= l->lock_data; + + if (global_shared_locks_acquired || global_shared_locks_pending) + { + /* + There is active or pending global shared lock we have + to wait until it goes away. + */ + signalled= TRUE; + break; + } + else if (!lock_data->active_exclusive.is_empty() || + !lock_data->active_shared_waiting_upgrade.is_empty()) + { + /* + Exclusive MDL owner won't wait on table-level lock the same + applies to shared lock waiting upgrade (in this cases we already + have some table-level lock). + */ + signalled= TRUE; + break; + } + else if ((lh= lock_data->active_shared.head())) + { + signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd); + break; + } + } + if (!l) + break; + if (signalled) + pthread_cond_wait(&COND_mdl, &LOCK_mdl); + else + { + /* + Another thread obtained shared MDL-lock on some table but + has not yet opened it and/or tried to obtain data lock on + it. In this case we need to wait until this happens and try + to abort this thread once again. + */ + struct timespec abstime; + set_timespec(abstime, 10); + pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime); + } + if (thd->killed) + { + /* Remove our pending lock requests from the locks. */ + it.rewind(); + while ((l= it++)) + { + DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING); + release_lock(l); + /* Return lock request to its initial state. */ + l->type= MDL_SHARED; + l->prio= MDL_NORMAL_PRIO; + l->upgradable= FALSE; + context->locks.remove(l); + } + /* Pending requests for shared locks can be satisfied now. */ + pthread_cond_broadcast(&COND_mdl); + thd->exit_cond(old_msg); + return TRUE; + } + } + it.rewind(); + while ((l= it++)) + { + global_intention_exclusive_locks_acquired++; + lock_data= l->lock_data; + lock_data->waiting_exclusive.remove(l); + lock_data->active_exclusive.push_front(l); + l->state= MDL_ACQUIRED; + if (lock_data->cached_object) + (*lock_data->cached_object_release_hook)(lock_data->cached_object); + lock_data->cached_object= NULL; + } + /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ + thd->exit_cond(old_msg); + return FALSE; +} + + +/** + Upgrade a shared metadata lock to exclusive. + + Used in ALTER TABLE, when a copy of the table with the + new definition has been constructed. + + @param context Context to which shared long belongs + @param type Id of object type + @param db Name of the database + @param name Name of the object + + @note In case of failure to upgrade locks (e.g. because upgrader + was killed) leaves locks in their original state (locked + in shared mode). + + @retval FALSE Success + @retval TRUE Failure (thread was killed) +*/ + +bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, + const char *db, const char *name) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + bool signalled= FALSE; + MDL_LOCK *l, *lh; + MDL_LOCK_DATA *lock_data; + I_P_List_iterator it(context->locks); + const char *old_msg; + THD *thd= context->thd; + + DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive"); + DBUG_PRINT("enter", ("db=%s name=%s", db, name)); + + DBUG_ASSERT(thd == current_thd); + + int4store(key, type); + key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; + + safe_mutex_assert_not_owner(&LOCK_open); + + pthread_mutex_lock(&LOCK_mdl); + + old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); + + while ((l= it++)) + if (l->key_length == key_length && !memcmp(l->key, key, key_length) && + l->type == MDL_SHARED) + { + DBUG_PRINT("info", ("found shared lock for upgrade")); + DBUG_ASSERT(l->state == MDL_ACQUIRED); + DBUG_ASSERT(l->upgradable); + l->state= MDL_PENDING_UPGRADE; + lock_data= l->lock_data; + lock_data->active_shared.remove(l); + lock_data->active_shared_waiting_upgrade.push_front(l); + } + + while (1) + { + DBUG_PRINT("info", ("looking at conflicting locks")); + it.rewind(); + while ((l= it++)) + { + if (l->state == MDL_PENDING_UPGRADE) + { + DBUG_ASSERT(l->type == MDL_SHARED); + + lock_data= l->lock_data; + + DBUG_ASSERT(global_shared_locks_acquired == 0 && + global_intention_exclusive_locks_acquired); + + if ((lh= lock_data->active_shared.head())) + { + DBUG_PRINT("info", ("found active shared locks")); + signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd); + break; + } + else if (!lock_data->active_exclusive.is_empty()) + { + DBUG_PRINT("info", ("found active exclusive locks")); + signalled= TRUE; + break; + } + } + } + if (!l) + break; + if (signalled) + pthread_cond_wait(&COND_mdl, &LOCK_mdl); + else + { + /* + Another thread obtained shared MDL-lock on some table but + has not yet opened it and/or tried to obtain data lock on + it. In this case we need to wait until this happens and try + to abort this thread once again. + */ + struct timespec abstime; + set_timespec(abstime, 10); + DBUG_PRINT("info", ("Failed to wake-up from table-level lock ... sleeping")); + pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime); + } + if (thd->killed) + { + it.rewind(); + while ((l= it++)) + if (l->state == MDL_PENDING_UPGRADE) + { + DBUG_ASSERT(l->type == MDL_SHARED); + l->state= MDL_ACQUIRED; + lock_data= l->lock_data; + lock_data->active_shared_waiting_upgrade.remove(l); + lock_data->active_shared.push_front(l); + } + /* Pending requests for shared locks can be satisfied now. */ + pthread_cond_broadcast(&COND_mdl); + thd->exit_cond(old_msg); + DBUG_RETURN(TRUE); + } + } + + it.rewind(); + while ((l= it++)) + if (l->state == MDL_PENDING_UPGRADE) + { + DBUG_ASSERT(l->type == MDL_SHARED); + lock_data= l->lock_data; + lock_data->active_shared_waiting_upgrade.remove(l); + lock_data->active_exclusive.push_front(l); + l->type= MDL_EXCLUSIVE; + l->state= MDL_ACQUIRED; + if (lock_data->cached_object) + (*lock_data->cached_object_release_hook)(lock_data->cached_object); + lock_data->cached_object= 0; + } + + /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ + thd->exit_cond(old_msg); + DBUG_RETURN(FALSE); +} + + +/** + Try to acquire an exclusive lock on the object if there are + no conflicting locks. + + Similar to the previous function, but returns + immediately without any side effect if encounters a lock + conflict. Otherwise takes the lock. + + This function is used in CREATE TABLE ... LIKE to acquire a lock + on the table to be created. In this statement we don't want to + block and wait for the lock if the table already exists. + + @param context [in] The context containing the lock request + @param lock [in] The lock request + + @retval FALSE the lock was granted + @retval TRUE there were conflicting locks. + + FIXME: Compared to lock_table_name_if_not_cached() + it gives sligthly more false negatives. +*/ + +bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *l) +{ + MDL_LOCK_DATA *lock_data; + + DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING); + + safe_mutex_assert_not_owner(&LOCK_open); + + pthread_mutex_lock(&LOCK_mdl); + + if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key, + l->key_length))) + { + lock_data= get_lock_data_object(); + lock_data->active_exclusive.push_front(l); + lock_data->users= 1; + my_hash_insert(&mdl_locks, (uchar*)lock_data); + l->state= MDL_ACQUIRED; + l->lock_data= lock_data; + lock_data= 0; + global_intention_exclusive_locks_acquired++; + } + pthread_mutex_unlock(&LOCK_mdl); + + /* + FIXME: We can't leave pending MDL_EXCLUSIVE lock request in the list since + for such locks we assume that they have MDL_LOCK::lock properly set. + Long term we should clearly define relation between lock types, + presence in the context lists and MDL_LOCK::lock values. + */ + if (lock_data) + context->locks.remove(l); + + return lock_data; +} + + +/** + Acquire global shared metadata lock. + + Holding this lock will block all requests for exclusive locks + and shared locks which can be potentially upgraded to exclusive + (see MDL_LOCK::upgradable). + + @param context Current metadata locking context. + + @retval FALSE Success -- the lock was granted. + @retval TRUE Failure -- our thread was killed. +*/ + +bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) +{ + THD *thd= context->thd; + const char *old_msg; + + safe_mutex_assert_not_owner(&LOCK_open); + DBUG_ASSERT(thd == current_thd); + DBUG_ASSERT(!context->has_global_shared_lock); + + pthread_mutex_lock(&LOCK_mdl); + + global_shared_locks_pending++; + old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); + + while (!thd->killed && global_intention_exclusive_locks_acquired) + pthread_cond_wait(&COND_mdl, &LOCK_mdl); + + global_shared_locks_pending--; + if (thd->killed) + { + /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ + thd->exit_cond(old_msg); + return TRUE; + } + global_shared_locks_acquired++; + context->has_global_shared_lock= TRUE; + /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ + thd->exit_cond(old_msg); + return FALSE; +} + + +/** + Wait until there will be no locks that conflict with lock requests + in the context. + + This is a part of the locking protocol and must be used by the + acquirer of shared locks after a back-off. + + Does not acquire the locks! + + @param context Context with which lock requests are associated. + + @retval FALSE Success. One can try to obtain metadata locks. + @retval TRUE Failure (thread was killed) +*/ + +bool mdl_wait_for_locks(MDL_CONTEXT *context) +{ + MDL_LOCK *l; + MDL_LOCK_DATA *lock_data; + I_P_List_iterator it(context->locks); + const char *old_msg; + THD *thd= context->thd; + + safe_mutex_assert_not_owner(&LOCK_open); + DBUG_ASSERT(thd == current_thd); + + while (!thd->killed) + { + /* + We have to check if there are some HANDLERs open by this thread + which conflict with some pending exclusive locks. Otherwise we + might have a deadlock in situations when we are waiting for + pending writer to go away, which in its turn waits for HANDLER + open by our thread. + + TODO: investigate situations in which we need to broadcast on + COND_mdl because of above scenario. + */ + mysql_ha_flush(context->thd); + pthread_mutex_lock(&LOCK_mdl); + old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); + it.rewind(); + while ((l= it++)) + { + DBUG_ASSERT(l->state == MDL_PENDING); + if ((l->upgradable || l->type == MDL_EXCLUSIVE) && + (global_shared_locks_acquired || global_shared_locks_pending)) + break; + /* + To avoid starvation we don't wait if we have pending MDL_EXCLUSIVE lock. + */ + if (l->type == MDL_SHARED && + (lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key, + l->key_length)) && + !(lock_data->active_exclusive.is_empty() && + lock_data->active_shared_waiting_upgrade.is_empty() && + lock_data->waiting_exclusive.is_empty())) + break; + } + if (!l) + { + pthread_mutex_unlock(&LOCK_mdl); + break; + } + pthread_cond_wait(&COND_mdl, &LOCK_mdl); + /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ + thd->exit_cond(old_msg); + } + return thd->killed; +} + + +/** + Auxiliary function which allows to release particular lock + ownership of which is represented by lock request object. +*/ + +static void release_lock(MDL_LOCK *l) +{ + MDL_LOCK_DATA *lock_data; + + DBUG_ENTER("release_lock"); + DBUG_PRINT("enter", ("db=%s name=%s", l->key + 4, + l->key + 4 + strlen(l->key + 4) + 1)); + + lock_data= l->lock_data; + if (lock_data->has_no_other_users()) + { + my_hash_delete(&mdl_locks, (uchar *)lock_data); + DBUG_PRINT("info", ("releasing cached_object cached_object=%p", + lock_data->cached_object)); + if (lock_data->cached_object) + (*lock_data->cached_object_release_hook)(lock_data->cached_object); + release_lock_data_object(lock_data); + if (l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED || + l->type == MDL_SHARED && l->state == MDL_ACQUIRED && l->upgradable) + global_intention_exclusive_locks_acquired--; + } + else + { + switch (l->type) + { + case MDL_SHARED: + lock_data->active_shared.remove(l); + if (l->upgradable) + global_intention_exclusive_locks_acquired--; + break; + case MDL_EXCLUSIVE: + if (l->state == MDL_PENDING) + lock_data->waiting_exclusive.remove(l); + else + { + lock_data->active_exclusive.remove(l); + global_intention_exclusive_locks_acquired--; + } + break; + default: + /* TODO Really? How about problems during lock upgrade ? */ + DBUG_ASSERT(0); + } + lock_data->users--; + } + + DBUG_VOID_RETURN; +} + + +/** + Release all locks associated with the context, but leave them + in the context as lock requests. + + This function is used to back off in case of a lock conflict. + It is also used to release shared locks in the end of an SQL + statement. + + @param context The context with which the locks to be released + are associated. +*/ + +void mdl_release_locks(MDL_CONTEXT *context) +{ + MDL_LOCK *l; + I_P_List_iterator it(context->locks); + DBUG_ENTER("mdl_release_locks"); + + safe_mutex_assert_not_owner(&LOCK_open); + + pthread_mutex_lock(&LOCK_mdl); + while ((l= it++)) + { + DBUG_PRINT("info", ("found lock to release l=%p", l)); + /* + We should not release locks which pending shared locks as these + are not associated with lock object and don't present in its + lists. Allows us to avoid problems in open_tables() in case of + back-off + */ + if (!(l->type == MDL_SHARED && l->state == MDL_PENDING)) + { + release_lock(l); + l->state= MDL_PENDING; +#ifndef DBUG_OFF + l->lock_data= 0; +#endif + } + /* + We will return lock request to its initial state only in + mdl_remove_all_locks() since we need to know type of lock + request and if it is upgradable in mdl_wait_for_locks(). + */ + } + /* Inefficient but will do for a while */ + pthread_cond_broadcast(&COND_mdl); + pthread_mutex_unlock(&LOCK_mdl); + DBUG_VOID_RETURN; +} + + +/** + Release all exclusive locks associated with context. + Removes the locks from the context. + + @param context Context with exclusive locks. + + @note Shared locks are left intact. + @note Resets lock requests for locks released back to their + initial state (i.e.sets type and priority to MDL_SHARED + and MDL_NORMAL_PRIO). +*/ + +void mdl_release_exclusive_locks(MDL_CONTEXT *context) +{ + MDL_LOCK *l; + I_P_List_iterator it(context->locks); + + safe_mutex_assert_not_owner(&LOCK_open); + + pthread_mutex_lock(&LOCK_mdl); + while ((l= it++)) + { + if (l->type == MDL_EXCLUSIVE) + { + DBUG_ASSERT(l->state == MDL_ACQUIRED); + release_lock(l); +#ifndef DBUG_OFF + l->ctx= 0; + l->lock_data= 0; +#endif + l->state= MDL_PENDING; + /* Return lock request to its initial state. */ + l->type= MDL_SHARED; + l->prio= MDL_NORMAL_PRIO; + l->upgradable= FALSE; + context->locks.remove(l); + } + } + pthread_cond_broadcast(&COND_mdl); + pthread_mutex_unlock(&LOCK_mdl); +} + + +/** + Release a lock. + Removes the lock from the context. + + @param context Context containing lock in question + @param lock Lock to be released + + @note Resets lock request for lock released back to its initial state + (i.e.sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO). +*/ + +void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lr) +{ + safe_mutex_assert_not_owner(&LOCK_open); + + pthread_mutex_lock(&LOCK_mdl); + release_lock(lr); +#ifndef DBUG_OFF + lr->ctx= 0; + lr->lock_data= 0; +#endif + lr->state= MDL_PENDING; + /* Return lock request to its initial state. */ + lr->type= MDL_SHARED; + lr->prio= MDL_NORMAL_PRIO; + lr->upgradable= FALSE; + context->locks.remove(lr); + pthread_cond_broadcast(&COND_mdl); + pthread_mutex_unlock(&LOCK_mdl); +} + + +/** + Downgrade all exclusive locks in the context to + shared. + + @param context A context with exclusive locks. +*/ + +void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context) +{ + MDL_LOCK *l; + MDL_LOCK_DATA *lock_data; + I_P_List_iterator it(context->locks); + + safe_mutex_assert_not_owner(&LOCK_open); + + pthread_mutex_lock(&LOCK_mdl); + while ((l= it++)) + if (l->type == MDL_EXCLUSIVE) + { + DBUG_ASSERT(l->state == MDL_ACQUIRED); + if (!l->upgradable) + global_intention_exclusive_locks_acquired--; + lock_data= l->lock_data; + lock_data->active_exclusive.remove(l); + l->type= MDL_SHARED; + lock_data->active_shared.push_front(l); + } + pthread_cond_broadcast(&COND_mdl); + pthread_mutex_unlock(&LOCK_mdl); +} + + +/** + Release global shared metadata lock. + + @param context Current context +*/ + +void mdl_release_global_shared_lock(MDL_CONTEXT *context) +{ + safe_mutex_assert_not_owner(&LOCK_open); + DBUG_ASSERT(context->has_global_shared_lock); + + pthread_mutex_lock(&LOCK_mdl); + global_shared_locks_acquired--; + context->has_global_shared_lock= FALSE; + pthread_cond_broadcast(&COND_mdl); + pthread_mutex_unlock(&LOCK_mdl); +} + + +/** + Auxiliary function which allows to check if we have exclusive lock + on the object. + + @param context Current context + @param type Id of object type + @param db Name of the database + @param name Name of the object + + @return TRUE if current context contains exclusive lock for the object, + FALSE otherwise. +*/ + +bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, + const char *db, const char *name) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + MDL_LOCK *l; + I_P_List_iterator it(context->locks); + + int4store(key, type); + key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; + + while ((l= it++) && (l->key_length != key_length || memcmp(l->key, key, key_length))) + continue; + return (l && l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED); +} + + +/** + Auxiliary function which allows to check if we some kind of lock on + the object. + + @param context Current context + @param type Id of object type + @param db Name of the database + @param name Name of the object + + @return TRUE if current context contains satisfied lock for the object, + FALSE otherwise. +*/ + +bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, + const char *name) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + MDL_LOCK *l; + I_P_List_iterator it(context->locks); + + int4store(key, type); + key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; + + while ((l= it++) && (l->key_length != key_length || + memcmp(l->key, key, key_length) || + l->state == MDL_PENDING)) + continue; + + return l; +} + + +/** + Check if we have any pending exclusive locks which conflict with + existing shared lock. + + @param l Shared lock against which check should be performed. + + @return TRUE if there are any conflicting locks, FALSE otherwise. +*/ + +bool mdl_has_pending_conflicting_lock(MDL_LOCK *l) +{ + bool result; + + DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_ACQUIRED); + safe_mutex_assert_not_owner(&LOCK_open); + + pthread_mutex_lock(&LOCK_mdl); + result= !(l->lock_data->waiting_exclusive.is_empty() && + l->lock_data->active_shared_waiting_upgrade.is_empty()); + pthread_mutex_unlock(&LOCK_mdl); + return result; +} + + +/** + Associate pointer to an opaque object with a lock. + + @param l Lock request for the lock with which the + object should be associated. + @param cached_object Pointer to the object + @param release_hook Cleanup function to be called when MDL subsystem + decides to remove lock or associate another object. + + This is used to cache a pointer to TABLE_SHARE in the lock + structure. Such caching can save one acquisition of LOCK_open + and one table definition cache lookup for every table. + + Since the pointer may be stored only inside an acquired lock, + the caching is only effective when there is more than one lock + granted on a given table. + + This function has the following usage pattern: + - try to acquire an MDL lock + - when done, call for mdl_get_cached_object(). If it returns NULL, our + thread has the only lock on this table. + - look up TABLE_SHARE in the table definition cache + - call mdl_set_cache_object() to assign the share to the opaque pointer. + + The release hook is invoked when the last shared metadata + lock on this name is released. +*/ + +void mdl_set_cached_object(MDL_LOCK *l, void *cached_object, + mdl_cached_object_release_hook release_hook) +{ + DBUG_ENTER("mdl_set_cached_object"); + DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", l->key + 4, + l->key + 4 + strlen(l->key + 4) + 1, + cached_object)); + + DBUG_ASSERT(l->state == MDL_ACQUIRED || l->state == MDL_PENDING_UPGRADE); + + /* + TODO: This assumption works now since we do mdl_get_cached_object() + and mdl_set_cached_object() in the same critical section. Once + this becomes false we will have to call release_hook here and + use additional mutex protecting 'cached_object' member. + */ + DBUG_ASSERT(!l->lock_data->cached_object); + + l->lock_data->cached_object= cached_object; + l->lock_data->cached_object_release_hook= release_hook; + + DBUG_VOID_RETURN; +} + + +/** + Get a pointer to an opaque object that associated with the lock. + + @param l Lock request for the lock with which the object is + associated. + + @return Pointer to an opaque object associated with the lock. +*/ + +void* mdl_get_cached_object(MDL_LOCK *l) +{ + DBUG_ASSERT(l->state == MDL_ACQUIRED || l->state == MDL_PENDING_UPGRADE); + return l->lock_data->cached_object; +} diff --git a/sql/mdl.h b/sql/mdl.h new file mode 100644 index 00000000000..ca3d8d0a784 --- /dev/null +++ b/sql/mdl.h @@ -0,0 +1,260 @@ +#ifndef MDL_H +#define MDL_H +/* Copyright (C) 2007-2008 MySQL AB + + 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 Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#include "sql_plist.h" +#include +#include + +class THD; + +struct MDL_LOCK; +struct MDL_LOCK_DATA; +struct MDL_CONTEXT; + +/** Type of metadata lock request. */ + +enum enum_mdl_type {MDL_SHARED=0, MDL_EXCLUSIVE}; + + +/** States which metadata lock request can have. */ + +enum enum_mdl_state {MDL_PENDING=0, MDL_ACQUIRED, MDL_PENDING_UPGRADE}; + + +/** + Priority of metadata lock requests. High priority attribute is + applicable only to requests for shared locks and indicates that + such request should ignore pending requests for exclusive locks + and for upgrading of shared locks to exclusive. +*/ + +enum enum_mdl_prio {MDL_NORMAL_PRIO=0, MDL_HIGH_PRIO}; + + +/** + A pending lock request or a granted metadata lock. A lock is requested + or granted based on a fully qualified name and type. E.g. for a table + the key consists of <0 (=table)>++. + Later in this document this triple will be referred to simply as + "key" or "name". +*/ + +struct MDL_LOCK +{ + char *key; + uint key_length; + enum enum_mdl_type type; + enum enum_mdl_state state; + enum enum_mdl_prio prio; + /** + TRUE -- if shared lock corresponding to this lock request at some + point might be upgraded to an exclusive lock and therefore conflicts + with global shared lock, FALSE -- otherwise. + */ + bool upgradable; + +private: + /** + Pointers for participating in the list of lock requests for this context. + */ + MDL_LOCK *next_context; + MDL_LOCK **prev_context; + /** + Pointers for participating in the list of satisfied/pending requests + for the lock. + */ + MDL_LOCK *next_lock; + MDL_LOCK **prev_lock; + + friend struct MDL_LOCK_context; + friend struct MDL_LOCK_lock; + +public: + /* + Pointer to the lock object for this lock request. Valid only if this lock + request is satisified or is present in the list of pending lock requests + for particular lock. + */ + MDL_LOCK_DATA *lock_data; + MDL_CONTEXT *ctx; +}; + + +/** + Helper class which specifies which members of MDL_LOCK are used for + participation in the list lock requests belonging to one context. +*/ + +struct MDL_LOCK_context +{ + static inline MDL_LOCK **next_ptr(MDL_LOCK *l) + { + return &l->next_context; + } + static inline MDL_LOCK ***prev_ptr(MDL_LOCK *l) + { + return &l->prev_context; + } +}; + + +/** + Helper class which specifies which members of MDL_LOCK are used for + participation in the list of satisfied/pending requests for the lock. +*/ + +struct MDL_LOCK_lock +{ + static inline MDL_LOCK **next_ptr(MDL_LOCK *l) + { + return &l->next_lock; + } + static inline MDL_LOCK ***prev_ptr(MDL_LOCK *l) + { + return &l->prev_lock; + } +}; + + +/** + Context of the owner of metadata locks. I.e. each server + connection has such a context. +*/ + +struct MDL_CONTEXT +{ + I_P_List locks; + bool has_global_shared_lock; + THD *thd; +}; + + +void mdl_init(); +void mdl_destroy(); + +void mdl_context_init(MDL_CONTEXT *context, THD *thd); +void mdl_context_destroy(MDL_CONTEXT *context); +void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); +void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); +void mdl_context_merge(MDL_CONTEXT *target, MDL_CONTEXT *source); + +void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db, + const char *name); +MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name, + MEM_ROOT *root); +void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock); +void mdl_remove_all_locks(MDL_CONTEXT *context); + +/** + Set type of lock request. Can be only applied to pending locks. +*/ + +inline void mdl_set_lock_type(MDL_LOCK *lock, enum_mdl_type lock_type) +{ + DBUG_ASSERT(lock->state == MDL_PENDING); + lock->type= lock_type; +} + +/** + Set priority for lock request. High priority can be only set + for shared locks. +*/ + +inline void mdl_set_lock_priority(MDL_LOCK *lock, enum_mdl_prio prio) +{ + DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING); + lock->prio= prio; +} + +/** + Mark request for shared lock as upgradable. Can be only applied + to pending locks. +*/ + +inline void mdl_set_upgradable(MDL_LOCK *lock) +{ + DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING); + lock->upgradable= TRUE; +} + +bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry); +bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context); +bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, + const char *db, const char *name); +bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *lock); +bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context); + +bool mdl_wait_for_locks(MDL_CONTEXT *context); + +void mdl_release_locks(MDL_CONTEXT *context); +void mdl_release_exclusive_locks(MDL_CONTEXT *context); +void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lock); +void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context); +void mdl_release_global_shared_lock(MDL_CONTEXT *context); + +bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, const char *db, + const char *name); +bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, + const char *name); + +bool mdl_has_pending_conflicting_lock(MDL_LOCK *l); + +inline bool mdl_has_locks(MDL_CONTEXT *context) +{ + return !context->locks.is_empty(); +} + + +/** + Get iterator for walking through all lock requests in the context. +*/ + +inline I_P_List_iterator mdl_get_locks(MDL_CONTEXT *ctx) +{ + I_P_List_iterator result(ctx->locks); + return result; +} + +/** + Give metadata lock request object for the table get table definition + cache key corresponding to it. + + @param l [in] Lock request object for the table. + @param key [out] LEX_STRING object where table definition cache key + should be put. + + @note This key will have the same life-time as this lock request object. + + @note This is yet another place where border between MDL subsystem and the + rest of the server is broken. OTOH it allows to save some CPU cycles + and memory by avoiding generating these TDC keys from table list. +*/ + +inline void mdl_get_tdc_key(MDL_LOCK *l, LEX_STRING *key) +{ + key->str= l->key + 4; + key->length= l->key_length - 4; +} + + +typedef void (* mdl_cached_object_release_hook)(void *); +void* mdl_get_cached_object(MDL_LOCK *l); +void mdl_set_cached_object(MDL_LOCK *l, void *cached_object, + mdl_cached_object_release_hook release_hook); + +#endif diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index c6454679e28..be03759b383 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -805,7 +805,7 @@ extern my_decimal decimal_zero; void free_items(Item *item); void cleanup_items(Item *item); class THD; -void close_thread_tables(THD *thd); +void close_thread_tables(THD *thd, bool skip_mdl= 0); #ifndef NO_EMBEDDED_ACCESS_CHECKS bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables); @@ -993,7 +993,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, bool drop_temporary, bool drop_view, bool log_query); bool quick_rm_table(handlerton *base,const char *db, const char *table_name, uint flags); -void close_cached_table(THD *thd, TABLE *table); bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent); bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name, char *new_table_alias, @@ -1034,10 +1033,8 @@ bool check_dup(const char *db, const char *name, TABLE_LIST *tables); bool compare_record(TABLE *table); bool append_file_to_dir(THD *thd, const char **filename_ptr, const char *table_name); -void wait_while_table_is_used(THD *thd, TABLE *table, +bool wait_while_table_is_used(THD *thd, TABLE *table, enum ha_extra_function function); -bool table_cache_init(void); -void table_cache_free(void); bool table_def_init(void); void table_def_free(void); void assign_new_table_id(TABLE_SHARE *share); @@ -1223,28 +1220,30 @@ void release_table_share(TABLE_SHARE *share, enum release_type type); TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name); TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, uint lock_flags); +enum enum_open_table_action {OT_NO_ACTION= 0, OT_BACK_OFF_AND_RETRY, + OT_DISCOVER, OT_REPAIR}; TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem, - bool *refresh, uint flags); + enum_open_table_action *action, uint flags); +bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, + char *cache_key, uint cache_key_length, + MEM_ROOT *mem_root, uint flags); bool name_lock_locked_table(THD *thd, TABLE_LIST *tables); -bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in); -TABLE *table_cache_insert_placeholder(THD *thd, const char *key, - uint key_length); -bool lock_table_name_if_not_cached(THD *thd, const char *db, - const char *table_name, TABLE **table); -TABLE *find_locked_table(THD *thd, const char *db,const char *table_name); +bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list); +TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name); +TABLE *find_write_locked_table(TABLE *list, const char *db, + const char *table_name); void detach_merge_children(TABLE *table, bool clear_refs); bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, TABLE_LIST *new_child_list, TABLE_LIST **new_last); bool reopen_table(TABLE *table); -bool reopen_tables(THD *thd,bool get_locks,bool in_refresh); +bool reopen_tables(THD *thd, bool get_locks); thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table); void close_data_files_and_morph_locks(THD *thd, const char *db, const char *table_name); void close_handle_and_leave_table_as_lock(TABLE *table); bool wait_for_tables(THD *thd); bool table_is_used(TABLE *table, bool wait_for_name_lock); -TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name); -void abort_locked_tables(THD *thd,const char *db, const char *table_name); +void unlock_locked_tables(THD *thd); void execute_init_command(THD *thd, sys_var_str *init_command_var, rw_lock_t *var_mutex); extern Field *not_found_field; @@ -1364,7 +1363,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables); bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *, List *,enum ha_rkey_function,Item *,ha_rows,ha_rows); void mysql_ha_flush(THD *thd); -void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked); +void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables); void mysql_ha_cleanup(THD *thd); /* sql_base.cc */ @@ -1501,7 +1500,7 @@ void free_io_cache(TABLE *entry); void intern_close_table(TABLE *entry); bool close_thread_table(THD *thd, TABLE **table_ptr); void close_temporary_tables(THD *thd); -void close_tables_for_reopen(THD *thd, TABLE_LIST **tables); +void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl); TABLE_LIST *find_table_in_list(TABLE_LIST *table, TABLE_LIST *TABLE_LIST::*link, const char *db_name, @@ -1550,6 +1549,9 @@ char *generate_partition_syntax(partition_info *part_info, #define RTFC_CHECK_KILLED_FLAG 0x0004 bool remove_table_from_cache(THD *thd, const char *db, const char *table, uint flags); +bool notify_thread_having_shared_lock(THD *thd, THD *in_use); +void expel_table_from_cache(THD *leave_thd, const char *db, + const char *table_name); #define NORMAL_PART_NAME 0 #define TEMP_PART_NAME 1 @@ -1681,7 +1683,7 @@ TABLE *open_performance_schema_table(THD *thd, TABLE_LIST *one_table, void close_performance_schema_table(THD *thd, Open_tables_state *backup); bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, - bool wait_for_refresh, bool wait_for_placeholders); + bool wait_for_refresh); bool close_cached_connection_tables(THD *thd, bool wait_for_refresh, LEX_STRING *connect_string, bool have_lock = FALSE); @@ -2001,8 +2003,9 @@ extern const char *opt_date_time_formats[]; extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[]; extern String null_string; -extern HASH open_cache, lock_db_cache; +extern HASH table_def_cache, lock_db_cache; extern TABLE *unused_tables; +extern uint table_cache_count; extern const char* any_db; extern struct my_option my_long_options[]; extern const LEX_STRING view_type; @@ -2070,18 +2073,8 @@ void unset_protect_against_global_read_lock(void); void broadcast_refresh(void); /* Lock based on name */ -int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list); -int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use); -void unlock_table_name(THD *thd, TABLE_LIST *table_list); -bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list); bool lock_table_names(THD *thd, TABLE_LIST *table_list); -void unlock_table_names(THD *thd, TABLE_LIST *table_list, - TABLE_LIST *last_table); -bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list); -bool is_table_name_exclusively_locked_by_this_thread(THD *thd, - TABLE_LIST *table_list); -bool is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key, - int key_length); +void unlock_table_names(THD *thd); /* old unireg functions */ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 1cab245b317..90924b5b662 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1301,9 +1301,9 @@ void clean_up(bool print_message) grant_free(); #endif query_cache_destroy(); - table_cache_free(); table_def_free(); hostname_cache_free(); + mdl_destroy(); item_user_lock_free(); lex_free(); /* Free some memory */ item_create_cleanup(); @@ -3755,7 +3755,8 @@ static int init_server_components() We need to call each of these following functions to ensure that all things are initialized so that unireg_abort() doesn't fail */ - if (table_cache_init() | table_def_init() | hostname_cache_init()) + mdl_init(); + if (table_def_init() | hostname_cache_init()) unireg_abort(1); query_cache_result_size_limit(query_cache_limit); diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index ae3cbf789a5..42cd12bd66c 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -1187,8 +1187,7 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) end_trans(thd, ROLLBACK); // if a "real transaction" } m_table_map.clear_tables(); - close_thread_tables(thd); - clear_tables_to_lock(); + slave_close_thread_tables(thd); clear_flag(IN_STMT); /* Cleanup for the flags that have been set at do_apply_event. @@ -1200,6 +1199,13 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) void Relay_log_info::clear_tables_to_lock() { + /* + Deallocating elements of table list below will also free memory where + meta-data locks are stored. So we want to be sure that we don't have + any references to this memory left. + */ + DBUG_ASSERT(!mdl_has_locks(&(current_thd->mdl_context))); + while (tables_to_lock) { uchar* to_free= reinterpret_cast(tables_to_lock); @@ -1216,4 +1222,15 @@ void Relay_log_info::clear_tables_to_lock() DBUG_ASSERT(tables_to_lock == NULL && tables_to_lock_count == 0); } +void Relay_log_info::slave_close_thread_tables(THD *thd) +{ + /* + Since we use same memory chunks for allocation of metadata lock + objects for tables as we use for allocating corresponding elements + of 'tables_to_lock' list, we have to release metadata locks by + closing tables before calling clear_tables_to_lock(). + */ + close_thread_tables(thd); + clear_tables_to_lock(); +} #endif diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index fd36d18adae..332bc4e53d0 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -346,6 +346,7 @@ public: bool cached_charset_compare(char *charset) const; void cleanup_context(THD *, bool); + void slave_close_thread_tables(THD *); void clear_tables_to_lock(); /* diff --git a/sql/set_var.cc b/sql/set_var.cc index 1028e5441ae..1490ffd9598 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -4333,7 +4333,7 @@ bool sys_var_opt_readonly::update(THD *thd, set_var *var) can cause to wait on a read lock, it's required for the client application to unlock everything, and acceptable for the server to wait on all locks. */ - if ((result= close_cached_tables(thd, NULL, FALSE, TRUE, TRUE))) + if ((result= close_cached_tables(thd, NULL, FALSE, TRUE))) goto end_with_read_lock; if ((result= make_global_read_lock_block_commit(thd))) diff --git a/sql/sp_head.cc b/sql/sp_head.cc index f90aefc2a3f..14573cd6884 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3984,6 +3984,9 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; + table->mdl_lock= mdl_alloc_lock(0, table->db, table->table_name, + thd->mdl_el_root ? thd->mdl_el_root : + thd->mem_root); /* Everyting else should be zeroed */ @@ -4025,7 +4028,9 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->lock_type= locktype; table->select_lex= lex->current_select; table->cacheable_table= 1; - + table->mdl_lock= mdl_alloc_lock(0, table->db, table->table_name, + thd->mdl_el_root ? thd->mdl_el_root : + thd->mem_root); lex->add_to_query_tables(table); return table; } diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 641423605aa..b01e7b1049d 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -676,12 +676,7 @@ my_bool acl_reload(THD *thd) my_bool return_val= 1; DBUG_ENTER("acl_reload"); - if (thd->locked_tables) - { // Can't have locked tables here - thd->lock=thd->locked_tables; - thd->locked_tables=0; - close_thread_tables(thd); - } + unlock_locked_tables(thd); // Can't have locked tables here /* To avoid deadlocks we should obtain table locks before @@ -697,6 +692,7 @@ my_bool acl_reload(THD *thd) tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ; tables[0].skip_temporary= tables[1].skip_temporary= tables[2].skip_temporary= TRUE; + alloc_mdl_locks(tables, thd->mem_root); if (simple_open_n_lock_tables(thd, tables)) { @@ -1601,6 +1597,7 @@ bool change_password(THD *thd, const char *host, const char *user, bzero((char*) &tables, sizeof(tables)); tables.alias= tables.table_name= (char*) "user"; tables.db= (char*) "mysql"; + alloc_mdl_locks(&tables, thd->mem_root); #ifdef HAVE_REPLICATION /* @@ -3053,6 +3050,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, ? tables+2 : 0); tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_WRITE; tables[0].db=tables[1].db=tables[2].db=(char*) "mysql"; + alloc_mdl_locks(tables, thd->mem_root); /* This statement will be replicated as a statement, even when using @@ -3270,6 +3268,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type=tables[1].lock_type=TL_WRITE; tables[0].db=tables[1].db=(char*) "mysql"; + alloc_mdl_locks(tables, thd->mem_root); /* This statement will be replicated as a statement, even when using @@ -3408,6 +3407,7 @@ bool mysql_grant(THD *thd, const char *db, List &list, tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type=tables[1].lock_type=TL_WRITE; tables[0].db=tables[1].db=(char*) "mysql"; + alloc_mdl_locks(tables, thd->mem_root); /* This statement will be replicated as a statement, even when using @@ -3741,6 +3741,7 @@ static my_bool grant_reload_procs_priv(THD *thd) table.db= (char *) "mysql"; table.lock_type= TL_READ; table.skip_temporary= 1; + alloc_mdl_locks(&table, thd->mem_root); if (simple_open_n_lock_tables(thd, &table)) { @@ -3807,6 +3808,8 @@ my_bool grant_reload(THD *thd) tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type= tables[1].lock_type= TL_READ; tables[0].skip_temporary= tables[1].skip_temporary= TRUE; + alloc_mdl_locks(tables, thd->mem_root); + /* To avoid deadlocks we should obtain table locks before obtaining LOCK_grant rwlock. @@ -5152,6 +5155,7 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) (tables+4)->lock_type= TL_WRITE; tables->db= (tables+1)->db= (tables+2)->db= (tables+3)->db= (tables+4)->db= (char*) "mysql"; + alloc_mdl_locks(tables, thd->mem_root); #ifdef HAVE_REPLICATION /* diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 8f31ef6999a..de4aaac633e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -96,17 +96,32 @@ bool Prelock_error_handler::safely_trapped_errors() @defgroup Data_Dictionary Data Dictionary @{ */ -TABLE *unused_tables; /* Used by mysql_test */ -HASH open_cache; /* Used by mysql_test */ -static HASH table_def_cache; + +/** + Total number of TABLE instances for tables in the table definition cache + (both in use by threads and not in use). This value is accessible to user + as "Open_tables" status variable. +*/ +uint table_cache_count= 0; +/** + List that contains all TABLE instances for tables in the table definition + cache that are not in use by any thread. Recently used TABLE instances are + appended to the end of the list. Thus the beginning of the list contains + tables which have been least recently used. +*/ +TABLE *unused_tables; +HASH table_def_cache; static TABLE_SHARE *oldest_unused_share, end_of_unused_share; static pthread_mutex_t LOCK_table_share; static bool table_def_inited= 0; -static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, - const char *alias, - char *cache_key, uint cache_key_length, - MEM_ROOT *mem_root, uint flags); +static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, + TABLE_SHARE *table_share); +static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, + const char *alias, char *cache_key, + uint cache_key_length); +static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry); +static bool auto_repair_table(THD *thd, TABLE_LIST *table_list); static void free_cache_entry(TABLE *entry); static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, @@ -114,41 +129,14 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, TABLE_LIST *table_desc, MEM_ROOT *mem_root); static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks, bool send_refresh); +static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context); static bool has_write_table_with_auto_increment(TABLE_LIST *tables); -extern "C" uchar *table_cache_key(const uchar *record, size_t *length, - my_bool not_used __attribute__((unused))) -{ - TABLE *entry=(TABLE*) record; - *length= entry->s->table_cache_key.length; - return (uchar*) entry->s->table_cache_key.str; -} - - -bool table_cache_init(void) -{ - return my_hash_init(&open_cache, &my_charset_bin, table_cache_size+16, - 0, 0, table_cache_key, - (my_hash_free_key) free_cache_entry, 0) != 0; -} - -void table_cache_free(void) -{ - DBUG_ENTER("table_cache_free"); - if (table_def_inited) - { - close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE); - if (!open_cache.records) // Safety first - my_hash_free(&open_cache); - } - DBUG_VOID_RETURN; -} - uint cached_open_tables(void) { - return open_cache.records; + return table_cache_count; } @@ -156,7 +144,8 @@ uint cached_open_tables(void) static void check_unused(void) { uint count= 0, open_files= 0, idx= 0; - TABLE *cur_link,*start_link; + TABLE *cur_link, *start_link, *entry; + TABLE_SHARE *share; if ((start_link=cur_link=unused_tables)) { @@ -167,45 +156,42 @@ static void check_unused(void) DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */ return; /* purecov: inspected */ } - } while (count++ < open_cache.records && + } while (count++ < table_cache_count && (cur_link=cur_link->next) != start_link); if (cur_link != start_link) { DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */ } } - for (idx=0 ; idx < open_cache.records ; idx++) + for (idx=0 ; idx < table_def_cache.records ; idx++) { - TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx); - if (!entry->in_use) + share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); + + I_P_List_iterator it(share->free_tables); + while ((entry= it++)) + { + if (entry->in_use) + { + DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */ + } count--; - if (entry->file) open_files++; + } + it.init(share->used_tables); + while ((entry= it++)) + { + if (!entry->in_use) + { + DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */ + } + open_files++; + } } if (count != 0) { DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */ count)); /* purecov: inspected */ } - -#ifdef NOT_SAFE_FOR_REPAIR - /* - check that open cache and table definition cache has same number of - aktive tables - */ - count= 0; - for (idx=0 ; idx < table_def_cache.records ; idx++) - { - TABLE_SHARE *entry= (TABLE_SHARE*) hash_element(&table_def_cache,idx); - count+= entry->ref_count; - } - if (count != open_files) - { - DBUG_PRINT("error", ("table_def ref_count: %u open_cache: %u", - count, open_files)); - DBUG_ASSERT(count == open_files); - } -#endif } #else #define check_unused() @@ -300,8 +286,11 @@ void table_def_free(void) DBUG_ENTER("table_def_free"); if (table_def_inited) { + /* Free all open TABLEs first. */ + close_cached_tables(NULL, NULL, FALSE, FALSE); table_def_inited= 0; pthread_mutex_destroy(&LOCK_table_share); + /* Free table definitions. */ my_hash_free(&table_def_cache); } DBUG_VOID_RETURN; @@ -314,6 +303,128 @@ uint cached_table_definitions(void) } +/* + Auxiliary routines for manipulating with per-share used/unused and + global unused lists of TABLE objects and table_cache_count counter. + Responsible for preserving invariants between those lists, counter + and TABLE::in_use member. + In fact those routines implement sort of implicit table cache as + part of table definition cache. +*/ + + +/** + Add newly created TABLE object for table share which is going + to be used right away. +*/ + +static void table_def_add_used_table(THD *thd, TABLE *table) +{ + DBUG_ASSERT(table->in_use == thd); + table->s->used_tables.push_front(table); + table_cache_count++; +} + + +/** + Prepare used or unused TABLE instance for destruction by removing + it from share's and global list. +*/ + +static void table_def_remove_table(TABLE *table) +{ + if (table->in_use) + { + /* Remove from per-share chain of used TABLE objects. */ + table->s->used_tables.remove(table); + } + else + { + /* Remove from per-share chain of unused TABLE objects. */ + table->s->free_tables.remove(table); + + /* And global unused chain. */ + table->next->prev=table->prev; + table->prev->next=table->next; + if (table == unused_tables) + { + unused_tables=unused_tables->next; + if (table == unused_tables) + unused_tables=0; + } + check_unused(); + } + table_cache_count--; +} + + +/** + Mark already existing TABLE instance as used. +*/ + +static void table_def_use_table(THD *thd, TABLE *table) +{ + DBUG_ASSERT(!table->in_use); + + /* Unlink table from list of unused tables for this share. */ + table->s->free_tables.remove(table); + /* Unlink able from global unused tables list. */ + if (table == unused_tables) + { // First unused + unused_tables=unused_tables->next; // Remove from link + if (table == unused_tables) + unused_tables=0; + } + table->prev->next=table->next; /* Remove from unused list */ + table->next->prev=table->prev; + check_unused(); + /* Add table to list of used tables for this share. */ + table->s->used_tables.push_front(table); + table->in_use= thd; +} + + +/** + Mark already existing used TABLE instance as unused. +*/ + +static void table_def_unuse_table(TABLE *table) +{ + DBUG_ASSERT(table->in_use); + + table->in_use= 0; + /* Remove table from the list of tables used in this share. */ + table->s->used_tables.remove(table); + /* Add table to the list of unused TABLE objects for this share. */ + table->s->free_tables.push_front(table); + /* Also link it last in the global list of unused TABLE objects. */ + if (unused_tables) + { + table->next=unused_tables; + table->prev=unused_tables->prev; + unused_tables->prev=table; + table->prev->next=table; + } + else + unused_tables=table->next=table->prev=table; + check_unused(); +} + + +/** + Bind used TABLE instance to another table share. + + @note Will go away once we refactor code responsible + for reopening tables under lock tables. +*/ + +static void table_def_change_share(TABLE *table, TABLE_SHARE *new_share) +{ + table->s->used_tables.remove(table); + new_share->used_tables.push_front(table); +} + + /* Get TABLE_SHARE for a table. @@ -347,6 +458,13 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, *error= 0; + /* + To be able perform any operation on table we should own + some kind of metadata lock on it. + */ + DBUG_ASSERT(mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db, + table_list->table_name)); + /* Read table definition from cache */ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, key_length))) @@ -569,6 +687,7 @@ void release_table_share(TABLE_SHARE *share, enum release_type type) safe_mutex_assert_owner(&LOCK_open); pthread_mutex_lock(&share->mutex); + DBUG_ASSERT(share->ref_count); if (!--share->ref_count) { if (share->version != refresh_version) @@ -629,6 +748,26 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name) } +/** + @brief Mark table share as having one more user (increase its reference + count). + + @param share Table share for which reference count should be increased. +*/ + +static void reference_table_share(TABLE_SHARE *share) +{ + DBUG_ENTER("reference_table_share"); + DBUG_ASSERT(share->ref_count); + pthread_mutex_lock(&share->mutex); + share->ref_count++; + pthread_mutex_unlock(&share->mutex); + DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", + (ulong) share, share->ref_count)); + DBUG_VOID_RETURN; +} + + /* Close file handle, but leave the table in the table cache @@ -680,6 +819,7 @@ void close_handle_and_leave_table_as_lock(TABLE *table) detach_merge_children(table, FALSE); table->file->close(); table->db_stat= 0; // Mark file closed + table_def_change_share(table, share); release_table_share(table->s, RELEASE_NORMAL); table->s= share; table->file->change_table_ptr(table, table->s); @@ -719,11 +859,9 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) start_list= &open_list; open_list=0; - for (uint idx=0 ; result == 0 && idx < open_cache.records; idx++) + for (uint idx=0 ; result == 0 && idx < table_def_cache.records; idx++) { - OPEN_TABLE_LIST *table; - TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx); - TABLE_SHARE *share= entry->s; + TABLE_SHARE *share= (TABLE_SHARE *)my_hash_element(&table_def_cache, idx); if (db && my_strcasecmp(system_charset_info, db, share->db.str)) continue; @@ -737,21 +875,7 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) if (check_table_access(thd,SELECT_ACL,&table_list, TRUE, 1, TRUE)) continue; - /* need to check if we haven't already listed it */ - for (table= open_list ; table ; table=table->next) - { - if (!strcmp(table->table, share->table_name.str) && - !strcmp(table->db, share->db.str)) - { - if (entry->in_use) - table->in_use++; - if (entry->locked_by_name) - table->locked++; - break; - } - } - if (table) - continue; + if (!(*start_list = (OPEN_TABLE_LIST *) sql_alloc(sizeof(**start_list)+share->table_cache_key.length))) { @@ -762,8 +886,11 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) strmov(((*start_list)->db= (char*) ((*start_list)+1)), share->db.str)+1, share->table_name.str); - (*start_list)->in_use= entry->in_use ? 1 : 0; - (*start_list)->locked= entry->locked_by_name ? 1 : 0; + (*start_list)->in_use= 0; + I_P_List_iterator it(share->used_tables); + while (it++) + ++(*start_list)->in_use; + (*start_list)->locked= (share->version == 0) ? 1 : 0; start_list= &(*start_list)->next; *start_list=0; } @@ -809,19 +936,11 @@ static void free_cache_entry(TABLE *table) /* Assert that MERGE children are not attached before final close. */ DBUG_ASSERT(!table->is_children_attached()); + /* This should be done before releasing table share. */ + table_def_remove_table(table); + intern_close_table(table); - if (!table->in_use) - { - table->next->prev=table->prev; /* remove from used chain */ - table->prev->next=table->next; - if (table == unused_tables) - { - unused_tables=unused_tables->next; - if (table == unused_tables) - unused_tables=0; - } - check_unused(); // consisty check - } + my_free((uchar*) table,MYF(0)); DBUG_VOID_RETURN; } @@ -841,6 +960,38 @@ void free_io_cache(TABLE *table) } +/** + Auxiliary function which allows to kill delayed threads for + particular table identified by its share. + + @param share Table share. +*/ + +static void kill_delayed_threads_for_table(TABLE_SHARE *share) +{ + I_P_List_iterator it(share->used_tables); + TABLE *tab; + while ((tab= it++)) + { + THD *in_use= tab->in_use; + + if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && + ! in_use->killed) + { + in_use->killed= THD::KILL_CONNECTION; + pthread_mutex_lock(&in_use->mysys_var->mutex); + if (in_use->mysys_var->current_cond) + { + pthread_mutex_lock(in_use->mysys_var->current_mutex); + pthread_cond_broadcast(in_use->mysys_var->current_cond); + pthread_mutex_unlock(in_use->mysys_var->current_mutex); + } + pthread_mutex_unlock(&in_use->mysys_var->mutex); + } + } +} + + /* Close all tables which aren't in use by any thread @@ -848,18 +999,23 @@ void free_io_cache(TABLE *table) @param tables List of tables to remove from the cache @param have_lock If LOCK_open is locked @param wait_for_refresh Wait for a impending flush - @param wait_for_placeholders Wait for tables being reopened so that the GRL - won't proceed while write-locked tables are being reopened by other - threads. - @remark THD can be NULL, but then wait_for_refresh must be FALSE - and tables must be NULL. + @note THD can be NULL, but then wait_for_refresh must be FALSE + and tables must be NULL. + + @note When called as part of FLUSH TABLES WITH READ LOCK this function + ignores metadata locks held by other threads. In order to avoid + situation when FLUSH TABLES WITH READ LOCK sneaks in at the moment + when some write-locked table is being reopened (by FLUSH TABLES or + ALTER TABLE) we have to rely on additional global shared metadata + lock taken by thread trying to obtain global read lock. */ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, - bool wait_for_refresh, bool wait_for_placeholders) + bool wait_for_refresh) { - bool result=0; + bool result= FALSE; + bool found= TRUE; DBUG_ENTER("close_cached_tables"); DBUG_ASSERT(thd || (!wait_for_refresh && !tables)); @@ -868,165 +1024,181 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, if (!tables) { refresh_version++; // Force close of open tables - while (unused_tables) - { -#ifdef EXTRA_DEBUG - if (my_hash_delete(&open_cache,(uchar*) unused_tables)) - printf("Warning: Couldn't delete open table from hash\n"); -#else - (void) my_hash_delete(&open_cache,(uchar*) unused_tables); -#endif - } - /* Free table shares */ - while (oldest_unused_share->next) - { - pthread_mutex_lock(&oldest_unused_share->mutex); - (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); - } DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", refresh_version)); - if (wait_for_refresh) - { - /* - Other threads could wait in a loop in open_and_lock_tables(), - trying to lock one or more of our tables. - - If they wait for the locks in thr_multi_lock(), their lock - request is aborted. They loop in open_and_lock_tables() and - enter open_table(). Here they notice the table is refreshed and - wait for COND_refresh. Then they loop again in - open_and_lock_tables() and this time open_table() succeeds. At - this moment, if we (the FLUSH TABLES thread) are scheduled and - on another FLUSH TABLES enter close_cached_tables(), they could - awake while we sleep below, waiting for others threads (us) to - close their open tables. If this happens, the other threads - would find the tables unlocked. They would get the locks, one - after the other, and could do their destructive work. This is an - issue if we have LOCK TABLES in effect. - - The problem is that the other threads passed all checks in - open_table() before we refresh the table. - - The fix for this problem is to set some_tables_deleted for all - threads with open tables. These threads can still get their - locks, but will immediately release them again after checking - this variable. They will then loop in open_and_lock_tables() - again. There they will wait until we update all tables version - below. - - Setting some_tables_deleted is done by remove_table_from_cache() - in the other branch. - - In other words (reviewer suggestion): You need this setting of - some_tables_deleted for the case when table was opened and all - related checks were passed before incrementing refresh_version - (which you already have) but attempt to lock the table happened - after the call to close_old_data_files() i.e. after removal of - current thread locks. - */ - for (uint idx=0 ; idx < open_cache.records ; idx++) - { - TABLE *table=(TABLE*) my_hash_element(&open_cache,idx); - if (table->in_use) - table->in_use->some_tables_deleted= 1; - } - } + kill_delayed_threads(); } else { bool found=0; for (TABLE_LIST *table= tables; table; table= table->next_local) { - if (remove_table_from_cache(thd, table->db, table->table_name, - RTFC_OWNED_BY_THD_FLAG)) + TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name); + + if (share) + { + share->version= 0; + kill_delayed_threads_for_table(share); found=1; + } } if (!found) wait_for_refresh=0; // Nothing to wait for } -#ifndef EMBEDDED_LIBRARY - if (!tables) - kill_delayed_threads(); -#endif - if (wait_for_refresh) + + /* + Get rid of all unused TABLE and TABLE_SHARE instances. By doing + this we automatically close all tables which were marked as "old". + + FIXME: Do not close all unused TABLE instances when flushing + particular table. + */ + while (unused_tables) + free_cache_entry(unused_tables); + /* Free table shares */ + while (oldest_unused_share->next) + { + pthread_mutex_lock(&oldest_unused_share->mutex); + (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); + } + + if (!wait_for_refresh) + { + if (!have_lock) + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(result); + } + + DBUG_ASSERT(!have_lock); + pthread_mutex_unlock(&LOCK_open); + + if (thd->locked_tables) { /* - If there is any table that has a lower refresh_version, wait until - this is closed (or this thread is killed) before returning + If we are under LOCK TABLES we need to reopen tables without + opening a door for any concurrent threads to sneak in and get + lock on our tables. To achieve this we use exclusive metadata + locks. */ - thd->mysys_var->current_mutex= &LOCK_open; - thd->mysys_var->current_cond= &COND_refresh; - thd_proc_info(thd, "Flushing tables"); + if (!tables) + { + for (TABLE *tab= thd->open_tables; tab; tab= tab->next) + { + /* + Checking TABLE::db_stat is essential in case when we have + several instances of the table open and locked. + */ + if (tab->db_stat) + { + char dbname[NAME_LEN+1]; + char tname[NAME_LEN+1]; + /* + Since close_data_files_and_morph_locks() frees share's memroot + we need to make copies of database and table names. + */ + strmov(dbname, tab->s->db.str); + strmov(tname, tab->s->table_name.str); + if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN)) + { + result= TRUE; + goto err_with_reopen; + } + pthread_mutex_lock(&LOCK_open); + close_data_files_and_morph_locks(thd, dbname, tname); + pthread_mutex_unlock(&LOCK_open); + } + } + } + else + { + for (TABLE_LIST *table= tables; table; table= table->next_local) + { + TABLE *tab= find_locked_table(thd->open_tables, table->db, + table->table_name); + /* + Checking TABLE::db_stat is essential in case when we have + several instances of the table open and locked. + */ + if (tab->db_stat) + { + if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN)) + { + result= TRUE; + goto err_with_reopen; + } + pthread_mutex_lock(&LOCK_open); + close_data_files_and_morph_locks(thd, table->db, table->table_name); + pthread_mutex_unlock(&LOCK_open); + } + } + } + } - close_old_data_files(thd,thd->open_tables,1,1); + /* Wait until all threads have closed all the tables we are flushing. */ + DBUG_PRINT("info", ("Waiting for other threads to close their open tables")); + + while (found && ! thd->killed) + { + found= FALSE; + /* + To avoid self and other kinds of deadlock we have to flush open HANDLERs. + */ mysql_ha_flush(thd); DEBUG_SYNC(thd, "after_flush_unlock"); - bool found=1; - /* Wait until all threads has closed all the tables we had locked */ - DBUG_PRINT("info", - ("Waiting for other threads to close their open tables")); - while (found && ! thd->killed) + pthread_mutex_lock(&LOCK_open); + + thd->enter_cond(&COND_refresh, &LOCK_open, "Flushing tables"); + + if (!tables) { - found=0; - for (uint idx=0 ; idx < open_cache.records ; idx++) + for (uint idx=0 ; idx < table_def_cache.records ; idx++) { - TABLE *table=(TABLE*) my_hash_element(&open_cache,idx); - /* Avoid a self-deadlock. */ - if (table->in_use == thd) - continue; - /* - Note that we wait here only for tables which are actually open, and - not for placeholders with TABLE::open_placeholder set. Waiting for - latter will cause deadlock in the following scenario, for example: - - conn1: lock table t1 write; - conn2: lock table t2 write; - conn1: flush tables; - conn2: flush tables; - - It also does not make sense to wait for those of placeholders that - are employed by CREATE TABLE as in this case table simply does not - exist yet. - */ - if (table->needs_reopen_or_name_lock() && (table->db_stat || - (table->open_placeholder && wait_for_placeholders))) - { - found=1; - DBUG_PRINT("signal", ("Waiting for COND_refresh")); - pthread_cond_wait(&COND_refresh,&LOCK_open); - break; - } + TABLE_SHARE *share=(TABLE_SHARE*) my_hash_element(&table_def_cache, + idx); + if (share->version != refresh_version) + { + found= TRUE; + break; + } } } + else + { + for (TABLE_LIST *table= tables; table; table= table->next_local) + { + TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name); + if (share && share->version != refresh_version) + { + found= TRUE; + break; + } + } + } + + if (found) + { + DBUG_PRINT("signal", ("Waiting for COND_refresh")); + pthread_cond_wait(&COND_refresh,&LOCK_open); + } + + thd->exit_cond(NULL); + } + +err_with_reopen: + if (thd->locked_tables) + { + pthread_mutex_lock(&LOCK_open); /* No other thread has the locked tables open; reopen them and get the old locks. This should always succeed (unless some external process has removed the tables) */ thd->in_lock_tables=1; - result=reopen_tables(thd,1,1); + result|= reopen_tables(thd, 1); thd->in_lock_tables=0; - /* Set version for table */ - for (TABLE *table=thd->open_tables; table ; table= table->next) - { - /* - Preserve the version (0) of write locked tables so that a impending - global read lock won't sneak in. - */ - if (table->reginfo.lock_type < TL_WRITE_ALLOW_WRITE) - table->s->version= refresh_version; - } - } - if (!have_lock) pthread_mutex_unlock(&LOCK_open); - if (wait_for_refresh) - { - pthread_mutex_lock(&thd->mysys_var->mutex); - thd->mysys_var->current_mutex= 0; - thd->mysys_var->current_cond= 0; - thd_proc_info(thd, 0); - pthread_mutex_unlock(&thd->mysys_var->mutex); + mdl_downgrade_exclusive_locks(&thd->mdl_context); } DBUG_RETURN(result); } @@ -1079,7 +1251,7 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh, } if (tables) - result= close_cached_tables(thd, tables, TRUE, FALSE, FALSE); + result= close_cached_tables(thd, tables, TRUE, FALSE); if (!have_lock) pthread_mutex_unlock(&LOCK_open); @@ -1208,9 +1380,8 @@ static void close_open_tables(THD *thd) thd->some_tables_deleted= 0; /* Free tables to hold down open files */ - while (open_cache.records > table_cache_size && unused_tables) - my_hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */ - check_unused(); + while (table_cache_count > table_cache_size && unused_tables) + free_cache_entry(unused_tables); if (found_old_table) { /* Tell threads waiting for refresh that something has happened */ @@ -1239,7 +1410,8 @@ static void close_open_tables(THD *thd) leave prelocked mode if needed. */ -void close_thread_tables(THD *thd) +void close_thread_tables(THD *thd, + bool skip_mdl) { TABLE *table; prelocked_mode_type prelocked_mode= thd->prelocked_mode; @@ -1328,6 +1500,10 @@ void close_thread_tables(THD *thd) if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES) DBUG_VOID_RETURN; + /* + Note that we are leaving prelocked mode so we don't need + to care about THD::locked_tables_root. + */ thd->lock= thd->locked_tables; thd->locked_tables= 0; /* Fallthrough */ @@ -1358,6 +1534,12 @@ void close_thread_tables(THD *thd) if (thd->open_tables) close_open_tables(thd); + mdl_release_locks(&thd->mdl_context); + if (!skip_mdl) + { + mdl_remove_all_locks(&thd->mdl_context); + } + if (prelocked_mode == PRELOCKED) { /* @@ -1381,8 +1563,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) DBUG_ENTER("close_thread_table"); DBUG_ASSERT(table->key_read == 0); DBUG_ASSERT(!table->file || table->file->inited == handler::NONE); - DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); + safe_mutex_assert_owner(&LOCK_open); *table_ptr=table->next; /* @@ -1392,10 +1573,11 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) if (table->child_l || table->parent) detach_merge_children(table, TRUE); + table->mdl_lock= 0; if (table->needs_reopen_or_name_lock() || thd->version != refresh_version || !table->db_stat) { - my_hash_delete(&open_cache,(uchar*) table); + free_cache_entry(table); found_old_table=1; } else @@ -1413,16 +1595,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) free_field_buffers_larger_than(table,MAX_TDC_BLOB_SIZE); table->file->ha_reset(); - table->in_use=0; - if (unused_tables) - { - table->next=unused_tables; /* Link in last */ - table->prev=unused_tables->prev; - unused_tables->prev=table; - table->prev->next=table; - } - else - unused_tables=table->next=table->prev=table; + table_def_unuse_table(table); } DBUG_RETURN(found_old_table); } @@ -2121,7 +2294,7 @@ void unlink_open_table(THD *thd, TABLE *find, bool unlock) /* Remove table from open_tables list. */ *prev= list->next; /* Close table. */ - my_hash_delete(&open_cache,(uchar*) list); // Close table + free_cache_entry(list); } else { @@ -2231,43 +2404,34 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond) bool name_lock_locked_table(THD *thd, TABLE_LIST *tables) { + bool result= TRUE; + DBUG_ENTER("name_lock_locked_table"); /* Under LOCK TABLES we must only accept write locked tables. */ - tables->table= find_locked_table(thd, tables->db, tables->table_name); + tables->table= find_write_locked_table(thd->open_tables, tables->db, + tables->table_name); - if (!tables->table) - my_error(ER_TABLE_NOT_LOCKED, MYF(0), tables->alias); - else if (tables->table->reginfo.lock_type < TL_WRITE_LOW_PRIORITY) - my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), tables->alias); - else + if (tables->table) { /* Ensures that table is opened only by this thread and that no other statement will open this table. */ - wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN); - DBUG_RETURN(FALSE); + result= wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN); } - DBUG_RETURN(TRUE); + DBUG_RETURN(result); } /* - Open table which is already name-locked by this thread. + Open table for which this thread has exclusive meta-data lock. SYNOPSIS reopen_name_locked_table() thd Thread handle - table_list TABLE_LIST object for table to be open, TABLE_LIST::table - member should point to TABLE object which was used for - name-locking. - link_in TRUE - if TABLE object for table to be opened should be - linked into THD::open_tables list. - FALSE - placeholder used for name-locking is already in - this list so we only need to preserve TABLE::next - pointer. + table_list TABLE_LIST object for table to be open. NOTE This function assumes that its caller already acquired LOCK_open mutex. @@ -2277,33 +2441,32 @@ bool name_lock_locked_table(THD *thd, TABLE_LIST *tables) TRUE - Error */ -bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) +bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list) { - TABLE *table= table_list->table; + TABLE *table; TABLE_SHARE *share; + char key[MAX_DBKEY_LENGTH]; + uint key_length; char *table_name= table_list->table_name; - TABLE orig_table; DBUG_ENTER("reopen_name_locked_table"); - safe_mutex_assert_owner(&LOCK_open); - - if (thd->killed || !table) + if (thd->killed) DBUG_RETURN(TRUE); - orig_table= *table; + key_length= create_table_def_key(thd, key, table_list, 0); - if (open_unireg_entry(thd, table, table_list, table_name, - table->s->table_cache_key.str, - table->s->table_cache_key.length, thd->mem_root, 0)) + if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) + DBUG_RETURN(TRUE); + + if (reopen_table_entry(thd, table, table_list, table_name, key, key_length)) { - intern_close_table(table); /* If there was an error during opening of table (for example if it does not exist) '*table' object can be wiped out. To be able properly release name-lock in this case we should restore this object to its original state. */ - *table= orig_table; + my_free((uchar*)table, MYF(0)); DBUG_RETURN(TRUE); } @@ -2318,21 +2481,11 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) */ share->version=0; table->in_use = thd; - check_unused(); - if (link_in) - { - table->next= thd->open_tables; - thd->open_tables= table; - } - else - { - /* - TABLE object should be already in THD::open_tables list so we just - need to set TABLE::next correctly. - */ - table->next= orig_table.next; - } + table_def_add_used_table(thd, table); + + table->next= thd->open_tables; + thd->open_tables= table; table->tablenr=thd->current_tablenr++; table->used_fields=0; @@ -2340,109 +2493,7 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) table->null_row= table->maybe_null= 0; table->force_index= table->force_index_order= table->force_index_group= 0; table->status=STATUS_NO_RECORD; - DBUG_RETURN(FALSE); -} - - -/** - Create and insert into table cache placeholder for table - which will prevent its opening (or creation) (a.k.a lock - table name). - - @param thd Thread context - @param key Table cache key for name to be locked - @param key_length Table cache key length - - @return Pointer to TABLE object used for name locking or 0 in - case of failure. -*/ - -TABLE *table_cache_insert_placeholder(THD *thd, const char *key, - uint key_length) -{ - TABLE *table; - TABLE_SHARE *share; - char *key_buff; - DBUG_ENTER("table_cache_insert_placeholder"); - - safe_mutex_assert_owner(&LOCK_open); - - /* - Create a table entry with the right key and with an old refresh version - Note that we must use my_multi_malloc() here as this is freed by the - table cache - */ - if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL), - &table, sizeof(*table), - &share, sizeof(*share), - &key_buff, key_length, - NULL)) - DBUG_RETURN(NULL); - - table->s= share; - share->set_table_cache_key(key_buff, key, key_length); - share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table - table->in_use= thd; - table->locked_by_name=1; - - if (my_hash_insert(&open_cache, (uchar*)table)) - { - my_free((uchar*) table, MYF(0)); - DBUG_RETURN(NULL); - } - - DBUG_RETURN(table); -} - - -/** - Obtain an exclusive name lock on the table if it is not cached - in the table cache. - - @param thd Thread context - @param db Name of database - @param table_name Name of table - @param[out] table Out parameter which is either: - - set to NULL if table cache contains record for - the table or - - set to point to the TABLE instance used for - name-locking. - - @note This function takes into account all records for table in table - cache, even placeholders used for name-locking. This means that - 'table' parameter can be set to NULL for some situations when - table does not really exist. - - @retval TRUE Error occured (OOM) - @retval FALSE Success. 'table' parameter set according to above rules. -*/ - -bool lock_table_name_if_not_cached(THD *thd, const char *db, - const char *table_name, TABLE **table) -{ - char key[MAX_DBKEY_LENGTH]; - uint key_length; - DBUG_ENTER("lock_table_name_if_not_cached"); - - key_length= (uint)(strmov(strmov(key, db) + 1, table_name) - key) + 1; - pthread_mutex_lock(&LOCK_open); - - if (my_hash_search(&open_cache, (uchar *)key, key_length)) - { - pthread_mutex_unlock(&LOCK_open); - DBUG_PRINT("info", ("Table is cached, name-lock is not obtained")); - *table= 0; - DBUG_RETURN(FALSE); - } - if (!(*table= table_cache_insert_placeholder(thd, key, key_length))) - { - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(TRUE); - } - (*table)->open_placeholder= 1; - (*table)->next= thd->open_tables; - thd->open_tables= *table; - pthread_mutex_unlock(&LOCK_open); + table_list->table= table; DBUG_RETURN(FALSE); } @@ -2511,6 +2562,20 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) } +/** + @brief Helper function used by MDL subsystem for releasing TABLE_SHARE + objects in cases when it no longer wants to cache reference to it. +*/ + +void table_share_release_hook(void *share) +{ + pthread_mutex_lock(&LOCK_open); + release_table_share((TABLE_SHARE*)share, RELEASE_NORMAL); + broadcast_refresh(); + pthread_mutex_unlock(&LOCK_open); +} + + /* Open a table. @@ -2518,13 +2583,17 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) open_table() thd Thread context. table_list Open first table in list. - refresh INOUT Pointer to memory that will be set to 1 if - we need to close all tables and reopen them. + action INOUT Pointer to variable of enum_open_table_action type + which will be set according to action which is + required to remedy problem appeared during attempt + to open table. If this is a NULL pointer, then the table is not put in the thread-open-list. flags Bitmap of flags to modify how open works: MYSQL_LOCK_IGNORE_FLUSH - Open table even if - someone has done a flush or namelock on it. + someone has done a flush or there is a pending + exclusive metadata lock requests against it + (i.e. request high priority metadata lock). No version number checking is done. MYSQL_OPEN_TEMPORARY_ONLY - Open only temporary table not the base table or view. @@ -2545,21 +2614,23 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, - bool *refresh, uint flags) + enum_open_table_action *action, uint flags) { reg1 TABLE *table; char key[MAX_DBKEY_LENGTH]; uint key_length; char *alias= table_list->alias; - HASH_SEARCH_STATE state; + MDL_LOCK *mdl_lock; + int error; + TABLE_SHARE *share; DBUG_ENTER("open_table"); /* Parsing of partitioning information from .frm needs thd->lex set up. */ DBUG_ASSERT(thd->lex->is_lex_started); /* find a unused table in the open table cache */ - if (refresh) - *refresh=0; + if (action) + *action= OT_NO_ACTION; /* an open table operation needs a lot of the stack space */ if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias)) @@ -2685,7 +2756,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, Is this table a view and not a base table? (it is work around to allow to open view with locked tables, real fix will be made after definition cache will be made) + + Since opening of view which was not explicitly locked by LOCK + TABLES breaks metadata locking protocol (potentially can lead + to deadlocks) it should be disallowed. */ + if (mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db, + table_list->table_name)) { char path[FN_REFLEN + 1]; enum legacy_db_type not_used; @@ -2693,21 +2770,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table_list->db, table_list->table_name, reg_ext, 0); if (mysql_frm_type(thd, path, ¬_used) == FRMTYPE_VIEW) { - /* - Will not be used (because it's VIEW) but has to be passed. - Also we will not free it (because it is a stack variable). - */ - TABLE tab; - table= &tab; - pthread_mutex_lock(&LOCK_open); - if (!open_unireg_entry(thd, table, table_list, alias, - key, key_length, mem_root, 0)) + if (!tdc_open_view(thd, table_list, alias, key, key_length, + mem_root, 0)) { DBUG_ASSERT(table_list->view != 0); - pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); // VIEW } - pthread_mutex_unlock(&LOCK_open); } } /* @@ -2740,6 +2808,32 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, on disk. */ + mdl_lock= table_list->mdl_lock; + mdl_add_lock(&thd->mdl_context, mdl_lock); + + if (table_list->open_table_type) + { + mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); + /* TODO: This case can be significantly optimized. */ + if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + DBUG_RETURN(0); + } + else + { + bool retry; + + if (table_list->mdl_upgradable) + mdl_set_upgradable(mdl_lock); + mdl_set_lock_priority(mdl_lock, (flags & MYSQL_LOCK_IGNORE_FLUSH) ? + MDL_HIGH_PRIO : MDL_NORMAL_PRIO); + if (mdl_acquire_shared_lock(mdl_lock, &retry)) + { + if (action && retry) + *action= OT_BACK_OFF_AND_RETRY; + DBUG_RETURN(0); + } + } + pthread_mutex_lock(&LOCK_open); /* @@ -2756,222 +2850,210 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ! (flags & MYSQL_LOCK_IGNORE_FLUSH)) { /* Someone did a refresh while thread was opening tables */ - if (refresh) - *refresh=1; + if (action) + *action= OT_BACK_OFF_AND_RETRY; pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } - /* - In order for the back off and re-start process to work properly, - handler tables having old versions (due to FLUSH TABLES or pending - name-lock) MUST be closed. This is specially important if a name-lock - is pending for any table of the handler_tables list, otherwise a - deadlock may occur. - */ - if (thd->handler_tables) - mysql_ha_flush(thd); - - /* - Actually try to find the table in the open_cache. - The cache may contain several "TABLE" instances for the same - physical table. The instances that are currently "in use" by - some thread have their "in_use" member != NULL. - There is no good reason for having more than one entry in the - hash for the same physical table, except that we use this as - an implicit "pending locks queue" - see - wait_for_locked_table_names for details. - */ - for (table= (TABLE*) my_hash_first(&open_cache, (uchar*) key, key_length, - &state); - table && table->in_use ; - table= (TABLE*) my_hash_next(&open_cache, (uchar*) key, key_length, - &state)) + if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE) { - DBUG_PRINT("tcache", ("in_use table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); - /* - Here we flush tables marked for flush. - Normally, table->s->version contains the value of - refresh_version from the moment when this table was - (re-)opened and added to the cache. - If since then we did (or just started) FLUSH TABLES - statement, refresh_version has been increased. - For "name-locked" TABLE instances, table->s->version is set - to 0 (see lock_table_name for details). - In case there is a pending FLUSH TABLES or a name lock, we - need to back off and re-start opening tables. - If we do not back off now, we may dead lock in case of lock - order mismatch with some other thread: - c1: name lock t1; -- sort of exclusive lock - c2: open t2; -- sort of shared lock - c1: name lock t2; -- blocks - c2: open t1; -- blocks - */ - if (table->needs_reopen_or_name_lock()) + bool exists; + + if (check_if_table_exists(thd, table_list, &exists)) + goto err_unlock2; + + if (!exists) { - DBUG_PRINT("note", - ("Found table '%s.%s' with different refresh version", - table_list->db, table_list->table_name)); + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); + } + /* Table exists. Let us try to open it. */ + } + else if (table_list->open_table_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL) + { + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); + } - if (flags & MYSQL_LOCK_IGNORE_FLUSH) - { - /* Force close at once after usage */ - thd->version= table->s->version; - continue; - } - - /* Avoid self-deadlocks by detecting self-dependencies. */ - if (table->open_placeholder && table->in_use == thd) - { - pthread_mutex_unlock(&LOCK_open); - my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name.str); - DBUG_RETURN(0); - } + if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock))) + { + if (!(share= get_table_share_with_create(thd, table_list, key, + key_length, OPEN_VIEW, + &error))) + goto err_unlock2; + if (share->is_view) + { /* - Back off, part 1: mark the table as "unused" for the - purpose of name-locking by setting table->db_stat to 0. Do - that only for the tables in this thread that have an old - table->s->version (this is an optimization (?)). - table->db_stat == 0 signals wait_for_locked_table_names - that the tables in question are not used any more. See - table_is_used call for details. + This table is a view. Validate its metadata version: in particular, + that it was a view when the statement was prepared. + */ + if (check_and_update_table_version(thd, table_list, share)) + goto err_unlock; + if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) + goto err_unlock; - Notice that HANDLER tables were already taken care of by - the earlier call to mysql_ha_flush() in this same critical - section. - */ - close_old_data_files(thd,thd->open_tables,0,0); - /* - Back-off part 2: try to avoid "busy waiting" on the table: - if the table is in use by some other thread, we suspend - and wait till the operation is complete: when any - operation that juggles with table->s->version completes, - it broadcasts COND_refresh condition variable. - If 'old' table we met is in use by current thread we return - without waiting since in this situation it's this thread - which is responsible for broadcasting on COND_refresh - (and this was done already in close_old_data_files()). - Good example of such situation is when we have statement - that needs two instances of table and FLUSH TABLES comes - after we open first instance but before we open second - instance. - */ - if (table->in_use != thd) + /* Open view */ + if (open_new_frm(thd, share, alias, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX | HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | + (flags & OPEN_VIEW_NO_PARSE), thd->open_options, + 0, table_list, mem_root)) + goto err_unlock; + + /* TODO: Don't free this */ + release_table_share(share, RELEASE_NORMAL); + + if (flags & OPEN_VIEW_NO_PARSE) { - /* wait_for_conditionwill unlock LOCK_open for us */ - wait_for_condition(thd, &LOCK_open, &COND_refresh); + /* + VIEW not really opened, only frm were read. + Set 1 as a flag here + */ + table_list->view= (LEX*)1; } else { - pthread_mutex_unlock(&LOCK_open); + DBUG_ASSERT(table_list->view); } - /* - There is a refresh in progress for this table. - Signal the caller that it has to try again. - */ - if (refresh) - *refresh=1; + + pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } - } - if (table) - { - DBUG_PRINT("tcache", ("unused table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); - /* Unlink the table from "unused_tables" list. */ - if (table == unused_tables) - { // First unused - unused_tables=unused_tables->next; // Remove from link - if (table == unused_tables) - unused_tables=0; + else if (table_list->view) + { + /* + We're trying to open a table for what was a view. + This can only happen during (re-)execution. + At prepared statement prepare the view has been opened and + merged into the statement parse tree. After that, someone + performed a DDL and replaced the view with a base table. + Don't try to open the table inside a prepared statement, + invalidate it instead. + + Note, the assert below is known to fail inside stored + procedures (Bug#27011). + */ + DBUG_ASSERT(thd->m_reprepare_observer); + check_and_update_table_version(thd, table_list, share); + /* Always an error. */ + DBUG_ASSERT(thd->is_error()); + goto err_unlock; } - table->prev->next=table->next; /* Remove from unused list */ - table->next->prev=table->prev; - table->in_use= thd; + + if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) + goto err_unlock; + + /* + We are going to to store extra reference to the share in MDL-subsystem + so we need to increase reference counter; + */ + reference_table_share(share); + mdl_set_cached_object(mdl_lock, share, table_share_release_hook); } else { - /* Insert a new TABLE instance into the open cache */ - int error; - DBUG_PRINT("tcache", ("opening new table")); - /* Free cache if too big */ - while (open_cache.records > table_cache_size && unused_tables) - my_hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */ - - if (table_list->create) + if (table_list->view) { - bool exists; - - if (check_if_table_exists(thd, table_list, &exists)) - { - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(NULL); - } - - if (!exists) - { - /* - Table to be created, so we need to create placeholder in table-cache. - */ - if (!(table= table_cache_insert_placeholder(thd, key, key_length))) - { - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(NULL); - } - /* - Link placeholder to the open tables list so it will be automatically - removed once tables are closed. Also mark it so it won't be ignored - by other trying to take name-lock. - */ - table->open_placeholder= 1; - table->next= thd->open_tables; - thd->open_tables= table; - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(table); - } - /* Table exists. Let us try to open it. */ + DBUG_ASSERT(thd->m_reprepare_observer); + check_and_update_table_version(thd, table_list, share); + /* Always an error. */ + DBUG_ASSERT(thd->is_error()); + goto err_unlock; } + /* When we have cached TABLE_SHARE we know that is not a view. */ + if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) + goto err_unlock; + + /* + We are going to use this share for construction of new TABLE object + so reference counter should be increased. + */ + reference_table_share(share); + } + + if (share->version != refresh_version) + { + if (!(flags & MYSQL_LOCK_IGNORE_FLUSH)) + { + if (action) + *action= OT_BACK_OFF_AND_RETRY; + release_table_share(share, RELEASE_NORMAL); + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); + } + /* Force close at once after usage */ + thd->version= share->version; + } + + if (!share->free_tables.is_empty()) + { + table= share->free_tables.head(); + table_def_use_table(thd, table); + /* We need to release share as we have EXTRA reference to it in our hands. */ + release_table_share(share, RELEASE_NORMAL); + } + else + { + /* We have too many TABLE instances around let us try to get rid of them. */ + while (table_cache_count > table_cache_size && unused_tables) + free_cache_entry(unused_tables); /* make a new table */ if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) + goto err_unlock; + + error= open_table_from_share(thd, share, alias, + (uint) (HA_OPEN_KEYFILE | + HA_OPEN_RNDFILE | + HA_GET_INDEX | + HA_TRY_READ_ONLY), + (READ_KEYINFO | COMPUTE_TYPES | + EXTRA_RECORD), + thd->open_options, table, FALSE); + + if (error) { - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(NULL); + my_free(table, MYF(0)); + + if (action) + { + if (error == 7) + { + share->version= 0; + *action= OT_DISCOVER; + } + else if (share->crashed) + { + share->version= 0; + *action= OT_REPAIR; + } + } + + goto err_unlock; } - error= open_unireg_entry(thd, table, table_list, alias, key, key_length, - mem_root, (flags & OPEN_VIEW_NO_PARSE)); - if (error > 0) + if (open_table_entry_fini(thd, share, table)) { + closefrm(table, 0); my_free((uchar*)table, MYF(0)); - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(NULL); + goto err_unlock; } - if (table_list->view || error < 0) - { - /* - VIEW not really opened, only frm were read. - Set 1 as a flag here - */ - if (error < 0) - table_list->view= (LEX*)1; - my_free((uchar*)table, MYF(0)); - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); // VIEW - } - DBUG_PRINT("info", ("inserting table '%s'.'%s' 0x%lx into the cache", - table->s->db.str, table->s->table_name.str, - (long) table)); - (void) my_hash_insert(&open_cache,(uchar*) table); + /* Add table to the share's used tables list. */ + table_def_add_used_table(thd, table); } - check_unused(); // Debugging call - pthread_mutex_unlock(&LOCK_open); - if (refresh) + + // Table existed + if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE) + mdl_downgrade_exclusive_locks(&thd->mdl_context); + + table->mdl_lock= mdl_lock; + if (action) { table->next=thd->open_tables; /* Link into simple list */ thd->open_tables=table; @@ -3013,15 +3095,32 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->clear_column_bitmaps(); DBUG_ASSERT(table->key_read == 0); DBUG_RETURN(table); + +err_unlock: + release_table_share(share, RELEASE_NORMAL); +err_unlock2: + pthread_mutex_unlock(&LOCK_open); + mdl_release_lock(&thd->mdl_context, mdl_lock); + DBUG_RETURN(0); } -TABLE *find_locked_table(THD *thd, const char *db,const char *table_name) +/** + Find table in the list of open tables. + + @param list List of TABLE objects to be inspected. + @param db Database name + @param table_name Table name + + @return Pointer to the TABLE object found, 0 if no table found. +*/ + +TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name) { char key[MAX_DBKEY_LENGTH]; uint key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; - for (TABLE *table=thd->open_tables; table ; table=table->next) + for (TABLE *table= list; table ; table=table->next) { if (table->s->table_cache_key.length == key_length && !memcmp(table->s->table_cache_key.str, key, key_length)) @@ -3031,6 +3130,41 @@ TABLE *find_locked_table(THD *thd, const char *db,const char *table_name) } +/** + Find write locked instance of table in the list of open tables, + emit error if no such instance found. + + @param thd List of TABLE objects to be searched + @param db Database name. + @param table_name Name of table. + + @return Pointer to write-locked TABLE instance, 0 - otherwise. +*/ + +TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name) +{ + TABLE *tab= find_locked_table(list, db, table_name); + + if (!tab) + { + my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name); + return 0; + } + else + { + while (tab->reginfo.lock_type < TL_WRITE_LOW_PRIORITY && + (tab= find_locked_table(tab->next, db, table_name))) + continue; + if (!tab) + { + my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name); + return 0; + } + } + return tab; +} + + /* Reopen an table because the definition has changed. @@ -3073,14 +3207,10 @@ bool reopen_table(TABLE *table) table_list.table_name= table->s->table_name.str; table_list.table= table; - if (wait_for_locked_table_names(thd, &table_list)) - DBUG_RETURN(1); // Thread was killed - - if (open_unireg_entry(thd, &tmp, &table_list, - table->alias, - table->s->table_cache_key.str, - table->s->table_cache_key.length, - thd->mem_root, 0)) + if (reopen_table_entry(thd, &tmp, &table_list, + table->alias, + table->s->table_cache_key.str, + table->s->table_cache_key.length)) goto end; /* This list copies variables set by open_table */ @@ -3112,6 +3242,12 @@ bool reopen_table(TABLE *table) (void) closefrm(&tmp, 1); // close file, free everything goto end; } + tmp.mdl_lock= table->mdl_lock; + + table_def_change_share(table, tmp.s); + /* Avoid wiping out TABLE's position in new share's used tables list. */ + tmp.share_next= table->share_next; + tmp.share_prev= table->share_prev; delete table->triggers; if (table->file) @@ -3214,6 +3350,7 @@ void close_data_files_and_morph_locks(THD *thd, const char *db, mysql_lock_remove(thd, thd->locked_tables, table, TRUE); } table->open_placeholder= 1; + table->s->version= 0; close_handle_and_leave_table_as_lock(table); } } @@ -3282,8 +3419,6 @@ static bool reattach_merge(THD *thd, TABLE **err_tables_p) @param thd Thread context @param get_locks Should we get locks after reopening tables ? - @param mark_share_as_old Mark share as old to protect from a impending - global read lock. @note Since this function can't properly handle prelocking and create placeholders it should be used in very special @@ -3297,11 +3432,11 @@ static bool reattach_merge(THD *thd, TABLE **err_tables_p) @return FALSE in case of success, TRUE - otherwise. */ -bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old) +bool reopen_tables(THD *thd, bool get_locks) { TABLE *table,*next,**prev; TABLE **tables,**tables_ptr; // For locks - TABLE *err_tables= NULL; + TABLE *err_tables= NULL, *err_tab_tmp; bool error=0, not_used; bool merge_table_found= FALSE; const uint flags= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN | @@ -3356,7 +3491,7 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old) */ if (table->child_l || table->parent) detach_merge_children(table, TRUE); - my_hash_delete(&open_cache,(uchar*) table); + free_cache_entry(table); error=1; } else @@ -3367,11 +3502,15 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old) prev= &table->next; /* Do not handle locks of MERGE children. */ if (get_locks && !db_stat && !table->parent) - *tables_ptr++= table; // need new lock on this - if (mark_share_as_old) { - table->s->version=0; - table->open_placeholder= 0; + *tables_ptr++= table; // need new lock on this + /* + We rely on having exclusive metadata lock on the table to be + able safely re-acquire table locks on it. + */ + DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, + table->s->db.str, + table->s->table_name.str)); } } } @@ -3385,8 +3524,9 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old) { while (err_tables) { - my_hash_delete(&open_cache, (uchar*) err_tables); - err_tables= err_tables->next; + err_tab_tmp= err_tables->next; + free_cache_entry(err_tables); + err_tables= err_tab_tmp; } } DBUG_PRINT("tcache", ("open tables to lock: %u", @@ -3534,41 +3674,24 @@ bool table_is_used(TABLE *table, bool wait_for_name_lock) { char *key= table->s->table_cache_key.str; uint key_length= table->s->table_cache_key.length; - - DBUG_PRINT("loop", ("table_name: %s", table->alias)); - HASH_SEARCH_STATE state; - for (TABLE *search= (TABLE*) my_hash_first(&open_cache, (uchar*) key, - key_length, &state); - search ; - search= (TABLE*) my_hash_next(&open_cache, (uchar*) key, - key_length, &state)) - { - DBUG_PRINT("info", ("share: 0x%lx " - "open_placeholder: %d locked_by_name: %d " - "db_stat: %u version: %lu", - (ulong) search->s, - search->open_placeholder, search->locked_by_name, - search->db_stat, - search->s->version)); - if (search->in_use == table->in_use) - continue; // Name locked by this thread - /* - We can't use the table under any of the following conditions: - - There is an name lock on it (Table is to be deleted or altered) - - If we are in flush table and we didn't execute the flush - - If the table engine is open and it's an old version - (We must wait until all engines are shut down to use the table) - */ - if ( (search->locked_by_name && wait_for_name_lock) || - (search->is_name_opened() && search->needs_reopen_or_name_lock())) - DBUG_RETURN(1); - } + /* Note that 'table' can use artificial TABLE_SHARE object. */ + TABLE_SHARE *share= (TABLE_SHARE*)my_hash_search(&table_def_cache, + (uchar*) key, key_length); + if (share && !share->used_tables.is_empty() && + share->version != refresh_version) + DBUG_RETURN(1); } while ((table=table->next)); DBUG_RETURN(0); } -/* Wait until all used tables are refreshed */ +/* + Wait until all used tables are refreshed. + + FIXME We should remove this function since for several functions which + are invoked by it new scenarios of usage are introduced, while + this function implements optimization useful only in rare cases. +*/ bool wait_for_tables(THD *thd) { @@ -3593,7 +3716,7 @@ bool wait_for_tables(THD *thd) /* Now we can open all tables without any interference */ thd_proc_info(thd, "Reopen tables"); thd->version= refresh_version; - result=reopen_tables(thd,0,0); + result=reopen_tables(thd, 0); } pthread_mutex_unlock(&LOCK_open); thd_proc_info(thd, 0); @@ -3601,111 +3724,27 @@ bool wait_for_tables(THD *thd) } -/* - drop tables from locked list +/** + Unlock and close tables open and locked by LOCK TABLES statement. - SYNOPSIS - drop_locked_tables() - thd Thread thandler - db Database - table_name Table name - - INFORMATION - This is only called on drop tables - - The TABLE object for the dropped table is unlocked but still kept around - as a name lock, which means that the table will be available for other - thread as soon as we call unlock_table_names(). - If there is multiple copies of the table locked, all copies except - the first, which acts as a name lock, is removed. - - RETURN - # If table existed, return table - 0 Table was not locked + @param thd Current thread's context. */ - -TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name) +void unlock_locked_tables(THD *thd) { - TABLE *table,*next,**prev, *found= 0; - prev= &thd->open_tables; - DBUG_ENTER("drop_locked_tables"); + DBUG_ASSERT(!thd->in_sub_stmt && + !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); - /* - Note that we need to hold LOCK_open while changing the - open_tables list. Another thread may work on it. - (See: remove_table_from_cache(), mysql_wait_completed_table()) - Closing a MERGE child before the parent would be fatal if the - other thread tries to abort the MERGE lock in between. - */ - for (table= thd->open_tables; table ; table=next) + if (thd->locked_tables) { - next=table->next; - if (!strcmp(table->s->table_name.str, table_name) && - !strcmp(table->s->db.str, db)) - { - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_remove(thd, thd->locked_tables, - table->parent ? table->parent : table, TRUE); - /* - When closing a MERGE parent or child table, detach the children first. - Clear child table references in case this object is opened again. - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); - - if (!found) - { - found= table; - /* Close engine table, but keep object around as a name lock */ - if (table->db_stat) - { - table->db_stat= 0; - table->file->close(); - } - } - else - { - /* We already have a name lock, remove copy */ - my_hash_delete(&open_cache,(uchar*) table); - } - } - else - { - *prev=table; - prev= &table->next; - } - } - *prev=0; - if (found) - broadcast_refresh(); - if (thd->locked_tables && thd->locked_tables->table_count == 0) - { - my_free((uchar*) thd->locked_tables,MYF(0)); + thd->lock= thd->locked_tables; thd->locked_tables=0; - } - DBUG_RETURN(found); -} - - -/* - If we have the table open, which only happens when a LOCK TABLE has been - done on the table, change the lock type to a lock that will abort all - other threads trying to get the lock. -*/ - -void abort_locked_tables(THD *thd,const char *db, const char *table_name) -{ - TABLE *table; - for (table= thd->open_tables; table ; table= table->next) - { - if (!strcmp(table->s->table_name.str, table_name) && - !strcmp(table->s->db.str, db)) - { - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE); - break; - } + close_thread_tables(thd); + /* + After closing tables we can free memory used for storing lock + request objects for metadata locks + */ + free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE)); } } @@ -3812,7 +3851,7 @@ static bool inject_reprepare(THD *thd) @retval FALSE success, version in TABLE_LIST has been updated */ -bool +static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, TABLE_SHARE *table_share) { @@ -3838,41 +3877,98 @@ check_and_update_table_version(THD *thd, return FALSE; } -/* - Load a table definition from file and open unireg table - SYNOPSIS - open_unireg_entry() - thd Thread handle - entry Store open table definition here - table_list TABLE_LIST with db, table_name & belong_to_view - alias Alias name - cache_key Key for share_cache - cache_key_length length of cache_key - mem_root temporary mem_root for parsing - flags the OPEN_VIEW_NO_PARSE flag to be passed to - openfrm()/open_new_frm() +/** + Open view by getting its definition from disk (and table cache in future). - NOTES - Extra argument for open is taken from thd->open_options - One must have a lock on LOCK_open when calling this function + @param thd Thread handle + @param table_list TABLE_LIST with db, table_name & belong_to_view + @param alias Alias name + @param cache_key Key for table definition cache + @param cache_key_length Length of cache_key + @param mem_root Memory to be used for .frm parsing. + @param flags Flags which modify how we open the view - RETURN - 0 ok - # Error + @todo This function is needed for special handling of views under + LOCK TABLES. We probably should get rid of it in long term. + + @return FALSE if success, TRUE - otherwise. */ -static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, - const char *alias, - char *cache_key, uint cache_key_length, - MEM_ROOT *mem_root, uint flags) +bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, + char *cache_key, uint cache_key_length, + MEM_ROOT *mem_root, uint flags) +{ + TABLE not_used; + int error; + TABLE_SHARE *share; + + pthread_mutex_lock(&LOCK_open); + + if (!(share= get_table_share_with_create(thd, table_list, cache_key, + cache_key_length, + OPEN_VIEW, &error))) + goto err; + + if (share->is_view && + !open_new_frm(thd, share, alias, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX | HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | + flags, thd->open_options, ¬_used, table_list, + mem_root)) + { + release_table_share(share, RELEASE_NORMAL); + pthread_mutex_unlock(&LOCK_open); + return FALSE; + } + + my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "VIEW"); + release_table_share(share, RELEASE_NORMAL); +err: + pthread_mutex_unlock(&LOCK_open); + return TRUE; +} + + +/** + Load table definition from file and open table while holding exclusive + meta-data lock on it. + + @param thd Thread handle + @param entry Memory for TABLE object to be created + @param table_list TABLE_LIST with db, table_name & belong_to_view + @param alias Alias name + @param cache_key Key for table definition cache + @param cache_key_length Length of cache_key + + @note This auxiliary function is mostly inteded for re-opening table + in situations when we hold exclusive meta-data lock. It is not + intended for normal case in which we have only shared meta-data + lock on the table to be open. + + @note Extra argument for open is taken from thd->open_options. + + @note One must have a lock on LOCK_open as well as exclusive meta-data + lock on the table when calling this function. + + @return FALSE in case of success, TRUE otherwise. +*/ + +static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, + const char *alias, char *cache_key, + uint cache_key_length) { int error; TABLE_SHARE *share; uint discover_retry_count= 0; - DBUG_ENTER("open_unireg_entry"); + DBUG_ENTER("reopen_table_entry"); safe_mutex_assert_owner(&LOCK_open); + DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, + table_list->db, + table_list->table_name)); + retry: if (!(share= get_table_share_with_create(thd, table_list, cache_key, cache_key_length, @@ -3901,40 +3997,11 @@ retry: goto err; if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) goto err; - - /* Open view */ - error= (int) open_new_frm(thd, share, alias, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX | HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | - (flags & OPEN_VIEW_NO_PARSE), - thd->open_options, entry, table_list, - mem_root); - if (error) - goto err; - /* TODO: Don't free this */ + /* Attempt to reopen view will bring havoc to upper layers anyway. */ release_table_share(share, RELEASE_NORMAL); - DBUG_RETURN((flags & OPEN_VIEW_NO_PARSE)? -1 : 0); - } - else if (table_list->view) - { - /* - We're trying to open a table for what was a view. - This can only happen during (re-)execution. - At prepared statement prepare the view has been opened and - merged into the statement parse tree. After that, someone - performed a DDL and replaced the view with a base table. - Don't try to open the table inside a prepared statement, - invalidate it instead. - - Note, the assert below is known to fail inside stored - procedures (Bug#27011). - */ - DBUG_ASSERT(thd->m_reprepare_observer); - check_and_update_table_version(thd, table_list, share); - /* Always an error. */ - DBUG_ASSERT(thd->is_error()); - goto err; + my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, + "BASE TABLE"); + DBUG_RETURN(1); } if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) @@ -3956,89 +4023,67 @@ retry: goto err; /* - TODO: - Here we should wait until all threads has released the table. - For now we do one retry. This may cause a deadlock if there - is other threads waiting for other tables used by this thread. - - Proper fix would be to if the second retry failed: - - Mark that table def changed - - Return from open table - - Close all tables used by this thread - - Start waiting that the share is released - - Retry by opening all tables again - */ - if (ha_create_table_from_engine(thd, table_list->db, - table_list->table_name)) - goto err; - /* - TO BE FIXED - To avoid deadlock, only wait for release if no one else is - using the share. + Since we have exclusive metadata lock on the table here the only + practical case when share->ref_count != 1 is when we have several + instances of the table opened by this thread (i.e we are under LOCK + TABLES). */ if (share->ref_count != 1) goto err; - /* Free share and wait until it's released by all threads */ - release_table_share(share, RELEASE_WAIT_FOR_DROP); - if (!thd->killed) - { - thd->warning_info->clear_warning_info(thd->query_id); - thd->clear_error(); // Clear error message - goto retry; - } - DBUG_RETURN(1); + + release_table_share(share, RELEASE_NORMAL); + + if (ha_create_table_from_engine(thd, table_list->db, + table_list->table_name)) + goto err; + + thd->warning_info->clear_warning_info(thd->query_id); + thd->clear_error(); // Clear error message + goto retry; } if (!entry->s || !entry->s->crashed) goto err; - // Code below is for repairing a crashed file - if ((error= lock_table_name(thd, table_list, TRUE))) - { - if (error < 0) - goto err; - if (wait_for_locked_table_names(thd, table_list)) - { - unlock_table_name(thd, table_list); - goto err; - } - } - pthread_mutex_unlock(&LOCK_open); - thd->clear_error(); // Clear error message - error= 0; - if (open_table_from_share(thd, share, alias, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX | - HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, - ha_open_options | HA_OPEN_FOR_REPAIR, - entry, FALSE) || ! entry->file || - (entry->file->is_crashed() && entry->file->ha_check_and_repair(thd))) - { - /* Give right error message */ - thd->clear_error(); - my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno); - sql_print_error("Couldn't repair table: %s.%s", share->db.str, - share->table_name.str); - if (entry->file) - closefrm(entry, 0); - error=1; - } - else - thd->clear_error(); // Clear error message - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - - if (error) - goto err; - break; - } - if (Table_triggers_list::check_n_load(thd, share->db.str, - share->table_name.str, entry, 0)) + entry->s->version= 0; + + /* TODO: We don't need to release share here. */ + release_table_share(share, RELEASE_NORMAL); + pthread_mutex_unlock(&LOCK_open); + error= (int)auto_repair_table(thd, table_list); + pthread_mutex_lock(&LOCK_open); + + if (error) + goto err; + + goto retry; + } + + if (open_table_entry_fini(thd, share, entry)) { closefrm(entry, 0); goto err; } + DBUG_RETURN(0); + +err: + release_table_share(share, RELEASE_NORMAL); + DBUG_RETURN(1); +} + + +/** + Auxiliary routine which finalizes process of TABLE object creation + by loading triggers and handling implicitly emptied tables. +*/ + +static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry) +{ + + if (Table_triggers_list::check_n_load(thd, share->db.str, + share->table_name.str, entry, 0)) + return TRUE; + /* If we are here, there was no fatal error (but error may be still unitialized). @@ -4070,18 +4115,141 @@ retry: */ sql_print_error("When opening HEAP table, could not allocate memory " "to write 'DELETE FROM `%s`.`%s`' to the binary log", - table_list->db, table_list->table_name); + share->db.str, share->table_name.str); delete entry->triggers; - closefrm(entry, 0); - goto err; + return TRUE; } } } - DBUG_RETURN(0); + return FALSE; +} -err: + +/** + Auxiliary routine which is used for performing automatical table repair. +*/ + +static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) +{ + char cache_key[MAX_DBKEY_LENGTH]; + uint cache_key_length; + TABLE_SHARE *share; + TABLE *entry; + int not_used; + bool result= FALSE; + + cache_key_length= create_table_def_key(thd, cache_key, table_list, 0); + + thd->clear_error(); + + pthread_mutex_lock(&LOCK_open); + + if (!(share= get_table_share_with_create(thd, table_list, cache_key, + cache_key_length, + OPEN_VIEW, ¬_used))) + { + pthread_mutex_unlock(&LOCK_open); + return TRUE; + } + + if (share->is_view) + goto end_with_lock_open; + + if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME)))) + { + result= TRUE; + goto end_with_lock_open; + } + share->version= 0; + pthread_mutex_unlock(&LOCK_open); + + if (open_table_from_share(thd, share, table_list->alias, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX | + HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, + ha_open_options | HA_OPEN_FOR_REPAIR, + entry, FALSE) || ! entry->file || + (entry->file->is_crashed() && entry->file->ha_check_and_repair(thd))) + { + /* Give right error message */ + thd->clear_error(); + my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno); + sql_print_error("Couldn't repair table: %s.%s", share->db.str, + share->table_name.str); + if (entry->file) + closefrm(entry, 0); + result= TRUE; + } + else + { + thd->clear_error(); // Clear error message + closefrm(entry, 0); + } + my_free(entry, MYF(0)); + + pthread_mutex_lock(&LOCK_open); + +end_with_lock_open: release_table_share(share, RELEASE_NORMAL); - DBUG_RETURN(1); + pthread_mutex_unlock(&LOCK_open); + return result; +} + + +/** + Handle failed attempt ot open table by performing requested action. + + @param thd Thread context + @param table Table list element for table that caused problem + @param action Type of action requested by failed open_table() call + + @retval FALSE - Success. One should try to open tables once again. + @retval TRUE - Error +*/ + +static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table, + enum_open_table_action action) +{ + bool result= FALSE; + + switch (action) + { + case OT_BACK_OFF_AND_RETRY: + result= (mdl_wait_for_locks(&thd->mdl_context) || + tdc_wait_for_old_versions(thd, &thd->mdl_context)); + mdl_remove_all_locks(&thd->mdl_context); + break; + case OT_DISCOVER: + mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, table->mdl_lock); + if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + return TRUE; + pthread_mutex_lock(&LOCK_open); + expel_table_from_cache(0, table->db, table->table_name); + ha_create_table_from_engine(thd, table->db, table->table_name); + pthread_mutex_unlock(&LOCK_open); + + thd->warning_info->clear_warning_info(thd->query_id); + thd->clear_error(); // Clear error message + mdl_release_exclusive_locks(&thd->mdl_context); + break; + case OT_REPAIR: + mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, table->mdl_lock); + if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + return TRUE; + pthread_mutex_lock(&LOCK_open); + expel_table_from_cache(0, table->db, table->table_name); + pthread_mutex_unlock(&LOCK_open); + + result= auto_repair_table(thd, table); + mdl_release_exclusive_locks(&thd->mdl_context); + break; + default: + DBUG_ASSERT(0); + } + return result; } @@ -4132,6 +4300,7 @@ static int add_merge_table_list(TABLE_LIST *tlist) /* Set lock type. */ child_l->lock_type= tlist->lock_type; + child_l->mdl_upgradable= tlist->mdl_upgradable; /* Set parent reference. */ child_l->parent_l= tlist; @@ -4487,7 +4656,7 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) { TABLE_LIST *tables= NULL; - bool refresh; + enum_open_table_action action; int result=0; MEM_ROOT new_frm_mem; /* Also used for indicating that prelocking is need */ @@ -4507,6 +4676,18 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) query_tables_last_own= 0; thd_proc_info(thd, "Opening tables"); + /* + Close HANDLER tables which are marked for flush or against which there + are pending exclusive metadata locks. Note that we do this not to avoid + deadlocks (calls to mysql_ha_flush() in mdl_wait_for_locks() and + tdc_wait_for_old_version() are enough for this) but in order to have + a point during statement execution at which such HANDLERs are closed + even if they don't create problems for current thread (i.e. to avoid + having DDL blocked by HANDLERs opened for long time). + */ + if (thd->handler_tables) + mysql_ha_flush(thd); + /* If we are not already executing prelocked statement and don't have statement for which table list for prelocking is already built, let @@ -4556,9 +4737,18 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) */ if (tables->derived) { - if (tables->view) - goto process_view_routines; - continue; + if (!tables->view) + continue; + /* + We restore view's name and database wiped out by derived tables + processing and fall back to standard open process in order to + obtain proper metadata locks and do other necessary steps like + stored routine processing. + */ + tables->db= tables->view_db.str; + tables->db_length= tables->view_db.length; + tables->table_name= tables->view_name.str; + tables->table_name_length= tables->view_name.length; } /* If this TABLE_LIST object is a placeholder for an information_schema @@ -4602,12 +4792,12 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) */ Prelock_error_handler prelock_handler; thd->push_internal_handler(& prelock_handler); - tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags); + tables->table= open_table(thd, tables, &new_frm_mem, &action, flags); thd->pop_internal_handler(); safe_to_ignore_table= prelock_handler.safely_trapped_errors(); } else - tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags); + tables->table= open_table(thd, tables, &new_frm_mem, &action, flags); } else DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx", @@ -4657,7 +4847,16 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) parent_l->next_global= *parent_l->table->child_last_l; } - if (refresh) // Refresh in progress + /* + FIXME This is a temporary hack. Actually we need check that will + allow us to differentiate between error while opening/creating + table and successful table creation. + ... + */ + if (tables->open_table_type) + continue; + + if (action) { /* We have met name-locked or old version of table. Now we have @@ -4675,7 +4874,17 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) */ if (query_tables_last_own) thd->lex->mark_as_requiring_prelocking(query_tables_last_own); - close_tables_for_reopen(thd, start); + close_tables_for_reopen(thd, start, (action == OT_BACK_OFF_AND_RETRY)); + /* + Here we rely on the fact that 'tables' still points to the valid + TABLE_LIST element. Altough currently this assumption is valid + it may change in future. + */ + if (handle_failed_open_table_attempt(thd, tables, action)) + { + result= -1; + goto err; + } goto restart; } @@ -4929,6 +5138,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, uint lock_flags) { TABLE *table; + enum_open_table_action action; bool refresh; DBUG_ENTER("open_ltable"); @@ -4939,9 +5149,20 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, thd->current_tablenr= 0; /* open_ltable can be used only for BASIC TABLEs */ table_list->required_type= FRMTYPE_TABLE; - while (!(table= open_table(thd, table_list, thd->mem_root, &refresh, 0)) && - refresh) - ; + +retry: + while (!(table= open_table(thd, table_list, thd->mem_root, &action, 0)) && + action) + { + /* + Even altough we have failed to open table we still need to + call close_thread_tables() to release metadata locks which + might have been acquired successfully. + */ + close_thread_tables(thd, (action == OT_BACK_OFF_AND_RETRY)); + if (handle_failed_open_table_attempt(thd, table_list, action)) + break; + } if (table) { @@ -4969,8 +5190,22 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, DBUG_ASSERT(thd->lock == 0); // You must lock everything at once if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK) if (! (thd->lock= mysql_lock_tables(thd, &table_list->table, 1, - lock_flags, &refresh))) - table= 0; + (lock_flags | + MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN), + &refresh))) + { + /* + FIXME: Actually we should get rid of MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN option + as all reopening should happen outside of mysql_lock_tables() code. + */ + if (refresh) + { + close_thread_tables(thd); + goto retry; + } + else + table= 0; + } } } @@ -5026,7 +5261,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived) break; if (!need_reopen) DBUG_RETURN(-1); - close_tables_for_reopen(thd, &tables); + close_tables_for_reopen(thd, &tables, FALSE); } if (derived && (mysql_handle_derived(thd->lex, &mysql_derived_prepare) || @@ -5383,6 +5618,10 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) table->table->query_id= thd->query_id; if (check_lock_and_start_stmt(thd, table->table, table->lock_type)) { + /* + This was an attempt to enter prelocked mode so there is no + need to care about THD::locked_tables_root here. + */ mysql_unlock_tables(thd, thd->locked_tables); thd->locked_tables= 0; thd->options&= ~(OPTION_TABLE_LOCK); @@ -5469,7 +5708,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) */ -void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) +void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl) { /* If table list consists only from tables from prelocking set, table list @@ -5481,7 +5720,7 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) sp_remove_not_own_routines(thd->lex); for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global) tmp->table= 0; - close_thread_tables(thd); + close_thread_tables(thd, skip_mdl); } @@ -8392,36 +8631,6 @@ my_bool mysql_rm_tmp_tables(void) unireg support functions *****************************************************************************/ -/* - Invalidate any cache entries that are for some DB - - SYNOPSIS - remove_db_from_cache() - db Database name. This will be in lower case if - lower_case_table_name is set - - NOTE: - We can't use hash_delete when looping hash_elements. We mark them first - and afterwards delete those marked unused. -*/ - -void remove_db_from_cache(const char *db) -{ - for (uint idx=0 ; idx < open_cache.records ; idx++) - { - TABLE *table=(TABLE*) my_hash_element(&open_cache,idx); - if (!strcmp(table->s->db.str, db)) - { - table->s->version= 0L; /* Free when thread is ready */ - if (!table->in_use) - relink_unused(table); - } - } - while (unused_tables && !unused_tables->s->version) - my_hash_delete(&open_cache,(uchar*) unused_tables); -} - - /* free all unused tables @@ -8434,7 +8643,7 @@ void flush_tables() { (void) pthread_mutex_lock(&LOCK_open); while (unused_tables) - my_hash_delete(&open_cache,(uchar*) unused_tables); + free_cache_entry(unused_tables); (void) pthread_mutex_unlock(&LOCK_open); } @@ -8468,90 +8677,82 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; for (;;) { - HASH_SEARCH_STATE state; result= signalled= 0; - for (table= (TABLE*) my_hash_first(&open_cache, (uchar*) key, key_length, - &state); - table; - table= (TABLE*) my_hash_next(&open_cache, (uchar*) key, key_length, - &state)) - { - THD *in_use; - DBUG_PRINT("tcache", ("found table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); - - table->s->version=0L; /* Free when thread is ready */ - if (!(in_use=table->in_use)) - { - DBUG_PRINT("info",("Table was not in use")); - relink_unused(table); - } - else if (in_use != thd) - { - DBUG_PRINT("info", ("Table was in use by other thread")); - /* - Mark that table is going to be deleted from cache. This will - force threads that are in mysql_lock_tables() (but not yet - in thr_multi_lock()) to abort it's locks, close all tables and retry - */ - in_use->some_tables_deleted= 1; - if (table->is_name_opened()) - { - DBUG_PRINT("info", ("Found another active instance of the table")); - result=1; - } - /* Kill delayed insert threads */ - if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && - ! in_use->killed) - { - in_use->killed= THD::KILL_CONNECTION; - pthread_mutex_lock(&in_use->mysys_var->mutex); - if (in_use->mysys_var->current_cond) - { - pthread_mutex_lock(in_use->mysys_var->current_mutex); - signalled= 1; - pthread_cond_broadcast(in_use->mysys_var->current_cond); - pthread_mutex_unlock(in_use->mysys_var->current_mutex); - } - pthread_mutex_unlock(&in_use->mysys_var->mutex); - } - /* - Now we must abort all tables locks used by this thread - as the thread may be waiting to get a lock for another table. - Note that we need to hold LOCK_open while going through the - list. So that the other thread cannot change it. The other - thread must also hold LOCK_open whenever changing the - open_tables list. Aborting the MERGE lock after a child was - closed and before the parent is closed would be fatal. - */ - for (TABLE *thd_table= in_use->open_tables; - thd_table ; - thd_table= thd_table->next) - { - /* Do not handle locks of MERGE children. */ - if (thd_table->db_stat && !thd_table->parent) // If table is open - signalled|= mysql_lock_abort_for_thread(thd, thd_table); - } - } - else - { - DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u", - table->db_stat)); - result= result || (flags & RTFC_OWNED_BY_THD_FLAG); - } - } - while (unused_tables && !unused_tables->s->version) - my_hash_delete(&open_cache,(uchar*) unused_tables); - - DBUG_PRINT("info", ("Removing table from table_def_cache")); - /* Remove table from table definition cache if it's not in use */ - if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, + if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache, (uchar*) key, key_length))) { + I_P_List_iterator it(share->free_tables); + share->version= 0; + while ((table= it++)) + relink_unused(table); + + it.init(share->used_tables); + while ((table= it++)) + { + THD *in_use= table->in_use; + DBUG_ASSERT(in_use); + if (in_use != thd) + { + DBUG_PRINT("info", ("Table was in use by other thread")); + /* + Mark that table is going to be deleted from cache. This will + force threads that are in mysql_lock_tables() (but not yet + in thr_multi_lock()) to abort it's locks, close all tables and retry + */ + in_use->some_tables_deleted= 1; + + if (table->is_name_opened()) + { + DBUG_PRINT("info", ("Found another active instance of the table")); + result=1; + } + /* Kill delayed insert threads */ + if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && + ! in_use->killed) + { + in_use->killed= THD::KILL_CONNECTION; + pthread_mutex_lock(&in_use->mysys_var->mutex); + if (in_use->mysys_var->current_cond) + { + pthread_mutex_lock(in_use->mysys_var->current_mutex); + signalled= 1; + pthread_cond_broadcast(in_use->mysys_var->current_cond); + pthread_mutex_unlock(in_use->mysys_var->current_mutex); + } + pthread_mutex_unlock(&in_use->mysys_var->mutex); + } + /* + Now we must abort all tables locks used by this thread + as the thread may be waiting to get a lock for another table. + Note that we need to hold LOCK_open while going through the + list. So that the other thread cannot change it. The other + thread must also hold LOCK_open whenever changing the + open_tables list. Aborting the MERGE lock after a child was + closed and before the parent is closed would be fatal. + */ + for (TABLE *thd_table= in_use->open_tables; + thd_table ; + thd_table= thd_table->next) + { + /* Do not handle locks of MERGE children. */ + if (thd_table->db_stat && !thd_table->parent) // If table is open + signalled|= mysql_lock_abort_for_thread(thd, thd_table); + } + } + else + { + DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u", + table->db_stat)); + result= result || (flags & RTFC_OWNED_BY_THD_FLAG); + } + } + + while (unused_tables && !unused_tables->s->version) + free_cache_entry(unused_tables); + DBUG_PRINT("info", ("share version: %lu ref_count: %u", share->version, share->ref_count)); - share->version= 0; // Mark for delete if (share->ref_count == 0) { pthread_mutex_lock(&share->mutex); @@ -8598,6 +8799,160 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, } +/** + A callback to the server internals that is used to address + special cases of the locking protocol. + Invoked when acquiring an exclusive lock, for each thread that + has a conflicting shared metadata lock. + + This function: + - aborts waiting of the thread on a data lock, to make it notice + the pending exclusive lock and back off. + - if the thread is an INSERT DELAYED thread, sends it a KILL + signal to terminate it. + + @note This function does not wait for the thread to give away its + locks. Waiting is done outside for all threads at once. + + @param thd Current thread context + @param in_use The thread to wake up + + @retval TRUE if the thread was woken up + @retval FALSE otherwise (e.g. it was not waiting for a table-level lock). + + @note It is one of two places where border between MDL and the + rest of the server is broken. +*/ + +bool notify_thread_having_shared_lock(THD *thd, THD *in_use) +{ + bool signalled= FALSE; + if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && + !in_use->killed) + { + in_use->killed= THD::KILL_CONNECTION; + pthread_mutex_lock(&in_use->mysys_var->mutex); + if (in_use->mysys_var->current_cond) + pthread_cond_broadcast(in_use->mysys_var->current_cond); + pthread_mutex_unlock(&in_use->mysys_var->mutex); + signalled= TRUE; + } + pthread_mutex_lock(&LOCK_open); + for (TABLE *thd_table= in_use->open_tables; + thd_table ; + thd_table= thd_table->next) + { + /* TODO With new MDL check for db_stat is probably a legacy */ + if (thd_table->db_stat) + signalled|= mysql_lock_abort_for_thread(thd, thd_table); + } + pthread_mutex_unlock(&LOCK_open); + return signalled; +} + + +/** + Remove all instances of the table from cache assuming that current thread + has exclusive meta-data lock on it (optionally leave instances belonging + to the current thread in cache). + + @param leave_thd 0 If we should remove all instances + non-0 Pointer to current thread context if we should + leave instances belonging to this thread. + @param db Name of database + @param table_name Name of table + + @note Unlike remove_table_from_cache() it assumes that table instances + are already not used by any (other) thread (this should be achieved + by using meta-data locks). +*/ + +void expel_table_from_cache(THD *leave_thd, const char *db, const char *table_name) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + TABLE *table; + TABLE_SHARE *share; + + key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; + + if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, + key_length))) + { + I_P_List_iterator it(share->free_tables); + share->version= 0; + + while ((table= it++)) + relink_unused(table); + } + + /* This may destroy share so we have to do new look-up later. */ + while (unused_tables && !unused_tables->s->version) + free_cache_entry(unused_tables); + + if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, + key_length))) + { + DBUG_ASSERT(leave_thd || share->ref_count == 0); + if (share->ref_count == 0) + { + pthread_mutex_lock(&share->mutex); + my_hash_delete(&table_def_cache, (uchar*) share); + } + } +} + + +/** + Wait until there are no old versions of tables in the table + definition cache for the metadata locks that we try to acquire. + + @param thd Thread context + @param context Metadata locking context with locks. +*/ + +static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context) +{ + MDL_LOCK *l; + TABLE_SHARE *share; + const char *old_msg; + LEX_STRING key; + + while (!thd->killed) + { + /* + Here we have situation as in mdl_wait_for_locks() we need to + get rid of offending HANDLERs to avoid deadlock. + TODO: We should also investigate in which situations we have + to broadcast on COND_refresh because of this. + */ + mysql_ha_flush(thd); + pthread_mutex_lock(&LOCK_open); + + I_P_List_iterator it= mdl_get_locks(context); + while ((l= it++)) + { + mdl_get_tdc_key(l, &key); + if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache, (uchar*) key.str, + key.length)) && + share->version != refresh_version && + !share->used_tables.is_empty()) + break; + } + if (!l) + { + pthread_mutex_unlock(&LOCK_open); + break; + } + old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table"); + pthread_cond_wait(&COND_refresh, &LOCK_open); + /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */ + thd->exit_cond(old_msg); + } + return thd->killed; +} + + int setup_ftfuncs(SELECT_LEX *select_lex) { List_iterator li(*(select_lex->ftfunc_list)), @@ -8695,7 +9050,6 @@ open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, } err: - bzero(outparam, sizeof(TABLE)); // do not run repair DBUG_RETURN(1); } @@ -8728,15 +9082,23 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b) int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt) { - uint flags= RTFC_WAIT_OTHER_THREAD_FLAG | RTFC_CHECK_KILLED_FLAG; DBUG_ENTER("abort_and_upgrade_locks"); lpt->old_lock_type= lpt->table->reginfo.lock_type; - pthread_mutex_lock(&LOCK_open); /* If MERGE child, forward lock handling to parent. */ mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent : lpt->table, TRUE); - (void) remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, flags); + if (mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context, 0, + lpt->db, lpt->table_name)) + { + mysql_lock_downgrade_write(lpt->thd, + lpt->table->parent ? lpt->table->parent : + lpt->table, + lpt->old_lock_type); + DBUG_RETURN(1); + } + pthread_mutex_lock(&LOCK_open); + expel_table_from_cache(lpt->thd, lpt->db, lpt->table_name); pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } @@ -8771,106 +9133,12 @@ void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt) /* + Tells if two (or more) tables have auto_increment columns and we want to + lock those tables with a write lock. + SYNOPSIS - mysql_wait_completed_table() - lpt Parameter passing struct - my_table My table object - All parameters passed through the ALTER_PARTITION_PARAM object - RETURN VALUES - TRUE Failure - FALSE Success - DESCRIPTION - We have changed the frm file and now we want to wait for all users of - the old frm to complete before proceeding to ensure that no one - remains that uses the old frm definition. - Start by ensuring that all users of the table will be removed from cache - once they are done. Then abort all that have stumbled on locks and - haven't been started yet. - - thd Thread object - table Table object - db Database name - table_name Table name -*/ - -void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table) -{ - char key[MAX_DBKEY_LENGTH]; - uint key_length; - TABLE *table; - DBUG_ENTER("mysql_wait_completed_table"); - - key_length=(uint) (strmov(strmov(key,lpt->db)+1,lpt->table_name)-key)+1; - pthread_mutex_lock(&LOCK_open); - HASH_SEARCH_STATE state; - for (table= (TABLE*) my_hash_first(&open_cache,(uchar*) key,key_length, - &state) ; - table; - table= (TABLE*) my_hash_next(&open_cache,(uchar*) key,key_length, - &state)) - { - THD *in_use= table->in_use; - table->s->version= 0L; - if (!in_use) - { - relink_unused(table); - } - else - { - /* Kill delayed insert threads */ - if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && - ! in_use->killed) - { - in_use->killed= THD::KILL_CONNECTION; - pthread_mutex_lock(&in_use->mysys_var->mutex); - if (in_use->mysys_var->current_cond) - { - pthread_mutex_lock(in_use->mysys_var->current_mutex); - pthread_cond_broadcast(in_use->mysys_var->current_cond); - pthread_mutex_unlock(in_use->mysys_var->current_mutex); - } - pthread_mutex_unlock(&in_use->mysys_var->mutex); - } - /* - Now we must abort all tables locks used by this thread - as the thread may be waiting to get a lock for another table. - Note that we need to hold LOCK_open while going through the - list. So that the other thread cannot change it. The other - thread must also hold LOCK_open whenever changing the - open_tables list. Aborting the MERGE lock after a child was - closed and before the parent is closed would be fatal. - */ - for (TABLE *thd_table= in_use->open_tables; - thd_table ; - thd_table= thd_table->next) - { - /* Do not handle locks of MERGE children. */ - if (thd_table->db_stat && !thd_table->parent) // If table is open - mysql_lock_abort_for_thread(lpt->thd, thd_table); - } - } - } - /* - We start by removing all unused objects from the cache and marking - those in use for removal after completion. Now we also need to abort - all that are locked and are not progressing due to being locked - by our lock. We don't upgrade our lock here. - If MERGE child, forward lock handling to parent. - */ - mysql_lock_abort(lpt->thd, my_table->parent ? my_table->parent : my_table, - FALSE); - pthread_mutex_unlock(&LOCK_open); - DBUG_VOID_RETURN; -} - - -/* - Check if one (or more) write tables have auto_increment columns. - - @param[in] tables Table list - - @retval 0 if at least one write tables has an auto_increment column - @retval 1 otherwise + has_two_write_locked_tables_with_auto_increment + tables Table list NOTES: Call this function only when you have established the list of all tables @@ -8924,10 +9192,13 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, { DBUG_ENTER("open_system_tables_for_read"); + alloc_mdl_locks(table_list, thd->mem_root); + thd->reset_n_backup_open_tables_state(backup); uint count= 0; - bool not_used; + enum_open_table_action not_used; + bool not_used_2; for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global) { TABLE *table= open_table(thd, tables, thd->mem_root, ¬_used, @@ -8950,7 +9221,7 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, *(ptr++)= tables->table; thd->lock= mysql_lock_tables(thd, list, count, - MYSQL_LOCK_IGNORE_FLUSH, ¬_used); + MYSQL_LOCK_IGNORE_FLUSH, ¬_used_2); } if (thd->lock) DBUG_RETURN(FALSE); @@ -9002,6 +9273,8 @@ open_system_table_for_update(THD *thd, TABLE_LIST *one_table) { DBUG_ENTER("open_system_table_for_update"); + alloc_mdl_locks(one_table, thd->mem_root); + TABLE *table= open_ltable(thd, one_table, one_table->lock_type, 0); if (table) { @@ -9038,6 +9311,7 @@ open_performance_schema_table(THD *thd, TABLE_LIST *one_table, thd->reset_n_backup_open_tables_state(backup); + alloc_mdl_locks(one_table, thd->mem_root); if ((table= open_ltable(thd, one_table, one_table->lock_type, flags))) { DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_PERFORMANCE); @@ -9116,6 +9390,9 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup) pthread_mutex_unlock(&LOCK_open); + mdl_release_locks(&thd->mdl_context); + mdl_remove_all_locks(&thd->mdl_context); + thd->restore_backup_open_tables_state(backup); } diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc index 58c309ef57b..31d4430cbe6 100644 --- a/sql/sql_binlog.cc +++ b/sql/sql_binlog.cc @@ -241,7 +241,7 @@ void mysql_client_binlog_statement(THD* thd) my_ok(thd); end: - rli->clear_tables_to_lock(); + rli->slave_close_thread_tables(thd); my_free(buf, MYF(MY_ALLOW_ZERO_PTR)); DBUG_VOID_RETURN; } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index b7d88eca89a..9445f092546 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -202,10 +202,10 @@ bool foreign_key_prefix(Key *a, Key *b) ** Thread specific functions ****************************************************************************/ -Open_tables_state::Open_tables_state(ulong version_arg) +Open_tables_state::Open_tables_state(THD *thd, ulong version_arg) :version(version_arg), state_flags(0U) { - reset_open_tables_state(); + reset_open_tables_state(thd); } /* @@ -440,7 +440,7 @@ bool Drop_table_error_handler::handle_condition(THD *thd, THD::THD() :Statement(&main_lex, &main_mem_root, CONVENTIONAL_EXECUTION, /* statement id */ 0), - Open_tables_state(refresh_version), rli_fake(0), + Open_tables_state(this, refresh_version), rli_fake(0), lock_id(&main_lock_id), user_time(0), in_sub_stmt(0), sql_log_bin_toplevel(false), @@ -468,7 +468,8 @@ THD::THD() #if defined(ENABLED_DEBUG_SYNC) debug_sync_control(0), #endif /* defined(ENABLED_DEBUG_SYNC) */ - main_warning_info(0) + main_warning_info(0), + mdl_el_root(NULL) { ulong tmp; @@ -573,6 +574,8 @@ THD::THD() thr_lock_owner_init(&main_lock_id, &lock_info); m_internal_handler= NULL; + + init_sql_alloc(&locked_tables_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0); } @@ -1051,6 +1054,9 @@ THD::~THD() if (!cleanup_done) cleanup(); + mdl_context_destroy(&mdl_context); + mdl_context_destroy(&handler_mdl_context); + ha_close_connection(this); plugin_thdvar_cleanup(this); @@ -1072,6 +1078,7 @@ THD::~THD() #endif free_root(&main_mem_root, MYF(0)); + free_root(&locked_tables_root, MYF(0)); DBUG_VOID_RETURN; } @@ -3014,7 +3021,7 @@ void THD::reset_n_backup_open_tables_state(Open_tables_state *backup) { DBUG_ENTER("reset_n_backup_open_tables_state"); backup->set_open_tables_state(this); - reset_open_tables_state(); + reset_open_tables_state(this); state_flags|= Open_tables_state::BACKUPS_AVAIL; DBUG_VOID_RETURN; } @@ -3032,6 +3039,9 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup) lock == 0 && locked_tables == 0 && prelocked_mode == NON_PRELOCKED && m_reprepare_observer == NULL); + mdl_context_destroy(&mdl_context); + mdl_context_destroy(&handler_mdl_context); + set_open_tables_state(backup); DBUG_VOID_RETURN; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 03a92c1a685..0f7d9d9a8d5 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -25,6 +25,7 @@ #include "log.h" #include "rpl_tblmap.h" +#include "mdl.h" class Reprepare_observer; @@ -976,26 +977,31 @@ public: */ uint state_flags; + MDL_CONTEXT mdl_context; + MDL_CONTEXT handler_mdl_context; + /* This constructor serves for creation of Open_tables_state instances which are used as backup storage. */ Open_tables_state() : state_flags(0U) { } - Open_tables_state(ulong version_arg); + Open_tables_state(THD *thd, ulong version_arg); void set_open_tables_state(Open_tables_state *state) { *this= *state; } - void reset_open_tables_state() + void reset_open_tables_state(THD *thd) { open_tables= temporary_tables= handler_tables= derived_tables= 0; extra_lock= lock= locked_tables= 0; prelocked_mode= NON_PRELOCKED; state_flags= 0U; m_reprepare_observer= NULL; + mdl_context_init(&mdl_context, thd); + mdl_context_init(&handler_mdl_context, thd); } }; @@ -1809,6 +1815,9 @@ public: struct st_debug_sync_control *debug_sync_control; #endif /* defined(ENABLED_DEBUG_SYNC) */ + MEM_ROOT *mdl_el_root; + MEM_ROOT locked_tables_root; + THD(); ~THD(); diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 17626f05aa1..44909880da0 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -904,10 +904,6 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) } else { - pthread_mutex_lock(&LOCK_open); - remove_db_from_cache(db); - pthread_mutex_unlock(&LOCK_open); - Drop_table_error_handler err_handler(thd->get_internal_handler()); thd->push_internal_handler(&err_handler); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index d8aa27c9695..fb48f32660b 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1089,12 +1089,13 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) TABLE *table; bool error; uint path_length; + MDL_LOCK *mdl_lock= 0; DBUG_ENTER("mysql_truncate"); bzero((char*) &create_info,sizeof(create_info)); /* Remove tables from the HANDLER's hash. */ - mysql_ha_rm_tables(thd, table_list, FALSE); + mysql_ha_rm_tables(thd, table_list); /* If it is a temporary table, close and regenerate it */ if (!dont_send_ok && (table= find_temporary_table(thd, table_list))) @@ -1158,8 +1159,20 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) goto trunc_by_del; - if (lock_and_wait_for_table_name(thd, table_list)) + /* + FIXME: Actually code of TRUNCATE breaks meta-data locking protocol since + tries to get table enging and therefore accesses table in some way + without holding any kind of meta-data lock. + */ + mdl_lock= mdl_alloc_lock(0, table_list->db, table_list->table_name, + thd->mem_root); + mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, mdl_lock); + if (mdl_acquire_exclusive_locks(&thd->mdl_context)) DBUG_RETURN(TRUE); + pthread_mutex_lock(&LOCK_open); + expel_table_from_cache(0, table_list->db, table_list->table_name); + pthread_mutex_unlock(&LOCK_open); } // Remove the .frm extension AIX 5.2 64-bit compiler bug (BUG#16155): this @@ -1184,15 +1197,13 @@ end: write_bin_log(thd, TRUE, thd->query(), thd->query_length()); my_ok(thd); // This should return record count } - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - pthread_mutex_unlock(&LOCK_open); + if (mdl_lock) + mdl_release_lock(&thd->mdl_context, mdl_lock); } else if (error) { - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - pthread_mutex_unlock(&LOCK_open); + if (mdl_lock) + mdl_release_lock(&thd->mdl_context, mdl_lock); } DBUG_RETURN(error); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index da5ee93fcb9..c8a66073a67 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -116,17 +116,16 @@ static void mysql_ha_hash_free(TABLE_LIST *tables) @param thd Thread identifier. @param tables A list of tables with the first entry to close. - @param is_locked If LOCK_open is locked. @note Though this function takes a list of tables, only the first list entry will be closed. @note Broadcasts refresh if it closed a table with old version. */ -static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables, - bool is_locked) +static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) { TABLE **table_ptr; + MDL_LOCK *mdl_lock; /* Though we could take the table pointer from hash_tables->table, @@ -142,15 +141,15 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables, if (*table_ptr) { (*table_ptr)->file->ha_index_or_rnd_end(); - if (! is_locked) - pthread_mutex_lock(&LOCK_open); + mdl_lock= (*table_ptr)->mdl_lock; + pthread_mutex_lock(&LOCK_open); if (close_thread_table(thd, table_ptr)) { /* Tell threads waiting for refresh that something has happened */ broadcast_refresh(); } - if (! is_locked) - pthread_mutex_unlock(&LOCK_open); + pthread_mutex_unlock(&LOCK_open); + mdl_release_lock(&thd->handler_mdl_context, mdl_lock); } else if (tables->table) { @@ -190,10 +189,12 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables, bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) { TABLE_LIST *hash_tables = NULL; - char *db, *name, *alias; + MDL_LOCK *mdl_lock; + char *db, *name, *alias, *mdlkey; uint dblen, namelen, aliaslen, counter; int error; TABLE *backup_open_tables; + MDL_CONTEXT backup_mdl_context; DBUG_ENTER("mysql_ha_open"); DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", tables->db, tables->table_name, tables->alias, @@ -216,7 +217,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) HANDLER_TABLES_HASH_SIZE, 0, 0, (my_hash_get_key) mysql_ha_hash_get_key, (my_hash_free_key) mysql_ha_hash_free, 0)) - goto err; + { + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } } else if (! reopen) /* Otherwise we have 'tables' already. */ { @@ -224,11 +228,52 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) strlen(tables->alias) + 1)) { DBUG_PRINT("info",("duplicate '%s'", tables->alias)); + DBUG_PRINT("exit",("ERROR")); my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias); - goto err; + DBUG_RETURN(TRUE); } } + if (! reopen) + { + /* copy the TABLE_LIST struct */ + dblen= strlen(tables->db) + 1; + namelen= strlen(tables->table_name) + 1; + aliaslen= strlen(tables->alias) + 1; + if (!(my_multi_malloc(MYF(MY_WME), + &hash_tables, (uint) sizeof(*hash_tables), + &db, (uint) dblen, + &name, (uint) namelen, + &alias, (uint) aliaslen, + &mdl_lock, sizeof(MDL_LOCK), + &mdlkey, MAX_DBKEY_LENGTH, + NullS))) + { + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } + /* structure copy */ + *hash_tables= *tables; + hash_tables->db= db; + hash_tables->table_name= name; + hash_tables->alias= alias; + memcpy(hash_tables->db, tables->db, dblen); + memcpy(hash_tables->table_name, tables->table_name, namelen); + memcpy(hash_tables->alias, tables->alias, aliaslen); + mdl_init_lock(mdl_lock, mdlkey, 0, db, name); + hash_tables->mdl_lock= mdl_lock; + + /* add to hash */ + if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) + { + my_free((char*) hash_tables, MYF(0)); + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } + } + else + hash_tables= tables; + /* Save and reset the open_tables list so that open_tables() won't be able to access (or know about) the previous list. And on return @@ -243,21 +288,22 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) */ backup_open_tables= thd->open_tables; thd->open_tables= NULL; + mdl_context_backup_and_reset(&thd->mdl_context, &backup_mdl_context); /* - open_tables() will set 'tables->table' if successful. + open_tables() will set 'hash_tables->table' if successful. It must be NULL for a real open when calling open_tables(). */ - DBUG_ASSERT(! tables->table); + DBUG_ASSERT(! hash_tables->table); /* for now HANDLER can be used only for real TABLES */ - tables->required_type= FRMTYPE_TABLE; + hash_tables->required_type= FRMTYPE_TABLE; /* We use open_tables() here, rather than, say, open_ltable() or open_table() because we would like to be able to open a temporary table. */ - error= open_tables(thd, &tables, &counter, 0); + error= open_tables(thd, &hash_tables, &counter, 0); if (thd->open_tables) { if (thd->open_tables->next) @@ -281,52 +327,26 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) thd->handler_tables= thd->open_tables; } } + mdl_context_merge(&thd->handler_mdl_context, &thd->mdl_context); - /* Restore the state. */ thd->open_tables= backup_open_tables; + mdl_context_restore(&thd->mdl_context, &backup_mdl_context); if (error) goto err; /* There can be only one table in '*tables'. */ - if (! (tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) + if (! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) { my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); goto err; } - if (! reopen) - { - /* copy the TABLE_LIST struct */ - dblen= strlen(tables->db) + 1; - namelen= strlen(tables->table_name) + 1; - aliaslen= strlen(tables->alias) + 1; - if (!(my_multi_malloc(MYF(MY_WME), - &hash_tables, (uint) sizeof(*hash_tables), - &db, (uint) dblen, - &name, (uint) namelen, - &alias, (uint) aliaslen, - NullS))) - goto err; - /* structure copy */ - *hash_tables= *tables; - hash_tables->db= db; - hash_tables->table_name= name; - hash_tables->alias= alias; - memcpy(hash_tables->db, tables->db, dblen); - memcpy(hash_tables->table_name, tables->table_name, namelen); - memcpy(hash_tables->alias, tables->alias, aliaslen); - - /* add to hash */ - if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) - goto err; - } - /* If it's a temp table, don't reset table->query_id as the table is being used by this handler. Otherwise, no meaning at all. */ - tables->table->open_by_handler= 1; + hash_tables->table->open_by_handler= 1; if (! reopen) my_ok(thd); @@ -334,10 +354,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) DBUG_RETURN(FALSE); err: - if (hash_tables) - my_free((char*) hash_tables, MYF(0)); - if (tables->table) - mysql_ha_close_table(thd, tables, FALSE); + if (hash_tables->table) + mysql_ha_close_table(thd, hash_tables); + if (!reopen) + my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); DBUG_PRINT("exit",("ERROR")); DBUG_RETURN(TRUE); } @@ -371,7 +391,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables) (uchar*) tables->alias, strlen(tables->alias) + 1))) { - mysql_ha_close_table(thd, hash_tables, FALSE); + mysql_ha_close_table(thd, hash_tables); my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); } else @@ -507,7 +527,7 @@ retry: if (need_reopen) { - mysql_ha_close_table(thd, hash_tables, FALSE); + mysql_ha_close_table(thd, hash_tables); /* The lock might have been aborted, we need to manually reset thd->some_tables_deleted because handler's tables are closed @@ -734,12 +754,11 @@ static TABLE_LIST *mysql_ha_find(THD *thd, TABLE_LIST *tables) @param thd Thread identifier. @param tables The list of tables to remove. - @param is_locked If LOCK_open is locked. @note Broadcasts refresh if it closed a table with old version. */ -void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked) +void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables) { TABLE_LIST *hash_tables, *next; DBUG_ENTER("mysql_ha_rm_tables"); @@ -752,7 +771,7 @@ void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked) { next= hash_tables->next_local; if (hash_tables->table) - mysql_ha_close_table(thd, hash_tables, is_locked); + mysql_ha_close_table(thd, hash_tables); my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); hash_tables= next; } @@ -775,13 +794,16 @@ void mysql_ha_flush(THD *thd) TABLE_LIST *hash_tables; DBUG_ENTER("mysql_ha_flush"); - safe_mutex_assert_owner(&LOCK_open); + safe_mutex_assert_not_owner(&LOCK_open); for (uint i= 0; i < thd->handler_tables_hash.records; i++) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); - if (hash_tables->table && hash_tables->table->needs_reopen_or_name_lock()) - mysql_ha_close_table(thd, hash_tables, TRUE); + if (hash_tables->table && + (hash_tables->table->mdl_lock && + mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock) || + hash_tables->table->needs_reopen_or_name_lock())) + mysql_ha_close_table(thd, hash_tables); } DBUG_VOID_RETURN; @@ -805,7 +827,7 @@ void mysql_ha_cleanup(THD *thd) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); if (hash_tables->table) - mysql_ha_close_table(thd, hash_tables, FALSE); + mysql_ha_close_table(thd, hash_tables); } my_hash_free(&thd->handler_tables_hash); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index efdc8caa3e5..eb4eee9abb5 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -612,7 +612,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, upgrade the lock here instead? */ if (table_list->lock_type == TL_WRITE_DELAYED && thd->locked_tables && - find_locked_table(thd, table_list->db, table_list->table_name)) + find_locked_table(thd->open_tables, table_list->db, + table_list->table_name)) { my_error(ER_DELAYED_INSERT_TABLE_LOCKED, MYF(0), table_list->table_name); @@ -2351,6 +2352,8 @@ pthread_handler_t handle_delayed_insert(void *arg) thd->lex->set_stmt_unsafe(); thd->set_current_stmt_binlog_row_based_if_mixed(); + alloc_mdl_locks(&di->table_list, thd->mem_root); + /* Open table */ if (!(di->table= open_n_lock_single_table(thd, &di->table_list, TL_WRITE_DELAYED))) @@ -3495,7 +3498,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, create_table, FALSE)) + if (reopen_name_locked_table(thd, create_table)) { quick_rm_table(create_info->db_type, create_table->db, table_case_name(create_info, create_table->table_name), @@ -3507,7 +3510,8 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, } else { - if (!(table= open_table(thd, create_table, thd->mem_root, (bool*) 0, + if (!(table= open_table(thd, create_table, thd->mem_root, + (enum_open_table_action*) 0, MYSQL_OPEN_TEMPORARY_ONLY)) && !create_info->table_existed) { @@ -3621,8 +3625,7 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) DBUG_EXECUTE_IF("sleep_create_select_before_check_if_exists", my_sleep(6000000);); - if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) && - (create_table->table && create_table->table->db_stat)) + if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) && create_table->table) { /* Table already exists and was open at open_and_lock_tables() stage. */ if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 1271c3112ff..2b2c736fd9e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -47,6 +47,7 @@ "FUNCTION" : "PROCEDURE") static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables); +static void adjust_mdl_locks_upgradability(TABLE_LIST *tables); const char *any_db="*any*"; // Special symbol for check_access @@ -143,17 +144,6 @@ static bool xa_trans_rollback(THD *thd) return status; } -static void unlock_locked_tables(THD *thd) -{ - if (thd->locked_tables) - { - thd->lock=thd->locked_tables; - thd->locked_tables=0; // Will be automatically closed - close_thread_tables(thd); // Free tables - } -} - - bool end_active_trans(THD *thd) { int error=0; @@ -194,12 +184,9 @@ bool begin_trans(THD *thd) my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); return 1; } - if (thd->locked_tables) - { - thd->lock=thd->locked_tables; - thd->locked_tables=0; // Will be automatically closed - close_thread_tables(thd); // Free tables - } + + unlock_locked_tables(thd); + if (end_active_trans(thd)) error= -1; else @@ -1342,6 +1329,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, select_lex.table_list.link_in_list((uchar*) &table_list, (uchar**) &table_list.next_local); thd->lex->add_to_query_tables(&table_list); + alloc_mdl_locks(&table_list, thd->mem_root); /* switch on VIEW optimisation: do not fill temporary tables */ thd->lex->sql_command= SQLCOM_SHOW_FIELDS; @@ -2643,7 +2631,7 @@ case SQLCOM_PREPARE: if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) { lex->link_first_table_back(create_table, link_to_local); - create_table->create= TRUE; + create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE; } if (!(res= open_and_lock_tables(thd, lex->query_tables))) @@ -3618,6 +3606,9 @@ end_with_restore_list: goto error; thd->in_lock_tables=1; thd->options|= OPTION_TABLE_LOCK; + alloc_mdl_locks(all_tables, &thd->locked_tables_root); + thd->mdl_el_root= &thd->locked_tables_root; + adjust_mdl_locks_upgradability(all_tables); if (!(res= simple_open_n_lock_tables(thd, all_tables))) { @@ -3641,6 +3632,7 @@ end_with_restore_list: thd->options&= ~(OPTION_TABLE_LOCK); } thd->in_lock_tables=0; + thd->mdl_el_root= 0; break; case SQLCOM_CREATE_DB: { @@ -6542,6 +6534,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); + ptr->mdl_lock= mdl_alloc_lock(0 , ptr->db, ptr->table_name, + thd->mdl_el_root ? thd->mdl_el_root : + thd->mem_root); DBUG_RETURN(ptr); } @@ -7079,23 +7074,15 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, if ((options & REFRESH_READ_LOCK) && thd) { /* - We must not try to aspire a global read lock if we have a write - locked table. This would lead to a deadlock when trying to - reopen (and re-lock) the table after the flush. + On the first hand we need write lock on the tables to be flushed, + on the other hand we must not try to aspire a global read lock + if we have a write locked table as this would lead to a deadlock + when trying to reopen (and re-lock) the table after the flush. */ if (thd->locked_tables) { - THR_LOCK_DATA **lock_p= thd->locked_tables->locks; - THR_LOCK_DATA **end_p= lock_p + thd->locked_tables->lock_count; - - for (; lock_p < end_p; lock_p++) - { - if ((*lock_p)->type >= TL_WRITE_ALLOW_WRITE) - { - my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); - return 1; - } - } + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + return 1; } /* Writing to the binlog could cause deadlocks, as we don't log @@ -7105,7 +7092,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, if (lock_global_read_lock(thd)) return 1; // Killed if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? - FALSE : TRUE, TRUE)) + FALSE : TRUE)) result= 1; if (make_global_read_lock_block_commit(thd)) // Killed @@ -7117,8 +7104,35 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, } else { + if (thd && thd->locked_tables) + { + /* + If we are under LOCK TABLES we should have a write + lock on tables which we are going to flush. + */ + if (tables) + { + for (TABLE_LIST *t= tables; t; t= t->next_local) + if (!find_write_locked_table(thd->open_tables, t->db, + t->table_name)) + return 1; + } + else + { + for (TABLE *tab= thd->open_tables; tab; tab= tab->next) + { + if (tab->reginfo.lock_type < TL_WRITE_ALLOW_WRITE) + { + my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), + tab->s->table_name.str); + return 1; + } + } + } + } + if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? - FALSE : TRUE, FALSE)) + FALSE : TRUE)) result= 1; } my_dbopt_cleanup(); @@ -8092,6 +8106,42 @@ bool parse_sql(THD *thd, return ret_value; } + +/** + Auxiliary function which marks metadata locks for all tables + on which we plan to take write lock as upgradable. +*/ + +static void adjust_mdl_locks_upgradability(TABLE_LIST *tables) +{ + TABLE_LIST *tab, *otab; + + for (tab= tables; tab; tab= tab->next_global) + { + if (tab->lock_type >= TL_WRITE_ALLOW_WRITE) + tab->mdl_upgradable= TRUE; + else + { + /* + TODO: To get rid of this loop we need to change our code to do + metadata lock upgrade only for those instances of tables + which are write locked instead of doing such upgrade for + all instances of tables. + */ + for (otab= tables; otab; otab= otab->next_global) + if (otab->lock_type >= TL_WRITE_ALLOW_WRITE && + otab->db_length == tab->db_length && + otab->table_name_length == tab->table_name_length && + !strcmp(otab->db, tab->db) && + !strcmp(otab->table_name, tab->table_name)) + { + tab->mdl_upgradable= TRUE; + break; + } + } + } +} + /** @} (end of group Runtime_Environment) */ diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 868dfc3e968..48f33bf3295 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -6225,7 +6225,7 @@ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) */ pthread_mutex_lock(&LOCK_open); lpt->thd->in_lock_tables= 1; - err= reopen_tables(lpt->thd, 1, 1); + err= reopen_tables(lpt->thd, 1); lpt->thd->in_lock_tables= 0; if (err) { @@ -6564,7 +6564,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, write_log_drop_partition(lpt) || ERROR_INJECT_CRASH("crash_drop_partition_3") || (not_completed= FALSE) || - abort_and_upgrade_lock(lpt) || /* Always returns 0 */ + abort_and_upgrade_lock(lpt) || ERROR_INJECT_CRASH("crash_drop_partition_4") || alter_close_tables(lpt) || ERROR_INJECT_CRASH("crash_drop_partition_5") || @@ -6631,7 +6631,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, ERROR_INJECT_CRASH("crash_add_partition_2") || mysql_change_partitions(lpt) || ERROR_INJECT_CRASH("crash_add_partition_3") || - abort_and_upgrade_lock(lpt) || /* Always returns 0 */ + abort_and_upgrade_lock(lpt) || ERROR_INJECT_CRASH("crash_add_partition_4") || alter_close_tables(lpt) || ERROR_INJECT_CRASH("crash_add_partition_5") || @@ -6647,7 +6647,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, ERROR_INJECT_CRASH("crash_add_partition_8") || (write_log_completed(lpt, FALSE), FALSE) || ERROR_INJECT_CRASH("crash_add_partition_9") || - (alter_partition_lock_handling(lpt), FALSE)) + (alter_partition_lock_handling(lpt), FALSE)) { handle_alter_part_error(lpt, not_completed, FALSE, frm_install); goto err; @@ -6721,7 +6721,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, write_log_final_change_partition(lpt) || ERROR_INJECT_CRASH("crash_change_partition_4") || (not_completed= FALSE) || - abort_and_upgrade_lock(lpt) || /* Always returns 0 */ + abort_and_upgrade_lock(lpt) || ERROR_INJECT_CRASH("crash_change_partition_5") || alter_close_tables(lpt) || ERROR_INJECT_CRASH("crash_change_partition_6") || diff --git a/sql/sql_plist.h b/sql/sql_plist.h new file mode 100644 index 00000000000..af2ed227ea1 --- /dev/null +++ b/sql/sql_plist.h @@ -0,0 +1,125 @@ +#ifndef SQL_PLIST_H +#define SQL_PLIST_H +/* Copyright (C) 2008 MySQL AB + + 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 Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#include + +template class I_P_List_iterator; + + +/** + Intrusive parameterized list. + + Unlike I_List does not require its elements to be descendant of ilink + class and therefore allows them to participate in several such lists + simultaneously. + + Unlike List is doubly-linked list and thus supports efficient deletion + of element without iterator. + + @param T Type of elements which will belong to list. + @param B Class which via its methods specifies which members + of T should be used for participating in this list. + Here is typical layout of such class: + + struct B + { + static inline T **next_ptr(T *el) + { + return &el->next; + } + static inline T ***prev_ptr(T *el) + { + return &el->prev; + } + }; +*/ + +template +class I_P_List +{ + T *first; + + /* + Do not prohibit copying of I_P_List object to simplify their usage in + backup/restore scenarios. Note that performing any operations on such + is a bad idea. + */ +public: + I_P_List() : first(NULL) { }; + inline void empty() { first= NULL; } + inline bool is_empty() { return (first == NULL); } + inline void push_front(T* a) + { + *B::next_ptr(a)= first; + if (first) + *B::prev_ptr(first)= B::next_ptr(a); + first= a; + *B::prev_ptr(a)= &first; + } + inline void remove(T *a) + { + T *next= *B::next_ptr(a); + if (next) + *B::prev_ptr(next)= *B::prev_ptr(a); + **B::prev_ptr(a)= next; + } + inline T* head() { return first; } + void swap(I_P_List &rhs) + { + swap_variables(T *, first, rhs.first); + if (first) + *B::prev_ptr(first)= &first; + if (rhs.first) + *B::prev_ptr(rhs.first)= &rhs.first; + } +#ifndef _lint + friend class I_P_List_iterator; +#endif +}; + + +/** + Iterator for I_P_List. +*/ + +template +class I_P_List_iterator +{ + I_P_List *list; + T *current; +public: + I_P_List_iterator(I_P_List &a) : list(&a), current(a.first) {} + inline void init(I_P_List &a) + { + list= &a; + current= a.first; + } + inline T* operator++(int) + { + T *result= current; + if (result) + current= *B::next_ptr(current); + return result; + } + inline void rewind() + { + current= list->first; + } +}; + +#endif diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 936c9ae8866..e9f4152f861 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1366,6 +1366,7 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) tables.alias= tables.table_name= (char*)"plugin"; tables.lock_type= TL_READ; tables.db= new_thd->db; + alloc_mdl_locks(&tables, tmp_root); #ifdef EMBEDDED_LIBRARY /* @@ -1659,6 +1660,8 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, const LEX_STRING *dl if (check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE)) DBUG_RETURN(TRUE); + alloc_mdl_locks(&tables, thd->mem_root); + /* need to open before acquiring LOCK_plugin or it will deadlock */ if (! (table = open_ltable(thd, &tables, TL_WRITE, 0))) DBUG_RETURN(TRUE); @@ -1732,6 +1735,7 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name) bzero(&tables, sizeof(tables)); tables.db= (char *)"mysql"; tables.table_name= tables.alias= (char *)"plugin"; + alloc_mdl_locks(&tables, thd->mem_root); /* need to open before acquiring LOCK_plugin or it will deadlock */ if (! (table= open_ltable(thd, &tables, TL_WRITE, 0))) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index d624c22f43a..582e18a3abf 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1673,7 +1673,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt) if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) { lex->link_first_table_back(create_table, link_to_local); - create_table->create= TRUE; + create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE; } if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0)) diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index dac96f2e9c4..857cccde50f 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -51,7 +51,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) DBUG_RETURN(1); } - mysql_ha_rm_tables(thd, table_list, FALSE); + mysql_ha_rm_tables(thd, table_list); if (wait_if_global_read_lock(thd,0,1)) DBUG_RETURN(1); @@ -133,12 +133,13 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) } } - pthread_mutex_lock(&LOCK_open); - if (lock_table_names_exclusively(thd, table_list)) - { - pthread_mutex_unlock(&LOCK_open); + if (lock_table_names(thd, table_list)) goto err; - } + + pthread_mutex_lock(&LOCK_open); + + for (ren_table= table_list; ren_table; ren_table= ren_table->next_local) + expel_table_from_cache(0, ren_table->db, ren_table->table_name); error=0; if ((ren_table=rename_tables(thd,table_list,0))) @@ -184,9 +185,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) if (!error) query_cache_invalidate3(thd, table_list, 0); - pthread_mutex_lock(&LOCK_open); - unlock_table_names(thd, table_list, (TABLE_LIST*) 0); - pthread_mutex_unlock(&LOCK_open); + unlock_table_names(thd); err: start_waiting_global_read_lock(thd); diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index e5fe06ce39b..5251a50cab9 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -224,12 +224,7 @@ bool servers_reload(THD *thd) bool return_val= TRUE; DBUG_ENTER("servers_reload"); - if (thd->locked_tables) - { // Can't have locked tables here - thd->lock=thd->locked_tables; - thd->locked_tables=0; - close_thread_tables(thd); - } + unlock_locked_tables(thd); // Can't have locked tables here DBUG_PRINT("info", ("locking servers_cache")); rw_wrlock(&THR_LOCK_servers); @@ -238,6 +233,7 @@ bool servers_reload(THD *thd) tables[0].alias= tables[0].table_name= (char*) "servers"; tables[0].db= (char*) "mysql"; tables[0].lock_type= TL_READ; + alloc_mdl_locks(tables, thd->mem_root); if (simple_open_n_lock_tables(thd, tables)) { @@ -368,6 +364,7 @@ insert_server(THD *thd, FOREIGN_SERVER *server) bzero((char*) &tables, sizeof(tables)); tables.db= (char*) "mysql"; tables.alias= tables.table_name= (char*) "servers"; + alloc_mdl_locks(&tables, thd->mem_root); /* need to open before acquiring THR_LOCK_plugin or it will deadlock */ if (! (table= open_ltable(thd, &tables, TL_WRITE, 0))) @@ -586,6 +583,7 @@ int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options) bzero((char*) &tables, sizeof(tables)); tables.db= (char*) "mysql"; tables.alias= tables.table_name= (char*) "servers"; + alloc_mdl_locks(&tables, thd->mem_root); rw_wrlock(&THR_LOCK_servers); @@ -710,6 +708,7 @@ int update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered) bzero((char*) &tables, sizeof(tables)); tables.db= (char*)"mysql"; tables.alias= tables.table_name= (char*)"servers"; + alloc_mdl_locks(&tables, thd->mem_root); if (!(table= open_ltable(thd, &tables, TL_WRITE, 0))) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index babadc34842..c83a6981166 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2963,7 +2963,7 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, table, res, db_name, table_name)); thd->temporary_tables= 0; - close_tables_for_reopen(thd, &show_table_list); + close_tables_for_reopen(thd, &show_table_list, FALSE); DBUG_RETURN(error); } @@ -3106,6 +3106,9 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, char key[MAX_DBKEY_LENGTH]; uint key_length; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; + MDL_LOCK mdl_lock; + char mdlkey[MAX_DBKEY_LENGTH]; + bool retry; bzero((char*) &table_list, sizeof(TABLE_LIST)); bzero((char*) &tbl, sizeof(TABLE)); @@ -3130,6 +3133,34 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, table_list.db= db_name->str; } + mdl_init_lock(&mdl_lock, mdlkey, 0, db_name->str, table_name->str); + table_list.mdl_lock= &mdl_lock; + mdl_add_lock(&thd->mdl_context, &mdl_lock); + mdl_set_lock_priority(&mdl_lock, MDL_HIGH_PRIO); + + /* + TODO: investigate if in this particular situation we can get by + simply obtaining internal lock of data-dictionary (ATM it + is LOCK_open) instead of obtaning full-blown metadata lock. + */ + while (1) + { + if (mdl_acquire_shared_lock(&mdl_lock, &retry)) + { + if (!retry || mdl_wait_for_locks(&thd->mdl_context)) + { + /* + Some error occured or we have been killed while waiting + for conflicting locks to go away, let the caller to handle + the situation. + */ + return 1; + } + continue; + } + break; + } + key_length= create_table_def_key(thd, key, &table_list, 0); pthread_mutex_lock(&LOCK_open); share= get_table_share(thd, &table_list, key, @@ -3137,7 +3168,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, if (!share) { res= 0; - goto err; + goto err_unlock; } if (share->is_view) @@ -3146,7 +3177,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, { /* skip view processing */ res= 0; - goto err1; + goto err_share; } else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL) { @@ -3155,7 +3186,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, open_normal_and_derived_tables() */ res= 1; - goto err1; + goto err_share; } } @@ -3171,14 +3202,17 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, res= schema_table->process_table(thd, &table_list, table, res, db_name, table_name); closefrm(&tbl, true); - goto err; + goto err_unlock; } -err1: +err_share: release_table_share(share, RELEASE_NORMAL); -err: +err_unlock: pthread_mutex_unlock(&LOCK_open); + +err: + mdl_release_lock(&thd->mdl_context, &mdl_lock); thd->clear_error(); return res; } @@ -3425,7 +3459,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) res= schema_table->process_table(thd, show_table_list, table, res, &orig_db_name, &tmp_lex_string); - close_tables_for_reopen(thd, &show_table_list); + close_tables_for_reopen(thd, &show_table_list, FALSE); } DBUG_ASSERT(!lex->query_tables_own_last); if (res) @@ -7273,6 +7307,8 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name) uint num_tables; /* NOTE: unused, only to pass to open_tables(). */ + alloc_mdl_locks(lst, thd->mem_root); + if (open_tables(thd, &lst, &num_tables, 0)) { my_error(ER_TRG_CANT_OPEN_TABLE, MYF(0), diff --git a/sql/sql_table.cc b/sql/sql_table.cc index dc0c876e882..f91bae8b76c 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -53,6 +53,7 @@ static bool mysql_prepare_alter_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, Alter_info *alter_info); +static bool close_cached_table(THD *thd, TABLE *table); #ifndef DBUG_OFF @@ -1791,11 +1792,6 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, DBUG_RETURN(TRUE); } - /* - Acquire LOCK_open after wait_if_global_read_lock(). If we would hold - LOCK_open during wait_if_global_read_lock(), other threads could not - close their tables. This would make a pretty deadlock. - */ thd->push_internal_handler(&err_handler); error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0); thd->pop_internal_handler(); @@ -1867,16 +1863,14 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, built_query.append("DROP TABLE "); } - mysql_ha_rm_tables(thd, tables, FALSE); - - pthread_mutex_lock(&LOCK_open); + mysql_ha_rm_tables(thd, tables); /* If we have the table in the definition cache, we don't have to check the .frm file to find if the table is a normal table (not view) and what engine to use. */ - + pthread_mutex_lock(&LOCK_open); for (table= tables; table; table= table->next_local) { TABLE_SHARE *share; @@ -1889,16 +1883,32 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, check_if_log_table(table->db_length, table->db, table->table_name_length, table->table_name, 1)) { - my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP"); pthread_mutex_unlock(&LOCK_open); + my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP"); DBUG_RETURN(1); } } + pthread_mutex_unlock(&LOCK_open); - if (!drop_temporary && lock_table_names_exclusively(thd, tables)) + if (!drop_temporary) { - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(1); + if (!thd->locked_tables) + { + if (lock_table_names(thd, tables)) + DBUG_RETURN(1); + pthread_mutex_lock(&LOCK_open); + for (table= tables; table; table= table->next_local) + expel_table_from_cache(0, table->db, table->table_name); + pthread_mutex_unlock(&LOCK_open); + } + else if (thd->locked_tables) + { + for (table= tables; table; table= table->next_local) + if (!find_temporary_table(thd, table->db, table->table_name) && + !find_write_locked_table(thd->open_tables, table->db, + table->table_name)) + DBUG_RETURN(1); + } } for (table= tables; table; table= table->next_local) @@ -1973,17 +1983,21 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table_type= table->db_type; if (!drop_temporary) { - TABLE *locked_table; - abort_locked_tables(thd, db, table->table_name); - remove_table_from_cache(thd, db, table->table_name, - RTFC_WAIT_OTHER_THREAD_FLAG | - RTFC_CHECK_KILLED_FLAG); - /* - If the table was used in lock tables, remember it so that - unlock_table_names can free it - */ - if ((locked_table= drop_locked_tables(thd, db, table->table_name))) - table->table= locked_table; + if (thd->locked_tables) + { + TABLE *tab= find_locked_table(thd->open_tables, db, table->table_name); + if (close_cached_table(thd, tab)) + { + error= -1; + goto err_with_placeholders; + } + /* + Leave LOCK TABLES mode if we managed to drop all tables + which were locked. + */ + if (thd->locked_tables->table_count == 0) + unlock_locked_tables(thd); + } if (thd->killed) { @@ -1997,6 +2011,11 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->internal_tmp_table ? FN_IS_TMP : 0); } + /* + TODO: Investigate what should be done to remove this lock completely. + Is exclusive meta-data lock enough ? + */ + pthread_mutex_lock(&LOCK_open); if (drop_temporary || ((table_type == NULL && access(path, F_OK) && @@ -2049,6 +2068,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, error|= new_error; } } + pthread_mutex_unlock(&LOCK_open); if (error) { if (wrong_tables.length()) @@ -2064,11 +2084,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->table_name);); } - /* - It's safe to unlock LOCK_open: we have an exclusive lock - on the table name. - */ - pthread_mutex_unlock(&LOCK_open); thd->thread_specific_used|= tmp_table_deleted; error= 0; if (wrong_tables.length()) @@ -2151,10 +2166,19 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ } } - pthread_mutex_lock(&LOCK_open); err_with_placeholders: - unlock_table_names(thd, tables, (TABLE_LIST*) 0); - pthread_mutex_unlock(&LOCK_open); + if (!drop_temporary) + { + /* + Under LOCK TABLES we should release meta-data locks on the tables + which were dropped. Otherwise we can rely on close_thread_tables() + doing this. Unfortunately in this case we are likely to get more + false positives in lock_table_name_if_not_cached() function. So + it makes sense to remove exclusive meta-data locks in all cases. + */ + mdl_release_exclusive_locks(&thd->mdl_context); + } + DBUG_RETURN(error); } @@ -4005,6 +4029,34 @@ warn: } +/** + Auxiliary function which obtains exclusive meta-data lock on the + table if there are no shared or exclusive on it already. + + See mdl_try_acquire_exclusive_lock() function for more info. + + TODO: This function is here mostly to simplify current patch + and probably should be removed. + TODO: Investigate if it is kosher to leave lock request in the + context in the case when we fail to obtain the lock. +*/ + +static bool lock_table_name_if_not_cached(THD *thd, const char *db, + const char *table_name, + MDL_LOCK **lock) +{ + if (!(*lock= mdl_alloc_lock(0, db, table_name, thd->mem_root))) + return TRUE; + mdl_set_lock_type(*lock, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, *lock); + if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock)) + { + *lock= 0; + } + return FALSE; +} + + /* Database and name-locking aware wrapper for mysql_create_table_no_lock(), */ @@ -4015,7 +4067,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, bool internal_tmp_table, uint select_field_count) { - TABLE *name_lock= 0; + MDL_LOCK *target_lock= 0; bool result; DBUG_ENTER("mysql_create_table"); @@ -4038,12 +4090,12 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock)) + if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock)) { result= TRUE; goto unlock; } - if (!name_lock) + if (!target_lock) { if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) { @@ -4069,12 +4121,8 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, select_field_count); unlock: - if (name_lock) - { - pthread_mutex_lock(&LOCK_open); - unlink_open_table(thd, name_lock, FALSE); - pthread_mutex_unlock(&LOCK_open); - } + if (target_lock) + mdl_release_exclusive_locks(&thd->mdl_context); pthread_mutex_lock(&LOCK_lock_db); if (!--creating_table && creating_database) pthread_cond_signal(&COND_refresh); @@ -4212,80 +4260,83 @@ mysql_rename_table(handlerton *base, const char *old_db, } -/* - Force all other threads to stop using the table +/** + Force all other threads to stop using the table by upgrading + metadata lock on it and remove unused TABLE instances from cache. - SYNOPSIS - wait_while_table_is_used() - thd Thread handler - table Table to remove from cache - function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted - HA_EXTRA_FORCE_REOPEN if table is not be used - HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed - NOTES - When returning, the table will be unusable for other threads until - the table is closed. + @param thd Thread handler + @param table Table to remove from cache + @param function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted + HA_EXTRA_FORCE_REOPEN if table is not be used + HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed - PREREQUISITES - Lock on LOCK_open - Win32 clients must also have a WRITE LOCK on the table ! + @note When returning, the table will be unusable for other threads + until metadata lock is downgraded. + + @retval FALSE Success. + @retval TRUE Failure (e.g. because thread was killed). */ -void wait_while_table_is_used(THD *thd, TABLE *table, +bool wait_while_table_is_used(THD *thd, TABLE *table, enum ha_extra_function function) { + enum thr_lock_type old_lock_type; + DBUG_ENTER("wait_while_table_is_used"); DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", table->s->table_name.str, (ulong) table->s, table->db_stat, table->s->version)); - safe_mutex_assert_owner(&LOCK_open); - (void) table->file->extra(function); - /* Mark all tables that are in use as 'old' */ + + old_lock_type= table->reginfo.lock_type; mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ - /* Wait until all there are no other threads that has this table open */ - remove_table_from_cache(thd, table->s->db.str, - table->s->table_name.str, - RTFC_WAIT_OTHER_THREAD_FLAG); - DBUG_VOID_RETURN; + if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, 0, + table->s->db.str, + table->s->table_name.str)) + { + mysql_lock_downgrade_write(thd, table, old_lock_type); + DBUG_RETURN(TRUE); + } + + pthread_mutex_lock(&LOCK_open); + expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str); + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(FALSE); } -/* - Close a cached table - SYNOPSIS - close_cached_table() - thd Thread handler - table Table to remove from cache +/** + Upgrade metadata lock on the table and close all its instances. - NOTES - Function ends by signaling threads waiting for the table to try to - reopen the table. + @param thd Thread handler + @param table Table to remove from cache - PREREQUISITES - Lock on LOCK_open - Win32 clients must also have a WRITE LOCK on the table ! + @retval FALSE Success. + @retval TRUE Failure (e.g. because thread was killed). */ -void close_cached_table(THD *thd, TABLE *table) +static bool close_cached_table(THD *thd, TABLE *table) { DBUG_ENTER("close_cached_table"); - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); + /* FIXME: check if we pass proper parameters everywhere. */ + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + DBUG_RETURN(TRUE); + /* Close lock if this is not got with LOCK TABLES */ if (thd->lock) { mysql_unlock_tables(thd, thd->lock); thd->lock=0; // Start locked threads } + + pthread_mutex_lock(&LOCK_open); /* Close all copies of 'table'. This also frees all LOCK TABLES lock */ unlink_open_table(thd, table, TRUE); - - /* When lock on LOCK_open is freed other threads can continue */ - broadcast_refresh(); - DBUG_VOID_RETURN; + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(FALSE); } static int send_check_errmsg(THD *thd, TABLE_LIST* table, @@ -4308,6 +4359,7 @@ static int send_check_errmsg(THD *thd, TABLE_LIST* table, static int prepare_for_restore(THD* thd, TABLE_LIST* table, HA_CHECK_OPT *check_opt) { + MDL_LOCK *mdl_lock= 0; DBUG_ENTER("prepare_for_restore"); if (table->table) // do not overwrite existing tables on restore @@ -4331,22 +4383,25 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, build_table_filename(dst_path, sizeof(dst_path) - 1, db, table_name, reg_ext, 0); - if (lock_and_wait_for_table_name(thd,table)) - DBUG_RETURN(-1); + mdl_lock= mdl_alloc_lock(0, table->db, table->table_name, + thd->mem_root); + mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, mdl_lock); + if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + DBUG_RETURN(TRUE); + pthread_mutex_lock(&LOCK_open); + expel_table_from_cache(0, table->db, table->table_name); + pthread_mutex_unlock(&LOCK_open); if (my_copy(src_path, dst_path, MYF(MY_WME))) { - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, table); - pthread_mutex_unlock(&LOCK_open); + mdl_release_lock(&thd->mdl_context, mdl_lock); DBUG_RETURN(send_check_errmsg(thd, table, "restore", "Failed copying .frm file")); } if (mysql_truncate(thd, table, 1)) { - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, table); - pthread_mutex_unlock(&LOCK_open); + mdl_release_lock(&thd->mdl_context, mdl_lock); DBUG_RETURN(send_check_errmsg(thd, table, "restore", "Failed generating table from .frm file")); } @@ -4357,10 +4412,11 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, to finish the restore in the handler later on */ pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table, TRUE)) + if (reopen_name_locked_table(thd, table)) { - unlock_table_name(thd, table); pthread_mutex_unlock(&LOCK_open); + if (mdl_lock) + mdl_release_lock(&thd->mdl_context, mdl_lock); DBUG_RETURN(send_check_errmsg(thd, table, "restore", "Failed to open partially restored table")); } @@ -4380,6 +4436,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, char from[FN_REFLEN],tmp[FN_REFLEN+32]; const char **ext; MY_STAT stat_info; + MDL_LOCK *mdl_lock; DBUG_ENTER("prepare_for_repair"); if (!(check_opt->sql_flags & TT_USEFRM)) @@ -4391,6 +4448,17 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, uint key_length; key_length= create_table_def_key(thd, key, table_list, 0); + /* + TODO: Check that REPAIR's code also conforms to meta-data + locking protocol. Fix if it is not. + */ + mdl_lock= mdl_alloc_lock(0, table_list->db, table_list->table_name, + thd->mem_root); + mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, mdl_lock); + if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + DBUG_RETURN(0); + pthread_mutex_lock(&LOCK_open); if (!(share= (get_table_share(thd, table_list, key, key_length, 0, &error)))) @@ -4457,41 +4525,29 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, my_snprintf(tmp, sizeof(tmp), "%s-%lx_%lx", from, current_pid, thd->thread_id); - /* If we could open the table, close it */ if (table_list->table) { - pthread_mutex_lock(&LOCK_open); - close_cached_table(thd, table); - pthread_mutex_unlock(&LOCK_open); - } - if (lock_and_wait_for_table_name(thd,table_list)) - { - error= -1; - goto end; + /* If we could open the table, close it */ + if (close_cached_table(thd, table)) + goto end; + table_list->table= 0; } + // After this point we have X mdl lock in both cases + if (my_rename(from, tmp, MYF(MY_WME))) { - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - pthread_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed renaming data file"); goto end; } if (mysql_truncate(thd, table_list, 1)) { - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - pthread_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed generating table from .frm file"); goto end; } if (my_rename(tmp, from, MYF(MY_WME))) { - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - pthread_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed restoring .MYD file"); goto end; @@ -4502,9 +4558,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, to finish the repair in the handler later on. */ pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table_list, TRUE)) + if (reopen_name_locked_table(thd, table_list)) { - unlock_table_name(thd, table_list); pthread_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed to open partially repaired table"); @@ -4519,6 +4574,8 @@ end: closefrm(table, 1); // Free allocated memory pthread_mutex_unlock(&LOCK_open); } + if (error) + mdl_release_exclusive_locks(&thd->mdl_context); DBUG_RETURN(error); } @@ -4566,7 +4623,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); - mysql_ha_rm_tables(thd, tables, FALSE); + mysql_ha_rm_tables(thd, tables); for (table= tables; table; table= table->next_local) { @@ -5063,6 +5120,7 @@ bool mysql_restore_table(THD* thd, TABLE_LIST* table_list) bool mysql_repair_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) { DBUG_ENTER("mysql_repair_table"); + set_all_mdl_upgradable(tables); DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, "repair", TL_WRITE, 1, test(check_opt->sql_flags & TT_USEFRM), @@ -5250,7 +5308,7 @@ bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO *create_info) { - TABLE *name_lock= 0; + MDL_LOCK *target_lock= 0; char src_path[FN_REFLEN], dst_path[FN_REFLEN + 1]; uint dst_path_length; char *db= table->db; @@ -5307,9 +5365,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, } else { - if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock)) + if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock)) goto err; - if (!name_lock) + if (!target_lock) goto table_exists; dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1, db, table_name, reg_ext, 0); @@ -5453,9 +5511,8 @@ binlog: The table will be closed by unlink_open_table() at the end of this function. */ - table->table= name_lock; pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table, FALSE)) + if (reopen_name_locked_table(thd, table)) { pthread_mutex_unlock(&LOCK_open); goto err; @@ -5468,6 +5525,10 @@ binlog: DBUG_ASSERT(result == 0); // store_create_info() always return 0 write_bin_log(thd, TRUE, query.ptr(), query.length()); + + pthread_mutex_lock(&LOCK_open); + unlink_open_table(thd, table->table, FALSE); + pthread_mutex_unlock(&LOCK_open); } else // Case 1 write_bin_log(thd, TRUE, thd->query(), thd->query_length()); @@ -5482,12 +5543,8 @@ binlog: res= FALSE; err: - if (name_lock) - { - pthread_mutex_lock(&LOCK_open); - unlink_open_table(thd, name_lock, FALSE); - pthread_mutex_unlock(&LOCK_open); - } + if (target_lock) + mdl_release_exclusive_locks(&thd->mdl_context); DBUG_RETURN(res); } @@ -5570,7 +5627,7 @@ mysql_discard_or_import_tablespace(THD *thd, err: ha_autocommit_or_rollback(thd, error); thd->tablespace_op=FALSE; - + if (error == 0) { my_ok(thd); @@ -5578,7 +5635,7 @@ err: } table->file->print_error(error, MYF(0)); - + DBUG_RETURN(-1); } @@ -6424,7 +6481,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, Alter_info *alter_info, uint order_num, ORDER *order, bool ignore) { - TABLE *table, *new_table= 0, *name_lock= 0; + TABLE *table, *new_table= 0; + MDL_LOCK *target_lock= 0; int error= 0; char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1]; char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias; @@ -6507,7 +6565,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, build_table_filename(reg_path, sizeof(reg_path) - 1, db, table_name, reg_ext, 0); build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0); - mysql_ha_rm_tables(thd, table_list, FALSE); + mysql_ha_rm_tables(thd, table_list); /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */ if (alter_info->tablespace_op != NO_TABLESPACE_OP) @@ -6563,13 +6621,14 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if (wait_if_global_read_lock(thd,0,1)) DBUG_RETURN(TRUE); - pthread_mutex_lock(&LOCK_open); if (lock_table_names(thd, table_list)) { error= 1; goto view_err; } - + + pthread_mutex_lock(&LOCK_open); + if (!do_rename(thd, table_list, new_db, new_name, new_name, 1)) { if (mysql_bin_log.is_open()) @@ -6581,15 +6640,17 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, } my_ok(thd); } + pthread_mutex_unlock(&LOCK_open); - unlock_table_names(thd, table_list, (TABLE_LIST*) 0); + unlock_table_names(thd); view_err: - pthread_mutex_unlock(&LOCK_open); start_waiting_global_read_lock(thd); DBUG_RETURN(error); } + table_list->mdl_upgradable= TRUE; + if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ))) DBUG_RETURN(TRUE); table->use_all_columns(); @@ -6644,9 +6705,9 @@ view_err: } else { - if (lock_table_name_if_not_cached(thd, new_db, new_name, &name_lock)) + if (lock_table_name_if_not_cached(thd, new_db, new_name, &target_lock)) DBUG_RETURN(TRUE); - if (!name_lock) + if (!target_lock) { my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); DBUG_RETURN(TRUE); @@ -6737,26 +6798,15 @@ view_err: case LEAVE_AS_IS: break; case ENABLE: - /* - wait_while_table_is_used() ensures that table being altered is - opened only by this thread and that TABLE::TABLE_SHARE::version - of TABLE object corresponding to this table is 0. - The latter guarantees that no DML statement will open this table - until ALTER TABLE finishes (i.e. until close_thread_tables()) - while the fact that the table is still open gives us protection - from concurrent DDL statements. - */ - pthread_mutex_lock(&LOCK_open); - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - pthread_mutex_unlock(&LOCK_open); + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto err; DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000);); error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); /* COND_refresh will be signaled in close_thread_tables() */ break; case DISABLE: - pthread_mutex_lock(&LOCK_open); - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - pthread_mutex_unlock(&LOCK_open); + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto err; error=table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); /* COND_refresh will be signaled in close_thread_tables() */ break; @@ -6773,24 +6823,19 @@ view_err: table->alias); } - pthread_mutex_lock(&LOCK_open); - /* - Unlike to the above case close_cached_table() below will remove ALL - instances of TABLE from table cache (it will also remove table lock - held by this thread). So to make actual table renaming and writing - to binlog atomic we have to put them into the same critical section - protected by LOCK_open mutex. This also removes gap for races between - access() and mysql_rename_table() calls. - */ - if (!error && (new_name != table_name || new_db != db)) { thd_proc_info(thd, "rename"); /* Then do a 'simple' rename of the table. First we need to close all instances of 'source' table. + Note that if close_cached_table() returns error here (i.e. if + this thread was killed) then it must be that previous step of + simple rename did nothing and therefore we can safely reture + without additional clean-up. */ - close_cached_table(thd, table); + if (close_cached_table(thd, table)) + goto err; /* Then, we want check once again that target table does not exist. Actually the order of these two steps does not matter since @@ -6807,6 +6852,7 @@ view_err: else { *fn_ext(new_name)=0; + pthread_mutex_lock(&LOCK_open); if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0)) error= -1; else if (Table_triggers_list::change_table_name(thd, db, table_name, @@ -6816,6 +6862,7 @@ view_err: table_name, 0); error= -1; } + pthread_mutex_unlock(&LOCK_open); } } @@ -6837,11 +6884,24 @@ view_err: table->file->print_error(error, MYF(0)); error= -1; } - if (name_lock) - unlink_open_table(thd, name_lock, FALSE); - pthread_mutex_unlock(&LOCK_open); table_list->table= NULL; // For query cache query_cache_invalidate3(thd, table_list, 0); + + if (thd->locked_tables) + { + /* + Under LOCK TABLES we should adjust meta-data locks before finishing + statement. Otherwise we can rely on close_thread_tables() releasing + them. + + TODO: Investigate what should be done with upgraded table-level + lock here... + */ + if (new_name != table_name || new_db != db) + mdl_release_exclusive_locks(&thd->mdl_context); + else + mdl_downgrade_exclusive_locks(&thd->mdl_context); + } DBUG_RETURN(error); } @@ -7073,7 +7133,7 @@ view_err: #ifdef WITH_PARTITION_STORAGE_ENGINE if (fast_alter_partition) { - DBUG_ASSERT(!name_lock); + DBUG_ASSERT(!target_lock); DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, create_info, table_list, db, table_name, @@ -7158,7 +7218,7 @@ view_err: tbl.db= new_db; tbl.table_name= tbl.alias= tmp_name; /* Table is in thd->temporary_tables */ - new_table= open_table(thd, &tbl, thd->mem_root, (bool*) 0, + new_table= open_table(thd, &tbl, thd->mem_root, (enum_open_table_action*) 0, MYSQL_LOCK_IGNORE_FLUSH); } else @@ -7168,10 +7228,10 @@ view_err: build_table_filename(path, sizeof(path) - 1, new_db, tmp_name, "", FN_IS_TMP); /* Open our intermediate table */ - new_table=open_temporary_table(thd, path, new_db, tmp_name,0); + new_table= open_temporary_table(thd, path, new_db, tmp_name, 1); } if (!new_table) - goto err1; + goto err_new_table_cleanup; /* Note: In case of MERGE table, we do not attach children. We do not copy data for MERGE tables. Only the children have data. @@ -7200,9 +7260,8 @@ view_err: } else { - pthread_mutex_lock(&LOCK_open); - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - pthread_mutex_unlock(&LOCK_open); + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto err_new_table_cleanup; thd_proc_info(thd, "manage keys"); alter_table_manage_keys(table, table->file->indexes_are_disabled(), alter_info->keys_onoff); @@ -7253,7 +7312,7 @@ view_err: table->key_info= key_info; table->file->print_error(error, MYF(0)); table->key_info= save_key_info; - goto err1; + goto err_new_table_cleanup; } } /*end of if (index_add_count)*/ @@ -7276,14 +7335,14 @@ view_err: index_drop_count))) { table->file->print_error(error, MYF(0)); - goto err1; + goto err_new_table_cleanup; } /* Tell the handler to finally drop the indexes. */ if ((error= table->file->final_drop_index(table))) { table->file->print_error(error, MYF(0)); - goto err1; + goto err_new_table_cleanup; } } /*end of if (index_drop_count)*/ @@ -7296,16 +7355,16 @@ view_err: /* Need to commit before a table is unlocked (NDB requirement). */ DBUG_PRINT("info", ("Committing before unlocking table")); if (ha_autocommit_or_rollback(thd, 0) || end_active_trans(thd)) - goto err1; + goto err_new_table_cleanup; committed= 1; } /*end of if (! new_table) for add/drop index*/ + if (error) + goto err_new_table_cleanup; + if (table->s->tmp_table != NO_TMP_TABLE) { - /* We changed a temporary table */ - if (error) - goto err1; /* Close lock if this is a transactional table */ if (thd->lock) { @@ -7316,28 +7375,22 @@ view_err: close_temporary_table(thd, table, 1, 1); /* Should pass the 'new_name' as we store table name in the cache */ if (rename_temporary_table(thd, new_table, new_db, new_name)) - goto err1; + goto err_new_table_cleanup; /* We don't replicate alter table statement on temporary tables */ if (!thd->current_stmt_binlog_row_based) write_bin_log(thd, TRUE, thd->query(), thd->query_length()); goto end_temporary; } + /* + Close the intermediate table that will be the new table, but do + not delete it! Even altough MERGE tables do not have their children + attached here it is safe to call close_temporary_table(). + */ if (new_table) { - /* - Close the intermediate table that will be the new table. - Note that MERGE tables do not have their children attached here. - */ - intern_close_table(new_table); - my_free(new_table,MYF(0)); - } - pthread_mutex_lock(&LOCK_open); - if (error) - { - (void) quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP); - pthread_mutex_unlock(&LOCK_open); - goto err; + close_temporary_table(thd, new_table, 1, 0); + new_table= 0; } /* @@ -7362,7 +7415,11 @@ view_err: if (lower_case_table_names) my_casedn_str(files_charset_info, old_name); - wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME); + if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME)) + goto err_new_table_cleanup; + + pthread_mutex_lock(&LOCK_open); + close_data_files_and_morph_locks(thd, db, table_name); error=0; @@ -7431,8 +7488,7 @@ view_err: table_list->table_name_length= strlen(new_name); table_list->db= new_db; table_list->db_length= strlen(new_db); - table_list->table= name_lock; - if (reopen_name_locked_table(thd, table_list, FALSE)) + if (reopen_name_locked_table(thd, table_list)) goto err_with_placeholders; t_table= table_list->table; } @@ -7446,16 +7502,24 @@ view_err: if (t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG, create_info)) goto err_with_placeholders; - if (thd->locked_tables && new_name == table_name && new_db == db) + if (thd->locked_tables) { - /* - We are going to reopen table down on the road, so we have to restore - state of the TABLE object which we used for obtaining of handler - object to make it suitable for reopening. - */ - DBUG_ASSERT(t_table == table); - table->open_placeholder= 1; - close_handle_and_leave_table_as_lock(table); + if (new_name == table_name && new_db == db) + { + /* + We are going to reopen table down on the road, so we have to restore + state of the TABLE object which we used for obtaining of handler + object to make it suitable for reopening. + */ + DBUG_ASSERT(t_table == table); + table->open_placeholder= 1; + close_handle_and_leave_table_as_lock(table); + } + else + { + /* Unlink the new name from the list of locked tables. */ + unlink_open_table(thd, t_table, FALSE); + } } } @@ -7464,7 +7528,7 @@ view_err: if (thd->locked_tables && new_name == table_name && new_db == db) { thd->in_lock_tables= 1; - error= reopen_tables(thd, 1, 1); + error= reopen_tables(thd, 1); thd->in_lock_tables= 0; if (error) goto err_with_placeholders; @@ -7508,18 +7572,17 @@ view_err: table_list->table=0; // For query cache query_cache_invalidate3(thd, table_list, 0); - if (thd->locked_tables && (new_name != table_name || new_db != db)) + if (thd->locked_tables) { - /* - If are we under LOCK TABLES and did ALTER TABLE with RENAME we need - to remove placeholders for the old table and for the target table - from the list of open tables and table cache. If we are not under - LOCK TABLES we can rely on close_thread_tables() doing this job. - */ - pthread_mutex_lock(&LOCK_open); - unlink_open_table(thd, table, FALSE); - unlink_open_table(thd, name_lock, FALSE); - pthread_mutex_unlock(&LOCK_open); + if ((new_name != table_name || new_db != db)) + { + pthread_mutex_lock(&LOCK_open); + unlink_open_table(thd, table, FALSE); + pthread_mutex_unlock(&LOCK_open); + mdl_release_exclusive_locks(&thd->mdl_context); + } + else + mdl_downgrade_exclusive_locks(&thd->mdl_context); } end_temporary: @@ -7530,7 +7593,7 @@ end_temporary: thd->some_tables_deleted=0; DBUG_RETURN(FALSE); -err1: +err_new_table_cleanup: if (new_table) { /* close_temporary_table() frees the new_table pointer. */ @@ -7574,12 +7637,8 @@ err: alter_info->datetime_field->field_name); thd->abort_on_warning= save_abort_on_warning; } - if (name_lock) - { - pthread_mutex_lock(&LOCK_open); - unlink_open_table(thd, name_lock, FALSE); - pthread_mutex_unlock(&LOCK_open); - } + if (target_lock) + mdl_release_exclusive_locks(&thd->mdl_context); DBUG_RETURN(TRUE); err_with_placeholders: @@ -7589,9 +7648,8 @@ err_with_placeholders: from list of open tables list and table cache. */ unlink_open_table(thd, table, FALSE); - if (name_lock) - unlink_open_table(thd, name_lock, FALSE); pthread_mutex_unlock(&LOCK_open); + mdl_release_exclusive_locks(&thd->mdl_context); DBUG_RETURN(TRUE); } /* mysql_alter_table */ diff --git a/sql/sql_test.cc b/sql/sql_test.cc index d9beb77f546..3907aa6a5ae 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -75,7 +75,8 @@ print_where(COND *cond,const char *info, enum_query_type query_type) void print_cached_tables(void) { uint idx,count,unused; - TABLE *start_link,*lnk; + TABLE_SHARE *share; + TABLE *start_link, *lnk, *entry; compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions)); @@ -83,16 +84,26 @@ void print_cached_tables(void) pthread_mutex_lock(&LOCK_open); puts("DB Table Version Thread Open Lock"); - for (idx=unused=0 ; idx < open_cache.records ; idx++) + for (idx=unused=0 ; idx < table_def_cache.records ; idx++) { - TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx); - printf("%-14.14s %-32s%6ld%8ld%6d %s\n", - entry->s->db.str, entry->s->table_name.str, entry->s->version, - entry->in_use ? entry->in_use->thread_id : 0L, - entry->db_stat ? 1 : 0, - entry->in_use ? lock_descriptions[(int)entry->reginfo.lock_type] : "Not in use"); - if (!entry->in_use) + share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); + + I_P_List_iterator it(share->used_tables); + while ((entry= it++)) + { + printf("%-14.14s %-32s%6ld%8ld%6d %s\n", + entry->s->db.str, entry->s->table_name.str, entry->s->version, + entry->in_use->thread_id, entry->db_stat ? 1 : 0, + lock_descriptions[(int)entry->reginfo.lock_type]); + } + it.init(share->free_tables); + while ((entry= it++)) + { unused++; + printf("%-14.14s %-32s%6ld%8ld%6d %s\n", + entry->s->db.str, entry->s->table_name.str, entry->s->version, + 0L, entry->db_stat ? 1 : 0, "Not in use"); + } } count=0; if ((start_link=lnk=unused_tables)) @@ -104,17 +115,18 @@ void print_cached_tables(void) printf("unused_links isn't linked properly\n"); return; } - } while (count++ < open_cache.records && (lnk=lnk->next) != start_link); + } while (count++ < table_cache_count && (lnk=lnk->next) != start_link); if (lnk != start_link) { printf("Unused_links aren't connected\n"); } } if (count != unused) - printf("Unused_links (%d) doesn't match open_cache: %d\n", count,unused); + printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count, + unused); printf("\nCurrent refresh version: %ld\n",refresh_version); - if (my_hash_check(&open_cache)) - printf("Error: File hash table is corrupted\n"); + if (my_hash_check(&table_def_cache)) + printf("Error: Table definition hash table is corrupted\n"); fflush(stdout); pthread_mutex_unlock(&LOCK_open); /* purecov: end */ @@ -380,7 +392,7 @@ static void display_table_locks(void) LIST *list; DYNAMIC_ARRAY saved_table_locks; - (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO),open_cache.records + 20,50); + (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), table_cache_count + 20,50); pthread_mutex_lock(&THR_LOCK_lock); for (list= thr_lock_thread_list; list; list= list_rest(list)) { diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index ecbb6473ec4..a623b3c80f3 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -387,8 +387,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) DBUG_RETURN(TRUE); - pthread_mutex_lock(&LOCK_open); - if (!create) { bool if_exists= thd->lex->drop_if_exists; @@ -444,26 +442,28 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) tables->required_type= FRMTYPE_TABLE; /* Keep consistent with respect to other DDL statements */ - mysql_ha_rm_tables(thd, tables, TRUE); + mysql_ha_rm_tables(thd, tables); if (thd->locked_tables) { - /* Table must be write locked */ if (name_lock_locked_table(thd, tables)) goto end; + pthread_mutex_lock(&LOCK_open); } else { - /* Grab the name lock and insert the placeholder*/ + /* + Obtain exlusive meta-data lock on the table and remove TABLE + instances from cache. + */ if (lock_table_names(thd, tables)) goto end; - /* Convert the placeholder to a real table */ - if (reopen_name_locked_table(thd, tables, TRUE)) - { - unlock_table_name(thd, tables); - goto end; - } + pthread_mutex_lock(&LOCK_open); + expel_table_from_cache(0, tables->db, tables->table_name); + + if (reopen_name_locked_table(thd, tables)) + goto end_unlock; } table= tables->table; @@ -472,11 +472,11 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) if (!create) { my_error(ER_TRG_DOES_NOT_EXIST, MYF(0)); - goto end; + goto end_unlock; } if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table))) - goto end; + goto end_unlock; } result= (create ? @@ -489,7 +489,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) /* Make table suitable for reopening */ close_data_files_and_morph_locks(thd, tables->db, tables->table_name); thd->in_lock_tables= 1; - if (reopen_tables(thd, 1, 1)) + if (reopen_tables(thd, 1)) { /* To be safe remove this table from the set of LOCKED TABLES */ unlink_open_table(thd, tables->table, FALSE); @@ -503,14 +503,22 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) thd->in_lock_tables= 0; } -end: +end_unlock: + pthread_mutex_unlock(&LOCK_open); +end: if (!result) { write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length()); } - pthread_mutex_unlock(&LOCK_open); + /* + If we are under LOCK TABLES we should restore original state of meta-data + locks. Otherwise call to close_thread_tables() will take care about both + TABLE instance created by reopen_name_locked_table() and meta-data lock. + */ + if (thd->locked_tables) + mdl_downgrade_exclusive_locks(&thd->mdl_context); if (need_start_waiting) start_waiting_global_read_lock(thd); @@ -1879,11 +1887,7 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, In the future, only an exclusive table name lock will be enough. */ #ifndef DBUG_OFF - uchar key[MAX_DBKEY_LENGTH]; - uint key_length= (uint) (strmov(strmov((char*)&key[0], db)+1, - old_table)-(char*)&key[0])+1; - - if (!is_table_name_exclusively_locked_by_this_thread(thd, key, key_length)) + if (mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, db, old_table)) safe_mutex_assert_owner(&LOCK_open); #endif diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc index a1a0d9633b7..25a0db2fbb8 100644 --- a/sql/sql_udf.cc +++ b/sql/sql_udf.cc @@ -142,6 +142,7 @@ void udf_init() tables.alias= tables.table_name= (char*) "func"; tables.lock_type = TL_READ; tables.db= db; + alloc_mdl_locks(&tables, new_thd->mem_root); if (simple_open_n_lock_tables(new_thd, &tables)) { @@ -485,6 +486,7 @@ int mysql_create_function(THD *thd,udf_func *udf) bzero((char*) &tables,sizeof(tables)); tables.db= (char*) "mysql"; tables.table_name= tables.alias= (char*) "func"; + alloc_mdl_locks(&tables, thd->mem_root); /* Allow creation of functions even if we can't open func table */ if (!(table = open_ltable(thd, &tables, TL_WRITE, 0))) goto err; @@ -563,6 +565,7 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) bzero((char*) &tables,sizeof(tables)); tables.db=(char*) "mysql"; tables.table_name= tables.alias= (char*) "func"; + alloc_mdl_locks(&tables, thd->mem_root); if (!(table = open_ltable(thd, &tables, TL_WRITE, 0))) goto err; table->use_all_columns(); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index da8b2d046bb..1ad39a2f838 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -226,7 +226,7 @@ int mysql_update(THD *thd, break; if (!need_reopen) DBUG_RETURN(1); - close_tables_for_reopen(thd, &table_list); + close_tables_for_reopen(thd, &table_list, FALSE); } if (mysql_handle_derived(thd->lex, &mysql_derived_prepare) || @@ -1149,7 +1149,7 @@ reopen_tables: */ cleanup_items(thd->free_list); - close_tables_for_reopen(thd, &table_list); + close_tables_for_reopen(thd, &table_list, FALSE); goto reopen_tables; } diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 83341a53c3e..a0acde600c7 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -178,40 +178,17 @@ err: static bool fill_defined_view_parts (THD *thd, TABLE_LIST *view) { + char key[MAX_DBKEY_LENGTH]; + uint key_length; LEX *lex= thd->lex; - bool not_used; TABLE_LIST decoy; memcpy (&decoy, view, sizeof (TABLE_LIST)); + key_length= create_table_def_key(thd, key, view, 0); - /* - Let's reset decoy.view before calling open_table(): when we start - supporting ALTER VIEW in PS/SP that may save us from a crash. - */ - - decoy.view= NULL; - - /* - open_table() will return NULL if 'decoy' is idenitifying a view *and* - there is no TABLE object for that view in the table cache. However, - decoy.view will be set to 1. - - If there is a TABLE-instance for the oject identified by 'decoy', - open_table() will return that instance no matter if it is a table or - a view. - - Thus, there is no need to check for the return value of open_table(), - since the return value itself does not mean anything. - */ - - open_table(thd, &decoy, thd->mem_root, ¬_used, OPEN_VIEW_NO_PARSE); - - if (!decoy.view) - { - /* It's a table. */ - my_error(ER_WRONG_OBJECT, MYF(0), view->db, view->table_name, "VIEW"); + if (tdc_open_view(thd, &decoy, decoy.alias, key, key_length, + thd->mem_root, OPEN_VIEW_NO_PARSE)) return TRUE; - } if (!lex->definer) { @@ -400,6 +377,35 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, DBUG_ASSERT(!lex->proc_list.first && !lex->result && !lex->param_list.elements); + /* + We can't allow taking exclusive meta-data locks of unlocked view under + LOCK TABLES since this might lead to deadlock. Since at the moment we + can't really lock view with LOCK TABLES we simply prohibit creation/ + alteration of views under LOCK TABLES. + */ + + if (thd->locked_tables) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + res= TRUE; + goto err; + } + + if ((res= create_view_precheck(thd, tables, view, mode))) + goto err; + + lex->link_first_table_back(view, link_to_local); + view->open_table_type= TABLE_LIST::TAKE_EXCLUSIVE_MDL; + + if (open_and_lock_tables(thd, lex->query_tables)) + { + view= lex->unlink_first_table(&link_to_local); + res= TRUE; + goto err; + } + + view= lex->unlink_first_table(&link_to_local); + if (mode != VIEW_CREATE_NEW) { if (mode == VIEW_ALTER && @@ -464,16 +470,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, } } #endif - - if ((res= create_view_precheck(thd, tables, view, mode))) - goto err; - - if (open_and_lock_tables(thd, tables)) - { - res= TRUE; - goto err; - } - /* check that tables are not temporary and this VIEW do not used in query (it is possible with ALTERing VIEW). @@ -615,11 +611,13 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, } #endif + if (wait_if_global_read_lock(thd, 0, 0)) { res= TRUE; goto err; } + pthread_mutex_lock(&LOCK_open); res= mysql_register_view(thd, view, mode); @@ -1331,7 +1329,10 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, anyway. */ for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local) + { tbl->lock_type= table->lock_type; + tbl->mdl_upgradable= table->mdl_upgradable; + } /* If the view is mergeable, we might want to INSERT/UPDATE/DELETE into tables of this view. Preserve the @@ -1579,6 +1580,21 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) bool something_wrong= FALSE; DBUG_ENTER("mysql_drop_view"); + /* + We can't allow dropping of unlocked view under LOCK TABLES since this + might lead to deadlock. But since we can't really lock view with LOCK + TABLES we have to simply prohibit dropping of views. + */ + + if (thd->locked_tables) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } + + if (lock_table_names(thd, views)) + DBUG_RETURN(TRUE); + pthread_mutex_lock(&LOCK_open); for (view= views; view; view= view->next_local) { diff --git a/sql/table.cc b/sql/table.cc index 7ea04ed3e15..181014df9f4 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -316,6 +316,9 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, share->table_map_id= ~0UL; share->cached_row_logging_check= -1; + share->used_tables.empty(); + share->free_tables.empty(); + memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root)); pthread_mutex_init(&share->mutex, MY_MUTEX_INIT_FAST); pthread_cond_init(&share->cond, NULL); @@ -382,6 +385,9 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key, */ share->table_map_id= (ulong) thd->query_id; + share->used_tables.empty(); + share->free_tables.empty(); + DBUG_VOID_RETURN; } @@ -4832,6 +4838,20 @@ size_t max_row_length(TABLE *table, const uchar *data) return length; } + +/** + Helper function which allows to allocate metadata lock request + objects for all elements of table list. +*/ + +void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root) +{ + for ( ; table_list ; table_list= table_list->next_global) + table_list->mdl_lock= mdl_alloc_lock(0, table_list->db, + table_list->table_name, root); +} + + /***************************************************************************** ** Instansiate templates *****************************************************************************/ diff --git a/sql/table.h b/sql/table.h index 76bebd3fdaa..b0598e07299 100644 --- a/sql/table.h +++ b/sql/table.h @@ -17,6 +17,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "sql_plist.h" + /* Structs that defines the TABLE */ class Item; /* Needed by ORDER */ @@ -28,6 +30,7 @@ class st_select_lex; class partition_info; class COND_EQUAL; class Security_context; +struct MDL_LOCK; /*************************************************************************/ @@ -288,6 +291,9 @@ typedef enum enum_table_category TABLE_CATEGORY; TABLE_CATEGORY get_table_category(const LEX_STRING *db, const LEX_STRING *name); + +struct TABLE_share; + /* This structure is shared between different table objects. There is one instance of table share per one table in the database. @@ -310,6 +316,13 @@ struct TABLE_SHARE pthread_cond_t cond; /* To signal that share is ready */ TABLE_SHARE *next, **prev; /* Link to unused shares */ + /* + Doubly-linked (back-linked) lists of used and unused TABLE objects + for this share. + */ + I_P_List used_tables; + I_P_List free_tables; + /* The following is copied to each TABLE on OPEN */ Field **field; Field **found_next_number_field; @@ -613,6 +626,19 @@ struct TABLE handler *file; TABLE *next, *prev; +private: + /** + Links for the lists of used/unused TABLE objects for this share. + Declared as private to avoid direct manipulation with those objects. + One should use methods of I_P_List template instead. + */ + TABLE *share_next, **share_prev; + + friend struct TABLE_share; + friend bool reopen_table(TABLE *table); + +public: + /* For the below MERGE related members see top comment in ha_myisammrg.cc */ TABLE *parent; /* Set in MERGE child. Ptr to parent */ TABLE_LIST *child_l; /* Set in MERGE parent. List of children */ @@ -814,6 +840,7 @@ struct TABLE partition_info *part_info; /* Partition related information */ bool no_partitions_used; /* If true, all partitions have been pruned away */ #endif + MDL_LOCK *mdl_lock; bool fill_item_list(List *item_list) const; void reset_item_list(List *item_list) const; @@ -859,6 +886,25 @@ struct TABLE bool is_children_attached(void); }; + +/** + Helper class which specifies which members of TABLE are used for + participation in the list of used/unused TABLE objects for the share. +*/ + +struct TABLE_share +{ + static inline TABLE **next_ptr(TABLE *l) + { + return &l->share_next; + } + static inline TABLE ***prev_ptr(TABLE *l) + { + return &l->share_prev; + } +}; + + enum enum_schema_table_state { NOT_PROCESSED= 0, @@ -1326,11 +1372,18 @@ struct TABLE_LIST */ bool prelocking_placeholder; /* - This TABLE_LIST object corresponds to the table to be created - so it is possible that it does not exist (used in CREATE TABLE - ... SELECT implementation). + This TABLE_LIST object corresponds to the table/view which requires + special handling/meta-data locking. For example this is a target + table in CREATE TABLE ... SELECT so it is possible that it does not + exist and we should take exclusive meta-data lock on it in this + case. */ - bool create; + enum {NORMAL_OPEN= 0, OPEN_OR_CREATE, TAKE_EXCLUSIVE_MDL} open_table_type; + /** + Indicates that for this table/view we need to take shared metadata + lock which should be upgradable to exclusive metadata lock. + */ + bool mdl_upgradable; bool internal_tmp_table; /** TRUE if an alias for this table was specified in the SQL. */ bool is_alias; @@ -1379,6 +1432,9 @@ struct TABLE_LIST bool has_table_lookup_value; uint table_open_method; enum enum_schema_table_state schema_table_state; + + MDL_LOCK *mdl_lock; + void calc_md5(char *buffer); void set_underlying_merge(); int view_check_option(THD *thd, bool ignore_failure); @@ -1386,8 +1442,7 @@ struct TABLE_LIST void cleanup_items(); bool placeholder() { - return derived || view || schema_table || (create && !table->db_stat) || - !table; + return derived || view || schema_table || !table; } void print(THD *thd, String *str, enum_query_type query_type); bool check_single_table(TABLE_LIST **table, table_map map, @@ -1746,4 +1801,17 @@ static inline void dbug_tmp_restore_column_maps(MY_BITMAP *read_set, size_t max_row_length(TABLE *table, const uchar *data); + +void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root); + +/** + Helper function which allows to mark all elements in table list + as requiring upgradable metadata locks. +*/ + +inline void set_all_mdl_upgradable(TABLE_LIST *tables) +{ + for (; tables; tables= tables->next_global) + tables->mdl_upgradable= TRUE; +} #endif /* TABLE_INCLUDED */ diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index 471e2243aac..b2181636492 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -270,6 +270,12 @@ static int myisammrg_parent_open_callback(void *callback_param, /* Set alias. */ child_l->alias= child_l->table_name; + /* + FIXME: Actually we should use some other mem-root here. + To be fixed once Ingo pushes his patch for WL4144. + */ + alloc_mdl_locks(child_l, &parent->mem_root); + /* Initialize table map to 'undefined'. */ child_l->init_child_def_version(); From 6fd08278768ccd514e7e0e69d1b7e5759214ea6f Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Mon, 30 Nov 2009 19:09:42 +0300 Subject: [PATCH 019/212] Backport of: ------------------------------------------------------------ revno: 2630.4.7 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Sun 2008-05-25 11:19:02 +0400 message: WL#3726 "DDL locking for all metadata objects". Fixed silly mistake in test case which caused sporadic kill.test failures. mysql-test/t/kill.test: Fixed silly mistake in several places where I was supposed to be waiting for certain state of connection I actually forgot to invoke wait_condition.inc script altough set appropriate variable. Indeed this caused sporadical test failures. --- mysql-test/t/kill.test | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mysql-test/t/kill.test b/mysql-test/t/kill.test index 98cb9f64d98..b91feb3a1d5 100644 --- a/mysql-test/t/kill.test +++ b/mysql-test/t/kill.test @@ -374,6 +374,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "drop table t1"; +--source include/wait_condition.inc --replace_result $ID ID eval kill query $ID; --echo # Switching to connection 'ddl' @@ -389,6 +390,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "create trigger t1_bi before insert on t1 for each row set @a:=1"; +--source include/wait_condition.inc --replace_result $ID ID eval kill query $ID; --echo # Switching to connection 'ddl' @@ -407,6 +409,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "alter table t1 add column j int"; +--source include/wait_condition.inc --replace_result $ID ID eval kill query $ID; --echo # Switching to connection 'ddl' @@ -422,6 +425,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "alter table t1 rename to t2"; +--source include/wait_condition.inc --replace_result $ID ID eval kill query $ID; --echo # Switching to connection 'ddl' @@ -435,6 +439,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "alter table t1 disable keys"; +--source include/wait_condition.inc --replace_result $ID ID eval kill query $ID; --echo # Switching to connection 'ddl' @@ -449,6 +454,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "alter table t1 alter column i set default 100"; +--source include/wait_condition.inc --replace_result $ID ID eval kill query $ID; --echo # Switching to connection 'ddl' @@ -470,6 +476,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "alter table t2 alter column i set default 100"; +--source include/wait_condition.inc --replace_result $ID ID eval kill query $ID; --echo # Switching to connection 'ddl' @@ -494,6 +501,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "rename tables t1 to t3, t2 to t1"; +--source include/wait_condition.inc let $ID2= `select connection_id()`; --send insert into t2 values (1) --echo # Switching to connection 'default' @@ -502,6 +510,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "insert into t2 values (1)"; +--source include/wait_condition.inc --replace_result $ID2 ID2 eval kill query $ID2; --echo # Switching to connection 'dml' @@ -529,6 +538,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Flushing tables" and info = "flush tables"; +--source include/wait_condition.inc --send select * from t1 --echo # Switching to connection 'default' connection default; @@ -536,6 +546,7 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Waiting for table" and info = "select * from t1"; +--source include/wait_condition.inc --replace_result $ID2 ID2 eval kill query $ID2; --echo # Switching to connection 'dml' From 8a54198cba96297de6528bed99f3e90e8e046739 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Mon, 30 Nov 2009 19:21:40 +0300 Subject: [PATCH 020/212] Backport of: ---------------------------------------------------------------- 2630.4.9 Dmitry Lenev 2008-05-26 WL#3726 "DDL locking for all metadata objects". After review fixes in progress. Adjusted test case according to review. mysql-test/suite/rpl/r/rpl_view_multi.result: Changed test case to check that statements are replicated properly instead of directly inspecting binary log. Also made result file more readable by adding echo there it was appropriate. Moved test case to "rpl" suite. mysql-test/suite/rpl/t/rpl_view_multi.test: Changed test case to check that statements are replicated properly instead of directly inspecting binary log. Also made result file more readable by adding echo there it was appropriate. Moved test case to "rpl" suite. --- mysql-test/r/view_multi.result | 48 ------- mysql-test/suite/rpl/r/rpl_view_multi.result | 90 +++++++++++++ mysql-test/suite/rpl/t/rpl_view_multi.test | 134 +++++++++++++++++++ mysql-test/t/view_multi.test | 110 --------------- 4 files changed, 224 insertions(+), 158 deletions(-) delete mode 100644 mysql-test/r/view_multi.result create mode 100644 mysql-test/suite/rpl/r/rpl_view_multi.result create mode 100644 mysql-test/suite/rpl/t/rpl_view_multi.test delete mode 100644 mysql-test/t/view_multi.test diff --git a/mysql-test/r/view_multi.result b/mysql-test/r/view_multi.result deleted file mode 100644 index 95a8d572be4..00000000000 --- a/mysql-test/r/view_multi.result +++ /dev/null @@ -1,48 +0,0 @@ -reset master; -create table t1 (i int); -create table t2 (i int); -create view v1 as select * from t1; -select get_lock("lock_bg25144", 1); -get_lock("lock_bg25144", 1) -1 -insert into v1 values (get_lock("lock_bg25144", 100));; -drop view v1;; -select release_lock("lock_bg25144"); -release_lock("lock_bg25144") -1 -select release_lock("lock_bg25144"); -release_lock("lock_bg25144") -1 -select * from t1; -i -1 -create view v1 as select * from t1; -select get_lock("lock_bg25144", 1); -get_lock("lock_bg25144", 1) -1 -insert into v1 values (get_lock("lock_bg25144", 100));; -alter view v1 as select * from t2;; -select release_lock("lock_bg25144"); -release_lock("lock_bg25144") -1 -select release_lock("lock_bg25144"); -release_lock("lock_bg25144") -1 -select * from t1; -i -1 -1 -select * from t2; -i -show binlog events in 'master-bin.000001' from 107; -Log_name Pos Event_type Server_id End_log_pos Info -master-bin.000001 # Query 1 # use `test`; create table t1 (i int) -master-bin.000001 # Query 1 # use `test`; create table t2 (i int) -master-bin.000001 # Query 1 # use `test`; CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t1 -master-bin.000001 # Query 1 # use `test`; insert into v1 values (get_lock("lock_bg25144", 100)) -master-bin.000001 # Query 1 # use `test`; drop view v1 -master-bin.000001 # Query 1 # use `test`; CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t1 -master-bin.000001 # Query 1 # use `test`; insert into v1 values (get_lock("lock_bg25144", 100)) -master-bin.000001 # Query 1 # use `test`; ALTER ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t2 -drop table t1, t2; -drop view v1; diff --git a/mysql-test/suite/rpl/r/rpl_view_multi.result b/mysql-test/suite/rpl/r/rpl_view_multi.result new file mode 100644 index 00000000000..b3f10584a24 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_view_multi.result @@ -0,0 +1,90 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +# +# Bug #25144 "replication / binlog with view breaks". +# Statements that used views didn't ensure that view were not modified +# during their execution. Indeed this led to incorrect binary log with +# statement based logging and as result to broken replication. +# +drop tables if exists t1, t2; +drop view if exists v1; +# Syncing slave with master and switching to connection 'slave' +# Switching to connection 'master' +create table t1 (i int); +create table t2 (i int); +create view v1 as select * from t1; +# First we try to concurrently execute statement that uses view +# and statement that drops it. We use "user" locks as means to +# suspend execution of first statement once it opens our view. +select get_lock("lock_bg25144", 1); +get_lock("lock_bg25144", 1) +1 +# Switching to connection 'master1' +insert into v1 values (get_lock("lock_bg25144", 100)); +# Switching to connection 'master2' +drop view v1; +# Switching to connection 'master' +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +# Switching to connection 'master1' +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +# Switching to connection 'master2' +# Switching to connection 'master' +# Check that insertion through view did happen. +select * from t1; +i +1 +# Syncing slave with master and switching to connection 'slave' +# Check that slave was able to replicate this sequence +# which means that we got correct binlog order. +select * from t1; +i +1 +# Switching to connection 'master' +# Now we will repeat the test by trying concurrently execute +# statement that uses a view and statement that alters it. +create view v1 as select * from t1; +select get_lock("lock_bg25144", 1); +get_lock("lock_bg25144", 1) +1 +# Switching to connection 'master1' +insert into v1 values (get_lock("lock_bg25144", 100)); +# Switching to connection 'master2' +alter view v1 as select * from t2; +# Switching to connection 'master' +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +# Switching to connection 'master1' +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +# Switching to connection 'master2' +# Switching to connection 'master' +# Second insertion should go to t1 as well. +select * from t1; +i +1 +1 +select * from t2; +i +# Syncing slave with master and switching to connection 'slave' +# Now let us check that statements were logged in proper order +# So we have same result on slave. +select * from t1; +i +1 +1 +select * from t2; +i +# Switching to connection 'master' +drop table t1, t2; +drop view v1; +# Syncing slave with master and switching to connection 'slave' diff --git a/mysql-test/suite/rpl/t/rpl_view_multi.test b/mysql-test/suite/rpl/t/rpl_view_multi.test new file mode 100644 index 00000000000..777ccb2a945 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_view_multi.test @@ -0,0 +1,134 @@ +# +# This file contains test cases for bugs which involve views, several +# concurren connections and manifest themselves as wrong binary log +# sequence which results in broken replication. In principle we are +# mostly interested in SBR here but this test will also work with RBR. +# +--source include/master-slave.inc + +--echo # +--echo # Bug #25144 "replication / binlog with view breaks". +--echo # Statements that used views didn't ensure that view were not modified +--echo # during their execution. Indeed this led to incorrect binary log with +--echo # statement based logging and as result to broken replication. +--echo # + +--disable_warnings +drop tables if exists t1, t2; +drop view if exists v1; +--enable_warnings +--echo # Syncing slave with master and switching to connection 'slave' +--sync_slave_with_master + +connect (master2,127.0.0.1,root,,test,$MASTER_MYPORT,); + +--echo # Switching to connection 'master' +connection master; +create table t1 (i int); +create table t2 (i int); +create view v1 as select * from t1; + +--echo # First we try to concurrently execute statement that uses view +--echo # and statement that drops it. We use "user" locks as means to +--echo # suspend execution of first statement once it opens our view. +select get_lock("lock_bg25144", 1); + +--echo # Switching to connection 'master1' +connection master1; +--send insert into v1 values (get_lock("lock_bg25144", 100)) + +--echo # Switching to connection 'master2' +connection master2; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "User lock" and info like "insert into v1 %lock_bg25144%"; +--source include/wait_condition.inc +--send drop view v1 + +--echo # Switching to connection 'master' +connection master; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "drop view v1"; +--source include/wait_condition.inc + +select release_lock("lock_bg25144"); + +--echo # Switching to connection 'master1' +connection master1; +--reap +select release_lock("lock_bg25144"); + +--echo # Switching to connection 'master2' +connection master2; +--reap + +--echo # Switching to connection 'master' +connection master; +--echo # Check that insertion through view did happen. +select * from t1; +--echo # Syncing slave with master and switching to connection 'slave' +--sync_slave_with_master +--echo # Check that slave was able to replicate this sequence +--echo # which means that we got correct binlog order. +select * from t1; + +--echo # Switching to connection 'master' +connection master; +--echo # Now we will repeat the test by trying concurrently execute +--echo # statement that uses a view and statement that alters it. +create view v1 as select * from t1; + +select get_lock("lock_bg25144", 1); + +--echo # Switching to connection 'master1' +connection master1; +--send insert into v1 values (get_lock("lock_bg25144", 100)) + +--echo # Switching to connection 'master2' +connection master2; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "User lock" and info like "insert into v1 %lock_bg25144%"; +--source include/wait_condition.inc +--send alter view v1 as select * from t2 + +--echo # Switching to connection 'master' +connection master; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter view v1 as select * from t2"; +--source include/wait_condition.inc + +select release_lock("lock_bg25144"); + +--echo # Switching to connection 'master1' +connection master1; +--reap +select release_lock("lock_bg25144"); + +--echo # Switching to connection 'master2' +connection master2; +--reap + +--echo # Switching to connection 'master' +connection master; + +--echo # Second insertion should go to t1 as well. +select * from t1; +select * from t2; + +--echo # Syncing slave with master and switching to connection 'slave' +--sync_slave_with_master +--echo # Now let us check that statements were logged in proper order +--echo # So we have same result on slave. +select * from t1; +select * from t2; + +--echo # Switching to connection 'master' +connection master; +drop table t1, t2; +drop view v1; +--echo # Syncing slave with master and switching to connection 'slave' +--sync_slave_with_master diff --git a/mysql-test/t/view_multi.test b/mysql-test/t/view_multi.test deleted file mode 100644 index a61e7738095..00000000000 --- a/mysql-test/t/view_multi.test +++ /dev/null @@ -1,110 +0,0 @@ -# -# QQ: Should we find a better place for this test? -# May be binlog or rpl suites ? -# ---source include/have_log_bin.inc ---source include/have_binlog_format_mixed_or_statement.inc - -# -# Bug #25144 "replication / binlog with view breaks". -# Statements that used views didn't ensure that view were not modified -# during their execution. Indeed this led to incorrect binary log with -# statement based logging. -# ---disable_parsing -drop table if not exists t1, t2; -drop view if exists v1; ---enable_parsing - -# We are going to use binary log later to check that statements are -# logged in proper order, so it is good idea to reset it here. -reset master; - -connect (addconn1,localhost,root,,); -connect (addconn2,localhost,root,,); -connection default; - -create table t1 (i int); -create table t2 (i int); -create view v1 as select * from t1; - -# First we try to concurrently execute statement that uses view -# and statement that drops it. We use "user" locks as means to -# suspend execution of first statement once it opens our view. -select get_lock("lock_bg25144", 1); - -connection addconn1; ---send insert into v1 values (get_lock("lock_bg25144", 100)); - -connection addconn2; -let $wait_condition= - select count(*) = 1 from information_schema.processlist - where state = "User lock" and info like "insert into v1 %lock_bg25144%"; ---source include/wait_condition.inc ---send drop view v1; - -connection default; -let $wait_condition= - select count(*) = 1 from information_schema.processlist - where state = "Waiting for table" and info = "drop view v1"; ---source include/wait_condition.inc - -select release_lock("lock_bg25144"); - -connection addconn1; ---reap -select release_lock("lock_bg25144"); - -connection addconn2; ---reap - -connection default; -# Check that insertion through view did happen. -select * from t1; -# At the end of test we will check that statements were -# logged in proper order. - -# Now we will repeat the test by trying concurrently execute -# statement that uses a view and statement that alters it. -create view v1 as select * from t1; - -select get_lock("lock_bg25144", 1); - -connection addconn1; ---send insert into v1 values (get_lock("lock_bg25144", 100)); - -connection addconn2; -let $wait_condition= - select count(*) = 1 from information_schema.processlist - where state = "User lock" and info like "insert into v1 %lock_bg25144%"; ---source include/wait_condition.inc ---send alter view v1 as select * from t2; - -connection default; -let $wait_condition= - select count(*) = 1 from information_schema.processlist - where state = "Waiting for table" and - info = "alter view v1 as select * from t2"; ---source include/wait_condition.inc - -select release_lock("lock_bg25144"); - -connection addconn1; ---reap -select release_lock("lock_bg25144"); - -connection addconn2; ---reap - -connection default; - -# Second insertion should go to t1 as well. -select * from t1; -select * from t2; - -# Now let us check that statements were logged in proper order ---replace_column 2 # 5 # -show binlog events in 'master-bin.000001' from 107; - -drop table t1, t2; -drop view v1; From de1979d3d69e55e40037b62d3ce502a4ddd05910 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Mon, 30 Nov 2009 19:44:46 +0300 Subject: [PATCH 021/212] Backport of: ------------------------------------------------------------ revno: 2630.4.10 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Mon 2008-05-26 15:11:26 +0400 message: WL#3726 "DDL locking for all metadata objects". After review changes in progress. Implemented some renames suggested by reviewer. sql/mdl.cc: Renamed: MDL_LOCK_DATA::users -> lock_count MDL_LOCK_DATA::has_no_other_users() -> has_one_lock() MDL_LOCK::upgradable -> is_upgradable Moved variables used in global metadata lock implementation to separate strucuture. sql/mdl.h: Renamed MDL_LOCK::upgradable to is_upgradable. --- sql/mdl.cc | 113 ++++++++++++++++++++++++++++++----------------------- sql/mdl.h | 4 +- 2 files changed, 67 insertions(+), 50 deletions(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index 00dcc12cdc8..ddd027c4027 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -41,7 +41,13 @@ struct MDL_LOCK_DATA I_P_List active_shared_waiting_upgrade; I_P_List active_exclusive; I_P_List waiting_exclusive; - uint users; + /** + Number of MDL_LOCK objects associated with this MDL_LOCK_DATA instance + and therefore present in one of above lists. Note that this number + doesn't account for pending requests for shared lock since we don't + associate them with MDL_LOCK_DATA and don't keep them in any list. + */ + uint lock_count; void *cached_object; mdl_cached_object_release_hook cached_object_release_hook; @@ -57,9 +63,9 @@ struct MDL_LOCK_DATA active_exclusive.head() : waiting_exclusive.head())); } - bool has_no_other_users() + bool has_one_lock() { - return (users == 1); + return (lock_count == 1); } }; @@ -67,10 +73,21 @@ struct MDL_LOCK_DATA pthread_mutex_t LOCK_mdl; pthread_cond_t COND_mdl; HASH mdl_locks; -uint global_shared_locks_pending; -uint global_shared_locks_acquired; -uint global_intention_exclusive_locks_acquired; +/** + Structure implementing global metadata lock. The only types + of locks which are supported at the moment are shared and + intention exclusive locks. Note that the latter type of global + lock acquired automatically when one tries to acquire exclusive + or shared upgradable lock on particular object. +*/ + +struct MDL_GLOBAL_LOCK_DATA +{ + uint shared_pending; + uint shared_acquired; + uint intention_exclusive_acquired; +} global_lock; extern "C" uchar *mdl_locks_key(const uchar *record, size_t *length, @@ -106,8 +123,8 @@ void mdl_init() pthread_cond_init(&COND_mdl, NULL); my_hash_init(&mdl_locks, &my_charset_bin, 16 /* FIXME */, 0, 0, mdl_locks_key, 0, 0); - global_shared_locks_pending= global_shared_locks_acquired= 0; - global_intention_exclusive_locks_acquired= 0; + global_lock.shared_pending= global_lock.shared_acquired= 0; + global_lock.intention_exclusive_acquired= 0; } @@ -261,7 +278,7 @@ void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db, mdl->type= MDL_SHARED; mdl->state= MDL_PENDING; mdl->prio= MDL_NORMAL_PRIO; - mdl->upgradable= FALSE; + mdl->is_upgradable= FALSE; #ifndef DBUG_OFF mdl->ctx= 0; mdl->lock_data= 0; @@ -362,7 +379,7 @@ void mdl_remove_all_locks(MDL_CONTEXT *context) /* Reset lock request back to its initial state. */ l->type= MDL_SHARED; l->prio= MDL_NORMAL_PRIO; - l->upgradable= FALSE; + l->is_upgradable= FALSE; #ifndef DBUG_OFF l->ctx= 0; #endif @@ -424,7 +441,7 @@ bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry) safe_mutex_assert_not_owner(&LOCK_open); - if (l->ctx->has_global_shared_lock && l->upgradable) + if (l->ctx->has_global_shared_lock && l->is_upgradable) { my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); return TRUE; @@ -432,8 +449,8 @@ bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry) pthread_mutex_lock(&LOCK_mdl); - if (l->upgradable && - (global_shared_locks_acquired || global_shared_locks_pending)) + if (l->is_upgradable && + (global_lock.shared_acquired || global_lock.shared_pending)) { pthread_mutex_unlock(&LOCK_mdl); *retry= TRUE; @@ -445,12 +462,12 @@ bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry) { lock_data= get_lock_data_object(); lock_data->active_shared.push_front(l); - lock_data->users= 1; + lock_data->lock_count= 1; my_hash_insert(&mdl_locks, (uchar*)lock_data); l->state= MDL_ACQUIRED; l->lock_data= lock_data; - if (l->upgradable) - global_intention_exclusive_locks_acquired++; + if (l->is_upgradable) + global_lock.intention_exclusive_acquired++; } else { @@ -467,11 +484,11 @@ bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry) ALTER VIEW ... AS .... */ lock_data->active_shared.push_front(l); - lock_data->users++; + lock_data->lock_count++; l->state= MDL_ACQUIRED; l->lock_data= lock_data; - if (l->upgradable) - global_intention_exclusive_locks_acquired++; + if (l->is_upgradable) + global_lock.intention_exclusive_acquired++; } else *retry= TRUE; @@ -535,14 +552,14 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) { lock_data= get_lock_data_object(); lock_data->waiting_exclusive.push_front(l); - lock_data->users= 1; + lock_data->lock_count= 1; my_hash_insert(&mdl_locks, (uchar*)lock_data); l->lock_data= lock_data; } else { lock_data->waiting_exclusive.push_front(l); - lock_data->users++; + lock_data->lock_count++; l->lock_data= lock_data; } } @@ -554,7 +571,7 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) { lock_data= l->lock_data; - if (global_shared_locks_acquired || global_shared_locks_pending) + if (global_lock.shared_acquired || global_lock.shared_pending) { /* There is active or pending global shared lock we have @@ -607,7 +624,7 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) /* Return lock request to its initial state. */ l->type= MDL_SHARED; l->prio= MDL_NORMAL_PRIO; - l->upgradable= FALSE; + l->is_upgradable= FALSE; context->locks.remove(l); } /* Pending requests for shared locks can be satisfied now. */ @@ -619,7 +636,7 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) it.rewind(); while ((l= it++)) { - global_intention_exclusive_locks_acquired++; + global_lock.intention_exclusive_acquired++; lock_data= l->lock_data; lock_data->waiting_exclusive.remove(l); lock_data->active_exclusive.push_front(l); @@ -685,7 +702,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, { DBUG_PRINT("info", ("found shared lock for upgrade")); DBUG_ASSERT(l->state == MDL_ACQUIRED); - DBUG_ASSERT(l->upgradable); + DBUG_ASSERT(l->is_upgradable); l->state= MDL_PENDING_UPGRADE; lock_data= l->lock_data; lock_data->active_shared.remove(l); @@ -704,8 +721,8 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, lock_data= l->lock_data; - DBUG_ASSERT(global_shared_locks_acquired == 0 && - global_intention_exclusive_locks_acquired); + DBUG_ASSERT(global_lock.shared_acquired == 0 && + global_lock.intention_exclusive_acquired); if ((lh= lock_data->active_shared.head())) { @@ -815,12 +832,12 @@ bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *l) { lock_data= get_lock_data_object(); lock_data->active_exclusive.push_front(l); - lock_data->users= 1; + lock_data->lock_count= 1; my_hash_insert(&mdl_locks, (uchar*)lock_data); l->state= MDL_ACQUIRED; l->lock_data= lock_data; lock_data= 0; - global_intention_exclusive_locks_acquired++; + global_lock.intention_exclusive_acquired++; } pthread_mutex_unlock(&LOCK_mdl); @@ -842,7 +859,7 @@ bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *l) Holding this lock will block all requests for exclusive locks and shared locks which can be potentially upgraded to exclusive - (see MDL_LOCK::upgradable). + (see MDL_LOCK::is_upgradable). @param context Current metadata locking context. @@ -861,20 +878,20 @@ bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) pthread_mutex_lock(&LOCK_mdl); - global_shared_locks_pending++; + global_lock.shared_pending++; old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); - while (!thd->killed && global_intention_exclusive_locks_acquired) + while (!thd->killed && global_lock.intention_exclusive_acquired) pthread_cond_wait(&COND_mdl, &LOCK_mdl); - global_shared_locks_pending--; + global_lock.shared_pending--; if (thd->killed) { /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ thd->exit_cond(old_msg); return TRUE; } - global_shared_locks_acquired++; + global_lock.shared_acquired++; context->has_global_shared_lock= TRUE; /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ thd->exit_cond(old_msg); @@ -927,8 +944,8 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) while ((l= it++)) { DBUG_ASSERT(l->state == MDL_PENDING); - if ((l->upgradable || l->type == MDL_EXCLUSIVE) && - (global_shared_locks_acquired || global_shared_locks_pending)) + if ((l->is_upgradable || l->type == MDL_EXCLUSIVE) && + (global_lock.shared_acquired || global_lock.shared_pending)) break; /* To avoid starvation we don't wait if we have pending MDL_EXCLUSIVE lock. @@ -968,7 +985,7 @@ static void release_lock(MDL_LOCK *l) l->key + 4 + strlen(l->key + 4) + 1)); lock_data= l->lock_data; - if (lock_data->has_no_other_users()) + if (lock_data->has_one_lock()) { my_hash_delete(&mdl_locks, (uchar *)lock_data); DBUG_PRINT("info", ("releasing cached_object cached_object=%p", @@ -977,8 +994,8 @@ static void release_lock(MDL_LOCK *l) (*lock_data->cached_object_release_hook)(lock_data->cached_object); release_lock_data_object(lock_data); if (l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED || - l->type == MDL_SHARED && l->state == MDL_ACQUIRED && l->upgradable) - global_intention_exclusive_locks_acquired--; + l->type == MDL_SHARED && l->state == MDL_ACQUIRED && l->is_upgradable) + global_lock.intention_exclusive_acquired--; } else { @@ -986,8 +1003,8 @@ static void release_lock(MDL_LOCK *l) { case MDL_SHARED: lock_data->active_shared.remove(l); - if (l->upgradable) - global_intention_exclusive_locks_acquired--; + if (l->is_upgradable) + global_lock.intention_exclusive_acquired--; break; case MDL_EXCLUSIVE: if (l->state == MDL_PENDING) @@ -995,14 +1012,14 @@ static void release_lock(MDL_LOCK *l) else { lock_data->active_exclusive.remove(l); - global_intention_exclusive_locks_acquired--; + global_lock.intention_exclusive_acquired--; } break; default: /* TODO Really? How about problems during lock upgrade ? */ DBUG_ASSERT(0); } - lock_data->users--; + lock_data->lock_count--; } DBUG_VOID_RETURN; @@ -1094,7 +1111,7 @@ void mdl_release_exclusive_locks(MDL_CONTEXT *context) /* Return lock request to its initial state. */ l->type= MDL_SHARED; l->prio= MDL_NORMAL_PRIO; - l->upgradable= FALSE; + l->is_upgradable= FALSE; context->locks.remove(l); } } @@ -1128,7 +1145,7 @@ void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lr) /* Return lock request to its initial state. */ lr->type= MDL_SHARED; lr->prio= MDL_NORMAL_PRIO; - lr->upgradable= FALSE; + lr->is_upgradable= FALSE; context->locks.remove(lr); pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); @@ -1155,8 +1172,8 @@ void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context) if (l->type == MDL_EXCLUSIVE) { DBUG_ASSERT(l->state == MDL_ACQUIRED); - if (!l->upgradable) - global_intention_exclusive_locks_acquired--; + if (!l->is_upgradable) + global_lock.intention_exclusive_acquired--; lock_data= l->lock_data; lock_data->active_exclusive.remove(l); l->type= MDL_SHARED; @@ -1179,7 +1196,7 @@ void mdl_release_global_shared_lock(MDL_CONTEXT *context) DBUG_ASSERT(context->has_global_shared_lock); pthread_mutex_lock(&LOCK_mdl); - global_shared_locks_acquired--; + global_lock.shared_acquired--; context->has_global_shared_lock= FALSE; pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); diff --git a/sql/mdl.h b/sql/mdl.h index ca3d8d0a784..2b144e250b1 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -66,7 +66,7 @@ struct MDL_LOCK point might be upgraded to an exclusive lock and therefore conflicts with global shared lock, FALSE -- otherwise. */ - bool upgradable; + bool is_upgradable; private: /** @@ -189,7 +189,7 @@ inline void mdl_set_lock_priority(MDL_LOCK *lock, enum_mdl_prio prio) inline void mdl_set_upgradable(MDL_LOCK *lock) { DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING); - lock->upgradable= TRUE; + lock->is_upgradable= TRUE; } bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry); From aeebede1954d12b80f4c4aea016460501bfc2f5a Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Mon, 30 Nov 2009 22:03:37 +0300 Subject: [PATCH 022/212] Backport of: ------------------------------------------------------------ revno: 2630.4.11 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Tue 2008-05-27 21:31:53 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. Changed mysql_lock_tables() to be no longer responsible for reopening table if waiting for the lock on it was aborted. This allows to get rid of several annoying functions. sql/ha_ndbcluster_binlog.cc: lock_tables() now also accepts set of options to be passed to mysql_lock_tables(). sql/lock.cc: Changed mysql_lock_tables() always requests caller to reopen table instead doing this on its own when waiting for lock was aborted. This allows us to get rid of several functions which were used in rare cases and significantly complicated our life. sql/log_event_old.cc: lock_tables() now also accepts set of options to be passed to mysql_lock_tables(). sql/mysql_priv.h: Now mysql_lock_tables() always requests caller to reopen table instead doing this on its own when waiting for lock was aborted. So we no longer need wait_for_tables() and table_is_used() functions and MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN flag. open_and_lock_table_derived() and lock_tables() now accept options to be passed to open_tables() and mysql_lock_tables() calls. sql/sql_base.cc: Since now mysql_lock_tables() always requests caller to reopen table instead doing this on its own when waiting for lock was aborted we no longer need wait_for_tables(), table_is_used() and close_old_data_files() functions. open_and_lock_table_derived() and lock_tables() now accept options to be passed to open_tables() and mysql_lock_tables() calls. This was needed in order to get rid of redundant code in open_system_tables_for_read() function. sql/sql_handler.cc: mysql_lock_tables() is now always requests reopen if waiting for lock is aborted. MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN flag was removed. sql/sql_insert.cc: handle_delayed_insert(): Since mysql_lock_tables() is no longer responsible for reopening tables when waiting for lock was aborted we have to handle such situation outside of this function. To simplify this extracted code opening table for delayed insert thread to separate method of Delayed_insert class. sql/sql_update.cc: lock_tables() now also accepts set of options to be passed to mysql_lock_tables(). --- sql/ha_ndbcluster_binlog.cc | 2 +- sql/lock.cc | 64 +++++----- sql/log_event_old.cc | 3 +- sql/mysql_priv.h | 19 ++- sql/sql_base.cc | 246 ++++++------------------------------ sql/sql_handler.cc | 3 +- sql/sql_insert.cc | 94 ++++++++++---- sql/sql_update.cc | 4 +- 8 files changed, 152 insertions(+), 283 deletions(-) diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index bf76960201d..f272526edb8 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -2391,7 +2391,7 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row) goto add_ndb_binlog_index_err; } - if (lock_tables(thd, &binlog_tables, 1, &need_reopen)) + if (lock_tables(thd, &binlog_tables, 1, 0, &need_reopen)) { if (need_reopen) { diff --git a/sql/lock.cc b/sql/lock.cc index 0c8c3095844..38b2d22f91f 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -94,31 +94,6 @@ static int lock_external(THD *thd, TABLE **table,uint count); static int unlock_external(THD *thd, TABLE **table,uint count); static void print_lock_error(int error, const char *); -/* - Lock tables. - - SYNOPSIS - mysql_lock_tables() - thd The current thread. - tables An array of pointers to the tables to lock. - count The number of tables to lock. - flags Options: - MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock - MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY Ignore SET GLOBAL READ_ONLY - MYSQL_LOCK_IGNORE_FLUSH Ignore a flush tables. - MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN Instead of reopening altered - or dropped tables by itself, - mysql_lock_tables() should - notify upper level and rely - on caller doing this. - need_reopen Out parameter, TRUE if some tables were altered - or deleted and should be reopened by caller. - - RETURN - A lock structure pointer on success. - NULL on error or if some tables should be reopen. -*/ - /* Map the return value of thr_lock to an error from errmsg.txt */ static int thr_lock_errno_to_mysql[]= { 0, 1, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK }; @@ -247,6 +222,28 @@ static void reset_lock_data_and_free(MYSQL_LOCK **mysql_lock) } +/** + Lock tables. + + @param thd The current thread. + @param tables An array of pointers to the tables to lock. + @param count The number of tables to lock. + @param flags Options: + MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock + MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY Ignore SET GLOBAL READ_ONLY + MYSQL_LOCK_IGNORE_FLUSH Ignore a flush tables. + @param need_reopen Out parameter, TRUE if some tables were altered + or deleted and should be reopened by caller. + + @note Caller of this function should always be ready to handle request to + reopen table unless there are external invariants which guarantee + that such thing won't be needed (for example we are obtaining lock + on table on which we already have exclusive metadata lock). + + @retval A lock structure pointer on success. + @retval NULL on error or if some tables should be reopen. +*/ + MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags, bool *need_reopen) { @@ -330,7 +327,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, my_error(rc, MYF(0)); break; } - else if (rc == 1) /* aborted */ + else if (rc == 1) /* aborted or killed */ { thd->some_tables_deleted=1; // Try again sql_lock->lock_count= 0; // Locks are already freed @@ -339,8 +336,9 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, else if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH)) { /* - Thread was killed or lock aborted. Let upper level close all - used tables and retry or give error. + Success and nobody set thd->some_tables_deleted to force reopen + or we were called with MYSQL_LOCK_IGNORE_FLUSH so such attempts + should be ignored. */ break; } @@ -366,13 +364,9 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, */ reset_lock_data_and_free(&sql_lock); retry: - if (flags & MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN) - { - *need_reopen= TRUE; - break; - } - if (wait_for_tables(thd)) - break; // Couldn't open tables + /* Let upper level close all used tables and retry or give error. */ + *need_reopen= TRUE; + break; } thd_proc_info(thd, 0); if (thd->killed) diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 030d51b3618..3dcf19f6b32 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -1461,7 +1461,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) lex_start(thd); while ((error= lock_tables(thd, rli->tables_to_lock, - rli->tables_to_lock_count, &need_reopen))) + rli->tables_to_lock_count, 0, + &need_reopen))) { if (!need_reopen) { diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index be03759b383..ab692c3ab22 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1241,8 +1241,6 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table); void close_data_files_and_morph_locks(THD *thd, const char *db, const char *table_name); void close_handle_and_leave_table_as_lock(TABLE *table); -bool wait_for_tables(THD *thd); -bool table_is_used(TABLE *table, bool wait_for_name_lock); void unlock_locked_tables(THD *thd); void execute_init_command(THD *thd, sys_var_str *init_command_var, rw_lock_t *var_mutex); @@ -1476,22 +1474,24 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond); int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags); /* open_and_lock_tables with optional derived handling */ -int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived); +int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, + uint flags); /* simple open_and_lock_tables without derived handling */ inline int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables) { - return open_and_lock_tables_derived(thd, tables, FALSE); + return open_and_lock_tables_derived(thd, tables, FALSE, 0); } /* open_and_lock_tables with derived handling */ inline int open_and_lock_tables(THD *thd, TABLE_LIST *tables) { - return open_and_lock_tables_derived(thd, tables, TRUE); + return open_and_lock_tables_derived(thd, tables, TRUE, 0); } /* simple open_and_lock_tables without derived handling for single table */ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, thr_lock_type lock_type); bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags); -int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, bool *need_reopen); +int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags, + bool *need_reopen); int decide_logging_format(THD *thd, TABLE_LIST *tables); TABLE *open_temporary_table(THD *thd, const char *path, const char *db, const char *table_name, bool link_in_list); @@ -2045,10 +2045,9 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, /* mysql_lock_tables() and open_table() flags bits */ #define MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK 0x0001 #define MYSQL_LOCK_IGNORE_FLUSH 0x0002 -#define MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN 0x0004 -#define MYSQL_OPEN_TEMPORARY_ONLY 0x0008 -#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0010 -#define MYSQL_LOCK_PERF_SCHEMA 0x0020 +#define MYSQL_OPEN_TEMPORARY_ONLY 0x0004 +#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 +#define MYSQL_LOCK_PERF_SCHEMA 0x0010 void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index de4aaac633e..6b0efadb712 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -127,8 +127,6 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root); -static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks, - bool send_refresh); static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context); static bool has_write_table_with_auto_increment(TABLE_LIST *tables); @@ -3439,9 +3437,6 @@ bool reopen_tables(THD *thd, bool get_locks) TABLE *err_tables= NULL, *err_tab_tmp; bool error=0, not_used; bool merge_table_found= FALSE; - const uint flags= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN | - MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK | - MYSQL_LOCK_IGNORE_FLUSH; DBUG_ENTER("reopen_tables"); @@ -3534,10 +3529,14 @@ bool reopen_tables(THD *thd, bool get_locks) if (tables != tables_ptr) // Should we get back old locks { MYSQL_LOCK *lock; + const uint flags= MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK | + MYSQL_LOCK_IGNORE_FLUSH; /* - We should always get these locks. Anyway, we must not go into - wait_for_tables() as it tries to acquire LOCK_open, which is - already locked. + Since we have exclusive metadata locks on tables which we + are reopening we should always get these locks (We won't + wait on table level locks so can't get aborted and we ignore + other threads that set THD::some_tables_deleted by using + MYSQL_LOCK_IGNORE_FLUSH flag). */ thd->some_tables_deleted=0; if ((lock= mysql_lock_tables(thd, tables, (uint) (tables_ptr - tables), @@ -3565,165 +3564,6 @@ bool reopen_tables(THD *thd, bool get_locks) } -/** - Close handlers for tables in list, but leave the TABLE structure - intact so that we can re-open these quickly. - - @param thd Thread context - @param table Head of the list of TABLE objects - @param morph_locks TRUE - remove locks which we have on tables being closed - but ensure that no DML or DDL will sneak in before - we will re-open the table (i.e. temporarily morph - our table-level locks into name-locks). - FALSE - otherwise - @param send_refresh Should we awake waiters even if we didn't close any tables? -*/ - -static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks, - bool send_refresh) -{ - bool found= send_refresh; - DBUG_ENTER("close_old_data_files"); - - for (; table ; table=table->next) - { - DBUG_PRINT("tcache", ("checking table: '%s'.'%s' 0x%lx", - table->s->db.str, table->s->table_name.str, - (long) table)); - DBUG_PRINT("tcache", ("needs refresh: %d is open: %u", - table->needs_reopen_or_name_lock(), table->db_stat)); - /* - Reopen marked for flush. - */ - if (table->needs_reopen_or_name_lock()) - { - found=1; - if (table->db_stat) - { - if (morph_locks) - { - /* - Forward lock handling to MERGE parent. But unlock parent - once only. - */ - TABLE *ulcktbl= table->parent ? table->parent : table; - if (ulcktbl->lock_count) - { - /* - Wake up threads waiting for table-level lock on this table - so they won't sneak in when we will temporarily remove our - lock on it. This will also give them a chance to close their - instances of this table. - */ - mysql_lock_abort(thd, ulcktbl, TRUE); - mysql_lock_remove(thd, thd->locked_tables, ulcktbl, TRUE); - ulcktbl->lock_count= 0; - } - if ((ulcktbl != table) && ulcktbl->db_stat) - { - /* - Close the parent too. Note that parent can come later in - the list of tables. It will then be noticed as closed and - as a placeholder. When this happens, do not clear the - placeholder flag. See the branch below ("***"). - */ - ulcktbl->open_placeholder= 1; - close_handle_and_leave_table_as_lock(ulcktbl); - } - /* - We want to protect the table from concurrent DDL operations - (like RENAME TABLE) until we will re-open and re-lock it. - */ - table->open_placeholder= 1; - } - close_handle_and_leave_table_as_lock(table); - } - else if (table->open_placeholder && !morph_locks) - { - /* - We come here only in close-for-back-off scenario. So we have to - "close" create placeholder here to avoid deadlocks (for example, - in case of concurrent execution of CREATE TABLE t1 SELECT * FROM t2 - and RENAME TABLE t2 TO t1). In close-for-re-open scenario we will - probably want to let it stay. - - Note "***": We must not enter this branch if the placeholder - flag has been set because of a former close through a child. - See above the comment that refers to this note. - */ - table->open_placeholder= 0; - } - } - } - if (found) - broadcast_refresh(); - DBUG_VOID_RETURN; -} - - -/* - Wait until all threads has closed the tables in the list - We have also to wait if there is thread that has a lock on this table even - if the table is closed -*/ - -bool table_is_used(TABLE *table, bool wait_for_name_lock) -{ - DBUG_ENTER("table_is_used"); - do - { - char *key= table->s->table_cache_key.str; - uint key_length= table->s->table_cache_key.length; - /* Note that 'table' can use artificial TABLE_SHARE object. */ - TABLE_SHARE *share= (TABLE_SHARE*)my_hash_search(&table_def_cache, - (uchar*) key, key_length); - if (share && !share->used_tables.is_empty() && - share->version != refresh_version) - DBUG_RETURN(1); - } while ((table=table->next)); - DBUG_RETURN(0); -} - - -/* - Wait until all used tables are refreshed. - - FIXME We should remove this function since for several functions which - are invoked by it new scenarios of usage are introduced, while - this function implements optimization useful only in rare cases. -*/ - -bool wait_for_tables(THD *thd) -{ - bool result; - DBUG_ENTER("wait_for_tables"); - - thd_proc_info(thd, "Waiting for tables"); - pthread_mutex_lock(&LOCK_open); - while (!thd->killed) - { - thd->some_tables_deleted=0; - close_old_data_files(thd,thd->open_tables,0,dropping_tables != 0); - mysql_ha_flush(thd); - if (!table_is_used(thd->open_tables,1)) - break; - (void) pthread_cond_wait(&COND_refresh,&LOCK_open); - } - if (thd->killed) - result= 1; // aborted - else - { - /* Now we can open all tables without any interference */ - thd_proc_info(thd, "Reopen tables"); - thd->version= refresh_version; - result=reopen_tables(thd, 0); - } - pthread_mutex_unlock(&LOCK_open); - thd_proc_info(thd, 0); - DBUG_RETURN(result); -} - - /** Unlock and close tables open and locked by LOCK TABLES statement. @@ -5190,14 +5030,8 @@ retry: DBUG_ASSERT(thd->lock == 0); // You must lock everything at once if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK) if (! (thd->lock= mysql_lock_tables(thd, &table_list->table, 1, - (lock_flags | - MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN), - &refresh))) + lock_flags, &refresh))) { - /* - FIXME: Actually we should get rid of MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN option - as all reopening should happen outside of mysql_lock_tables() code. - */ if (refresh) { close_thread_tables(thd); @@ -5222,6 +5056,8 @@ retry: open_and_lock_tables_derived() thd - thread handler tables - list of tables for open&locking + flags - set of options to be used to open and lock tables (see + open_tables() and mysql_lock_tables() for details). derived - if to handle derived tables RETURN @@ -5239,7 +5075,8 @@ retry: the third argument set appropriately. */ -int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived) +int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, + uint flags) { uint counter; bool need_reopen; @@ -5248,7 +5085,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived) for ( ; ; ) { - if (open_tables(thd, &tables, &counter, 0)) + if (open_tables(thd, &tables, &counter, flags)) DBUG_RETURN(-1); DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", { @@ -5257,7 +5094,8 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived) my_sleep(6000000); thd->proc_info= old_proc_info;}); - if (!lock_tables(thd, tables, counter, &need_reopen)) + if (!lock_tables(thd, tables, counter, flags, + &need_reopen)) break; if (!need_reopen) DBUG_RETURN(-1); @@ -5492,6 +5330,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) thd Thread handler tables Tables to lock count Number of opened tables + flags Options (see mysql_lock_tables() for details) need_reopen Out parameter which if TRUE indicates that some tables were dropped or altered during this call and therefore invoker should reopen tables and @@ -5512,7 +5351,8 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) -1 Error */ -int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) +int lock_tables(THD *thd, TABLE_LIST *tables, uint count, + uint flags, bool *need_reopen) { TABLE_LIST *table; @@ -5540,7 +5380,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) { DBUG_ASSERT(thd->lock == 0); // You must lock everything at once TABLE **start,**ptr; - uint lock_flag= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN; if (!(ptr=start=(TABLE**) thd->alloc(sizeof(TABLE*)*count))) DBUG_RETURN(-1); @@ -5573,7 +5412,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) DEBUG_SYNC(thd, "before_lock_tables_takes_lock"); if (! (thd->lock= mysql_lock_tables(thd, start, (uint) (ptr - start), - lock_flag, need_reopen))) + flags, need_reopen))) { if (thd->lex->requires_prelocking()) { @@ -9190,41 +9029,38 @@ bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, Open_tables_state *backup) { + Query_tables_list query_tables_list_backup; + LEX *lex= thd->lex; + DBUG_ENTER("open_system_tables_for_read"); alloc_mdl_locks(table_list, thd->mem_root); + /* + Besides using new Open_tables_state for opening system tables, + we also have to backup and reset/and then restore part of LEX + which is accessed by open_tables() in order to determine if + prelocking is needed and what tables should be added for it. + close_system_tables() doesn't require such treatment. + */ + lex->reset_n_backup_query_tables_list(&query_tables_list_backup); thd->reset_n_backup_open_tables_state(backup); - uint count= 0; - enum_open_table_action not_used; - bool not_used_2; + if (open_and_lock_tables_derived(thd, table_list, FALSE, + MYSQL_LOCK_IGNORE_FLUSH)) + { + lex->restore_backup_query_tables_list(&query_tables_list_backup); + goto error; + } + for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global) { - TABLE *table= open_table(thd, tables, thd->mem_root, ¬_used, - MYSQL_LOCK_IGNORE_FLUSH); - if (!table) - goto error; - - DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_SYSTEM); - - table->use_all_columns(); - table->reginfo.lock_type= tables->lock_type; - tables->table= table; - count++; + DBUG_ASSERT(tables->table->s->table_category == TABLE_CATEGORY_SYSTEM); + tables->table->use_all_columns(); } + lex->restore_backup_query_tables_list(&query_tables_list_backup); - { - TABLE **list= (TABLE**) thd->alloc(sizeof(TABLE*) * count); - TABLE **ptr= list; - for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global) - *(ptr++)= tables->table; - - thd->lock= mysql_lock_tables(thd, list, count, - MYSQL_LOCK_IGNORE_FLUSH, ¬_used_2); - } - if (thd->lock) - DBUG_RETURN(FALSE); + DBUG_RETURN(FALSE); error: close_system_tables(thd, backup); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index c8a66073a67..5c034a0452a 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -519,8 +519,7 @@ retry: */ thd->open_tables= thd->handler_tables; - lock= mysql_lock_tables(thd, &tables->table, 1, - MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN, &need_reopen); + lock= mysql_lock_tables(thd, &tables->table, 1, 0, &need_reopen); /* restore previous context */ thd->open_tables= backup_open_tables; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index eb4eee9abb5..525f6b9a4d8 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1817,6 +1817,7 @@ public: inline uint lock_count() { return locks_in_memory; } TABLE* get_local_table(THD* client_thd); + bool open_and_lock_table(); bool handle_inserts(void); }; @@ -2292,6 +2293,42 @@ void kill_delayed_threads(void) } +/** + Open and lock table for use by delayed thread and check that + this table is suitable for delayed inserts. + + @retval FALSE - Success. + @retval TRUE - Failure. +*/ + +bool Delayed_insert::open_and_lock_table() +{ + if (!(table= open_n_lock_single_table(&thd, &table_list, + TL_WRITE_DELAYED))) + { + thd.fatal_error(); // Abort waiting inserts + return TRUE; + } + if (!(table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED)) + { + my_error(ER_DELAYED_NOT_SUPPORTED, MYF(ME_FATALERROR), + table_list.table_name); + return TRUE; + } + if (table->triggers) + { + /* + Table has triggers. This is not an error, but we do + not support triggers with delayed insert. Terminate the delayed + thread without an error and thus request lock upgrade. + */ + return TRUE; + } + table->copy_blobs= 1; + return FALSE; +} + + /* * Create a new delayed insert thread */ @@ -2354,29 +2391,8 @@ pthread_handler_t handle_delayed_insert(void *arg) alloc_mdl_locks(&di->table_list, thd->mem_root); - /* Open table */ - if (!(di->table= open_n_lock_single_table(thd, &di->table_list, - TL_WRITE_DELAYED))) - { - thd->fatal_error(); // Abort waiting inserts + if (di->open_and_lock_table()) goto err; - } - if (!(di->table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED)) - { - my_error(ER_DELAYED_NOT_SUPPORTED, MYF(ME_FATALERROR), - di->table_list.table_name); - goto err; - } - if (di->table->triggers) - { - /* - Table has triggers. This is not an error, but we do - not support triggers with delayed insert. Terminate the delayed - thread without an error and thus request lock upgrade. - */ - goto err; - } - di->table->copy_blobs=1; /* Tell client that the thread is initialized */ pthread_cond_signal(&di->cond_client); @@ -2450,7 +2466,7 @@ pthread_handler_t handle_delayed_insert(void *arg) if (di->tables_in_use && ! thd->lock) { - bool not_used; + bool need_reopen; /* Request for new delayed insert. Lock the table, but avoid to be blocked by a global read lock. @@ -2463,11 +2479,29 @@ pthread_handler_t handle_delayed_insert(void *arg) */ if (! (thd->lock= mysql_lock_tables(thd, &di->table, 1, MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK, - ¬_used))) + &need_reopen))) { - /* Fatal error */ - di->dead= 1; - thd->killed= THD::KILL_CONNECTION; + if (need_reopen) + { + /* + We were waiting to obtain TL_WRITE_DELAYED (probably due to + someone having or requesting TL_WRITE_ALLOW_READ) and got + aborted. Try to reopen table and if it fails die. + */ + close_thread_tables(thd); + di->table= 0; + if (di->open_and_lock_table()) + { + di->dead= 1; + thd->killed= THD::KILL_CONNECTION; + } + } + else + { + /* Fatal error */ + di->dead= 1; + thd->killed= THD::KILL_CONNECTION; + } } pthread_cond_broadcast(&di->cond_client); } @@ -3533,6 +3567,12 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, table->reginfo.lock_type=TL_WRITE; hooks->prelock(&table, 1); // Call prelock hooks + /* + mysql_lock_tables() below should never fail with request to reopen table + since it won't wait for the table lock (we have exclusive metadata lock on + the table) and thus can't get aborted and since it ignores other threads + setting THD::some_tables_deleted thanks to MYSQL_LOCK_IGNORE_FLUSH. + */ if (! ((*lock)= mysql_lock_tables(thd, &table, 1, MYSQL_LOCK_IGNORE_FLUSH, ¬_used)) || hooks->postlock(&table, 1)) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 1ad39a2f838..5cf6ceaa394 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -222,7 +222,7 @@ int mysql_update(THD *thd, /* convert to multiupdate */ DBUG_RETURN(2); } - if (!lock_tables(thd, table_list, table_count, &need_reopen)) + if (!lock_tables(thd, table_list, table_count, 0, &need_reopen)) break; if (!need_reopen) DBUG_RETURN(1); @@ -1099,7 +1099,7 @@ reopen_tables: /* now lock and fill tables */ if (!thd->stmt_arena->is_stmt_prepare() && - lock_tables(thd, table_list, table_count, &need_reopen)) + lock_tables(thd, table_list, table_count, 0, &need_reopen)) { if (!need_reopen) DBUG_RETURN(TRUE); From 40d98edd220d1ec7a317d3c1cbcbf58c5748388b Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Mon, 30 Nov 2009 22:11:32 +0300 Subject: [PATCH 023/212] Backport of: ------------------------------------------------------------ revno: 2630.6.1 committer: Konstantin Osipov branch nick: mysql-6.0-3726 timestamp: Tue 2008-05-27 13:45:34 +0400 message: Remove an unused argument from release_table_share(). Remove unused members from TABLE_SHARE struct. Review comments in scope of WL#3726 "DDL locking for all metadata objects" sql/mysql_priv.h: Update declaration. sql/sql_base.cc: Upate declaration and the comment (release_share()). sql/sql_plist.h: A cosmetic change, is_empty() is a const method. sql/sql_table.cc: Update to use the new declaration of release_table_share(). sql/sql_view.cc: Update to use the new declaration of release_table_share(). sql/table.cc: Update to use the new declaration of release_table_share(). Remove dead code. sql/table.h: Remove unused members of TABLE. --- sql/mysql_priv.h | 2 +- sql/sql_base.cc | 52 +++++++++++++++++++----------------------------- sql/sql_plist.h | 2 +- sql/sql_show.cc | 2 +- sql/sql_table.cc | 2 +- sql/sql_view.cc | 2 +- sql/table.cc | 10 +--------- sql/table.h | 2 -- 8 files changed, 26 insertions(+), 48 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index ab692c3ab22..063b36c4ddc 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1216,7 +1216,7 @@ uint create_table_def_key(THD *thd, char *key, TABLE_LIST *table_list, bool tmp_table); TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, uint key_length, uint db_flags, int *error); -void release_table_share(TABLE_SHARE *share, enum release_type type); +void release_table_share(TABLE_SHARE *share); TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name); TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, uint lock_flags); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 6b0efadb712..f963d74102b 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -651,29 +651,17 @@ static TABLE_SHARE db_flags, error)); } +/** + Mark that we are not using table share anymore. -/* - Mark that we are not using table share anymore. + @param share Table share - SYNOPSIS - release_table_share() - share Table share - release_type How the release should be done: - RELEASE_NORMAL - - Release without checking - RELEASE_WAIT_FOR_DROP - - Don't return until we get a signal that the - table is deleted or the thread is killed. - - IMPLEMENTATION - If ref_count goes to zero and (we have done a refresh or if we have - already too many open table shares) then delete the definition. - - If type == RELEASE_WAIT_FOR_DROP then don't return until we get a signal - that the table is deleted or the thread is killed. + If the share has no open tables and (we have done a refresh or + if we have already too many open table shares) then delete the + definition. */ -void release_table_share(TABLE_SHARE *share, enum release_type type) +void release_table_share(TABLE_SHARE *share) { bool to_be_deleted= 0; DBUG_ENTER("release_table_share"); @@ -818,7 +806,7 @@ void close_handle_and_leave_table_as_lock(TABLE *table) table->file->close(); table->db_stat= 0; // Mark file closed table_def_change_share(table, share); - release_table_share(table->s, RELEASE_NORMAL); + release_table_share(table->s); table->s= share; table->file->change_table_ptr(table, table->s); @@ -2568,7 +2556,7 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) void table_share_release_hook(void *share) { pthread_mutex_lock(&LOCK_open); - release_table_share((TABLE_SHARE*)share, RELEASE_NORMAL); + release_table_share((TABLE_SHARE*) share); broadcast_refresh(); pthread_mutex_unlock(&LOCK_open); } @@ -2902,7 +2890,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, goto err_unlock; /* TODO: Don't free this */ - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); if (flags & OPEN_VIEW_NO_PARSE) { @@ -2978,7 +2966,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { if (action) *action= OT_BACK_OFF_AND_RETRY; - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } @@ -2991,7 +2979,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table= share->free_tables.head(); table_def_use_table(thd, table); /* We need to release share as we have EXTRA reference to it in our hands. */ - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); } else { @@ -3095,7 +3083,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, DBUG_RETURN(table); err_unlock: - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); err_unlock2: pthread_mutex_unlock(&LOCK_open); mdl_release_lock(&thd->mdl_context, mdl_lock); @@ -3758,13 +3746,13 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, flags, thd->open_options, ¬_used, table_list, mem_root)) { - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); pthread_mutex_unlock(&LOCK_open); return FALSE; } my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "VIEW"); - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); err: pthread_mutex_unlock(&LOCK_open); return TRUE; @@ -3838,7 +3826,7 @@ retry: if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) goto err; /* Attempt to reopen view will bring havoc to upper layers anyway. */ - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "BASE TABLE"); DBUG_RETURN(1); @@ -3871,7 +3859,7 @@ retry: if (share->ref_count != 1) goto err; - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); if (ha_create_table_from_engine(thd, table_list->db, table_list->table_name)) @@ -3887,7 +3875,7 @@ retry: entry->s->version= 0; /* TODO: We don't need to release share here. */ - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); pthread_mutex_unlock(&LOCK_open); error= (int)auto_repair_table(thd, table_list); pthread_mutex_lock(&LOCK_open); @@ -3907,7 +3895,7 @@ retry: DBUG_RETURN(0); err: - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); DBUG_RETURN(1); } @@ -4031,7 +4019,7 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) pthread_mutex_lock(&LOCK_open); end_with_lock_open: - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); pthread_mutex_unlock(&LOCK_open); return result; } diff --git a/sql/sql_plist.h b/sql/sql_plist.h index af2ed227ea1..b05a6318f0f 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -62,7 +62,7 @@ class I_P_List public: I_P_List() : first(NULL) { }; inline void empty() { first= NULL; } - inline bool is_empty() { return (first == NULL); } + inline bool is_empty() const { return (first == NULL); } inline void push_front(T* a) { *B::next_ptr(a)= first; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index c83a6981166..236bca76c7d 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3206,7 +3206,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, } err_share: - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); err_unlock: pthread_mutex_unlock(&LOCK_open); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index f91bae8b76c..640cd71fc62 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4469,7 +4469,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE)) { - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); // Out of memory } diff --git a/sql/sql_view.cc b/sql/sql_view.cc index a0acde600c7..3dc5ca4e09c 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1647,7 +1647,7 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) share->ref_count++; share->version= 0; pthread_mutex_unlock(&share->mutex); - release_table_share(share, RELEASE_WAIT_FOR_DROP); + release_table_share(share); } query_cache_invalidate3(thd, view, 0); sp_cache_invalidate(); diff --git a/sql/table.cc b/sql/table.cc index 181014df9f4..b232ba23a89 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -321,7 +321,6 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root)); pthread_mutex_init(&share->mutex, MY_MUTEX_INIT_FAST); - pthread_cond_init(&share->cond, NULL); } DBUG_RETURN(share); } @@ -418,16 +417,9 @@ void free_table_share(TABLE_SHARE *share) */ if (share->tmp_table == NO_TMP_TABLE) { - /* share->mutex is locked in release_table_share() */ - while (share->waiting_on_cond) - { - pthread_cond_broadcast(&share->cond); - pthread_cond_wait(&share->cond, &share->mutex); - } /* No thread refers to this anymore */ pthread_mutex_unlock(&share->mutex); pthread_mutex_destroy(&share->mutex); - pthread_cond_destroy(&share->cond); } my_hash_free(&share->name_hash); @@ -2001,7 +1993,7 @@ int closefrm(register TABLE *table, bool free_share) if (free_share) { if (table->s->tmp_table == NO_TMP_TABLE) - release_table_share(table->s, RELEASE_NORMAL); + release_table_share(table->s); else free_table_share(table->s); } diff --git a/sql/table.h b/sql/table.h index b0598e07299..016a99e5452 100644 --- a/sql/table.h +++ b/sql/table.h @@ -313,7 +313,6 @@ struct TABLE_SHARE TYPELIB fieldnames; /* Pointer to fieldnames */ TYPELIB *intervals; /* pointer to interval info */ pthread_mutex_t mutex; /* For locking the share */ - pthread_cond_t cond; /* To signal that share is ready */ TABLE_SHARE *next, **prev; /* Link to unused shares */ /* @@ -410,7 +409,6 @@ struct TABLE_SHARE bool crashed; bool is_view; bool name_lock, replace_with_name_lock; - bool waiting_on_cond; /* Protection against free */ ulong table_map_id; /* for row-based replication */ ulonglong table_map_version; From cd055f0efe330ace1d4d83536f1cbbeb1c8e0ab9 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Mon, 30 Nov 2009 22:38:25 +0300 Subject: [PATCH 024/212] Backport of: ------------------------------------------------------------------- revno: 2630.6.6 committer: Konstantin Osipov branch nick: mysql-6.0-3726 timestamp: Tue 2008-05-27 16:15:44 +0400 message: Implement code review fixes for WL#3726 "DDL locking for all metadata objects": cleanup the code from share->mutex acquisitions, which are now obsolete. sql/ha_partition.cc: Rename share->mutex to share->LOCK_ha_data. The mutex never protected the entire share. The right way to protect a share is to acquire an MDL lock. sql/ha_partition.h: Rename share->mutex to share->LOCK_ha_data. sql/sql_base.cc: Remove LOCK_table_share. Do not acquire share->mutex when deleting a table share or incrementing its ref_count. All these operations are and must continue to be protected by LOCK_open and respective MDL locks. sql/sql_view.cc: Remove acquisition of share->mutex when incrementing share->ref_count. sql/table.cc: Simplify release_table_shar() by removing dead code related to share->mutex from it. sql/table.h: Rename TABLE_SHARE::mutex to TABLE_SHARE::LOCK_ha_data to better reflect its purpose. storage/myisam/ha_myisam.cc: Rename share->mutex to share->LOCK_ha_data. --- sql/ha_partition.cc | 6 +++--- sql/ha_partition.h | 4 ++-- sql/sql_base.cc | 41 ++----------------------------------- sql/sql_view.cc | 2 -- sql/table.cc | 15 ++++---------- sql/table.h | 2 +- storage/myisam/ha_myisam.cc | 4 ++-- 7 files changed, 14 insertions(+), 60 deletions(-) diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 2ec92173d14..1514651c1c5 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -2606,7 +2606,7 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) for the same table. */ if (is_not_tmp_table) - pthread_mutex_lock(&table_share->mutex); + pthread_mutex_lock(&table_share->LOCK_ha_data); if (!table_share->ha_data) { HA_DATA_PARTITION *ha_data; @@ -2617,7 +2617,7 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) if (!ha_data) { if (is_not_tmp_table) - pthread_mutex_unlock(&table_share->mutex); + pthread_mutex_unlock(&table_share->LOCK_ha_data); goto err_handler; } DBUG_PRINT("info", ("table_share->ha_data 0x%p", ha_data)); @@ -2626,7 +2626,7 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) pthread_mutex_init(&ha_data->mutex, MY_MUTEX_INIT_FAST); } if (is_not_tmp_table) - pthread_mutex_unlock(&table_share->mutex); + pthread_mutex_unlock(&table_share->LOCK_ha_data); /* Some handlers update statistics as part of the open call. This will in some cases corrupt the statistics of the partition handler and thus diff --git a/sql/ha_partition.h b/sql/ha_partition.h index d4579d013fd..fc0e98348db 100644 --- a/sql/ha_partition.h +++ b/sql/ha_partition.h @@ -933,7 +933,7 @@ private: if(table_share->tmp_table == NO_TMP_TABLE) { auto_increment_lock= TRUE; - pthread_mutex_lock(&table_share->mutex); + pthread_mutex_lock(&table_share->LOCK_ha_data); } } virtual void unlock_auto_increment() @@ -946,7 +946,7 @@ private: */ if(auto_increment_lock && !auto_increment_safe_stmt_log_lock) { - pthread_mutex_unlock(&table_share->mutex); + pthread_mutex_unlock(&table_share->LOCK_ha_data); auto_increment_lock= FALSE; } } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index f963d74102b..1356a2cd16f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -112,7 +112,6 @@ uint table_cache_count= 0; TABLE *unused_tables; HASH table_def_cache; static TABLE_SHARE *oldest_unused_share, end_of_unused_share; -static pthread_mutex_t LOCK_table_share; static bool table_def_inited= 0; static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, @@ -253,13 +252,12 @@ extern "C" uchar *table_def_key(const uchar *record, size_t *length, static void table_def_free_entry(TABLE_SHARE *share) { DBUG_ENTER("table_def_free_entry"); + safe_mutex_assert_owner(&LOCK_open); if (share->prev) { /* remove from old_unused_share list */ - pthread_mutex_lock(&LOCK_table_share); *share->prev= share->next; share->next->prev= share->prev; - pthread_mutex_unlock(&LOCK_table_share); } free_table_share(share); DBUG_VOID_RETURN; @@ -269,7 +267,6 @@ static void table_def_free_entry(TABLE_SHARE *share) bool table_def_init(void) { table_def_inited= 1; - pthread_mutex_init(&LOCK_table_share, MY_MUTEX_INIT_FAST); oldest_unused_share= &end_of_unused_share; end_of_unused_share.prev= &oldest_unused_share; @@ -287,7 +284,6 @@ void table_def_free(void) /* Free all open TABLEs first. */ close_cached_tables(NULL, NULL, FALSE, FALSE); table_def_inited= 0; - pthread_mutex_destroy(&LOCK_table_share); /* Free table definitions. */ my_hash_free(&table_def_cache); } @@ -473,12 +469,6 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, DBUG_RETURN(0); } - /* - Lock mutex to be able to read table definition from file without - conflicts - */ - (void) pthread_mutex_lock(&share->mutex); - /* We assign a new table id under the protection of the LOCK_open and the share's own mutex. We do this insted of creating a new mutex @@ -508,7 +498,6 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, share->ref_count++; // Mark in use DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", (ulong) share, share->ref_count)); - (void) pthread_mutex_unlock(&share->mutex); DBUG_RETURN(share); found: @@ -516,20 +505,15 @@ found: We found an existing table definition. Return it if we didn't get an error when reading the table definition from file. */ - - /* We must do a lock to ensure that the structure is initialized */ - (void) pthread_mutex_lock(&share->mutex); if (share->error) { /* Table definition contained an error */ open_table_error(share, share->error, share->open_errno, share->errarg); - (void) pthread_mutex_unlock(&share->mutex); DBUG_RETURN(0); } if (share->is_view && !(db_flags & OPEN_VIEW)) { open_table_error(share, 1, ENOENT, 0); - (void) pthread_mutex_unlock(&share->mutex); DBUG_RETURN(0); } @@ -540,22 +524,16 @@ found: Unlink share from this list */ DBUG_PRINT("info", ("Unlinking from not used list")); - pthread_mutex_lock(&LOCK_table_share); *share->prev= share->next; share->next->prev= share->prev; share->next= 0; share->prev= 0; - pthread_mutex_unlock(&LOCK_table_share); } - (void) pthread_mutex_unlock(&share->mutex); /* Free cache if too big */ while (table_def_cache.records > table_def_size && oldest_unused_share->next) - { - pthread_mutex_lock(&oldest_unused_share->mutex); my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); - } DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", (ulong) share, share->ref_count)); @@ -672,7 +650,6 @@ void release_table_share(TABLE_SHARE *share) safe_mutex_assert_owner(&LOCK_open); - pthread_mutex_lock(&share->mutex); DBUG_ASSERT(share->ref_count); if (!--share->ref_count) { @@ -684,12 +661,10 @@ void release_table_share(TABLE_SHARE *share) DBUG_PRINT("info",("moving share to unused list")); DBUG_ASSERT(share->next == 0); - pthread_mutex_lock(&LOCK_table_share); share->prev= end_of_unused_share.prev; *end_of_unused_share.prev= share; end_of_unused_share.prev= &share->next; share->next= &end_of_unused_share; - pthread_mutex_unlock(&LOCK_table_share); to_be_deleted= (table_def_cache.records > table_def_size); } @@ -699,9 +674,7 @@ void release_table_share(TABLE_SHARE *share) { DBUG_PRINT("info", ("Deleting share")); my_hash_delete(&table_def_cache, (uchar*) share); - DBUG_VOID_RETURN; } - pthread_mutex_unlock(&share->mutex); DBUG_VOID_RETURN; } @@ -745,9 +718,8 @@ static void reference_table_share(TABLE_SHARE *share) { DBUG_ENTER("reference_table_share"); DBUG_ASSERT(share->ref_count); - pthread_mutex_lock(&share->mutex); + safe_mutex_assert_owner(&LOCK_open); share->ref_count++; - pthread_mutex_unlock(&share->mutex); DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", (ulong) share, share->ref_count)); DBUG_VOID_RETURN; @@ -1043,10 +1015,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, free_cache_entry(unused_tables); /* Free table shares */ while (oldest_unused_share->next) - { - pthread_mutex_lock(&oldest_unused_share->mutex); (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); - } if (!wait_for_refresh) { @@ -8581,10 +8550,7 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, DBUG_PRINT("info", ("share version: %lu ref_count: %u", share->version, share->ref_count)); if (share->ref_count == 0) - { - pthread_mutex_lock(&share->mutex); my_hash_delete(&table_def_cache, (uchar*) share); - } } if (result && (flags & RTFC_WAIT_OTHER_THREAD_FLAG)) @@ -8722,10 +8688,7 @@ void expel_table_from_cache(THD *leave_thd, const char *db, const char *table_na { DBUG_ASSERT(leave_thd || share->ref_count == 0); if (share->ref_count == 0) - { - pthread_mutex_lock(&share->mutex); my_hash_delete(&table_def_cache, (uchar*) share); - } } } diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 3dc5ca4e09c..55181a58e53 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1643,10 +1643,8 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) if ((share= get_cached_table_share(view->db, view->table_name))) { DBUG_ASSERT(share->ref_count == 0); - pthread_mutex_lock(&share->mutex); share->ref_count++; share->version= 0; - pthread_mutex_unlock(&share->mutex); release_table_share(share); } query_cache_invalidate3(thd, view, 0); diff --git a/sql/table.cc b/sql/table.cc index b232ba23a89..c8814cff685 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -320,7 +320,7 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, share->free_tables.empty(); memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root)); - pthread_mutex_init(&share->mutex, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&share->LOCK_ha_data, MY_MUTEX_INIT_FAST); } DBUG_RETURN(share); } @@ -411,18 +411,11 @@ void free_table_share(TABLE_SHARE *share) DBUG_PRINT("enter", ("table: %s.%s", share->db.str, share->table_name.str)); DBUG_ASSERT(share->ref_count == 0); - /* - If someone is waiting for this to be deleted, inform it about this. - Don't do a delete until we know that no one is refering to this anymore. - */ + /* The mutex is initialized only for shares that are part of the TDC */ if (share->tmp_table == NO_TMP_TABLE) - { - /* No thread refers to this anymore */ - pthread_mutex_unlock(&share->mutex); - pthread_mutex_destroy(&share->mutex); - } + pthread_mutex_destroy(&share->LOCK_ha_data); my_hash_free(&share->name_hash); - + plugin_unlock(NULL, share->db_plugin); share->db_plugin= NULL; diff --git a/sql/table.h b/sql/table.h index 016a99e5452..60560029725 100644 --- a/sql/table.h +++ b/sql/table.h @@ -312,7 +312,7 @@ struct TABLE_SHARE TYPELIB keynames; /* Pointers to keynames */ TYPELIB fieldnames; /* Pointer to fieldnames */ TYPELIB *intervals; /* pointer to interval info */ - pthread_mutex_t mutex; /* For locking the share */ + pthread_mutex_t LOCK_ha_data; /* To protect access to ha_data */ TABLE_SHARE *next, **prev; /* Link to unused shares */ /* diff --git a/storage/myisam/ha_myisam.cc b/storage/myisam/ha_myisam.cc index cb8333767f8..9269b331754 100644 --- a/storage/myisam/ha_myisam.cc +++ b/storage/myisam/ha_myisam.cc @@ -1812,7 +1812,7 @@ int ha_myisam::info(uint flag) /* Update share */ if (share->tmp_table == NO_TMP_TABLE) - pthread_mutex_lock(&share->mutex); + pthread_mutex_lock(&share->LOCK_ha_data); share->keys_in_use.set_prefix(share->keys); share->keys_in_use.intersect_extended(misam_info.key_map); share->keys_for_keyread.intersect(share->keys_in_use); @@ -1822,7 +1822,7 @@ int ha_myisam::info(uint flag) (char*) misam_info.rec_per_key, sizeof(table->key_info[0].rec_per_key[0])*share->key_parts); if (share->tmp_table == NO_TMP_TABLE) - pthread_mutex_unlock(&share->mutex); + pthread_mutex_unlock(&share->LOCK_ha_data); /* Set data_file_name and index_file_name to point at the symlink value From b58e79566c5c5899c4d54a4b36ed4e876fa197ca Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 00:44:05 +0300 Subject: [PATCH 025/212] Backport of: ------------------------------------------------------------ revno: 2630.4.13 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Wed 2008-05-28 12:07:30 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. Get rid of remove_table_from_cache() function since it was doing two things at once -- waiting while no one uses particular table (now job of metadata locking) and removing TABLE/TABLE_SHARE instances from table definition cache (now job of expel_table_from_cache()). sql/mysql_priv.h: Got rid of remove_table_from_cache() function. Now one should use exclusive metadata lock for waiting until all other connections will stop using particular table and use expel_table_from_cache() for removing table instances and table share from table definition cache. Removed unused mysql_wait_completed_table() function. sql/sql_base.cc: Got rid of remove_table_from_cache() function. Now one should use exclusive metadata lock for waiting until all other connections will stop using particular table and use expel_table_from_cache() for removing table instances and table share from table definition cache. Removed unused mysql_wait_completed_table() function. sql/sql_table.cc: Get rid of two last places where we use remove_table_from_cache() by using wait_while_table_is_used() (which uses metadata locks) and close_cached_tables() instead. --- sql/mysql_priv.h | 8 --- sql/sql_base.cc | 182 +---------------------------------------------- sql/sql_table.cc | 29 ++++---- 3 files changed, 15 insertions(+), 204 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 063b36c4ddc..e96534bad15 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1542,13 +1542,6 @@ char *generate_partition_syntax(partition_info *part_info, Alter_info *alter_info); #endif -/* bits for last argument to remove_table_from_cache() */ -#define RTFC_NO_FLAG 0x0000 -#define RTFC_OWNED_BY_THD_FLAG 0x0001 -#define RTFC_WAIT_OTHER_THREAD_FLAG 0x0002 -#define RTFC_CHECK_KILLED_FLAG 0x0004 -bool remove_table_from_cache(THD *thd, const char *db, const char *table, - uint flags); bool notify_thread_having_shared_lock(THD *thd, THD *in_use); void expel_table_from_cache(THD *leave_thd, const char *db, const char *table_name); @@ -1670,7 +1663,6 @@ extern pthread_mutex_t LOCK_gdl; bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags); int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt); void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt); -void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table); /* Functions to work with system tables. */ bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 1356a2cd16f..40377a99d21 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1482,7 +1482,7 @@ void close_thread_tables(THD *thd, /* Note that we need to hold LOCK_open while changing the open_tables list. Another thread may work on it. - (See: remove_table_from_cache(), mysql_wait_completed_table()) + (See: notify_thread_having_shared_lock()) Closing a MERGE child before the parent would be fatal if the other thread tries to abort the MERGE lock in between. */ @@ -2228,7 +2228,7 @@ void unlink_open_table(THD *thd, TABLE *find, bool unlock) /* Note that we need to hold LOCK_open while changing the open_tables list. Another thread may work on it. - (See: remove_table_from_cache(), mysql_wait_completed_table()) + (See: notify_thread_having_shared_lock()) Closing a MERGE child before the parent would be fatal if the other thread tries to abort the MERGE lock in between. */ @@ -8444,154 +8444,6 @@ void flush_tables() } -/* - Mark all entries with the table as deleted to force an reopen of the table - - The table will be closed (not stored in cache) by the current thread when - close_thread_tables() is called. - - PREREQUISITES - Lock on LOCK_open() - - RETURN - 0 This thread now have exclusive access to this table and no other thread - can access the table until close_thread_tables() is called. - 1 Table is in use by another thread -*/ - -bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, - uint flags) -{ - char key[MAX_DBKEY_LENGTH]; - uint key_length; - TABLE *table; - TABLE_SHARE *share; - bool result= 0, signalled= 0; - DBUG_ENTER("remove_table_from_cache"); - DBUG_PRINT("enter", ("table: '%s'.'%s' flags: %u", db, table_name, flags)); - - key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; - for (;;) - { - result= signalled= 0; - - if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache, (uchar*) key, - key_length))) - { - I_P_List_iterator it(share->free_tables); - share->version= 0; - while ((table= it++)) - relink_unused(table); - - it.init(share->used_tables); - while ((table= it++)) - { - THD *in_use= table->in_use; - DBUG_ASSERT(in_use); - if (in_use != thd) - { - DBUG_PRINT("info", ("Table was in use by other thread")); - /* - Mark that table is going to be deleted from cache. This will - force threads that are in mysql_lock_tables() (but not yet - in thr_multi_lock()) to abort it's locks, close all tables and retry - */ - in_use->some_tables_deleted= 1; - - if (table->is_name_opened()) - { - DBUG_PRINT("info", ("Found another active instance of the table")); - result=1; - } - /* Kill delayed insert threads */ - if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && - ! in_use->killed) - { - in_use->killed= THD::KILL_CONNECTION; - pthread_mutex_lock(&in_use->mysys_var->mutex); - if (in_use->mysys_var->current_cond) - { - pthread_mutex_lock(in_use->mysys_var->current_mutex); - signalled= 1; - pthread_cond_broadcast(in_use->mysys_var->current_cond); - pthread_mutex_unlock(in_use->mysys_var->current_mutex); - } - pthread_mutex_unlock(&in_use->mysys_var->mutex); - } - /* - Now we must abort all tables locks used by this thread - as the thread may be waiting to get a lock for another table. - Note that we need to hold LOCK_open while going through the - list. So that the other thread cannot change it. The other - thread must also hold LOCK_open whenever changing the - open_tables list. Aborting the MERGE lock after a child was - closed and before the parent is closed would be fatal. - */ - for (TABLE *thd_table= in_use->open_tables; - thd_table ; - thd_table= thd_table->next) - { - /* Do not handle locks of MERGE children. */ - if (thd_table->db_stat && !thd_table->parent) // If table is open - signalled|= mysql_lock_abort_for_thread(thd, thd_table); - } - } - else - { - DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u", - table->db_stat)); - result= result || (flags & RTFC_OWNED_BY_THD_FLAG); - } - } - - while (unused_tables && !unused_tables->s->version) - free_cache_entry(unused_tables); - - DBUG_PRINT("info", ("share version: %lu ref_count: %u", - share->version, share->ref_count)); - if (share->ref_count == 0) - my_hash_delete(&table_def_cache, (uchar*) share); - } - - if (result && (flags & RTFC_WAIT_OTHER_THREAD_FLAG)) - { - /* - Signal any thread waiting for tables to be freed to - reopen their tables - */ - broadcast_refresh(); - DBUG_PRINT("info", ("Waiting for refresh signal")); - if (!(flags & RTFC_CHECK_KILLED_FLAG) || !thd->killed) - { - dropping_tables++; - if (likely(signalled)) - (void) pthread_cond_wait(&COND_refresh, &LOCK_open); - else - { - struct timespec abstime; - /* - It can happen that another thread has opened the - table but has not yet locked any table at all. Since - it can be locked waiting for a table that our thread - has done LOCK TABLE x WRITE on previously, we need to - ensure that the thread actually hears our signal - before we go to sleep. Thus we wait for a short time - and then we retry another loop in the - remove_table_from_cache routine. - */ - set_timespec(abstime, 10); - pthread_cond_timedwait(&COND_refresh, &LOCK_open, &abstime); - } - dropping_tables--; - continue; - } - } - break; - } - DBUG_RETURN(result); -} - - /** A callback to the server internals that is used to address special cases of the locking protocol. @@ -8894,34 +8746,6 @@ int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt) } -/* - SYNOPSIS - close_open_tables_and_downgrade() - RESULT VALUES - NONE - DESCRIPTION - We need to ensure that any thread that has managed to open the table - but not yet encountered our lock on the table is also thrown out to - ensure that no threads see our frm changes premature to the final - version. The intermediate versions are only meant for use after a - crash and later REPAIR TABLE. - We also downgrade locks after the upgrade to WRITE_ONLY -*/ - -/* purecov: begin deadcode */ -void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt) -{ - pthread_mutex_lock(&LOCK_open); - remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, - RTFC_WAIT_OTHER_THREAD_FLAG); - pthread_mutex_unlock(&LOCK_open); - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_downgrade_write(lpt->thd, lpt->table->parent ? lpt->table->parent : - lpt->table, lpt->old_lock_type); -} -/* purecov: end */ - - /* Tells if two (or more) tables have auto_increment columns and we want to lock those tables with a write lock. @@ -9165,7 +8989,7 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup) /* Note that we need to hold LOCK_open while changing the open_tables list. Another thread may work on it. - (See: remove_table_from_cache(), mysql_wait_completed_table()) + (See: notify_thread_having_shared_lock()) Closing a MERGE child before the parent would be fatal if the other thread tries to abort the MERGE lock in between. */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 640cd71fc62..5dee7015bd4 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4800,19 +4800,13 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, /* Close all instances of the table to allow repair to rename files */ if (lock_type == TL_WRITE && table->table->s->version) { - DBUG_PRINT("admin", ("removing table from cache")); - pthread_mutex_lock(&LOCK_open); - const char *old_message=thd->enter_cond(&COND_refresh, &LOCK_open, - "Waiting to get writelock"); - mysql_lock_abort(thd,table->table, TRUE); - remove_table_from_cache(thd, table->table->s->db.str, - table->table->s->table_name.str, - RTFC_WAIT_OTHER_THREAD_FLAG | - RTFC_CHECK_KILLED_FLAG); - thd->exit_cond(old_message); - DBUG_EXECUTE_IF("wait_in_mysql_admin_table", wait_for_kill_signal(thd);); - if (thd->killed) - goto err; + if (wait_while_table_is_used(thd, table->table, + HA_EXTRA_PREPARE_FOR_RENAME)) + goto err; + DBUG_EXECUTE_IF("wait_in_mysql_admin_table", + wait_for_kill_signal(thd); + if (thd->killed) + goto err;); /* Flush entries in the query cache involving this table. */ query_cache_invalidate3(thd, table->table, 0); open_for_modify= 0; @@ -5064,10 +5058,10 @@ send_result_message: table->table->file->info(HA_STATUS_CONST); else { - pthread_mutex_lock(&LOCK_open); - remove_table_from_cache(thd, table->table->s->db.str, - table->table->s->table_name.str, RTFC_NO_FLAG); - pthread_mutex_unlock(&LOCK_open); + TABLE_LIST *save_next_global= table->next_global; + table->next_global= 0; + close_cached_tables(thd, table, FALSE, FALSE); + table->next_global= save_next_global; } /* May be something modified consequently we have to invalidate cache */ query_cache_invalidate3(thd, table->table, 0); @@ -5133,6 +5127,7 @@ bool mysql_repair_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) bool mysql_optimize_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) { DBUG_ENTER("mysql_optimize_table"); + set_all_mdl_upgradable(tables); DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, "optimize", TL_WRITE, 1,0,0,0, &handler::ha_optimize, 0)); From 0dcead9f61ede536f0f4b4f2d291800f82fb5043 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 01:01:27 +0300 Subject: [PATCH 026/212] Backport of: ------------------------------------------------------------ revno: 2630.4.14 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Wed 2008-05-28 12:16:03 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. Removed unused code and adjusted names of functions/methods to better reflect their current function. sql/mysql_priv.h: Changed names of close_data_files_and_morph_locks() and close_handle_and_leave_table_as_lock() to better reflect their current function (locking is now responsibility of metadata locking subsystem). sql/sql_base.cc: Changed names of close_data_files_and_morph_locks() and close_handle_and_leave_table_as_lock() to better reflect their current function (locking is now responsibility of metadata locking subsystem). Also adjusted comments describing these functions. Got rid of TABLE::open_placeholder since it is no longer used (its value is never read anywhere). TABLE::needs_reopen_or_name_lock() was renamed to needs_reopen() since we no longer use name-locks sql/sql_handler.cc: TABLE::needs_reopen_or_name_lock() was renamed to needs_reopen() since we no longer use name-locks. sql/sql_insert.cc: TABLE::needs_reopen_or_name_lock() was renamed to needs_reopen() since we no longer use name-locks sql/sql_partition.cc: Changed name of close_data_files_and_morph_locks() to better reflect its current function (locking is now responsibility of metadata locking subsystem). sql/sql_table.cc: Changed names of close_data_files_and_morph_locks() and close_handle_and_leave_table_as_lock() to better reflect their current function (locking is now responsibility of metadata locking subsystem). Got rid of TABLE::open_placeholder since it is no longer used. sql/sql_trigger.cc: Changed name of close_data_files_and_morph_locks() to better reflect its current function (locking is now responsibility of metadata locking subsystem). sql/table.h: Got rid of TABLE::open_placeholder which is no longer used altough its value was set in several places no code reads it). Removed unused TABLE::is_name_opened() method. Finally TABLE::needs_reopen_or_name_lock() was renamed to needs_reopen() since we no longer use name-locks. --- sql/mysql_priv.h | 6 ++--- sql/sql_base.cc | 60 +++++++++++++++++--------------------------- sql/sql_handler.cc | 2 +- sql/sql_insert.cc | 2 +- sql/sql_partition.cc | 2 +- sql/sql_table.cc | 5 ++-- sql/sql_trigger.cc | 3 ++- sql/table.h | 24 ++---------------- 8 files changed, 35 insertions(+), 69 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index e96534bad15..1028c7d0d29 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1238,9 +1238,9 @@ bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, bool reopen_table(TABLE *table); bool reopen_tables(THD *thd, bool get_locks); thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table); -void close_data_files_and_morph_locks(THD *thd, const char *db, - const char *table_name); -void close_handle_and_leave_table_as_lock(TABLE *table); +void close_data_files_and_leave_as_placeholders(THD *thd, const char *db, + const char *table_name); +void close_handle_and_leave_table_as_placeholder(TABLE *table); void unlock_locked_tables(THD *thd); void execute_init_command(THD *thd, sys_var_str *init_command_var, rw_lock_t *var_mutex); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 40377a99d21..b21a9f3b931 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -726,25 +726,23 @@ static void reference_table_share(TABLE_SHARE *share) } -/* - Close file handle, but leave the table in the table cache +/** + Close file handle, but leave the table in THD::open_tables list + to allow its future reopening. - SYNOPSIS - close_handle_and_leave_table_as_lock() - table Table handler + @param table Table handler - NOTES - By leaving the table in the table cache, it disallows any other thread - to open the table + @note THD::killed will be set if we run out of memory - thd->killed will be set if we run out of memory + @note If closing a MERGE child, the calling function has to + take care for closing the parent too, if necessary. - If closing a MERGE child, the calling function has to take care for - closing the parent too, if necessary. + @todo Get rid of this function once we refactor LOCK TABLES + to keep around TABLE_LIST elements used for opening + of tables until UNLOCK TABLES. */ - -void close_handle_and_leave_table_as_lock(TABLE *table) +void close_handle_and_leave_table_as_placeholder(TABLE *table) { TABLE_SHARE *share, *old_share= table->s; char *key_buff; @@ -1048,8 +1046,8 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, char dbname[NAME_LEN+1]; char tname[NAME_LEN+1]; /* - Since close_data_files_and_morph_locks() frees share's memroot - we need to make copies of database and table names. + Since close_data_files_and_leave_as_placeholders() frees share's + memroot we need to make copies of database and table names. */ strmov(dbname, tab->s->db.str); strmov(tname, tab->s->table_name.str); @@ -1059,7 +1057,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, goto err_with_reopen; } pthread_mutex_lock(&LOCK_open); - close_data_files_and_morph_locks(thd, dbname, tname); + close_data_files_and_leave_as_placeholders(thd, dbname, tname); pthread_mutex_unlock(&LOCK_open); } } @@ -1082,7 +1080,8 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, goto err_with_reopen; } pthread_mutex_lock(&LOCK_open); - close_data_files_and_morph_locks(thd, table->db, table->table_name); + close_data_files_and_leave_as_placeholders(thd, table->db, + table->table_name); pthread_mutex_unlock(&LOCK_open); } } @@ -1529,7 +1528,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) detach_merge_children(table, TRUE); table->mdl_lock= 0; - if (table->needs_reopen_or_name_lock() || + if (table->needs_reopen() || thd->version != refresh_version || !table->db_stat) { free_cache_entry(table); @@ -1537,12 +1536,6 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) } else { - /* - Open placeholders have TABLE::db_stat set to 0, so they should be - handled by the first alternative. - */ - DBUG_ASSERT(!table->open_placeholder); - /* Assert that MERGE children are not attached in unused_tables. */ DBUG_ASSERT(!table->is_children_attached()); @@ -3244,7 +3237,7 @@ bool reopen_table(TABLE *table) /** Close all instances of a table open by this thread and replace - them with exclusive name-locks. + them with placeholder in THD::open_tables list for future reopening. @param thd Thread context @param db Database name for the table to be closed @@ -3259,11 +3252,11 @@ bool reopen_table(TABLE *table) the strings are used in a loop even after the share may be freed. */ -void close_data_files_and_morph_locks(THD *thd, const char *db, - const char *table_name) +void close_data_files_and_leave_as_placeholders(THD *thd, const char *db, + const char *table_name) { TABLE *table; - DBUG_ENTER("close_data_files_and_morph_locks"); + DBUG_ENTER("close_data_files_and_leave_as_placeholders"); safe_mutex_assert_owner(&LOCK_open); @@ -3277,11 +3270,6 @@ void close_data_files_and_morph_locks(THD *thd, const char *db, thd->lock= 0; } - /* - Note that open table list may contain a name-lock placeholder - for target table name if we process ALTER TABLE ... RENAME. - So loop below makes sense even if we are not under LOCK TABLES. - */ for (table=thd->open_tables; table ; table=table->next) { if (!strcmp(table->s->table_name.str, table_name) && @@ -3298,15 +3286,13 @@ void close_data_files_and_morph_locks(THD *thd, const char *db, won't have multiple children with the same db.table_name. */ mysql_lock_remove(thd, thd->locked_tables, table->parent, TRUE); - table->parent->open_placeholder= 1; - close_handle_and_leave_table_as_lock(table->parent); + close_handle_and_leave_table_as_placeholder(table->parent); } else mysql_lock_remove(thd, thd->locked_tables, table, TRUE); } - table->open_placeholder= 1; table->s->version= 0; - close_handle_and_leave_table_as_lock(table); + close_handle_and_leave_table_as_placeholder(table); } } DBUG_VOID_RETURN; diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 5c034a0452a..6087b0b7700 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -801,7 +801,7 @@ void mysql_ha_flush(THD *thd) if (hash_tables->table && (hash_tables->table->mdl_lock && mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock) || - hash_tables->table->needs_reopen_or_name_lock())) + hash_tables->table->needs_reopen())) mysql_ha_close_table(thd, hash_tables); } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 525f6b9a4d8..5da9f2e6bd4 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2638,7 +2638,7 @@ bool Delayed_insert::handle_inserts(void) thd_proc_info(&thd, "insert"); max_rows= delayed_insert_limit; - if (thd.killed || table->needs_reopen_or_name_lock()) + if (thd.killed || table->needs_reopen()) { thd.killed= THD::KILL_CONNECTION; max_rows= ULONG_MAX; // Do as much as possible diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 48f33bf3295..3c67574d8c1 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -6262,7 +6262,7 @@ static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) and we set db_stat to zero to ensure we don't close twice. */ pthread_mutex_lock(&LOCK_open); - close_data_files_and_morph_locks(thd, db, table_name); + close_data_files_and_leave_as_placeholders(thd, db, table_name); pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 5dee7015bd4..649ba28bcac 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -7415,7 +7415,7 @@ view_err: pthread_mutex_lock(&LOCK_open); - close_data_files_and_morph_locks(thd, db, table_name); + close_data_files_and_leave_as_placeholders(thd, db, table_name); error=0; save_old_db_type= old_db_type; @@ -7507,8 +7507,7 @@ view_err: object to make it suitable for reopening. */ DBUG_ASSERT(t_table == table); - table->open_placeholder= 1; - close_handle_and_leave_table_as_lock(table); + close_handle_and_leave_table_as_placeholder(table); } else { diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index a623b3c80f3..a7a4d48b593 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -487,7 +487,8 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) if (!result && thd->locked_tables) { /* Make table suitable for reopening */ - close_data_files_and_morph_locks(thd, tables->db, tables->table_name); + close_data_files_and_leave_as_placeholders(thd, tables->db, + tables->table_name); thd->in_lock_tables= 1; if (reopen_tables(thd, 1)) { diff --git a/sql/table.h b/sql/table.h index 60560029725..a31b96e0828 100644 --- a/sql/table.h +++ b/sql/table.h @@ -793,24 +793,6 @@ public: */ my_bool key_read; my_bool no_keyread; - /* - Placeholder for an open table which prevents other connections - from taking name-locks on this table. Typically used with - TABLE_SHARE::version member to take an exclusive name-lock on - this table name -- a name lock that not only prevents other - threads from opening the table, but also blocks other name - locks. This is achieved by: - - setting open_placeholder to 1 - this will block other name - locks, as wait_for_locked_table_name will be forced to wait, - see table_is_used for details. - - setting version to 0 - this will force other threads to close - the instance of this table and wait (this is the same approach - as used for usual name locks). - An exclusively name-locked table currently can have no handler - object associated with it (db_stat is always 0), but please do - not rely on that. - */ - my_bool open_placeholder; my_bool locked_by_logger; my_bool no_replicate; my_bool locked_by_name; @@ -874,12 +856,10 @@ public: read_set= &def_read_set; write_set= &def_write_set; } - /* Is table open or should be treated as such by name-locking? */ - inline bool is_name_opened() { return db_stat || open_placeholder; } /* - Is this instance of the table should be reopen or represents a name-lock? + Is this instance of the table should be reopen? */ - inline bool needs_reopen_or_name_lock() + inline bool needs_reopen() { return s->version != refresh_version; } bool is_children_attached(void); }; From cf45b61a6a611415161e6176b8c8ae85a51e9349 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 01:13:06 +0300 Subject: [PATCH 027/212] Backport of: ------------------------------------------------------------ revno: 2630.4.16 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w timestamp: Thu 2008-05-29 09:45:02 +0400 message: WL#3726 "DDL locking for all metadata objects". After review changes in progress. Tweaked some comments and did some renames to avoid ambiguites. sql/mysql_priv.h: Removed name_lock_locked_table() function. sql/sql_base.cc: Got rid of name_lock_locked_table() function after replacing the only call to it with its body. Simplified open_table() code by making "action" argument mandatory (i.e. one now should always pass non-0 pointer in this argument). Renamed TABLE_LIST::open_table_type to open_type to avoid confusing it with type of table. Adjusted comments according to review. sql/sql_handler.cc: Added comment clarifying in which cases we can have TABLE::mdl_lock set to 0. sql/sql_insert.cc: Now the 4th argument of open_table() is mandatory (it makes no sense to complicate open_table() code when we can simply pass dummy variable). sql/sql_parse.cc: Renamed TABLE_LIST::open_table_type to open_type to avoid confusing it with type of table. sql/sql_prepare.cc: Renamed TABLE_LIST::open_table_type to open_type to avoid confusing it with type of table. sql/sql_table.cc: Now the 4th argument of open_table() is mandatory (it makes no sense to complicate open_table() code when we can simply pass dummy variable). sql/sql_trigger.cc: Replaced the only call to name_lock_locked_table() function with its body. sql/sql_view.cc: Renamed TABLE_LIST::open_table_type to open_type to avoid confusing it with type of table. sql/table.h: Renamed TABLE_LIST::open_table_type to open_type (to avoid confusing it with type of table) and improved comments describing this member. --- sql/mysql_priv.h | 1 - sql/sql_base.cc | 135 +++++++++++++++++++-------------------------- sql/sql_handler.cc | 1 + sql/sql_insert.cc | 4 +- sql/sql_parse.cc | 2 +- sql/sql_prepare.cc | 2 +- sql/sql_table.cc | 3 +- sql/sql_trigger.cc | 11 +++- sql/sql_view.cc | 2 +- sql/table.h | 26 ++++++--- 10 files changed, 93 insertions(+), 94 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 1028c7d0d29..3e3d3b6df24 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1227,7 +1227,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem, bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, char *cache_key, uint cache_key_length, MEM_ROOT *mem_root, uint flags); -bool name_lock_locked_table(THD *thd, TABLE_LIST *tables); bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list); TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name); TABLE *find_write_locked_table(TABLE *list, const char *db, diff --git a/sql/sql_base.cc b/sql/sql_base.cc index b21a9f3b931..13218f3a193 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2340,39 +2340,6 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond) } -/** - Exclusively name-lock a table that is already write-locked by the - current thread. - - @param thd current thread context - @param tables table list containing one table to open. - - @return FALSE on success, TRUE otherwise. -*/ - -bool name_lock_locked_table(THD *thd, TABLE_LIST *tables) -{ - bool result= TRUE; - - DBUG_ENTER("name_lock_locked_table"); - - /* Under LOCK TABLES we must only accept write locked tables. */ - tables->table= find_write_locked_table(thd->open_tables, tables->db, - tables->table_name); - - if (tables->table) - { - /* - Ensures that table is opened only by this thread and that no - other statement will open this table. - */ - result= wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN); - } - - DBUG_RETURN(result); -} - - /* Open table for which this thread has exclusive meta-data lock. @@ -2576,9 +2543,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, /* Parsing of partitioning information from .frm needs thd->lex set up. */ DBUG_ASSERT(thd->lex->is_lex_started); - /* find a unused table in the open table cache */ - if (action) - *action= OT_NO_ACTION; + *action= OT_NO_ACTION; /* an open table operation needs a lot of the stack space */ if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias)) @@ -2716,6 +2681,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, enum legacy_db_type not_used; build_table_filename(path, sizeof(path) - 1, table_list->db, table_list->table_name, reg_ext, 0); + /* + Note that we can't be 100% sure that it is a view since it's + possible that we either simply have not found unused TABLE + instance in THD::open_tables list or were unable to open table + during prelocking process (in this case in theory we still + should hold shared metadata lock on it). + */ if (mysql_frm_type(thd, path, ¬_used) == FRMTYPE_VIEW) { if (!tdc_open_view(thd, table_list, alias, key, key_length, @@ -2741,28 +2713,25 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } /* - Non pre-locked/LOCK TABLES mode, and the table is not temporary: - this is the normal use case. - Now we should: - - try to find the table in the table cache. - - if one of the discovered TABLE instances is name-locked - (table->s->version == 0) or some thread has started FLUSH TABLES - (refresh_version > table->s->version), back off -- we have to wait - until no one holds a name lock on the table. - - if there is no such TABLE in the name cache, read the table definition - and insert it into the cache. - We perform all of the above under LOCK_open which currently protects - the open cache (also known as table cache) and table definitions stored - on disk. + Non pre-locked/LOCK TABLES mode, and the table is not temporary. + This is the normal use case. */ mdl_lock= table_list->mdl_lock; mdl_add_lock(&thd->mdl_context, mdl_lock); - if (table_list->open_table_type) + if (table_list->open_type) { + /* + In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table + may not yet exist. Let's acquire an exclusive lock for that + case. If later it turns out the table existsed, we will + downgrade the lock to shared. Note that, according to the + locking protocol, all exclusive locks must be acquired before + shared locks. This invariant is preserved here and is also + enforced by asserts in metadata locking subsystem. + */ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); - /* TODO: This case can be significantly optimized. */ if (mdl_acquire_exclusive_locks(&thd->mdl_context)) DBUG_RETURN(0); } @@ -2776,7 +2745,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, MDL_HIGH_PRIO : MDL_NORMAL_PRIO); if (mdl_acquire_shared_lock(mdl_lock, &retry)) { - if (action && retry) + if (retry) *action= OT_BACK_OFF_AND_RETRY; DBUG_RETURN(0); } @@ -2798,13 +2767,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ! (flags & MYSQL_LOCK_IGNORE_FLUSH)) { /* Someone did a refresh while thread was opening tables */ - if (action) - *action= OT_BACK_OFF_AND_RETRY; + *action= OT_BACK_OFF_AND_RETRY; pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } - if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE) + if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE) { bool exists; @@ -2818,7 +2786,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } /* Table exists. Let us try to open it. */ } - else if (table_list->open_table_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL) + else if (table_list->open_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL) { pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); @@ -2926,8 +2894,17 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { if (!(flags & MYSQL_LOCK_IGNORE_FLUSH)) { - if (action) - *action= OT_BACK_OFF_AND_RETRY; + /* + We already have an MDL lock. But we have encountered an old + version of table in the table definition cache which is possible + when someone changes the table version directly in the cache + without acquiring a metadata lock (e.g. this can happen during + "rolling" FLUSH TABLE(S)). + Note, that to avoid a "busywait" in this case, we have to wait + separately in the caller for old table versions to go away + (see tdc_wait_for_old_versions()). + */ + *action= OT_BACK_OFF_AND_RETRY; release_table_share(share); pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); @@ -2966,18 +2943,15 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { my_free(table, MYF(0)); - if (action) + if (error == 7) { - if (error == 7) - { - share->version= 0; - *action= OT_DISCOVER; - } - else if (share->crashed) - { - share->version= 0; - *action= OT_REPAIR; - } + share->version= 0; + *action= OT_DISCOVER; + } + else if (share->crashed) + { + share->version= 0; + *action= OT_REPAIR; } goto err_unlock; @@ -2996,16 +2970,19 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, pthread_mutex_unlock(&LOCK_open); - // Table existed - if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE) + /* + In CREATE TABLE .. If NOT EXISTS .. SELECT we have found that + table exists now we should downgrade our exclusive metadata + lock on this table to shared metadata lock. + */ + if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE) mdl_downgrade_exclusive_locks(&thd->mdl_context); table->mdl_lock= mdl_lock; - if (action) - { - table->next=thd->open_tables; /* Link into simple list */ - thd->open_tables=table; - } + + table->next=thd->open_tables; /* Link into simple list */ + thd->open_tables=table; + table->reginfo.lock_type=TL_READ; /* Assume read */ reset: @@ -3856,8 +3833,8 @@ err: /** - Auxiliary routine which finalizes process of TABLE object creation - by loading triggers and handling implicitly emptied tables. + Finalize the process of TABLE creation by loading table triggers + and taking action if a HEAP table content was emptied implicitly. */ static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry) @@ -4636,7 +4613,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) table and successful table creation. ... */ - if (tables->open_table_type) + if (tables->open_type) continue; if (action) diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 6087b0b7700..87e9538b48f 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -798,6 +798,7 @@ void mysql_ha_flush(THD *thd) for (uint i= 0; i < thd->handler_tables_hash.records; i++) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); + /* TABLE::mdl_lock is 0 for temporary tables so we need extra check. */ if (hash_tables->table && (hash_tables->table->mdl_lock && mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock) || diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 5da9f2e6bd4..a0c198f3196 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3453,6 +3453,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, Item *item; Field *tmp_field; bool not_used; + enum_open_table_action not_used2; DBUG_ENTER("create_table_from_items"); tmp_table.alias= 0; @@ -3544,8 +3545,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, } else { - if (!(table= open_table(thd, create_table, thd->mem_root, - (enum_open_table_action*) 0, + if (!(table= open_table(thd, create_table, thd->mem_root, ¬_used2, MYSQL_OPEN_TEMPORARY_ONLY)) && !create_info->table_existed) { diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2b2c736fd9e..2c043922cc8 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2631,7 +2631,7 @@ case SQLCOM_PREPARE: if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) { lex->link_first_table_back(create_table, link_to_local); - create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE; + create_table->open_type= TABLE_LIST::OPEN_OR_CREATE; } if (!(res= open_and_lock_tables(thd, lex->query_tables))) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 582e18a3abf..5efa0cea7a9 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1673,7 +1673,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt) if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) { lex->link_first_table_back(create_table, link_to_local); - create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE; + create_table->open_type= TABLE_LIST::OPEN_OR_CREATE; } if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0)) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 649ba28bcac..9888dceef54 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -7208,12 +7208,13 @@ view_err: { if (table->s->tmp_table) { + enum_open_table_action not_used; TABLE_LIST tbl; bzero((void*) &tbl, sizeof(tbl)); tbl.db= new_db; tbl.table_name= tbl.alias= tmp_name; /* Table is in thd->temporary_tables */ - new_table= open_table(thd, &tbl, thd->mem_root, (enum_open_table_action*) 0, + new_table= open_table(thd, &tbl, thd->mem_root, ¬_used, MYSQL_LOCK_IGNORE_FLUSH); } else diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index a7a4d48b593..4e2b77292d8 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -446,8 +446,17 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) if (thd->locked_tables) { - if (name_lock_locked_table(thd, tables)) + /* Under LOCK TABLES we must only accept write locked tables. */ + if (!(tables->table= find_write_locked_table(thd->open_tables, tables->db, + tables->table_name))) goto end; + /* + Ensure that table is opened only by this thread and that no other + statement will open this table. + */ + if (wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN)) + goto end; + pthread_mutex_lock(&LOCK_open); } else diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 55181a58e53..c40f6643042 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -395,7 +395,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, goto err; lex->link_first_table_back(view, link_to_local); - view->open_table_type= TABLE_LIST::TAKE_EXCLUSIVE_MDL; + view->open_type= TABLE_LIST::TAKE_EXCLUSIVE_MDL; if (open_and_lock_tables(thd, lex->query_tables)) { diff --git a/sql/table.h b/sql/table.h index a31b96e0828..e64111ef988 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1349,14 +1349,26 @@ struct TABLE_LIST used for implicit LOCK TABLES only and won't be used in real statement. */ bool prelocking_placeholder; - /* - This TABLE_LIST object corresponds to the table/view which requires - special handling/meta-data locking. For example this is a target - table in CREATE TABLE ... SELECT so it is possible that it does not - exist and we should take exclusive meta-data lock on it in this - case. + /** + Indicates that if TABLE_LIST object corresponds to the table/view + which requires special handling/meta-data locking. */ - enum {NORMAL_OPEN= 0, OPEN_OR_CREATE, TAKE_EXCLUSIVE_MDL} open_table_type; + enum + { + /* Normal open, shared metadata lock should be taken. */ + NORMAL_OPEN= 0, + /* + It's target table of CREATE TABLE ... SELECT so we should + either open table if it exists (and take shared metadata lock) + or take exclusive metadata lock if it doesn't exist. + */ + OPEN_OR_CREATE, + /* + It's target view of CREATE/ALTER VIEW. We should take exclusive + metadata lock for this table list element. + */ + TAKE_EXCLUSIVE_MDL + } open_type; /** Indicates that for this table/view we need to take shared metadata lock which should be upgradable to exclusive metadata lock. From e23046d1bcaf54838167dd6bd3d8c6900e24b2ef Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 01:33:22 +0300 Subject: [PATCH 028/212] Backport of: ------------------------------------------------------------ revno: 2630.4.17 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Thu 2008-05-29 16:52:56 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. "The great correction of names". Renamed MDL_LOCK and MDL_LOCK_DATA classes to make usage of these names in metadata locking subsystem consistent with other parts of server (i.e. thr_lock.cc). Now we MDL_LOCK_DATA corresponds to request for a lock and MDL_LOCK to the lock itself. Adjusted code in MDL subsystem and other places using these classes accordingly. Did similar thing for GLOBAL_MDL_LOCK_DATA class and also changed name of its members to correspond to names of MDL_LOCK_DATA members. Finally got rid of usage of one letter variables in MDL code since it makes code harder to search in (according to reviewer). --- sql/ha_ndbcluster_binlog.cc | 8 +- sql/lock.cc | 11 +- sql/log_event.cc | 9 +- sql/mdl.cc | 624 ++++++++++++++++++------------------ sql/mdl.h | 94 +++--- sql/sp_head.cc | 13 +- sql/sql_base.cc | 47 +-- sql/sql_delete.cc | 18 +- sql/sql_handler.cc | 22 +- sql/sql_parse.cc | 6 +- sql/sql_show.cc | 14 +- sql/sql_table.cc | 67 ++-- sql/table.cc | 5 +- sql/table.h | 6 +- 14 files changed, 484 insertions(+), 460 deletions(-) diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index f272526edb8..14d14db6b7d 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -140,8 +140,8 @@ static Uint64 *p_latest_trans_gci= 0; */ static TABLE *ndb_binlog_index= 0; static TABLE_LIST binlog_tables; -static MDL_LOCK binlog_mdl_lock; -static char binlog_mdlkey[MAX_DBKEY_LENGTH]; +static MDL_LOCK_DATA binlog_mdl_lock_data; +static char binlog_mdlkey[MAX_DBKEY_LENGTH]; /* Helper functions @@ -2343,9 +2343,9 @@ static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index) tables->alias= tables->table_name= reptable; tables->lock_type= TL_WRITE; thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE; - mdl_init_lock(&binlog_mdl_lock, binlog_mdlkey, 0, tables->db, + mdl_init_lock(&binlog_mdl_lock_data, binlog_mdlkey, 0, tables->db, tables->table_name); - tables->mdl_lock= &binlog_mdl_lock; + tables->mdl_lock_data= &binlog_mdl_lock_data; tables->required_type= FRMTYPE_TABLE; uint counter; thd->clear_error(); diff --git a/sql/lock.cc b/sql/lock.cc index 38b2d22f91f..b5eaaa05fff 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -966,15 +966,16 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, bool lock_table_names(THD *thd, TABLE_LIST *table_list) { TABLE_LIST *lock_table; - MDL_LOCK *mdl_lock; + MDL_LOCK_DATA *mdl_lock_data; for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) { - if (!(mdl_lock= mdl_alloc_lock(0, lock_table->db, lock_table->table_name, - thd->mem_root))) + if (!(mdl_lock_data= mdl_alloc_lock(0, lock_table->db, + lock_table->table_name, + thd->mem_root))) goto end; - mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, mdl_lock); + mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, mdl_lock_data); } if (mdl_acquire_exclusive_locks(&thd->mdl_context)) return 1; diff --git a/sql/log_event.cc b/sql/log_event.cc index fd0e20d690d..92de9933181 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -8058,7 +8058,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) { RPL_TABLE_LIST *table_list; char *db_mem, *tname_mem, *mdlkey; - MDL_LOCK *mdl_lock; + MDL_LOCK_DATA *mdl_lock_data; size_t dummy_len; void *memory; DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)"); @@ -8073,7 +8073,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) &table_list, (uint) sizeof(RPL_TABLE_LIST), &db_mem, (uint) NAME_LEN + 1, &tname_mem, (uint) NAME_LEN + 1, - &mdl_lock, sizeof(MDL_LOCK), + &mdl_lock_data, sizeof(MDL_LOCK_DATA), &mdlkey, MAX_DBKEY_LENGTH, NullS))) DBUG_RETURN(HA_ERR_OUT_OF_MEM); @@ -8087,8 +8087,9 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) table_list->updating= 1; strmov(table_list->db, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len)); strmov(table_list->table_name, m_tblnam); - mdl_init_lock(mdl_lock, mdlkey, 0, table_list->db, table_list->table_name); - table_list->mdl_lock= mdl_lock; + mdl_init_lock(mdl_lock_data, mdlkey, 0, table_list->db, + table_list->table_name); + table_list->mdl_lock_data= mdl_lock_data; int error= 0; diff --git a/sql/mdl.cc b/sql/mdl.cc index ddd027c4027..64c011d34bf 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -26,34 +26,34 @@ /** The lock context. Created internally for an acquired lock. - For a given name, there exists only one MDL_LOCK_DATA instance, + For a given name, there exists only one MDL_LOCK instance, and it exists only when the lock has been granted. Can be seen as an MDL subsystem's version of TABLE_SHARE. */ -struct MDL_LOCK_DATA +struct MDL_LOCK { - I_P_List active_shared; + I_P_List active_shared; /* There can be several upgraders and active exclusive belonging to the same context. */ - I_P_List active_shared_waiting_upgrade; - I_P_List active_exclusive; - I_P_List waiting_exclusive; + I_P_List active_shared_waiting_upgrade; + I_P_List active_exclusive; + I_P_List waiting_exclusive; /** - Number of MDL_LOCK objects associated with this MDL_LOCK_DATA instance + Number of MDL_LOCK_DATA objects associated with this MDL_LOCK instance and therefore present in one of above lists. Note that this number doesn't account for pending requests for shared lock since we don't - associate them with MDL_LOCK_DATA and don't keep them in any list. + associate them with MDL_LOCK and don't keep them in any list. */ - uint lock_count; + uint lock_data_count; void *cached_object; mdl_cached_object_release_hook cached_object_release_hook; - MDL_LOCK_DATA() : cached_object(0), cached_object_release_hook(0) {} + MDL_LOCK() : cached_object(0), cached_object_release_hook(0) {} - MDL_LOCK *get_key_owner() + MDL_LOCK_DATA *get_key_owner() { return !active_shared.is_empty() ? active_shared.head() : @@ -63,9 +63,9 @@ struct MDL_LOCK_DATA active_exclusive.head() : waiting_exclusive.head())); } - bool has_one_lock() + bool has_one_lock_data() { - return (lock_count == 1); + return (lock_data_count == 1); } }; @@ -82,18 +82,18 @@ HASH mdl_locks; or shared upgradable lock on particular object. */ -struct MDL_GLOBAL_LOCK_DATA +struct MDL_GLOBAL_LOCK { - uint shared_pending; - uint shared_acquired; - uint intention_exclusive_acquired; + uint waiting_shared; + uint active_shared; + uint active_intention_exclusive; } global_lock; extern "C" uchar *mdl_locks_key(const uchar *record, size_t *length, my_bool not_used __attribute__((unused))) { - MDL_LOCK_DATA *entry=(MDL_LOCK_DATA*) record; + MDL_LOCK *entry=(MDL_LOCK*) record; *length= entry->get_key_owner()->key_length; return (uchar*) entry->get_key_owner()->key; } @@ -123,8 +123,8 @@ void mdl_init() pthread_cond_init(&COND_mdl, NULL); my_hash_init(&mdl_locks, &my_charset_bin, 16 /* FIXME */, 0, 0, mdl_locks_key, 0, 0); - global_lock.shared_pending= global_lock.shared_acquired= 0; - global_lock.intention_exclusive_acquired= 0; + global_lock.waiting_shared= global_lock.active_shared= 0; + global_lock.active_intention_exclusive= 0; } @@ -211,18 +211,18 @@ void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) { - MDL_LOCK *l; + MDL_LOCK_DATA *lock_data; DBUG_ASSERT(dst->thd == src->thd); if (!src->locks.is_empty()) { - I_P_List_iterator it(src->locks); - while ((l= it++)) + I_P_List_iterator it(src->locks); + while ((lock_data= it++)) { - DBUG_ASSERT(l->ctx); - l->ctx= dst; - dst->locks.push_front(l); + DBUG_ASSERT(lock_data->ctx); + lock_data->ctx= dst; + dst->locks.push_front(lock_data); } src->locks.empty(); } @@ -247,7 +247,7 @@ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) request encloses calls to mdl_acquire_shared_lock() and mdl_release_locks(). - @param mdl Pointer to an MDL_LOCK object to initialize + @param lock_data Pointer to an MDL_LOCK_DATA object to initialize @param key_buff Pointer to the buffer for key for the lock request (should be at least strlen(db) + strlen(name) + 2 bytes, or, if the lengths are not known, MAX_DBNAME_LENGTH) @@ -269,19 +269,19 @@ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) they share the same name space in the SQL standard. */ -void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db, - const char *name) +void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, + const char *db, const char *name) { int4store(key, type); - mdl->key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; - mdl->key= key; - mdl->type= MDL_SHARED; - mdl->state= MDL_PENDING; - mdl->prio= MDL_NORMAL_PRIO; - mdl->is_upgradable= FALSE; + lock_data->key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; + lock_data->key= key; + lock_data->type= MDL_SHARED; + lock_data->state= MDL_PENDING; + lock_data->prio= MDL_NORMAL_PRIO; + lock_data->is_upgradable= FALSE; #ifndef DBUG_OFF - mdl->ctx= 0; - mdl->lock_data= 0; + lock_data->ctx= 0; + lock_data->lock= 0; #endif } @@ -305,19 +305,19 @@ void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db, @retval non-0 Pointer to an object representing a lock request */ -MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name, - MEM_ROOT *root) +MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name, + MEM_ROOT *root) { - MDL_LOCK *lock; + MDL_LOCK_DATA *lock_data; char *key; - if (!multi_alloc_root(root, &lock, sizeof(MDL_LOCK), &key, + if (!multi_alloc_root(root, &lock_data, sizeof(MDL_LOCK_DATA), &key, MAX_DBKEY_LENGTH, NULL)) return NULL; - mdl_init_lock(lock, key, type, db, name); + mdl_init_lock(lock_data, key, type, db, name); - return lock; + return lock_data; } @@ -334,16 +334,16 @@ MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name, @param context The MDL context to associate the lock with. There should be no more than one context per connection, to avoid deadlocks. - @param lock The lock request to be added. + @param lock_data The lock request to be added. */ -void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock) +void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) { DBUG_ENTER("mdl_add_lock"); - DBUG_ASSERT(lock->state == MDL_PENDING); - DBUG_ASSERT(!lock->ctx); - lock->ctx= context; - context->locks.push_front(lock); + DBUG_ASSERT(lock_data->state == MDL_PENDING); + DBUG_ASSERT(!lock_data->ctx); + lock_data->ctx= context; + context->locks.push_front(lock_data); DBUG_VOID_RETURN; } @@ -372,16 +372,16 @@ void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock) void mdl_remove_all_locks(MDL_CONTEXT *context) { - MDL_LOCK *l; - I_P_List_iterator it(context->locks); - while ((l= it++)) + MDL_LOCK_DATA *lock_data; + I_P_List_iterator it(context->locks); + while ((lock_data= it++)) { /* Reset lock request back to its initial state. */ - l->type= MDL_SHARED; - l->prio= MDL_NORMAL_PRIO; - l->is_upgradable= FALSE; + lock_data->type= MDL_SHARED; + lock_data->prio= MDL_NORMAL_PRIO; + lock_data->is_upgradable= FALSE; #ifndef DBUG_OFF - l->ctx= 0; + lock_data->ctx= 0; #endif } context->locks.empty(); @@ -389,20 +389,20 @@ void mdl_remove_all_locks(MDL_CONTEXT *context) /** - Auxiliary functions needed for creation/destruction of MDL_LOCK_DATA + Auxiliary functions needed for creation/destruction of MDL_LOCK objects. @todo This naive implementation should be replaced with one that saves on memory allocation by reusing released objects. */ -static MDL_LOCK_DATA* get_lock_data_object(void) +static MDL_LOCK* get_lock_object(void) { - return new MDL_LOCK_DATA(); + return new MDL_LOCK(); } -static void release_lock_data_object(MDL_LOCK_DATA *lock) +static void release_lock_object(MDL_LOCK *lock) { delete lock; } @@ -421,27 +421,27 @@ static void release_lock_data_object(MDL_LOCK_DATA *lock) This function must be called after the lock is added to a context. - @param lock [in] Lock request object for lock to be acquired - @param retry [out] Indicates that conflicting lock exists and another - attempt should be made after releasing all current - locks and waiting for conflicting lock go away - (using mdl_wait_for_locks()). + @param lock_data [in] Lock request object for lock to be acquired + @param retry [out] Indicates that conflicting lock exists and another + attempt should be made after releasing all current + locks and waiting for conflicting lock go away + (using mdl_wait_for_locks()). @retval FALSE Success. @retval TRUE Failure. Either error occured or conflicting lock exists. In the latter case "retry" parameter is set to TRUE. */ -bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry) +bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry) { - MDL_LOCK_DATA *lock_data; + MDL_LOCK *lock; *retry= FALSE; - DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_PENDING); + DBUG_ASSERT(lock_data->type == MDL_SHARED && lock_data->state == MDL_PENDING); safe_mutex_assert_not_owner(&LOCK_open); - if (l->ctx->has_global_shared_lock && l->is_upgradable) + if (lock_data->ctx->has_global_shared_lock && lock_data->is_upgradable) { my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); return TRUE; @@ -449,46 +449,46 @@ bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry) pthread_mutex_lock(&LOCK_mdl); - if (l->is_upgradable && - (global_lock.shared_acquired || global_lock.shared_pending)) + if (lock_data->is_upgradable && + (global_lock.active_shared || global_lock.waiting_shared)) { pthread_mutex_unlock(&LOCK_mdl); *retry= TRUE; return TRUE; } - if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key, - l->key_length))) + if (!(lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, + lock_data->key_length))) { - lock_data= get_lock_data_object(); - lock_data->active_shared.push_front(l); - lock_data->lock_count= 1; - my_hash_insert(&mdl_locks, (uchar*)lock_data); - l->state= MDL_ACQUIRED; - l->lock_data= lock_data; - if (l->is_upgradable) - global_lock.intention_exclusive_acquired++; + lock= get_lock_object(); + lock->active_shared.push_front(lock_data); + lock->lock_data_count= 1; + my_hash_insert(&mdl_locks, (uchar*)lock); + lock_data->state= MDL_ACQUIRED; + lock_data->lock= lock; + if (lock_data->is_upgradable) + global_lock.active_intention_exclusive++; } else { - if ((lock_data->active_exclusive.is_empty() && - (l->prio == MDL_HIGH_PRIO || - lock_data->waiting_exclusive.is_empty() && - lock_data->active_shared_waiting_upgrade.is_empty())) || - (!lock_data->active_exclusive.is_empty() && - lock_data->active_exclusive.head()->ctx == l->ctx)) + if ((lock->active_exclusive.is_empty() && + (lock_data->prio == MDL_HIGH_PRIO || + lock->waiting_exclusive.is_empty() && + lock->active_shared_waiting_upgrade.is_empty())) || + (!lock->active_exclusive.is_empty() && + lock->active_exclusive.head()->ctx == lock_data->ctx)) { /* When exclusive lock comes from the same context we can satisfy our shared lock. This is required for CREATE TABLE ... SELECT ... and ALTER VIEW ... AS .... */ - lock_data->active_shared.push_front(l); - lock_data->lock_count++; - l->state= MDL_ACQUIRED; - l->lock_data= lock_data; - if (l->is_upgradable) - global_lock.intention_exclusive_acquired++; + lock->active_shared.push_front(lock_data); + lock->lock_data_count++; + lock_data->state= MDL_ACQUIRED; + lock_data->lock= lock; + if (lock_data->is_upgradable) + global_lock.active_intention_exclusive++; } else *retry= TRUE; @@ -499,7 +499,7 @@ bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry) } -static void release_lock(MDL_LOCK *l); +static void release_lock(MDL_LOCK_DATA *lock_data); /** @@ -523,11 +523,11 @@ static void release_lock(MDL_LOCK *l); bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) { - MDL_LOCK *l, *lh; - MDL_LOCK_DATA *lock_data; + MDL_LOCK_DATA *lock_data, *conf_lock_data; + MDL_LOCK *lock; bool signalled= FALSE; const char *old_msg; - I_P_List_iterator it(context->locks); + I_P_List_iterator it(context->locks); THD *thd= context->thd; DBUG_ASSERT(thd == current_thd); @@ -544,34 +544,35 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); - while ((l= it++)) + while ((lock_data= it++)) { - DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING); - if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key, - l->key_length))) + DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE && + lock_data->state == MDL_PENDING); + if (!(lock= (MDL_LOCK *) my_hash_search(&mdl_locks, (uchar*)lock_data->key, + lock_data->key_length))) { - lock_data= get_lock_data_object(); - lock_data->waiting_exclusive.push_front(l); - lock_data->lock_count= 1; - my_hash_insert(&mdl_locks, (uchar*)lock_data); - l->lock_data= lock_data; + lock= get_lock_object(); + lock->waiting_exclusive.push_front(lock_data); + lock->lock_data_count= 1; + my_hash_insert(&mdl_locks, (uchar*)lock); + lock_data->lock= lock; } else { - lock_data->waiting_exclusive.push_front(l); - lock_data->lock_count++; - l->lock_data= lock_data; + lock->waiting_exclusive.push_front(lock_data); + lock->lock_data_count++; + lock_data->lock= lock; } } while (1) { it.rewind(); - while ((l= it++)) + while ((lock_data= it++)) { - lock_data= l->lock_data; + lock= lock_data->lock; - if (global_lock.shared_acquired || global_lock.shared_pending) + if (global_lock.active_shared || global_lock.waiting_shared) { /* There is active or pending global shared lock we have @@ -580,8 +581,8 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) signalled= TRUE; break; } - else if (!lock_data->active_exclusive.is_empty() || - !lock_data->active_shared_waiting_upgrade.is_empty()) + else if (!lock->active_exclusive.is_empty() || + !lock->active_shared_waiting_upgrade.is_empty()) { /* Exclusive MDL owner won't wait on table-level lock the same @@ -591,13 +592,14 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) signalled= TRUE; break; } - else if ((lh= lock_data->active_shared.head())) + else if ((conf_lock_data= lock->active_shared.head())) { - signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd); + signalled= notify_thread_having_shared_lock(thd, + conf_lock_data->ctx->thd); break; } } - if (!l) + if (!lock_data) break; if (signalled) pthread_cond_wait(&COND_mdl, &LOCK_mdl); @@ -617,15 +619,16 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) { /* Remove our pending lock requests from the locks. */ it.rewind(); - while ((l= it++)) + while ((lock_data= it++)) { - DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING); - release_lock(l); + DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE && + lock_data->state == MDL_PENDING); + release_lock(lock_data); /* Return lock request to its initial state. */ - l->type= MDL_SHARED; - l->prio= MDL_NORMAL_PRIO; - l->is_upgradable= FALSE; - context->locks.remove(l); + lock_data->type= MDL_SHARED; + lock_data->prio= MDL_NORMAL_PRIO; + lock_data->is_upgradable= FALSE; + context->locks.remove(lock_data); } /* Pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); @@ -634,20 +637,20 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) } } it.rewind(); - while ((l= it++)) + while ((lock_data= it++)) { - global_lock.intention_exclusive_acquired++; - lock_data= l->lock_data; - lock_data->waiting_exclusive.remove(l); - lock_data->active_exclusive.push_front(l); - l->state= MDL_ACQUIRED; - if (lock_data->cached_object) - (*lock_data->cached_object_release_hook)(lock_data->cached_object); - lock_data->cached_object= NULL; + global_lock.active_intention_exclusive++; + lock= lock_data->lock; + lock->waiting_exclusive.remove(lock_data); + lock->active_exclusive.push_front(lock_data); + lock_data->state= MDL_ACQUIRED; + if (lock->cached_object) + (*lock->cached_object_release_hook)(lock->cached_object); + lock->cached_object= NULL; } /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ thd->exit_cond(old_msg); - return FALSE; + return FALSE; } @@ -676,9 +679,9 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, char key[MAX_DBKEY_LENGTH]; uint key_length; bool signalled= FALSE; - MDL_LOCK *l, *lh; - MDL_LOCK_DATA *lock_data; - I_P_List_iterator it(context->locks); + MDL_LOCK_DATA *lock_data, *conf_lock_data; + MDL_LOCK *lock; + I_P_List_iterator it(context->locks); const char *old_msg; THD *thd= context->thd; @@ -696,41 +699,43 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); - while ((l= it++)) - if (l->key_length == key_length && !memcmp(l->key, key, key_length) && - l->type == MDL_SHARED) + while ((lock_data= it++)) + if (lock_data->key_length == key_length && + !memcmp(lock_data->key, key, key_length) && + lock_data->type == MDL_SHARED) { DBUG_PRINT("info", ("found shared lock for upgrade")); - DBUG_ASSERT(l->state == MDL_ACQUIRED); - DBUG_ASSERT(l->is_upgradable); - l->state= MDL_PENDING_UPGRADE; - lock_data= l->lock_data; - lock_data->active_shared.remove(l); - lock_data->active_shared_waiting_upgrade.push_front(l); + DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); + DBUG_ASSERT(lock_data->is_upgradable); + lock_data->state= MDL_PENDING_UPGRADE; + lock= lock_data->lock; + lock->active_shared.remove(lock_data); + lock->active_shared_waiting_upgrade.push_front(lock_data); } while (1) { DBUG_PRINT("info", ("looking at conflicting locks")); it.rewind(); - while ((l= it++)) + while ((lock_data= it++)) { - if (l->state == MDL_PENDING_UPGRADE) + if (lock_data->state == MDL_PENDING_UPGRADE) { - DBUG_ASSERT(l->type == MDL_SHARED); + DBUG_ASSERT(lock_data->type == MDL_SHARED); - lock_data= l->lock_data; + lock= lock_data->lock; - DBUG_ASSERT(global_lock.shared_acquired == 0 && - global_lock.intention_exclusive_acquired); + DBUG_ASSERT(global_lock.active_shared == 0 && + global_lock.active_intention_exclusive); - if ((lh= lock_data->active_shared.head())) + if ((conf_lock_data= lock->active_shared.head())) { DBUG_PRINT("info", ("found active shared locks")); - signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd); + signalled= notify_thread_having_shared_lock(thd, + conf_lock_data->ctx->thd); break; } - else if (!lock_data->active_exclusive.is_empty()) + else if (!lock->active_exclusive.is_empty()) { DBUG_PRINT("info", ("found active exclusive locks")); signalled= TRUE; @@ -738,7 +743,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, } } } - if (!l) + if (!lock_data) break; if (signalled) pthread_cond_wait(&COND_mdl, &LOCK_mdl); @@ -758,14 +763,14 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, if (thd->killed) { it.rewind(); - while ((l= it++)) - if (l->state == MDL_PENDING_UPGRADE) + while ((lock_data= it++)) + if (lock_data->state == MDL_PENDING_UPGRADE) { - DBUG_ASSERT(l->type == MDL_SHARED); - l->state= MDL_ACQUIRED; - lock_data= l->lock_data; - lock_data->active_shared_waiting_upgrade.remove(l); - lock_data->active_shared.push_front(l); + DBUG_ASSERT(lock_data->type == MDL_SHARED); + lock_data->state= MDL_ACQUIRED; + lock= lock_data->lock; + lock->active_shared_waiting_upgrade.remove(lock_data); + lock->active_shared.push_front(lock_data); } /* Pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); @@ -775,18 +780,18 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, } it.rewind(); - while ((l= it++)) - if (l->state == MDL_PENDING_UPGRADE) + while ((lock_data= it++)) + if (lock_data->state == MDL_PENDING_UPGRADE) { - DBUG_ASSERT(l->type == MDL_SHARED); - lock_data= l->lock_data; - lock_data->active_shared_waiting_upgrade.remove(l); - lock_data->active_exclusive.push_front(l); - l->type= MDL_EXCLUSIVE; - l->state= MDL_ACQUIRED; - if (lock_data->cached_object) - (*lock_data->cached_object_release_hook)(lock_data->cached_object); - lock_data->cached_object= 0; + DBUG_ASSERT(lock_data->type == MDL_SHARED); + lock= lock_data->lock; + lock->active_shared_waiting_upgrade.remove(lock_data); + lock->active_exclusive.push_front(lock_data); + lock_data->type= MDL_EXCLUSIVE; + lock_data->state= MDL_ACQUIRED; + if (lock->cached_object) + (*lock->cached_object_release_hook)(lock->cached_object); + lock->cached_object= 0; } /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ @@ -817,40 +822,42 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, it gives sligthly more false negatives. */ -bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *l) +bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data) { - MDL_LOCK_DATA *lock_data; + MDL_LOCK *lock; - DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING); + DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE && + lock_data->state == MDL_PENDING); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key, - l->key_length))) + if (!(lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, + lock_data->key_length))) { - lock_data= get_lock_data_object(); - lock_data->active_exclusive.push_front(l); - lock_data->lock_count= 1; - my_hash_insert(&mdl_locks, (uchar*)lock_data); - l->state= MDL_ACQUIRED; - l->lock_data= lock_data; - lock_data= 0; - global_lock.intention_exclusive_acquired++; + lock= get_lock_object(); + lock->active_exclusive.push_front(lock_data); + lock->lock_data_count= 1; + my_hash_insert(&mdl_locks, (uchar*)lock); + lock_data->state= MDL_ACQUIRED; + lock_data->lock= lock; + lock= 0; + global_lock.active_intention_exclusive++; } pthread_mutex_unlock(&LOCK_mdl); /* FIXME: We can't leave pending MDL_EXCLUSIVE lock request in the list since - for such locks we assume that they have MDL_LOCK::lock properly set. + for such locks we assume that they have MDL_LOCK_DATA::lock properly set. Long term we should clearly define relation between lock types, - presence in the context lists and MDL_LOCK::lock values. + presence in the context lists and MDL_LOCK_DATA::lock values. */ - if (lock_data) - context->locks.remove(l); + if (lock) + context->locks.remove(lock_data); - return lock_data; + return lock; } @@ -859,7 +866,7 @@ bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *l) Holding this lock will block all requests for exclusive locks and shared locks which can be potentially upgraded to exclusive - (see MDL_LOCK::is_upgradable). + (see MDL_LOCK_DATA::is_upgradable). @param context Current metadata locking context. @@ -878,20 +885,20 @@ bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) pthread_mutex_lock(&LOCK_mdl); - global_lock.shared_pending++; + global_lock.waiting_shared++; old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); - while (!thd->killed && global_lock.intention_exclusive_acquired) + while (!thd->killed && global_lock.active_intention_exclusive) pthread_cond_wait(&COND_mdl, &LOCK_mdl); - global_lock.shared_pending--; + global_lock.waiting_shared--; if (thd->killed) { /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ thd->exit_cond(old_msg); return TRUE; } - global_lock.shared_acquired++; + global_lock.active_shared++; context->has_global_shared_lock= TRUE; /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ thd->exit_cond(old_msg); @@ -916,9 +923,9 @@ bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) bool mdl_wait_for_locks(MDL_CONTEXT *context) { - MDL_LOCK *l; MDL_LOCK_DATA *lock_data; - I_P_List_iterator it(context->locks); + MDL_LOCK *lock; + I_P_List_iterator it(context->locks); const char *old_msg; THD *thd= context->thd; @@ -941,24 +948,24 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) pthread_mutex_lock(&LOCK_mdl); old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); it.rewind(); - while ((l= it++)) + while ((lock_data= it++)) { - DBUG_ASSERT(l->state == MDL_PENDING); - if ((l->is_upgradable || l->type == MDL_EXCLUSIVE) && - (global_lock.shared_acquired || global_lock.shared_pending)) + DBUG_ASSERT(lock_data->state == MDL_PENDING); + if ((lock_data->is_upgradable || lock_data->type == MDL_EXCLUSIVE) && + (global_lock.active_shared || global_lock.waiting_shared)) break; /* To avoid starvation we don't wait if we have pending MDL_EXCLUSIVE lock. */ - if (l->type == MDL_SHARED && - (lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key, - l->key_length)) && - !(lock_data->active_exclusive.is_empty() && - lock_data->active_shared_waiting_upgrade.is_empty() && - lock_data->waiting_exclusive.is_empty())) + if (lock_data->type == MDL_SHARED && + (lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, + lock_data->key_length)) && + !(lock->active_exclusive.is_empty() && + lock->active_shared_waiting_upgrade.is_empty() && + lock->waiting_exclusive.is_empty())) break; } - if (!l) + if (!lock_data) { pthread_mutex_unlock(&LOCK_mdl); break; @@ -976,50 +983,51 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) ownership of which is represented by lock request object. */ -static void release_lock(MDL_LOCK *l) +static void release_lock(MDL_LOCK_DATA *lock_data) { - MDL_LOCK_DATA *lock_data; + MDL_LOCK *lock; DBUG_ENTER("release_lock"); - DBUG_PRINT("enter", ("db=%s name=%s", l->key + 4, - l->key + 4 + strlen(l->key + 4) + 1)); + DBUG_PRINT("enter", ("db=%s name=%s", lock_data->key + 4, + lock_data->key + 4 + strlen(lock_data->key + 4) + 1)); - lock_data= l->lock_data; - if (lock_data->has_one_lock()) + lock= lock_data->lock; + if (lock->has_one_lock_data()) { - my_hash_delete(&mdl_locks, (uchar *)lock_data); + my_hash_delete(&mdl_locks, (uchar *)lock); DBUG_PRINT("info", ("releasing cached_object cached_object=%p", - lock_data->cached_object)); - if (lock_data->cached_object) - (*lock_data->cached_object_release_hook)(lock_data->cached_object); - release_lock_data_object(lock_data); - if (l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED || - l->type == MDL_SHARED && l->state == MDL_ACQUIRED && l->is_upgradable) - global_lock.intention_exclusive_acquired--; + lock->cached_object)); + if (lock->cached_object) + (*lock->cached_object_release_hook)(lock->cached_object); + release_lock_object(lock); + if (lock_data->type == MDL_EXCLUSIVE && lock_data->state == MDL_ACQUIRED || + lock_data->type == MDL_SHARED && lock_data->state == MDL_ACQUIRED && + lock_data->is_upgradable) + global_lock.active_intention_exclusive--; } else { - switch (l->type) + switch (lock_data->type) { case MDL_SHARED: - lock_data->active_shared.remove(l); - if (l->is_upgradable) - global_lock.intention_exclusive_acquired--; + lock->active_shared.remove(lock_data); + if (lock_data->is_upgradable) + global_lock.active_intention_exclusive--; break; case MDL_EXCLUSIVE: - if (l->state == MDL_PENDING) - lock_data->waiting_exclusive.remove(l); + if (lock_data->state == MDL_PENDING) + lock->waiting_exclusive.remove(lock_data); else { - lock_data->active_exclusive.remove(l); - global_lock.intention_exclusive_acquired--; + lock->active_exclusive.remove(lock_data); + global_lock.active_intention_exclusive--; } break; default: /* TODO Really? How about problems during lock upgrade ? */ DBUG_ASSERT(0); } - lock_data->lock_count--; + lock->lock_data_count--; } DBUG_VOID_RETURN; @@ -1040,28 +1048,28 @@ static void release_lock(MDL_LOCK *l) void mdl_release_locks(MDL_CONTEXT *context) { - MDL_LOCK *l; - I_P_List_iterator it(context->locks); + MDL_LOCK_DATA *lock_data; + I_P_List_iterator it(context->locks); DBUG_ENTER("mdl_release_locks"); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - while ((l= it++)) + while ((lock_data= it++)) { - DBUG_PRINT("info", ("found lock to release l=%p", l)); + DBUG_PRINT("info", ("found lock to release lock_data=%p", lock_data)); /* We should not release locks which pending shared locks as these are not associated with lock object and don't present in its lists. Allows us to avoid problems in open_tables() in case of back-off */ - if (!(l->type == MDL_SHARED && l->state == MDL_PENDING)) + if (!(lock_data->type == MDL_SHARED && lock_data->state == MDL_PENDING)) { - release_lock(l); - l->state= MDL_PENDING; + release_lock(lock_data); + lock_data->state= MDL_PENDING; #ifndef DBUG_OFF - l->lock_data= 0; + lock_data->lock= 0; #endif } /* @@ -1091,28 +1099,28 @@ void mdl_release_locks(MDL_CONTEXT *context) void mdl_release_exclusive_locks(MDL_CONTEXT *context) { - MDL_LOCK *l; - I_P_List_iterator it(context->locks); + MDL_LOCK_DATA *lock_data; + I_P_List_iterator it(context->locks); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - while ((l= it++)) + while ((lock_data= it++)) { - if (l->type == MDL_EXCLUSIVE) + if (lock_data->type == MDL_EXCLUSIVE) { - DBUG_ASSERT(l->state == MDL_ACQUIRED); - release_lock(l); + DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); + release_lock(lock_data); #ifndef DBUG_OFF - l->ctx= 0; - l->lock_data= 0; + lock_data->ctx= 0; + lock_data->lock= 0; #endif - l->state= MDL_PENDING; + lock_data->state= MDL_PENDING; /* Return lock request to its initial state. */ - l->type= MDL_SHARED; - l->prio= MDL_NORMAL_PRIO; - l->is_upgradable= FALSE; - context->locks.remove(l); + lock_data->type= MDL_SHARED; + lock_data->prio= MDL_NORMAL_PRIO; + lock_data->is_upgradable= FALSE; + context->locks.remove(lock_data); } } pthread_cond_broadcast(&COND_mdl); @@ -1124,29 +1132,29 @@ void mdl_release_exclusive_locks(MDL_CONTEXT *context) Release a lock. Removes the lock from the context. - @param context Context containing lock in question - @param lock Lock to be released + @param context Context containing lock in question + @param lock_data Lock to be released @note Resets lock request for lock released back to its initial state (i.e.sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO). */ -void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lr) +void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) { safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - release_lock(lr); + release_lock(lock_data); #ifndef DBUG_OFF - lr->ctx= 0; - lr->lock_data= 0; + lock_data->ctx= 0; + lock_data->lock= 0; #endif - lr->state= MDL_PENDING; + lock_data->state= MDL_PENDING; /* Return lock request to its initial state. */ - lr->type= MDL_SHARED; - lr->prio= MDL_NORMAL_PRIO; - lr->is_upgradable= FALSE; - context->locks.remove(lr); + lock_data->type= MDL_SHARED; + lock_data->prio= MDL_NORMAL_PRIO; + lock_data->is_upgradable= FALSE; + context->locks.remove(lock_data); pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); } @@ -1161,23 +1169,23 @@ void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lr) void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context) { - MDL_LOCK *l; MDL_LOCK_DATA *lock_data; - I_P_List_iterator it(context->locks); + MDL_LOCK *lock; + I_P_List_iterator it(context->locks); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - while ((l= it++)) - if (l->type == MDL_EXCLUSIVE) + while ((lock_data= it++)) + if (lock_data->type == MDL_EXCLUSIVE) { - DBUG_ASSERT(l->state == MDL_ACQUIRED); - if (!l->is_upgradable) - global_lock.intention_exclusive_acquired--; - lock_data= l->lock_data; - lock_data->active_exclusive.remove(l); - l->type= MDL_SHARED; - lock_data->active_shared.push_front(l); + DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); + if (!lock_data->is_upgradable) + global_lock.active_intention_exclusive--; + lock= lock_data->lock; + lock->active_exclusive.remove(lock_data); + lock_data->type= MDL_SHARED; + lock->active_shared.push_front(lock_data); } pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); @@ -1196,7 +1204,7 @@ void mdl_release_global_shared_lock(MDL_CONTEXT *context) DBUG_ASSERT(context->has_global_shared_lock); pthread_mutex_lock(&LOCK_mdl); - global_lock.shared_acquired--; + global_lock.active_shared--; context->has_global_shared_lock= FALSE; pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); @@ -1221,15 +1229,18 @@ bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, { char key[MAX_DBKEY_LENGTH]; uint key_length; - MDL_LOCK *l; - I_P_List_iterator it(context->locks); + MDL_LOCK_DATA *lock_data; + I_P_List_iterator it(context->locks); int4store(key, type); key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; - while ((l= it++) && (l->key_length != key_length || memcmp(l->key, key, key_length))) + while ((lock_data= it++) && + (lock_data->key_length != key_length || + memcmp(lock_data->key, key, key_length))) continue; - return (l && l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED); + return (lock_data && lock_data->type == MDL_EXCLUSIVE && + lock_data->state == MDL_ACQUIRED); } @@ -1251,18 +1262,18 @@ bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, { char key[MAX_DBKEY_LENGTH]; uint key_length; - MDL_LOCK *l; - I_P_List_iterator it(context->locks); + MDL_LOCK_DATA *lock_data; + I_P_List_iterator it(context->locks); int4store(key, type); key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; - while ((l= it++) && (l->key_length != key_length || - memcmp(l->key, key, key_length) || - l->state == MDL_PENDING)) + while ((lock_data= it++) && (lock_data->key_length != key_length || + memcmp(lock_data->key, key, key_length) || + lock_data->state == MDL_PENDING)) continue; - return l; + return lock_data; } @@ -1270,21 +1281,22 @@ bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, Check if we have any pending exclusive locks which conflict with existing shared lock. - @param l Shared lock against which check should be performed. + @param lock_data Shared lock against which check should be performed. @return TRUE if there are any conflicting locks, FALSE otherwise. */ -bool mdl_has_pending_conflicting_lock(MDL_LOCK *l) +bool mdl_has_pending_conflicting_lock(MDL_LOCK_DATA *lock_data) { bool result; - DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_ACQUIRED); + DBUG_ASSERT(lock_data->type == MDL_SHARED && + lock_data->state == MDL_ACQUIRED); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - result= !(l->lock_data->waiting_exclusive.is_empty() && - l->lock_data->active_shared_waiting_upgrade.is_empty()); + result= !(lock_data->lock->waiting_exclusive.is_empty() && + lock_data->lock->active_shared_waiting_upgrade.is_empty()); pthread_mutex_unlock(&LOCK_mdl); return result; } @@ -1293,7 +1305,7 @@ bool mdl_has_pending_conflicting_lock(MDL_LOCK *l) /** Associate pointer to an opaque object with a lock. - @param l Lock request for the lock with which the + @param lock_data Lock request for the lock with which the object should be associated. @param cached_object Pointer to the object @param release_hook Cleanup function to be called when MDL subsystem @@ -1318,15 +1330,16 @@ bool mdl_has_pending_conflicting_lock(MDL_LOCK *l) lock on this name is released. */ -void mdl_set_cached_object(MDL_LOCK *l, void *cached_object, +void mdl_set_cached_object(MDL_LOCK_DATA *lock_data, void *cached_object, mdl_cached_object_release_hook release_hook) { DBUG_ENTER("mdl_set_cached_object"); - DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", l->key + 4, - l->key + 4 + strlen(l->key + 4) + 1, + DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", lock_data->key + 4, + lock_data->key + 4 + strlen(lock_data->key + 4) + 1, cached_object)); - DBUG_ASSERT(l->state == MDL_ACQUIRED || l->state == MDL_PENDING_UPGRADE); + DBUG_ASSERT(lock_data->state == MDL_ACQUIRED || + lock_data->state == MDL_PENDING_UPGRADE); /* TODO: This assumption works now since we do mdl_get_cached_object() @@ -1334,10 +1347,10 @@ void mdl_set_cached_object(MDL_LOCK *l, void *cached_object, this becomes false we will have to call release_hook here and use additional mutex protecting 'cached_object' member. */ - DBUG_ASSERT(!l->lock_data->cached_object); + DBUG_ASSERT(!lock_data->lock->cached_object); - l->lock_data->cached_object= cached_object; - l->lock_data->cached_object_release_hook= release_hook; + lock_data->lock->cached_object= cached_object; + lock_data->lock->cached_object_release_hook= release_hook; DBUG_VOID_RETURN; } @@ -1346,14 +1359,15 @@ void mdl_set_cached_object(MDL_LOCK *l, void *cached_object, /** Get a pointer to an opaque object that associated with the lock. - @param l Lock request for the lock with which the object is - associated. + @param lock_data Lock request for the lock with which the object is + associated. @return Pointer to an opaque object associated with the lock. */ -void* mdl_get_cached_object(MDL_LOCK *l) +void* mdl_get_cached_object(MDL_LOCK_DATA *lock_data) { - DBUG_ASSERT(l->state == MDL_ACQUIRED || l->state == MDL_PENDING_UPGRADE); - return l->lock_data->cached_object; + DBUG_ASSERT(lock_data->state == MDL_ACQUIRED || + lock_data->state == MDL_PENDING_UPGRADE); + return lock_data->lock->cached_object; } diff --git a/sql/mdl.h b/sql/mdl.h index 2b144e250b1..74c90c01730 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -22,8 +22,8 @@ class THD; -struct MDL_LOCK; struct MDL_LOCK_DATA; +struct MDL_LOCK; struct MDL_CONTEXT; /** Type of metadata lock request. */ @@ -54,7 +54,7 @@ enum enum_mdl_prio {MDL_NORMAL_PRIO=0, MDL_HIGH_PRIO}; "key" or "name". */ -struct MDL_LOCK +struct MDL_LOCK_DATA { char *key; uint key_length; @@ -72,17 +72,17 @@ private: /** Pointers for participating in the list of lock requests for this context. */ - MDL_LOCK *next_context; - MDL_LOCK **prev_context; + MDL_LOCK_DATA *next_context; + MDL_LOCK_DATA **prev_context; /** Pointers for participating in the list of satisfied/pending requests for the lock. */ - MDL_LOCK *next_lock; - MDL_LOCK **prev_lock; + MDL_LOCK_DATA *next_lock; + MDL_LOCK_DATA **prev_lock; - friend struct MDL_LOCK_context; - friend struct MDL_LOCK_lock; + friend struct MDL_LOCK_DATA_context; + friend struct MDL_LOCK_DATA_lock; public: /* @@ -90,23 +90,23 @@ public: request is satisified or is present in the list of pending lock requests for particular lock. */ - MDL_LOCK_DATA *lock_data; + MDL_LOCK *lock; MDL_CONTEXT *ctx; }; /** - Helper class which specifies which members of MDL_LOCK are used for + Helper class which specifies which members of MDL_LOCK_DATA are used for participation in the list lock requests belonging to one context. */ -struct MDL_LOCK_context +struct MDL_LOCK_DATA_context { - static inline MDL_LOCK **next_ptr(MDL_LOCK *l) + static inline MDL_LOCK_DATA **next_ptr(MDL_LOCK_DATA *l) { return &l->next_context; } - static inline MDL_LOCK ***prev_ptr(MDL_LOCK *l) + static inline MDL_LOCK_DATA ***prev_ptr(MDL_LOCK_DATA *l) { return &l->prev_context; } @@ -114,17 +114,17 @@ struct MDL_LOCK_context /** - Helper class which specifies which members of MDL_LOCK are used for + Helper class which specifies which members of MDL_LOCK_DATA are used for participation in the list of satisfied/pending requests for the lock. */ -struct MDL_LOCK_lock +struct MDL_LOCK_DATA_lock { - static inline MDL_LOCK **next_ptr(MDL_LOCK *l) + static inline MDL_LOCK_DATA **next_ptr(MDL_LOCK_DATA *l) { return &l->next_lock; } - static inline MDL_LOCK ***prev_ptr(MDL_LOCK *l) + static inline MDL_LOCK_DATA ***prev_ptr(MDL_LOCK_DATA *l) { return &l->prev_lock; } @@ -138,7 +138,7 @@ struct MDL_LOCK_lock struct MDL_CONTEXT { - I_P_List locks; + I_P_List locks; bool has_global_shared_lock; THD *thd; }; @@ -153,21 +153,21 @@ void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); void mdl_context_merge(MDL_CONTEXT *target, MDL_CONTEXT *source); -void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db, - const char *name); -MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name, - MEM_ROOT *root); -void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock); +void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, + const char *db, const char *name); +MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name, + MEM_ROOT *root); +void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); void mdl_remove_all_locks(MDL_CONTEXT *context); /** Set type of lock request. Can be only applied to pending locks. */ -inline void mdl_set_lock_type(MDL_LOCK *lock, enum_mdl_type lock_type) +inline void mdl_set_lock_type(MDL_LOCK_DATA *lock_data, enum_mdl_type lock_type) { - DBUG_ASSERT(lock->state == MDL_PENDING); - lock->type= lock_type; + DBUG_ASSERT(lock_data->state == MDL_PENDING); + lock_data->type= lock_type; } /** @@ -175,10 +175,10 @@ inline void mdl_set_lock_type(MDL_LOCK *lock, enum_mdl_type lock_type) for shared locks. */ -inline void mdl_set_lock_priority(MDL_LOCK *lock, enum_mdl_prio prio) +inline void mdl_set_lock_priority(MDL_LOCK_DATA *lock_data, enum_mdl_prio prio) { - DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING); - lock->prio= prio; + DBUG_ASSERT(lock_data->type == MDL_SHARED && lock_data->state == MDL_PENDING); + lock_data->prio= prio; } /** @@ -186,24 +186,25 @@ inline void mdl_set_lock_priority(MDL_LOCK *lock, enum_mdl_prio prio) to pending locks. */ -inline void mdl_set_upgradable(MDL_LOCK *lock) +inline void mdl_set_upgradable(MDL_LOCK_DATA *lock_data) { - DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING); - lock->is_upgradable= TRUE; + DBUG_ASSERT(lock_data->type == MDL_SHARED && lock_data->state == MDL_PENDING); + lock_data->is_upgradable= TRUE; } -bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry); +bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry); bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context); bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, const char *db, const char *name); -bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *lock); +bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data); bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context); bool mdl_wait_for_locks(MDL_CONTEXT *context); void mdl_release_locks(MDL_CONTEXT *context); void mdl_release_exclusive_locks(MDL_CONTEXT *context); -void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lock); +void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context); void mdl_release_global_shared_lock(MDL_CONTEXT *context); @@ -212,7 +213,7 @@ bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, const char *db, bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, const char *name); -bool mdl_has_pending_conflicting_lock(MDL_LOCK *l); +bool mdl_has_pending_conflicting_lock(MDL_LOCK_DATA *lock_data); inline bool mdl_has_locks(MDL_CONTEXT *context) { @@ -224,9 +225,10 @@ inline bool mdl_has_locks(MDL_CONTEXT *context) Get iterator for walking through all lock requests in the context. */ -inline I_P_List_iterator mdl_get_locks(MDL_CONTEXT *ctx) +inline I_P_List_iterator +mdl_get_locks(MDL_CONTEXT *ctx) { - I_P_List_iterator result(ctx->locks); + I_P_List_iterator result(ctx->locks); return result; } @@ -234,9 +236,9 @@ inline I_P_List_iterator mdl_get_locks(MDL_CONTEXT * Give metadata lock request object for the table get table definition cache key corresponding to it. - @param l [in] Lock request object for the table. - @param key [out] LEX_STRING object where table definition cache key - should be put. + @param lock_data [in] Lock request object for the table. + @param key [out] LEX_STRING object where table definition cache key + should be put. @note This key will have the same life-time as this lock request object. @@ -245,16 +247,16 @@ inline I_P_List_iterator mdl_get_locks(MDL_CONTEXT * and memory by avoiding generating these TDC keys from table list. */ -inline void mdl_get_tdc_key(MDL_LOCK *l, LEX_STRING *key) +inline void mdl_get_tdc_key(MDL_LOCK_DATA *lock_data, LEX_STRING *key) { - key->str= l->key + 4; - key->length= l->key_length - 4; + key->str= lock_data->key + 4; + key->length= lock_data->key_length - 4; } typedef void (* mdl_cached_object_release_hook)(void *); -void* mdl_get_cached_object(MDL_LOCK *l); -void mdl_set_cached_object(MDL_LOCK *l, void *cached_object, +void* mdl_get_cached_object(MDL_LOCK_DATA *lock_data); +void mdl_set_cached_object(MDL_LOCK_DATA *lock_data, void *cached_object, mdl_cached_object_release_hook release_hook); #endif diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 14573cd6884..fdf9909f85e 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3984,9 +3984,10 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; - table->mdl_lock= mdl_alloc_lock(0, table->db, table->table_name, - thd->mdl_el_root ? thd->mdl_el_root : - thd->mem_root); + table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name, + thd->mdl_el_root ? + thd->mdl_el_root : + thd->mem_root); /* Everyting else should be zeroed */ @@ -4028,9 +4029,9 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->lock_type= locktype; table->select_lex= lex->current_select; table->cacheable_table= 1; - table->mdl_lock= mdl_alloc_lock(0, table->db, table->table_name, - thd->mdl_el_root ? thd->mdl_el_root : - thd->mem_root); + table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name, + thd->mdl_el_root ? thd->mdl_el_root : + thd->mem_root); lex->add_to_query_tables(table); return table; } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 13218f3a193..44db7938caf 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1527,7 +1527,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) if (table->child_l || table->parent) detach_merge_children(table, TRUE); - table->mdl_lock= 0; + table->mdl_lock_data= 0; if (table->needs_reopen() || thd->version != refresh_version || !table->db_stat) { @@ -2535,7 +2535,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, char key[MAX_DBKEY_LENGTH]; uint key_length; char *alias= table_list->alias; - MDL_LOCK *mdl_lock; + MDL_LOCK_DATA *mdl_lock_data; int error; TABLE_SHARE *share; DBUG_ENTER("open_table"); @@ -2717,8 +2717,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, This is the normal use case. */ - mdl_lock= table_list->mdl_lock; - mdl_add_lock(&thd->mdl_context, mdl_lock); + mdl_lock_data= table_list->mdl_lock_data; + mdl_add_lock(&thd->mdl_context, mdl_lock_data); if (table_list->open_type) { @@ -2731,7 +2731,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, shared locks. This invariant is preserved here and is also enforced by asserts in metadata locking subsystem. */ - mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); + mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) DBUG_RETURN(0); } @@ -2740,10 +2740,10 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, bool retry; if (table_list->mdl_upgradable) - mdl_set_upgradable(mdl_lock); - mdl_set_lock_priority(mdl_lock, (flags & MYSQL_LOCK_IGNORE_FLUSH) ? - MDL_HIGH_PRIO : MDL_NORMAL_PRIO); - if (mdl_acquire_shared_lock(mdl_lock, &retry)) + mdl_set_upgradable(mdl_lock_data); + mdl_set_lock_priority(mdl_lock_data, (flags & MYSQL_LOCK_IGNORE_FLUSH) ? + MDL_HIGH_PRIO : MDL_NORMAL_PRIO); + if (mdl_acquire_shared_lock(mdl_lock_data, &retry)) { if (retry) *action= OT_BACK_OFF_AND_RETRY; @@ -2792,7 +2792,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, DBUG_RETURN(0); } - if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock))) + if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock_data))) { if (!(share= get_table_share_with_create(thd, table_list, key, key_length, OPEN_VIEW, @@ -2867,7 +2867,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, so we need to increase reference counter; */ reference_table_share(share); - mdl_set_cached_object(mdl_lock, share, table_share_release_hook); + mdl_set_cached_object(mdl_lock_data, share, table_share_release_hook); } else { @@ -2978,7 +2978,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE) mdl_downgrade_exclusive_locks(&thd->mdl_context); - table->mdl_lock= mdl_lock; + table->mdl_lock_data= mdl_lock_data; table->next=thd->open_tables; /* Link into simple list */ thd->open_tables=table; @@ -3025,7 +3025,7 @@ err_unlock: release_table_share(share); err_unlock2: pthread_mutex_unlock(&LOCK_open); - mdl_release_lock(&thd->mdl_context, mdl_lock); + mdl_release_lock(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(0); } @@ -3167,7 +3167,7 @@ bool reopen_table(TABLE *table) (void) closefrm(&tmp, 1); // close file, free everything goto end; } - tmp.mdl_lock= table->mdl_lock; + tmp.mdl_lock_data= table->mdl_lock_data; table_def_change_share(table, tmp.s); /* Avoid wiping out TABLE's position in new share's used tables list. */ @@ -3981,8 +3981,8 @@ static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table, mdl_remove_all_locks(&thd->mdl_context); break; case OT_DISCOVER: - mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, table->mdl_lock); + mdl_set_lock_type(table->mdl_lock_data, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, table->mdl_lock_data); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) return TRUE; pthread_mutex_lock(&LOCK_open); @@ -3995,8 +3995,8 @@ static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table, mdl_release_exclusive_locks(&thd->mdl_context); break; case OT_REPAIR: - mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, table->mdl_lock); + mdl_set_lock_type(table->mdl_lock_data, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, table->mdl_lock_data); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) return TRUE; pthread_mutex_lock(&LOCK_open); @@ -8518,7 +8518,7 @@ void expel_table_from_cache(THD *leave_thd, const char *db, const char *table_na static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context) { - MDL_LOCK *l; + MDL_LOCK_DATA *lock_data; TABLE_SHARE *share; const char *old_msg; LEX_STRING key; @@ -8534,17 +8534,18 @@ static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context) mysql_ha_flush(thd); pthread_mutex_lock(&LOCK_open); - I_P_List_iterator it= mdl_get_locks(context); - while ((l= it++)) + I_P_List_iterator it= mdl_get_locks(context); + while ((lock_data= it++)) { - mdl_get_tdc_key(l, &key); + mdl_get_tdc_key(lock_data, &key); if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache, (uchar*) key.str, key.length)) && share->version != refresh_version && !share->used_tables.is_empty()) break; } - if (!l) + if (!lock_data) { pthread_mutex_unlock(&LOCK_open); break; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index fb48f32660b..f5c6dfd8986 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1089,7 +1089,7 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) TABLE *table; bool error; uint path_length; - MDL_LOCK *mdl_lock= 0; + MDL_LOCK_DATA *mdl_lock_data= 0; DBUG_ENTER("mysql_truncate"); bzero((char*) &create_info,sizeof(create_info)); @@ -1164,10 +1164,10 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) tries to get table enging and therefore accesses table in some way without holding any kind of meta-data lock. */ - mdl_lock= mdl_alloc_lock(0, table_list->db, table_list->table_name, - thd->mem_root); - mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, mdl_lock); + mdl_lock_data= mdl_alloc_lock(0, table_list->db, table_list->table_name, + thd->mem_root); + mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, mdl_lock_data); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) DBUG_RETURN(TRUE); pthread_mutex_lock(&LOCK_open); @@ -1197,13 +1197,13 @@ end: write_bin_log(thd, TRUE, thd->query(), thd->query_length()); my_ok(thd); // This should return record count } - if (mdl_lock) - mdl_release_lock(&thd->mdl_context, mdl_lock); + if (mdl_lock_data) + mdl_release_lock(&thd->mdl_context, mdl_lock_data); } else if (error) { - if (mdl_lock) - mdl_release_lock(&thd->mdl_context, mdl_lock); + if (mdl_lock_data) + mdl_release_lock(&thd->mdl_context, mdl_lock_data); } DBUG_RETURN(error); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 87e9538b48f..15c73fcadfe 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -125,7 +125,7 @@ static void mysql_ha_hash_free(TABLE_LIST *tables) static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) { TABLE **table_ptr; - MDL_LOCK *mdl_lock; + MDL_LOCK_DATA *mdl_lock_data; /* Though we could take the table pointer from hash_tables->table, @@ -141,7 +141,7 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) if (*table_ptr) { (*table_ptr)->file->ha_index_or_rnd_end(); - mdl_lock= (*table_ptr)->mdl_lock; + mdl_lock_data= (*table_ptr)->mdl_lock_data; pthread_mutex_lock(&LOCK_open); if (close_thread_table(thd, table_ptr)) { @@ -149,7 +149,7 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) broadcast_refresh(); } pthread_mutex_unlock(&LOCK_open); - mdl_release_lock(&thd->handler_mdl_context, mdl_lock); + mdl_release_lock(&thd->handler_mdl_context, mdl_lock_data); } else if (tables->table) { @@ -189,7 +189,7 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) { TABLE_LIST *hash_tables = NULL; - MDL_LOCK *mdl_lock; + MDL_LOCK_DATA *mdl_lock_data; char *db, *name, *alias, *mdlkey; uint dblen, namelen, aliaslen, counter; int error; @@ -245,7 +245,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) &db, (uint) dblen, &name, (uint) namelen, &alias, (uint) aliaslen, - &mdl_lock, sizeof(MDL_LOCK), + &mdl_lock_data, sizeof(MDL_LOCK_DATA), &mdlkey, MAX_DBKEY_LENGTH, NullS))) { @@ -260,8 +260,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) memcpy(hash_tables->db, tables->db, dblen); memcpy(hash_tables->table_name, tables->table_name, namelen); memcpy(hash_tables->alias, tables->alias, aliaslen); - mdl_init_lock(mdl_lock, mdlkey, 0, db, name); - hash_tables->mdl_lock= mdl_lock; + mdl_init_lock(mdl_lock_data, mdlkey, 0, db, name); + hash_tables->mdl_lock_data= mdl_lock_data; /* add to hash */ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) @@ -798,10 +798,12 @@ void mysql_ha_flush(THD *thd) for (uint i= 0; i < thd->handler_tables_hash.records; i++) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); - /* TABLE::mdl_lock is 0 for temporary tables so we need extra check. */ + /* + TABLE::mdl_lock_data is 0 for temporary tables so we need extra check. + */ if (hash_tables->table && - (hash_tables->table->mdl_lock && - mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock) || + (hash_tables->table->mdl_lock_data && + mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock_data) || hash_tables->table->needs_reopen())) mysql_ha_close_table(thd, hash_tables); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2c043922cc8..24939f1cd21 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -6534,9 +6534,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); - ptr->mdl_lock= mdl_alloc_lock(0 , ptr->db, ptr->table_name, - thd->mdl_el_root ? thd->mdl_el_root : - thd->mem_root); + ptr->mdl_lock_data= mdl_alloc_lock(0 , ptr->db, ptr->table_name, + thd->mdl_el_root ? thd->mdl_el_root : + thd->mem_root); DBUG_RETURN(ptr); } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 236bca76c7d..647f914d28c 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3106,7 +3106,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, char key[MAX_DBKEY_LENGTH]; uint key_length; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; - MDL_LOCK mdl_lock; + MDL_LOCK_DATA mdl_lock_data; char mdlkey[MAX_DBKEY_LENGTH]; bool retry; @@ -3133,10 +3133,10 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, table_list.db= db_name->str; } - mdl_init_lock(&mdl_lock, mdlkey, 0, db_name->str, table_name->str); - table_list.mdl_lock= &mdl_lock; - mdl_add_lock(&thd->mdl_context, &mdl_lock); - mdl_set_lock_priority(&mdl_lock, MDL_HIGH_PRIO); + mdl_init_lock(&mdl_lock_data, mdlkey, 0, db_name->str, table_name->str); + table_list.mdl_lock_data= &mdl_lock_data; + mdl_add_lock(&thd->mdl_context, &mdl_lock_data); + mdl_set_lock_priority(&mdl_lock_data, MDL_HIGH_PRIO); /* TODO: investigate if in this particular situation we can get by @@ -3145,7 +3145,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, */ while (1) { - if (mdl_acquire_shared_lock(&mdl_lock, &retry)) + if (mdl_acquire_shared_lock(&mdl_lock_data, &retry)) { if (!retry || mdl_wait_for_locks(&thd->mdl_context)) { @@ -3212,7 +3212,7 @@ err_unlock: pthread_mutex_unlock(&LOCK_open); err: - mdl_release_lock(&thd->mdl_context, &mdl_lock); + mdl_release_lock(&thd->mdl_context, &mdl_lock_data); thd->clear_error(); return res; } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 9888dceef54..2a41fe2008c 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4043,15 +4043,15 @@ warn: static bool lock_table_name_if_not_cached(THD *thd, const char *db, const char *table_name, - MDL_LOCK **lock) + MDL_LOCK_DATA **lock_data) { - if (!(*lock= mdl_alloc_lock(0, db, table_name, thd->mem_root))) + if (!(*lock_data= mdl_alloc_lock(0, db, table_name, thd->mem_root))) return TRUE; - mdl_set_lock_type(*lock, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, *lock); - if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock)) + mdl_set_lock_type(*lock_data, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, *lock_data); + if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock_data)) { - *lock= 0; + *lock_data= 0; } return FALSE; } @@ -4067,7 +4067,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, bool internal_tmp_table, uint select_field_count) { - MDL_LOCK *target_lock= 0; + MDL_LOCK_DATA *target_lock_data= 0; bool result; DBUG_ENTER("mysql_create_table"); @@ -4090,12 +4090,12 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock)) + if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock_data)) { result= TRUE; goto unlock; } - if (!target_lock) + if (!target_lock_data) { if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) { @@ -4121,7 +4121,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, select_field_count); unlock: - if (target_lock) + if (target_lock_data) mdl_release_exclusive_locks(&thd->mdl_context); pthread_mutex_lock(&LOCK_lock_db); if (!--creating_table && creating_database) @@ -4359,7 +4359,7 @@ static int send_check_errmsg(THD *thd, TABLE_LIST* table, static int prepare_for_restore(THD* thd, TABLE_LIST* table, HA_CHECK_OPT *check_opt) { - MDL_LOCK *mdl_lock= 0; + MDL_LOCK_DATA *mdl_lock_data= 0; DBUG_ENTER("prepare_for_restore"); if (table->table) // do not overwrite existing tables on restore @@ -4383,10 +4383,10 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, build_table_filename(dst_path, sizeof(dst_path) - 1, db, table_name, reg_ext, 0); - mdl_lock= mdl_alloc_lock(0, table->db, table->table_name, - thd->mem_root); - mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, mdl_lock); + mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name, + thd->mem_root); + mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, mdl_lock_data); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) DBUG_RETURN(TRUE); pthread_mutex_lock(&LOCK_open); @@ -4395,13 +4395,13 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, if (my_copy(src_path, dst_path, MYF(MY_WME))) { - mdl_release_lock(&thd->mdl_context, mdl_lock); + mdl_release_lock(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(send_check_errmsg(thd, table, "restore", "Failed copying .frm file")); } if (mysql_truncate(thd, table, 1)) { - mdl_release_lock(&thd->mdl_context, mdl_lock); + mdl_release_lock(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(send_check_errmsg(thd, table, "restore", "Failed generating table from .frm file")); } @@ -4415,8 +4415,8 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, if (reopen_name_locked_table(thd, table)) { pthread_mutex_unlock(&LOCK_open); - if (mdl_lock) - mdl_release_lock(&thd->mdl_context, mdl_lock); + if (mdl_lock_data) + mdl_release_lock(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(send_check_errmsg(thd, table, "restore", "Failed to open partially restored table")); } @@ -4436,7 +4436,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, char from[FN_REFLEN],tmp[FN_REFLEN+32]; const char **ext; MY_STAT stat_info; - MDL_LOCK *mdl_lock; + MDL_LOCK_DATA *mdl_lock_data; DBUG_ENTER("prepare_for_repair"); if (!(check_opt->sql_flags & TT_USEFRM)) @@ -4452,10 +4452,10 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, TODO: Check that REPAIR's code also conforms to meta-data locking protocol. Fix if it is not. */ - mdl_lock= mdl_alloc_lock(0, table_list->db, table_list->table_name, - thd->mem_root); - mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, mdl_lock); + mdl_lock_data= mdl_alloc_lock(0, table_list->db, table_list->table_name, + thd->mem_root); + mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); + mdl_add_lock(&thd->mdl_context, mdl_lock_data); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) DBUG_RETURN(0); @@ -5303,7 +5303,7 @@ bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO *create_info) { - MDL_LOCK *target_lock= 0; + MDL_LOCK_DATA *target_lock_data= 0; char src_path[FN_REFLEN], dst_path[FN_REFLEN + 1]; uint dst_path_length; char *db= table->db; @@ -5360,9 +5360,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, } else { - if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock)) + if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock_data)) goto err; - if (!target_lock) + if (!target_lock_data) goto table_exists; dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1, db, table_name, reg_ext, 0); @@ -5538,7 +5538,7 @@ binlog: res= FALSE; err: - if (target_lock) + if (target_lock_data) mdl_release_exclusive_locks(&thd->mdl_context); DBUG_RETURN(res); } @@ -6477,7 +6477,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, uint order_num, ORDER *order, bool ignore) { TABLE *table, *new_table= 0; - MDL_LOCK *target_lock= 0; + MDL_LOCK_DATA *target_lock_data= 0; int error= 0; char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1]; char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias; @@ -6700,9 +6700,10 @@ view_err: } else { - if (lock_table_name_if_not_cached(thd, new_db, new_name, &target_lock)) + if (lock_table_name_if_not_cached(thd, new_db, new_name, + &target_lock_data)) DBUG_RETURN(TRUE); - if (!target_lock) + if (!target_lock_data) { my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); DBUG_RETURN(TRUE); @@ -7128,7 +7129,7 @@ view_err: #ifdef WITH_PARTITION_STORAGE_ENGINE if (fast_alter_partition) { - DBUG_ASSERT(!target_lock); + DBUG_ASSERT(!target_lock_data); DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, create_info, table_list, db, table_name, @@ -7632,7 +7633,7 @@ err: alter_info->datetime_field->field_name); thd->abort_on_warning= save_abort_on_warning; } - if (target_lock) + if (target_lock_data) mdl_release_exclusive_locks(&thd->mdl_context); DBUG_RETURN(TRUE); diff --git a/sql/table.cc b/sql/table.cc index c8814cff685..3943dc8e50b 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4832,8 +4832,9 @@ size_t max_row_length(TABLE *table, const uchar *data) void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root) { for ( ; table_list ; table_list= table_list->next_global) - table_list->mdl_lock= mdl_alloc_lock(0, table_list->db, - table_list->table_name, root); + table_list->mdl_lock_data= mdl_alloc_lock(0, table_list->db, + table_list->table_name, + root); } diff --git a/sql/table.h b/sql/table.h index e64111ef988..601f1e154c9 100644 --- a/sql/table.h +++ b/sql/table.h @@ -30,7 +30,7 @@ class st_select_lex; class partition_info; class COND_EQUAL; class Security_context; -struct MDL_LOCK; +struct MDL_LOCK_DATA; /*************************************************************************/ @@ -820,7 +820,7 @@ public: partition_info *part_info; /* Partition related information */ bool no_partitions_used; /* If true, all partitions have been pruned away */ #endif - MDL_LOCK *mdl_lock; + MDL_LOCK_DATA *mdl_lock_data; bool fill_item_list(List *item_list) const; void reset_item_list(List *item_list) const; @@ -1423,7 +1423,7 @@ struct TABLE_LIST uint table_open_method; enum enum_schema_table_state schema_table_state; - MDL_LOCK *mdl_lock; + MDL_LOCK_DATA *mdl_lock_data; void calc_md5(char *buffer); void set_underlying_merge(); From b05303c132995ed78f38d725ffae198dcaaba364 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 01:39:13 +0300 Subject: [PATCH 029/212] Backport of: ------------------------------------------------------------ revno: 2630.4.18 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Tue 2008-06-03 21:07:58 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. Now during upgrading/downgrading metadata locks we deal with individual metadata lock requests rather than with all requests for this object in the context. This makes API a bit more clear and makes adjust_mdl_locks_upgradability() much nicer. sql/lock.cc: lock_table_names(): Set TABLE_LIST::mdl_lock_data when allocating new metadata lock request object for table list element. sql/mdl.cc: Now during upgrading/downgrading metadata locks we deal with individual metadata lock requests rather than with all requests for this object in the context. Adjusted upgrade/ downgrade functions accordingly. We also got rid of mdl_release_exclusive_locks() and now release locks individually. To simplify this process mdl_release_all_locks_for_name() was introduced. sql/mdl.h: Now during upgrading/downgrading metadata locks we deal with individual metadata lock requests rather than with all requests for this object in the context. Adjusted upgrade/ downgrade functions accordingly. We also got rid of mdl_release_exclusive_locks() and now release locks individually. To simplify this process mdl_release_all_locks_for_name() was introduced. sql/sql_base.cc: Now during upgrading/downgrading metadata locks we deal with individual metadata lock requests rather than with all requests for this object in the context. We also got rid of mdl_release_exclusive_locks() and now release locks individually. sql/sql_parse.cc: adjust_mdl_locks_upgradability() is much simplier now due to the fact that now during upgrading/downgrading metadata locks we deal with individual metadata lock requests rather than with all requests for this object in the context. sql/sql_table.cc: Now during upgrading/downgrading metadata locks we deal with individual metadata lock requests rather than with all requests for this object in the context. Adjusted upgrade/ downgrade functions accordingly. We also got rid of mdl_release_exclusive_locks() and now release locks individually. To simplify this process mdl_release_all_locks_for_name() was introduced. sql/sql_trigger.cc: ow during upgrading/downgrading metadata locks we deal with individual metadata lock requests rather than with all requests for this object in the context. --- sql/lock.cc | 1 + sql/mdl.cc | 259 +++++++++++++++++++++------------------------ sql/mdl.h | 10 +- sql/sql_base.cc | 23 ++-- sql/sql_parse.cc | 23 +--- sql/sql_table.cc | 102 +++++++++++++----- sql/sql_trigger.cc | 5 +- 7 files changed, 221 insertions(+), 202 deletions(-) diff --git a/sql/lock.cc b/sql/lock.cc index b5eaaa05fff..f391b323a59 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -976,6 +976,7 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list) goto end; mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); mdl_add_lock(&thd->mdl_context, mdl_lock_data); + lock_table->mdl_lock_data= mdl_lock_data; } if (mdl_acquire_exclusive_locks(&thd->mdl_context)) return 1; diff --git a/sql/mdl.cc b/sql/mdl.cc index 64c011d34bf..7ed2f6e8bdf 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -660,91 +660,95 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) Used in ALTER TABLE, when a copy of the table with the new definition has been constructed. - @param context Context to which shared long belongs - @param type Id of object type - @param db Name of the database - @param name Name of the object + @param context Context to which shared lock belongs + @param lock_data Satisfied request for shared lock to be upgraded - @note In case of failure to upgrade locks (e.g. because upgrader - was killed) leaves locks in their original state (locked - in shared mode). + @note In case of failure to upgrade lock (e.g. because upgrader + was killed) leaves lock in its original state (locked in + shared mode). @retval FALSE Success @retval TRUE Failure (thread was killed) */ -bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, - const char *db, const char *name) +bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data) { - char key[MAX_DBKEY_LENGTH]; - uint key_length; - bool signalled= FALSE; - MDL_LOCK_DATA *lock_data, *conf_lock_data; + MDL_LOCK_DATA *conf_lock_data; MDL_LOCK *lock; - I_P_List_iterator it(context->locks); const char *old_msg; THD *thd= context->thd; DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive"); - DBUG_PRINT("enter", ("db=%s name=%s", db, name)); DBUG_ASSERT(thd == current_thd); - int4store(key, type); - key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; - safe_mutex_assert_not_owner(&LOCK_open); + DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); + + /* Allow this function to be called twice for the same lock request. */ + if (lock_data->type == MDL_EXCLUSIVE) + DBUG_RETURN(FALSE); + + DBUG_ASSERT(lock_data->is_upgradable); + + lock= lock_data->lock; + pthread_mutex_lock(&LOCK_mdl); old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); - while ((lock_data= it++)) - if (lock_data->key_length == key_length && - !memcmp(lock_data->key, key, key_length) && - lock_data->type == MDL_SHARED) - { - DBUG_PRINT("info", ("found shared lock for upgrade")); - DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); - DBUG_ASSERT(lock_data->is_upgradable); - lock_data->state= MDL_PENDING_UPGRADE; - lock= lock_data->lock; - lock->active_shared.remove(lock_data); - lock->active_shared_waiting_upgrade.push_front(lock_data); - } + lock_data->state= MDL_PENDING_UPGRADE; + lock->active_shared.remove(lock_data); + /* + There can be only one upgrader for this lock or we will have deadlock. + This invariant is ensured by code outside of metadata subsystem usually + by obtaining some sort of exclusive table-level lock (e.g. TL_WRITE, + TL_WRITE_ALLOW_READ) before performing upgrade of metadata lock. + */ + DBUG_ASSERT(lock->active_shared_waiting_upgrade.is_empty()); + lock->active_shared_waiting_upgrade.push_front(lock_data); + + /* + There should be no conflicting global locks since for each upgradable + shared lock we obtain intention exclusive global lock first. + */ + DBUG_ASSERT(global_lock.active_shared == 0 && + global_lock.active_intention_exclusive); while (1) { + bool signalled= FALSE; + bool found_conflict= FALSE; + I_P_List_iterator it(lock->active_shared); + DBUG_PRINT("info", ("looking at conflicting locks")); - it.rewind(); - while ((lock_data= it++)) + + while ((conf_lock_data= it++)) { - if (lock_data->state == MDL_PENDING_UPGRADE) + /* + We can have other shared locks for the same object in the same context, + e.g. in case when several instances of TABLE are open. + */ + if (conf_lock_data->ctx != context) { - DBUG_ASSERT(lock_data->type == MDL_SHARED); - - lock= lock_data->lock; - - DBUG_ASSERT(global_lock.active_shared == 0 && - global_lock.active_intention_exclusive); - - if ((conf_lock_data= lock->active_shared.head())) - { - DBUG_PRINT("info", ("found active shared locks")); - signalled= notify_thread_having_shared_lock(thd, - conf_lock_data->ctx->thd); - break; - } - else if (!lock->active_exclusive.is_empty()) - { - DBUG_PRINT("info", ("found active exclusive locks")); - signalled= TRUE; - break; - } + DBUG_PRINT("info", ("found active shared locks")); + found_conflict= TRUE; + signalled|= notify_thread_having_shared_lock(thd, + conf_lock_data->ctx->thd); } } - if (!lock_data) + + /* + There should be no active exclusive locks since we own shared lock + on the object. + */ + DBUG_ASSERT(lock->active_exclusive.is_empty()); + + if (!found_conflict) break; + if (signalled) pthread_cond_wait(&COND_mdl, &LOCK_mdl); else @@ -762,16 +766,9 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, } if (thd->killed) { - it.rewind(); - while ((lock_data= it++)) - if (lock_data->state == MDL_PENDING_UPGRADE) - { - DBUG_ASSERT(lock_data->type == MDL_SHARED); - lock_data->state= MDL_ACQUIRED; - lock= lock_data->lock; - lock->active_shared_waiting_upgrade.remove(lock_data); - lock->active_shared.push_front(lock_data); - } + lock_data->state= MDL_ACQUIRED; + lock->active_shared_waiting_upgrade.remove(lock_data); + lock->active_shared.push_front(lock_data); /* Pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); thd->exit_cond(old_msg); @@ -779,20 +776,13 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, } } - it.rewind(); - while ((lock_data= it++)) - if (lock_data->state == MDL_PENDING_UPGRADE) - { - DBUG_ASSERT(lock_data->type == MDL_SHARED); - lock= lock_data->lock; - lock->active_shared_waiting_upgrade.remove(lock_data); - lock->active_exclusive.push_front(lock_data); - lock_data->type= MDL_EXCLUSIVE; - lock_data->state= MDL_ACQUIRED; - if (lock->cached_object) - (*lock->cached_object_release_hook)(lock->cached_object); - lock->cached_object= 0; - } + lock->active_shared_waiting_upgrade.remove(lock_data); + lock->active_exclusive.push_front(lock_data); + lock_data->type= MDL_EXCLUSIVE; + lock_data->state= MDL_ACQUIRED; + if (lock->cached_object) + (*lock->cached_object_release_hook)(lock->cached_object); + lock->cached_object= 0; /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ thd->exit_cond(old_msg); @@ -1085,49 +1075,6 @@ void mdl_release_locks(MDL_CONTEXT *context) } -/** - Release all exclusive locks associated with context. - Removes the locks from the context. - - @param context Context with exclusive locks. - - @note Shared locks are left intact. - @note Resets lock requests for locks released back to their - initial state (i.e.sets type and priority to MDL_SHARED - and MDL_NORMAL_PRIO). -*/ - -void mdl_release_exclusive_locks(MDL_CONTEXT *context) -{ - MDL_LOCK_DATA *lock_data; - I_P_List_iterator it(context->locks); - - safe_mutex_assert_not_owner(&LOCK_open); - - pthread_mutex_lock(&LOCK_mdl); - while ((lock_data= it++)) - { - if (lock_data->type == MDL_EXCLUSIVE) - { - DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); - release_lock(lock_data); -#ifndef DBUG_OFF - lock_data->ctx= 0; - lock_data->lock= 0; -#endif - lock_data->state= MDL_PENDING; - /* Return lock request to its initial state. */ - lock_data->type= MDL_SHARED; - lock_data->prio= MDL_NORMAL_PRIO; - lock_data->is_upgradable= FALSE; - context->locks.remove(lock_data); - } - } - pthread_cond_broadcast(&COND_mdl); - pthread_mutex_unlock(&LOCK_mdl); -} - - /** Release a lock. Removes the lock from the context. @@ -1161,32 +1108,68 @@ void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) /** - Downgrade all exclusive locks in the context to - shared. + Release all locks in the context which correspond to the same name/ + object as this lock request. - @param context A context with exclusive locks. + @param context Context containing locks in question + @param lock_data One of the locks for the name/object for which all + locks should be released. + + @see mdl_release_lock() */ -void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context) +void mdl_release_all_locks_for_name(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data) { - MDL_LOCK_DATA *lock_data; MDL_LOCK *lock; I_P_List_iterator it(context->locks); + DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); + + /* + We can use MDL_LOCK_DATA::lock here to identify other locks for the same + object since even altough MDL_LOCK object might be reused for different + lock after the first lock for this object have been released we can't + have references to this other MDL_LOCK object in this context. + */ + lock= lock_data->lock; + + while ((lock_data= it++)) + { + DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); + if (lock_data->lock == lock) + mdl_release_lock(context, lock_data); + } +} + + +/** + Downgrade an exclusive lock to shared metadata lock. + + @param context A context to which exclusive lock belongs + @param lock_data Satisfied request for exclusive lock to be downgraded +*/ + +void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data) +{ + MDL_LOCK *lock; + safe_mutex_assert_not_owner(&LOCK_open); + DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); + + if (lock_data->type == MDL_SHARED) + return; + + lock= lock_data->lock; + pthread_mutex_lock(&LOCK_mdl); - while ((lock_data= it++)) - if (lock_data->type == MDL_EXCLUSIVE) - { - DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); - if (!lock_data->is_upgradable) - global_lock.active_intention_exclusive--; - lock= lock_data->lock; - lock->active_exclusive.remove(lock_data); - lock_data->type= MDL_SHARED; - lock->active_shared.push_front(lock_data); - } + if (!lock_data->is_upgradable) + global_lock.active_intention_exclusive--; + lock->active_exclusive.remove(lock_data); + lock_data->type= MDL_SHARED; + lock->active_shared.push_front(lock_data); pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); } diff --git a/sql/mdl.h b/sql/mdl.h index 74c90c01730..12ce2bb9820 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -194,8 +194,8 @@ inline void mdl_set_upgradable(MDL_LOCK_DATA *lock_data) bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry); bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context); -bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type, - const char *db, const char *name); +bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data); bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context); @@ -203,9 +203,11 @@ bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context); bool mdl_wait_for_locks(MDL_CONTEXT *context); void mdl_release_locks(MDL_CONTEXT *context); -void mdl_release_exclusive_locks(MDL_CONTEXT *context); +void mdl_release_all_locks_for_name(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data); void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); -void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context); +void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data); void mdl_release_global_shared_lock(MDL_CONTEXT *context); bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, const char *db, diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 44db7938caf..6dc9c67f348 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1066,8 +1066,9 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, { for (TABLE_LIST *table= tables; table; table= table->next_local) { - TABLE *tab= find_locked_table(thd->open_tables, table->db, - table->table_name); + /* This should always succeed thanks to check in caller. */ + TABLE *tab= find_write_locked_table(thd->open_tables, table->db, + table->table_name); /* Checking TABLE::db_stat is essential in case when we have several instances of the table open and locked. @@ -1152,7 +1153,13 @@ err_with_reopen: result|= reopen_tables(thd, 1); thd->in_lock_tables=0; pthread_mutex_unlock(&LOCK_open); - mdl_downgrade_exclusive_locks(&thd->mdl_context); + /* + Since mdl_downgrade_exclusive_lock() won't do anything with shared + metadata lock it is much simplier to go through all open tables rather + than picking only those tables that were flushed. + */ + for (TABLE *tab= thd->open_tables; tab; tab= tab->next) + mdl_downgrade_exclusive_lock(&thd->mdl_context, tab->mdl_lock_data); } DBUG_RETURN(result); } @@ -2976,7 +2983,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, lock on this table to shared metadata lock. */ if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE) - mdl_downgrade_exclusive_locks(&thd->mdl_context); + mdl_downgrade_exclusive_lock(&thd->mdl_context, table_list->mdl_lock_data); table->mdl_lock_data= mdl_lock_data; @@ -3992,7 +3999,7 @@ static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table, thd->warning_info->clear_warning_info(thd->query_id); thd->clear_error(); // Clear error message - mdl_release_exclusive_locks(&thd->mdl_context); + mdl_release_lock(&thd->mdl_context, table->mdl_lock_data); break; case OT_REPAIR: mdl_set_lock_type(table->mdl_lock_data, MDL_EXCLUSIVE); @@ -4004,7 +4011,7 @@ static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table, pthread_mutex_unlock(&LOCK_open); result= auto_repair_table(thd, table); - mdl_release_exclusive_locks(&thd->mdl_context); + mdl_release_lock(&thd->mdl_context, table->mdl_lock_data); break; default: DBUG_ASSERT(0); @@ -8694,8 +8701,8 @@ int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt) /* If MERGE child, forward lock handling to parent. */ mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent : lpt->table, TRUE); - if (mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context, 0, - lpt->db, lpt->table_name)) + if (mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context, + lpt->table->mdl_lock_data)) { mysql_lock_downgrade_write(lpt->thd, lpt->table->parent ? lpt->table->parent : diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 24939f1cd21..52a4a4e0144 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -8114,31 +8114,10 @@ bool parse_sql(THD *thd, static void adjust_mdl_locks_upgradability(TABLE_LIST *tables) { - TABLE_LIST *tab, *otab; - - for (tab= tables; tab; tab= tab->next_global) + for (TABLE_LIST *tab= tables; tab; tab= tab->next_global) { if (tab->lock_type >= TL_WRITE_ALLOW_WRITE) tab->mdl_upgradable= TRUE; - else - { - /* - TODO: To get rid of this loop we need to change our code to do - metadata lock upgrade only for those instances of tables - which are write locked instead of doing such upgrade for - all instances of tables. - */ - for (otab= tables; otab; otab= otab->next_global) - if (otab->lock_type >= TL_WRITE_ALLOW_WRITE && - otab->db_length == tab->db_length && - otab->table_name_length == tab->table_name_length && - !strcmp(otab->db, tab->db) && - !strcmp(otab->table_name, tab->table_name)) - { - tab->mdl_upgradable= TRUE; - break; - } - } } } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 2a41fe2008c..9a55b386628 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1904,10 +1904,27 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, else if (thd->locked_tables) { for (table= tables; table; table= table->next_local) - if (!find_temporary_table(thd, table->db, table->table_name) && - !find_write_locked_table(thd->open_tables, table->db, - table->table_name)) - DBUG_RETURN(1); + if (find_temporary_table(thd, table->db, table->table_name)) + { + /* + Since we don't acquire metadata lock if we have found temporary + table, we should do something to avoid releasing it at the end. + */ + table->mdl_lock_data= 0; + } + else + { + /* + Since 'tables' list can't contain duplicates (this is ensured + by parser) it is safe to cache pointer to the TABLE instances + in its elements. + */ + table->table= find_write_locked_table(thd->open_tables, table->db, + table->table_name); + if (!table->table) + DBUG_RETURN(1); + table->mdl_lock_data= table->table->mdl_lock_data; + } } } @@ -1956,6 +1973,9 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, error= 0; } + /* Probably a non-temporary table. */ + non_temp_tables_count++; + /* If row-based replication is used and the table is not a temporary table, we add the table name to the drop statement @@ -1964,7 +1984,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ if (!drop_temporary && thd->current_stmt_binlog_row_based && !dont_log_query) { - non_temp_tables_count++; /* Don't write the database name if it is the current one (or if thd->db is NULL). @@ -1985,18 +2004,12 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, { if (thd->locked_tables) { - TABLE *tab= find_locked_table(thd->open_tables, db, table->table_name); - if (close_cached_table(thd, tab)) + if (close_cached_table(thd, table->table)) { error= -1; goto err_with_placeholders; } - /* - Leave LOCK TABLES mode if we managed to drop all tables - which were locked. - */ - if (thd->locked_tables->table_count == 0) - unlock_locked_tables(thd); + table->table= 0; } if (thd->killed) @@ -2175,10 +2188,32 @@ err_with_placeholders: doing this. Unfortunately in this case we are likely to get more false positives in lock_table_name_if_not_cached() function. So it makes sense to remove exclusive meta-data locks in all cases. + + Leave LOCK TABLES mode if we managed to drop all tables which were + locked. Additional check for 'non_temp_tables_count' is to avoid + leaving LOCK TABLES mode if we have dropped only temporary tables. */ - mdl_release_exclusive_locks(&thd->mdl_context); + if (thd->locked_tables && thd->locked_tables->table_count == 0 && + non_temp_tables_count > 0) + { + unlock_locked_tables(thd); + goto end; + } + for (table= tables; table; table= table->next_local) + { + if (table->mdl_lock_data) + { + /* + Under LOCK TABLES we may have several instances of table open + and locked and therefore have to remove several metadata lock + requests associated with them. + */ + mdl_release_all_locks_for_name(&thd->mdl_context, table->mdl_lock_data); + } + } } +end: DBUG_RETURN(error); } @@ -4122,7 +4157,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, unlock: if (target_lock_data) - mdl_release_exclusive_locks(&thd->mdl_context); + mdl_release_lock(&thd->mdl_context, target_lock_data); pthread_mutex_lock(&LOCK_lock_db); if (!--creating_table && creating_database) pthread_cond_signal(&COND_refresh); @@ -4292,9 +4327,8 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, old_lock_type= table->reginfo.lock_type; mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ - if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, 0, - table->s->db.str, - table->s->table_name.str)) + if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, + table->mdl_lock_data)) { mysql_lock_downgrade_write(thd, table, old_lock_type); DBUG_RETURN(TRUE); @@ -4476,6 +4510,10 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, table= &tmp_table; pthread_mutex_unlock(&LOCK_open); } + else + { + mdl_lock_data= table->mdl_lock_data; + } /* A MERGE table must not come here. */ DBUG_ASSERT(!table->child_l); @@ -4574,8 +4612,9 @@ end: closefrm(table, 1); // Free allocated memory pthread_mutex_unlock(&LOCK_open); } - if (error) - mdl_release_exclusive_locks(&thd->mdl_context); + /* In case of a temporary table there will be no metadata lock. */ + if (error && mdl_lock_data) + mdl_release_lock(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(error); } @@ -5539,7 +5578,7 @@ binlog: err: if (target_lock_data) - mdl_release_exclusive_locks(&thd->mdl_context); + mdl_release_lock(&thd->mdl_context, target_lock_data); DBUG_RETURN(res); } @@ -6477,7 +6516,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, uint order_num, ORDER *order, bool ignore) { TABLE *table, *new_table= 0; - MDL_LOCK_DATA *target_lock_data= 0; + MDL_LOCK_DATA *mdl_lock_data, *target_lock_data= 0; int error= 0; char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1]; char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias; @@ -6649,6 +6688,7 @@ view_err: if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ))) DBUG_RETURN(TRUE); table->use_all_columns(); + mdl_lock_data= table->mdl_lock_data; /* Prohibit changing of the UNION list of a non-temporary MERGE table @@ -6894,9 +6934,12 @@ view_err: lock here... */ if (new_name != table_name || new_db != db) - mdl_release_exclusive_locks(&thd->mdl_context); + { + mdl_release_lock(&thd->mdl_context, target_lock_data); + mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); + } else - mdl_downgrade_exclusive_locks(&thd->mdl_context); + mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_data); } DBUG_RETURN(error); } @@ -7575,10 +7618,11 @@ view_err: pthread_mutex_lock(&LOCK_open); unlink_open_table(thd, table, FALSE); pthread_mutex_unlock(&LOCK_open); - mdl_release_exclusive_locks(&thd->mdl_context); + mdl_release_lock(&thd->mdl_context, target_lock_data); + mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); } else - mdl_downgrade_exclusive_locks(&thd->mdl_context); + mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_data); } end_temporary: @@ -7634,7 +7678,7 @@ err: thd->abort_on_warning= save_abort_on_warning; } if (target_lock_data) - mdl_release_exclusive_locks(&thd->mdl_context); + mdl_release_lock(&thd->mdl_context, target_lock_data); DBUG_RETURN(TRUE); err_with_placeholders: @@ -7645,7 +7689,9 @@ err_with_placeholders: */ unlink_open_table(thd, table, FALSE); pthread_mutex_unlock(&LOCK_open); - mdl_release_exclusive_locks(&thd->mdl_context); + if (target_lock_data) + mdl_release_lock(&thd->mdl_context, target_lock_data); + mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(TRUE); } /* mysql_alter_table */ diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 4e2b77292d8..caf5c84e1f9 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -527,8 +527,9 @@ end: locks. Otherwise call to close_thread_tables() will take care about both TABLE instance created by reopen_name_locked_table() and meta-data lock. */ - if (thd->locked_tables) - mdl_downgrade_exclusive_locks(&thd->mdl_context); + if (thd->locked_tables && tables && tables->table) + mdl_downgrade_exclusive_lock(&thd->mdl_context, + tables->table->mdl_lock_data); if (need_start_waiting) start_waiting_global_read_lock(thd); From 379a441d9cc2c60585f96150bcacb41312c260f6 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 16:14:06 +0300 Subject: [PATCH 030/212] Backport of: --------------------------------------------------------- 2630.7.2 Konstantin Osipov 2008-06-02 Fix alignment in sql_table.cc (no other changes). sql/sql_table.cc: Fix alignment (no other changes). --- sql/sql_table.cc | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 9a55b386628..6aa3566b8fa 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -6855,8 +6855,8 @@ view_err: { error= 0; push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), - table->alias); + ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + table->alias); } if (!error && (new_name != table_name || new_db != db)) @@ -6882,20 +6882,20 @@ view_err: */ if (!access(new_name_buff,F_OK)) { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name); - error= -1; + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name); + error= -1; } else { - *fn_ext(new_name)=0; + *fn_ext(new_name)=0; pthread_mutex_lock(&LOCK_open); - if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0)) - error= -1; + if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0)) + error= -1; else if (Table_triggers_list::change_table_name(thd, db, table_name, new_db, new_alias)) { (void) mysql_rename_table(old_db_type, new_db, new_alias, db, - table_name, 0); + table_name, 0); error= -1; } pthread_mutex_unlock(&LOCK_open); @@ -6906,8 +6906,8 @@ view_err: { error= 0; push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), - table->alias); + ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + table->alias); } if (!error) @@ -6931,7 +6931,7 @@ view_err: them. TODO: Investigate what should be done with upgraded table-level - lock here... + lock here... */ if (new_name != table_name || new_db != db) { @@ -6966,7 +6966,7 @@ view_err: if (mysql_prepare_alter_table(thd, table, create_info, alter_info)) goto err; - + need_copy_table= alter_info->change_level; set_table_default_charset(thd, create_info, db); @@ -6990,7 +6990,7 @@ view_err: &index_add_buffer, &index_add_count, &candidate_key_count)) goto err; - + if (need_copy_table == ALTER_TABLE_METADATA_ONLY) need_copy_table= need_copy_table_res; } From 1ed585adff48bffbd5440eda246e1918d53ca7ac Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 16:27:03 +0300 Subject: [PATCH 031/212] Backport of: --------------------------------------------- 2630.7.3 Konstantin Osipov 2008-06-02 Various style changes preceding the removal of reopen_table(). (Post-review fixes for WL#3726). sql/event_db_repository.cc: Update to use the new signature of TABLE_LIST::init_one_table(). sql/mysql_priv.h: Move close_cached_table() and wait_while_table_is_used() to sql_base.cc. sql/sql_base.cc: Move close_cached_table() and wait_while_table_is_used() from sql_table.cc. sql/sql_table.cc: Move close_cached_table() and wait_while_table_is_used() to sql_base.cc. sql/table.h: Update the signature of TABLE_LIST::init_one_table(). --- sql/event_db_repository.cc | 8 ++-- sql/mysql_priv.h | 5 ++- sql/sql_base.cc | 79 ++++++++++++++++++++++++++++++++++++++ sql/sql_table.cc | 79 -------------------------------------- sql/table.h | 4 +- 5 files changed, 89 insertions(+), 86 deletions(-) diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 0cf16e3a8a4..b17785a6be7 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -554,7 +554,7 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE_LIST tables; DBUG_ENTER("Event_db_repository::open_event_table"); - tables.init_one_table("mysql", "event", lock_type); + tables.init_one_table("mysql", "event", "event", lock_type); alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) @@ -1109,7 +1109,7 @@ Event_db_repository::check_system_tables(THD *thd) /* Check mysql.db */ - tables.init_one_table("mysql", "db", TL_READ); + tables.init_one_table("mysql", "db", "db", TL_READ); alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) @@ -1127,7 +1127,7 @@ Event_db_repository::check_system_tables(THD *thd) close_thread_tables(thd); } /* Check mysql.user */ - tables.init_one_table("mysql", "user", TL_READ); + tables.init_one_table("mysql", "user", "user", TL_READ); alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) @@ -1148,7 +1148,7 @@ Event_db_repository::check_system_tables(THD *thd) close_thread_tables(thd); } /* Check mysql.event */ - tables.init_one_table("mysql", "event", TL_READ); + tables.init_one_table("mysql", "event", "event", TL_READ); alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 3e3d3b6df24..a29e819f90b 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1033,8 +1033,6 @@ bool check_dup(const char *db, const char *name, TABLE_LIST *tables); bool compare_record(TABLE *table); bool append_file_to_dir(THD *thd, const char **filename_ptr, const char *table_name); -bool wait_while_table_is_used(THD *thd, TABLE *table, - enum ha_extra_function function); bool table_def_init(void); void table_def_free(void); void assign_new_table_id(TABLE_SHARE *share); @@ -1390,6 +1388,9 @@ void add_join_on(TABLE_LIST *b,Item *expr); void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List *using_fields, SELECT_LEX *lex); bool add_proc_to_list(THD *thd, Item *item); +bool close_cached_table(THD *thd, TABLE *table); +bool wait_while_table_is_used(THD *thd, TABLE *table, + enum ha_extra_function function); void unlink_open_table(THD *thd, TABLE *find, bool unlock); void drop_open_table(THD *thd, TABLE *table, const char *db_name, const char *table_name); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 6dc9c67f348..237c4c8e771 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2200,6 +2200,85 @@ static void unlink_open_merge(THD *thd, TABLE *table, TABLE ***prev_pp) } +/** + Force all other threads to stop using the table by upgrading + metadata lock on it and remove unused TABLE instances from cache. + + @param thd Thread handler + @param table Table to remove from cache + @param function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted + HA_EXTRA_FORCE_REOPEN if table is not be used + HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed + + @note When returning, the table will be unusable for other threads + until metadata lock is downgraded. + + @retval FALSE Success. + @retval TRUE Failure (e.g. because thread was killed). +*/ + +bool wait_while_table_is_used(THD *thd, TABLE *table, + enum ha_extra_function function) +{ + enum thr_lock_type old_lock_type; + + DBUG_ENTER("wait_while_table_is_used"); + DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", + table->s->table_name.str, (ulong) table->s, + table->db_stat, table->s->version)); + + (void) table->file->extra(function); + + old_lock_type= table->reginfo.lock_type; + mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ + + if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, + table->mdl_lock_data)) + { + mysql_lock_downgrade_write(thd, table, old_lock_type); + DBUG_RETURN(TRUE); + } + + pthread_mutex_lock(&LOCK_open); + expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str); + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(FALSE); +} + + +/** + Upgrade metadata lock on the table and close all its instances. + + @param thd Thread handler + @param table Table to remove from cache + + @retval FALSE Success. + @retval TRUE Failure (e.g. because thread was killed). +*/ + +bool close_cached_table(THD *thd, TABLE *table) +{ + DBUG_ENTER("close_cached_table"); + + /* FIXME: check if we pass proper parameters everywhere. */ + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + DBUG_RETURN(TRUE); + + /* Close lock if this is not got with LOCK TABLES */ + if (thd->lock) + { + mysql_unlock_tables(thd, thd->lock); + thd->lock=0; // Start locked threads + } + + pthread_mutex_lock(&LOCK_open); + /* Close all copies of 'table'. This also frees all LOCK TABLES lock */ + unlink_open_table(thd, table, TRUE); + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(FALSE); +} + + /** Remove all instances of table from thread's open list and table cache. diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 6aa3566b8fa..f5c22d32e94 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -53,7 +53,6 @@ static bool mysql_prepare_alter_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, Alter_info *alter_info); -static bool close_cached_table(THD *thd, TABLE *table); #ifndef DBUG_OFF @@ -4295,84 +4294,6 @@ mysql_rename_table(handlerton *base, const char *old_db, } -/** - Force all other threads to stop using the table by upgrading - metadata lock on it and remove unused TABLE instances from cache. - - @param thd Thread handler - @param table Table to remove from cache - @param function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted - HA_EXTRA_FORCE_REOPEN if table is not be used - HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed - - @note When returning, the table will be unusable for other threads - until metadata lock is downgraded. - - @retval FALSE Success. - @retval TRUE Failure (e.g. because thread was killed). -*/ - -bool wait_while_table_is_used(THD *thd, TABLE *table, - enum ha_extra_function function) -{ - enum thr_lock_type old_lock_type; - - DBUG_ENTER("wait_while_table_is_used"); - DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", - table->s->table_name.str, (ulong) table->s, - table->db_stat, table->s->version)); - - (void) table->file->extra(function); - - old_lock_type= table->reginfo.lock_type; - mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ - - if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, - table->mdl_lock_data)) - { - mysql_lock_downgrade_write(thd, table, old_lock_type); - DBUG_RETURN(TRUE); - } - - pthread_mutex_lock(&LOCK_open); - expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str); - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(FALSE); -} - - -/** - Upgrade metadata lock on the table and close all its instances. - - @param thd Thread handler - @param table Table to remove from cache - - @retval FALSE Success. - @retval TRUE Failure (e.g. because thread was killed). -*/ - -static bool close_cached_table(THD *thd, TABLE *table) -{ - DBUG_ENTER("close_cached_table"); - - /* FIXME: check if we pass proper parameters everywhere. */ - if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) - DBUG_RETURN(TRUE); - - /* Close lock if this is not got with LOCK TABLES */ - if (thd->lock) - { - mysql_unlock_tables(thd, thd->lock); - thd->lock=0; // Start locked threads - } - - pthread_mutex_lock(&LOCK_open); - /* Close all copies of 'table'. This also frees all LOCK TABLES lock */ - unlink_open_table(thd, table, TRUE); - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(FALSE); -} - static int send_check_errmsg(THD *thd, TABLE_LIST* table, const char* operator_name, const char* errmsg) diff --git a/sql/table.h b/sql/table.h index 601f1e154c9..de1354e17e5 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1121,11 +1121,13 @@ struct TABLE_LIST */ inline void init_one_table(const char *db_name_arg, const char *table_name_arg, + const char *alias_arg, enum thr_lock_type lock_type_arg) { bzero((char*) this, sizeof(*this)); db= (char*) db_name_arg; - table_name= alias= (char*) table_name_arg; + table_name= (char*) table_name_arg; + alias= (char*) alias_arg; lock_type= lock_type_arg; } From 93b55a006d75c54ff4088020c39dbc658a062869 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 16:38:00 +0300 Subject: [PATCH 032/212] Backport of: ------------------------------------------------------------ revno: 2630.4.20 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Wed 2008-06-04 16:27:06 +0400 message: WL#3726 "DDL locking for all metadata objects" After review fixes in progress. Got rid of TABLE_LIST::mdl_upgradable member and related functions by using special flag which to be passed to open_table() which asks it to take upgradable metadata lock on table being opened. sql/mysql_priv.h: Added one more argument to open_n_lock_single_table() call to be able to pass flags to open_table() and mysql_lock_tables() called by it. Added new flag for controlling open_table() behavior asks it to take upgradable metadata locks on tables open for write. sql/sql_base.cc: Added new flag for controlling open_table() behavior asks it to take upgradable metadata locks on tables open for write. This allowed us to get rid of TABLE_LIST::mdl_upgradable member. Added one more argument to open_n_lock_single_table() call to be able to pass flags to open_table() and mysql_lock_tables() called by it. sql/sql_insert.cc: Added one more argument to open_n_lock_single_table() call to be able to pass flags to open_table() and mysql_lock_tables() called by it. sql/sql_parse.cc: Got rid of TABLE_LIST::mdl_upgradable member by using special flag which to be passed to open_table() which asks it to take upgradable metadata lock on table being opened. As result also got rid of adjust_mdl_locks_upgradability() function. sql/sql_table.cc: Got rid of TABLE_LIST::mdl_upgradable member and related functions by using special flag which to be passed to open_table() which asks it to take upgradable metadata lock on table being opened. sql/sql_view.cc: Got rid of TABLE_LIST::mdl_upgradable member and related functions by using special flag which to be passed to open_table() which asks it to take upgradable metadata lock on table being opened. sql/table.h: Got rid of TABLE_LIST::mdl_upgradable member and related functions by using special flag which to be passed to open_table() which asks it to take upgradable metadata lock on table being opened. --- sql/mysql_priv.h | 3 ++- sql/sql_base.cc | 13 +++++++++---- sql/sql_insert.cc | 2 +- sql/sql_parse.cc | 20 ++------------------ sql/sql_table.cc | 12 +++++------- sql/sql_view.cc | 3 --- sql/table.h | 15 --------------- 7 files changed, 19 insertions(+), 49 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index a29e819f90b..94875210d0b 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1488,7 +1488,7 @@ inline int open_and_lock_tables(THD *thd, TABLE_LIST *tables) } /* simple open_and_lock_tables without derived handling for single table */ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, - thr_lock_type lock_type); + thr_lock_type lock_type, uint flags); bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags); int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags, bool *need_reopen); @@ -2040,6 +2040,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, #define MYSQL_OPEN_TEMPORARY_ONLY 0x0004 #define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 #define MYSQL_LOCK_PERF_SCHEMA 0x0010 +#define MYSQL_OPEN_TAKE_UPGRADABLE_MDL 0x0020 void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 237c4c8e771..a08a96465ca 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2598,6 +2598,9 @@ void table_share_release_hook(void *share) No version number checking is done. MYSQL_OPEN_TEMPORARY_ONLY - Open only temporary table not the base table or view. + MYSQL_OPEN_TAKE_UPGRADABLE_MDL - Obtain upgradable + metadata lock for tables on which we are going to + take some kind of write table-level lock. IMPLEMENTATION Uses a cache of open tables to find a table not in use. @@ -2825,7 +2828,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { bool retry; - if (table_list->mdl_upgradable) + if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL && + table_list->lock_type >= TL_WRITE_ALLOW_WRITE) mdl_set_upgradable(mdl_lock_data); mdl_set_lock_priority(mdl_lock_data, (flags & MYSQL_LOCK_IGNORE_FLUSH) ? MDL_HIGH_PRIO : MDL_NORMAL_PRIO); @@ -4146,7 +4150,6 @@ static int add_merge_table_list(TABLE_LIST *tlist) /* Set lock type. */ child_l->lock_type= tlist->lock_type; - child_l->mdl_upgradable= tlist->mdl_upgradable; /* Set parent reference. */ child_l->parent_l= tlist; @@ -4905,6 +4908,8 @@ static bool check_lock_and_start_stmt(THD *thd, TABLE *table, @param[in] thd thread handle @param[in] table_l table to open is first table in this list @param[in] lock_type lock to use for table + @param[in] flags options to be used while opening and locking + table (see open_table(), mysql_lock_tables()) @return table @retval != NULL OK, opened table returned @@ -4930,7 +4935,7 @@ static bool check_lock_and_start_stmt(THD *thd, TABLE *table, */ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, - thr_lock_type lock_type) + thr_lock_type lock_type, uint flags) { TABLE_LIST *save_next_global; DBUG_ENTER("open_n_lock_single_table"); @@ -4946,7 +4951,7 @@ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, table_l->required_type= FRMTYPE_TABLE; /* Open the table. */ - if (simple_open_n_lock_tables(thd, table_l)) + if (open_and_lock_tables_derived(thd, table_l, FALSE, flags)) table_l->table= NULL; /* Just to be sure. */ /* Restore list. */ diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index a0c198f3196..75ad46f1440 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2304,7 +2304,7 @@ void kill_delayed_threads(void) bool Delayed_insert::open_and_lock_table() { if (!(table= open_n_lock_single_table(&thd, &table_list, - TL_WRITE_DELAYED))) + TL_WRITE_DELAYED, 0))) { thd.fatal_error(); // Abort waiting inserts return TRUE; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 52a4a4e0144..31bd34531e3 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -47,7 +47,6 @@ "FUNCTION" : "PROCEDURE") static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables); -static void adjust_mdl_locks_upgradability(TABLE_LIST *tables); const char *any_db="*any*"; // Special symbol for check_access @@ -3608,9 +3607,9 @@ end_with_restore_list: thd->options|= OPTION_TABLE_LOCK; alloc_mdl_locks(all_tables, &thd->locked_tables_root); thd->mdl_el_root= &thd->locked_tables_root; - adjust_mdl_locks_upgradability(all_tables); - if (!(res= simple_open_n_lock_tables(thd, all_tables))) + if (!(res= open_and_lock_tables_derived(thd, all_tables, FALSE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL))) { #ifdef HAVE_QUERY_CACHE if (thd->variables.query_cache_wlock_invalidate) @@ -8106,21 +8105,6 @@ bool parse_sql(THD *thd, return ret_value; } - -/** - Auxiliary function which marks metadata locks for all tables - on which we plan to take write lock as upgradable. -*/ - -static void adjust_mdl_locks_upgradability(TABLE_LIST *tables) -{ - for (TABLE_LIST *tab= tables; tab; tab= tab->next_global) - { - if (tab->lock_type >= TL_WRITE_ALLOW_WRITE) - tab->mdl_upgradable= TRUE; - } -} - /** @} (end of group Runtime_Environment) */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index f5c22d32e94..306fa5dcdf4 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4617,7 +4617,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, if (view_operator_func == NULL) table->required_type=FRMTYPE_TABLE; - open_and_lock_tables(thd, table); + open_and_lock_tables_derived(thd, table, TRUE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL); thd->no_warnings_for_error= 0; table->next_global= save_next_global; table->next_local= save_next_local; @@ -5074,7 +5075,6 @@ bool mysql_restore_table(THD* thd, TABLE_LIST* table_list) bool mysql_repair_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) { DBUG_ENTER("mysql_repair_table"); - set_all_mdl_upgradable(tables); DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, "repair", TL_WRITE, 1, test(check_opt->sql_flags & TT_USEFRM), @@ -5087,7 +5087,6 @@ bool mysql_repair_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) bool mysql_optimize_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) { DBUG_ENTER("mysql_optimize_table"); - set_all_mdl_upgradable(tables); DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, "optimize", TL_WRITE, 1,0,0,0, &handler::ha_optimize, 0)); @@ -6604,9 +6603,8 @@ view_err: DBUG_RETURN(error); } - table_list->mdl_upgradable= TRUE; - - if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ))) + if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL))) DBUG_RETURN(TRUE); table->use_all_columns(); mdl_lock_data= table->mdl_lock_data; @@ -7898,7 +7896,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, strxmov(table_name, table->db ,".", table->table_name, NullS); - t= table->table= open_n_lock_single_table(thd, table, TL_READ); + t= table->table= open_n_lock_single_table(thd, table, TL_READ, 0); thd->clear_error(); // these errors shouldn't get client protocol->prepare_for_resend(); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index c40f6643042..9382880ba1b 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1329,10 +1329,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, anyway. */ for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local) - { tbl->lock_type= table->lock_type; - tbl->mdl_upgradable= table->mdl_upgradable; - } /* If the view is mergeable, we might want to INSERT/UPDATE/DELETE into tables of this view. Preserve the diff --git a/sql/table.h b/sql/table.h index de1354e17e5..e7d7e2b08bf 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1371,11 +1371,6 @@ struct TABLE_LIST */ TAKE_EXCLUSIVE_MDL } open_type; - /** - Indicates that for this table/view we need to take shared metadata - lock which should be upgradable to exclusive metadata lock. - */ - bool mdl_upgradable; bool internal_tmp_table; /** TRUE if an alias for this table was specified in the SQL. */ bool is_alias; @@ -1796,14 +1791,4 @@ size_t max_row_length(TABLE *table, const uchar *data); void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root); -/** - Helper function which allows to mark all elements in table list - as requiring upgradable metadata locks. -*/ - -inline void set_all_mdl_upgradable(TABLE_LIST *tables) -{ - for (; tables; tables= tables->next_global) - tables->mdl_upgradable= TRUE; -} #endif /* TABLE_INCLUDED */ From 6d5dd31584d984686fefa1ae3271deeedcc34cc6 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 16:42:28 +0300 Subject: [PATCH 033/212] Backport of: ---------------------------------------------------------- revno: 2630.2.20 committer: Konstantin Osipov branch nick: mysql-6.0-runtime timestamp: Fri 2008-06-27 20:10:42 +0400 message: Fix a regression introduced by WL#3726 when a table was left in the table cache after DROP DATABASE. Implementation of DROP DATABASE reads a list of files in the database directory and constructs from it the list of tables to be dropped. If the filesystem is case-insensitive and case-preserving, the table names should be lowercased, because the same has been done when entries for them were inserted into the table cache. Skipping this step will lead to orphaned TABLEs left in the table cache. Fixes lowercase_table2 failure on powermacg5. sql/sql_db.cc: Lowercase the table name, it's used to construct the table cache key. --- sql/sql_db.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 44909880da0..b5c51601faf 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1142,6 +1142,11 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, (void) filename_to_tablename(file->name, table_list->table_name, MYSQL50_TABLE_NAME_PREFIX_LENGTH + strlen(file->name) + 1); + + /* To be able to correctly look up the table in the table cache. */ + if (lower_case_table_names) + my_casedn_str(files_charset_info, table_list->table_name); + table_list->alias= table_list->table_name; // If lower_case_table_names=2 table_list->internal_tmp_table= is_prefix(file->name, tmp_file_prefix); /* Link into list */ From 1480dafe2f018ad794f52ed7257aba53d6050afa Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 16:51:50 +0300 Subject: [PATCH 034/212] Backport of: ------------------------------------------------------------ revno: 2630.8.3 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w4 timestamp: Thu 2008-06-05 10:48:36 +0400 message: WL#3726 "DDL locking for all metadata objects". After-review fixes in progress. Adjust some comments that were using old terminology (name locks instead of exclusive metadata locks), brought some of them up-to-date with current situation in code. sql/sql_base.cc: Adjusted comments to use proper terminology. sql/sql_delete.cc: Adjusted comments to use proper terminology. sql/sql_handler.cc: Adjusted comments to use proper terminology. sql/sql_partition.cc: Adjusted comments to use proper terminology also fixed one comment to correspond to what really happens in code. sql/sql_show.cc: Adjusted comments to use proper terminology. sql/sql_table.cc: Adjusted comments to use proper terminology, brought one of them up-to-date with current situation. sql/sql_trigger.cc: Adjusted comments to use proper terminology. sql/table.h: Removed two unused members of TABLE_SHARE struct. --- sql/sql_base.cc | 30 +++++++++------------ sql/sql_delete.cc | 3 ++- sql/sql_handler.cc | 3 ++- sql/sql_partition.cc | 48 ++++++++++++++++----------------- sql/sql_show.cc | 7 ++--- sql/sql_table.cc | 63 ++++++++++++++++++++++---------------------- sql/sql_trigger.cc | 8 +++--- sql/table.h | 1 - 8 files changed, 77 insertions(+), 86 deletions(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index a08a96465ca..8d7b0a3c88c 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -869,7 +869,7 @@ void intern_close_table(TABLE *table) free_io_cache(table); delete table->triggers; - if (table->file) // Not true if name lock + if (table->file) // Not true if placeholder (void) closefrm(table, 1); // close file DBUG_VOID_RETURN; } @@ -2461,12 +2461,6 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list) if (reopen_table_entry(thd, table, table_list, table_name, key, key_length)) { - /* - If there was an error during opening of table (for example if it - does not exist) '*table' object can be wiped out. To be able - properly release name-lock in this case we should restore this - object to its original state. - */ my_free((uchar*)table, MYF(0)); DBUG_RETURN(TRUE); } @@ -2509,8 +2503,8 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list) exists and to FALSE otherwise. @note This function assumes that caller owns LOCK_open mutex. - It also assumes that the fact that there are no name-locks - on the table was checked beforehand. + It also assumes that the fact that there are no exclusive + metadata locks on the table was checked beforehand. @note If there is no .FRM file for the table but it exists in one of engines (e.g. it was created on another node of NDB cluster) @@ -2605,10 +2599,10 @@ void table_share_release_hook(void *share) IMPLEMENTATION Uses a cache of open tables to find a table not in use. - If table list element for the table to be opened has "create" flag - set and table does not exist, this function will automatically insert - a placeholder for exclusive name lock into the open tables cache and - will return the TABLE instance that corresponds to this placeholder. + If table list element for the table to be opened has "open_type" set + to OPEN_OR_CREATE and table does not exist, this function will take + exclusive metadata lock on the table, also it will do this if + "open_type" is TAKE_EXCLUSIVE_MDL. RETURN NULL Open failed. If refresh is set then one should close @@ -4708,11 +4702,11 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) if (action) { /* - We have met name-locked or old version of table. Now we have - to close all tables which are not up to date. We also have to - throw away set of prelocked tables (and thus close tables from - this set that were open by now) since it possible that one of - tables which determined its content was changed. + We have met exclusive metadata lock or old version of table. Now we + have to close all tables which are not up to date/release metadata + locks. We also have to throw away set of prelocked tables (and thus + close tables from this set that were open by now) since it possible + that one of tables which determined its content was changed. Instead of implementing complex/non-robust logic mentioned above we simply close and then reopen all tables. diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index f5c6dfd8986..612f9d1954d 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1079,7 +1079,8 @@ static bool mysql_truncate_by_delete(THD *thd, TABLE_LIST *table_list) normally can't safely do this. - We don't want an ok to be sent to the end user. - We don't want to log the truncate command - - If we want to have a name lock on the table on exit without errors. + - If we want to keep exclusive metadata lock on the table (obtained by + caller) on exit without errors. */ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 15c73fcadfe..a2c1f0e3782 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -282,7 +282,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) The thd->handler_tables list is kept as-is to avoid deadlocks if open_table(), called by open_tables(), needs to back-off because - of a pending name-lock on the table being opened. + of a pending exclusive metadata lock or flush for the table being + opened. See open_table() back-off comments for more details. */ diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 3c67574d8c1..edc9055fb39 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -6528,10 +6528,10 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 3) Lock the table in TL_WRITE_ONLY to ensure all other accesses to the table have completed. This ensures that other threads can not execute on the table in parallel. - 4) Get a name lock on the table. This ensures that we can release all - locks on the table and since no one can open the table, there can - be no new threads accessing the table. They will be hanging on the - name lock. + 4) Get an exclusive metadata lock on the table. This ensures that we + can release all other locks on the table and since no one can open + the table, there can be no new threads accessing the table. They + will be hanging on this exclusive lock. 5) Close all tables that have already been opened but didn't stumble on the abort locked previously. This is done as part of the close_data_files_and_morph_locks call. @@ -6606,14 +6606,15 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 3) Lock all partitions in TL_WRITE_ONLY to ensure that no users are still using the old partitioning scheme. Wait until all ongoing users have completed before progressing. - 4) Get a name lock on the table. This ensures that we can release all - locks on the table and since no one can open the table, there can - be no new threads accessing the table. They will be hanging on the - name lock. + 4) Get an exclusive metadata lock on the table. This ensures that we + can release all other locks on the table and since no one can open + the table, there can be no new threads accessing the table. They + will be hanging on this exclusive lock. 5) Close all tables that have already been opened but didn't stumble on the abort locked previously. This is done as part of the close_data_files_and_morph_locks call. - 6) Close all table handlers and unlock all handlers but retain name lock + 6) Close all table handlers and unlock all handlers but retain + metadata lock. 7) Write binlog 8) Now the change is completed except for the installation of the new frm file. We thus write an action in the log to change to @@ -6694,23 +6695,18 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, Copy from the reorganised partitions to the new partitions 4) Log that operation is completed and log all complete actions needed to complete operation from here - 5) Lock all partitions in TL_WRITE_ONLY to ensure that no users - are still using the old partitioning scheme. Wait until all - ongoing users have completed before progressing. - 6) Get a name lock of the table - 7) Close all tables opened but not yet locked, after this call we are - certain that no other thread is in the lock wait queue or has - opened the table. The name lock will ensure that they are blocked - on the open call. - This is achieved also by close_data_files_and_morph_locks call. - 8) Close all partitions opened by this thread, but retain name lock. - 9) Write bin log - 10) Prepare handlers for rename and delete of partitions - 11) Rename and drop the reorged partitions such that they are no - longer used and rename those added to their real new names. - 12) Install the shadow frm file - 13) Reopen the table if under lock tables - 14) Complete query + 5) Upgrade shared metadata lock on the table to an exclusive one. + After this we can be sure that there is no other connection + using this table (they will be waiting for metadata lock). + 6) Close all table instances opened by this thread, but retain + exclusive metadata lock. + 7) Write bin log + 8) Prepare handlers for rename and delete of partitions + 9) Rename and drop the reorged partitions such that they are no + longer used and rename those added to their real new names. + 10) Install the shadow frm file + 11) Reopen the table if under lock tables + 12) Complete query */ if (write_log_add_change_partition(lpt) || ERROR_INJECT_CRASH("crash_change_partition_1") || diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 647f914d28c..4d8e482cf04 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3271,7 +3271,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) /* We should not introduce deadlocks even if we already have some tables open and locked, since we won't lock tables which we will - open and will ignore possible name-locks for these tables. + open and will ignore pending exclusive metadata locks for these + tables by using high-priority requests for shared metadata locks. */ thd->reset_n_backup_open_tables_state(&open_tables_state_backup); @@ -7301,8 +7302,8 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name) Open the table by name in order to load Table_triggers_list object. NOTE: there is race condition here -- the table can be dropped after - LOCK_open is released. It will be fixed later by introducing - acquire-shared-table-name-lock functionality. + LOCK_open is released. It will be fixed later by acquiring shared + metadata lock on trigger or table name. */ uint num_tables; /* NOTE: unused, only to pass to open_tables(). */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 306fa5dcdf4..b5f7fad36a6 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -3674,9 +3674,9 @@ static inline void write_create_table_bin_log(THD *thd, If one creates a temporary table, this is automatically opened Note that this function assumes that caller already have taken - name-lock on table being created or used some other way to ensure - that concurrent operations won't intervene. mysql_create_table() - is a wrapper that can be used for this. + exclusive metadata lock on table being created or used some other + way to ensure that concurrent operations won't intervene. + mysql_create_table() is a wrapper that can be used for this. no_log is needed for the case of CREATE ... SELECT, as the logging will be done later in sql_insert.cc @@ -5279,13 +5279,13 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, /* - By opening source table we guarantee that it exists and no concurrent - DDL operation will mess with it. Later we also take an exclusive - name-lock on target table name, which makes copying of .frm file, - call to ha_create_table() and binlogging atomic against concurrent DML - and DDL operations on target table. Thus by holding both these "locks" - we ensure that our statement is properly isolated from all concurrent - operations which matter. + By opening source table and thus acquiring shared metadata lock on it + we guarantee that it exists and no concurrent DDL operation will mess + with it. Later we also take an exclusive metadata lock on target table + name, which makes copying of .frm file, call to ha_create_table() and + binlogging atomic against concurrent DML and DDL operations on target + table. Thus by holding both these "locks" we ensure that our statement + is properly isolated from all concurrent operations which matter. */ if (open_tables(thd, &src_table, ¬_used, 0)) DBUG_RETURN(TRUE); @@ -5338,15 +5338,13 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, Create a new table by copying from source table and sync the new table if the flag MY_SYNC is set - Altough exclusive name-lock on target table protects us from concurrent - DML and DDL operations on it we still want to wrap .FRM creation and call - to ha_create_table() in critical section protected by LOCK_open in order - to provide minimal atomicity against operations which disregard name-locks, - like I_S implementation, for example. This is a temporary and should not - be copied. Instead we should fix our code to always honor name-locks. - - Also some engines (e.g. NDB cluster) require that LOCK_open should be held - during the call to ha_create_table(). See bug #28614 for more info. + TODO: Obtaining LOCK_open mutex here is actually a legacy from the + times when some operations (e.g. I_S implementation) ignored + exclusive metadata lock on target table. Also some engines + (e.g. NDB cluster) require that LOCK_open should be held + during the call to ha_create_table() (See bug #28614 for more + info). So we should double check and probably fix this code + to not acquire this mutex. */ pthread_mutex_lock(&LOCK_open); if (src_table->schema_table) @@ -5461,9 +5459,9 @@ binlog: /* Here we open the destination table, on which we already have - name-lock. This is needed for store_create_info() to work. - The table will be closed by unlink_open_table() at the end - of this function. + exclusive metada lock. This is needed for store_create_info() + to work. The table will be closed by unlink_open_table() at + the end of this function. */ pthread_mutex_lock(&LOCK_open); if (reopen_name_locked_table(thd, table)) @@ -6794,9 +6792,9 @@ view_err: /* Then, we want check once again that target table does not exist. Actually the order of these two steps does not matter since - earlier we took name-lock on the target table, so we do them - in this particular order only to be consistent with 5.0, in which - we don't take this name-lock and where this order really matters. + earlier we took exclusive metadata lock on the target table, so + we do them in this particular order only to be consistent with 5.0, + in which we don't take this lock and where this order really matters. TODO: Investigate if we need this access() check at all. */ if (!access(new_name_buff,F_OK)) @@ -7354,18 +7352,19 @@ view_err: /* Data is copied. Now we: - 1) Wait until all other threads close old version of table. + 1) Wait until all other threads will stop using old version of table + by upgrading shared metadata lock to exclusive one. 2) Close instances of table open by this thread and replace them - with exclusive name-locks. + with placeholders to simplify reopen process. 3) Rename the old table to a temp name, rename the new one to the old name. 4) If we are under LOCK TABLES and don't do ALTER TABLE ... RENAME we reopen new version of table. 5) Write statement to the binary log. 6) If we are under LOCK TABLES and do ALTER TABLE ... RENAME we - remove name-locks from list of open tables and table cache. + remove placeholders and release metadata locks. 7) If we are not not under LOCK TABLES we rely on close_thread_tables() - call to remove name-locks from table cache and list of open table. + call to remove placeholders and releasing metadata locks. */ thd_proc_info(thd, "rename result table"); @@ -7602,9 +7601,9 @@ err: err_with_placeholders: /* - An error happened while we were holding exclusive name-lock on table - being altered. To be safe under LOCK TABLES we should remove placeholders - from list of open tables list and table cache. + An error happened while we were holding exclusive name metadata lock + on table being altered. To be safe under LOCK TABLES we should remove + placeholders from the list of open tables and relese metadata lock. */ unlink_open_table(thd, table, FALSE); pthread_mutex_unlock(&LOCK_open); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index caf5c84e1f9..61cd9bffa57 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -525,7 +525,7 @@ end: /* If we are under LOCK TABLES we should restore original state of meta-data locks. Otherwise call to close_thread_tables() will take care about both - TABLE instance created by reopen_name_locked_table() and meta-data lock. + TABLE instance created by reopen_name_locked_table() and metadata lock. */ if (thd->locked_tables && tables && tables->table) mdl_downgrade_exclusive_lock(&thd->mdl_context, @@ -1872,7 +1872,7 @@ Table_triggers_list::change_table_name_in_trignames(const char *old_db_name, i.e. it either will complete successfully, or will fail leaving files in their initial state. Also this method assumes that subject table is not renamed to itself. - This method needs to be called under an exclusive table name lock. + This method needs to be called under an exclusive table metadata lock. @retval FALSE Success @retval TRUE Error @@ -1894,8 +1894,8 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, /* This method interfaces the mysql server code protected by - either LOCK_open mutex or with an exclusive table name lock. - In the future, only an exclusive table name lock will be enough. + either LOCK_open mutex or with an exclusive metadata lock. + In the future, only an exclusive metadata lock will be enough. */ #ifndef DBUG_OFF if (mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, db, old_table)) diff --git a/sql/table.h b/sql/table.h index e7d7e2b08bf..48caf962894 100644 --- a/sql/table.h +++ b/sql/table.h @@ -408,7 +408,6 @@ struct TABLE_SHARE bool db_low_byte_first; /* Portable row format */ bool crashed; bool is_view; - bool name_lock, replace_with_name_lock; ulong table_map_id; /* for row-based replication */ ulonglong table_map_version; From 9d3c9344038912afcad4ebb0af1f76fb0e97b441 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 16:55:45 +0300 Subject: [PATCH 035/212] Backport of: ------------------------------------------------------------ revno: 2630.4.22 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Thu 2008-06-05 22:06:48 +0400 message: WL#3726 "DDL locking for all metadata objects" After review fixes in progress. Moved code checking that current lock request can be satisfied given the current state of individual or global metadata lock to separate well-documented functions. sql/mdl.cc: Moved code. --- sql/mdl.cc | 283 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 221 insertions(+), 62 deletions(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index 7ed2f6e8bdf..c63edf143e8 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -408,6 +408,188 @@ static void release_lock_object(MDL_LOCK *lock) } +/** + Check if request for the lock on particular object can be satisfied given + current state of the global metadata lock. + + @note In other words, we're trying to check that the individual lock + request, implying a form of lock on the global metadata, is + compatible with the current state of the global metadata lock. + + @param lock_data Request for lock on an individual object, implying a + certain kind of global metadata lock. + + @retval TRUE - Lock request can be satisfied + @retval FALSE - There is some conflicting lock + + Here is a compatibility matrix defined by this function: + + | | Satisfied or pending requests + | | for global metadata lock + ----------------+-------------+-------------------------------------------- + Type of request | Correspond. | + for indiv. lock | global lock | Active-S Pending-S Active-IS(**) Active-IX + ----------------+-------------+-------------------------------------------- + S | IS | + + + + + upgradable S | IX | - - + + + X | IX | - - + + + S upgraded to X | IX (*) | 0 + + + + + Here: "+" -- means that request can be satisfied + "-" -- means that request can't be satisfied and should wait + "0" -- means impossible situation which will trigger assert + + (*) Since for upgradable shared locks we always take intention exclusive + global lock at the same time when obtaining the shared lock, there + is no need to obtain such lock during the upgrade itself. + (**) Since intention shared global locks are compatible with all other + type of locks we don't even have any accounting for them. +*/ + +static bool can_grant_global_lock(MDL_LOCK_DATA *lock_data) +{ + switch (lock_data->type) + { + case MDL_SHARED: + if (lock_data->is_upgradable && + (global_lock.active_shared || global_lock.waiting_shared)) + { + /* + We are going to obtain intention exclusive global lock and + there is active or pending shared global lock. Have to wait. + */ + return FALSE; + } + else + return TRUE; + break; + case MDL_EXCLUSIVE: + if (lock_data->state == MDL_PENDING_UPGRADE) + { + /* + We are upgrading MDL_SHARED to MDL_EXCLUSIVE. + + There should be no conflicting global locks since for each upgradable + shared lock we obtain intention exclusive global lock first. + */ + DBUG_ASSERT(global_lock.active_shared == 0 && + global_lock.active_intention_exclusive); + return TRUE; + } + else + { + if (global_lock.active_shared || global_lock.waiting_shared) + { + /* + We are going to obtain intention exclusive global lock and + there is active or pending shared global lock. + */ + return FALSE; + } + else + return TRUE; + } + break; + default: + DBUG_ASSERT(0); + } + return FALSE; +} + + +/** + Check if request for the lock can be satisfied given current state of lock. + + @param lock Lock. + @param lock_data Request for lock. + + @retval TRUE Lock request can be satisfied + @retval FALSE There is some conflicting lock. + + This function defines the following compatibility matrix for metadata locks: + + | Satisfied or pending requests which we have in MDL_LOCK + ----------------+--------------------------------------------------------- + Current request | Active-S Pending-X Active-X Act-S-pend-upgrade-to-X + ----------------+--------------------------------------------------------- + S | + - - (*) - + High-prio S | + + - + + X | - + - - + S upgraded to X | - (**) + 0 0 + + Here: "+" -- means that request can be satisfied + "-" -- means that request can't be satisfied and should wait + "0" -- means impossible situation which will trigger assert + + (*) Unless active exclusive lock belongs to the same context as shared + lock being requested. + (**) Unless all active shared locks belong to the same context as one + being upgraded. +*/ + +static bool can_grant_lock(MDL_LOCK *lock, MDL_LOCK_DATA *lock_data) +{ + switch (lock_data->type) + { + case MDL_SHARED: + if ((lock->active_exclusive.is_empty() && + (lock_data->prio == MDL_HIGH_PRIO || + lock->waiting_exclusive.is_empty() && + lock->active_shared_waiting_upgrade.is_empty())) || + (!lock->active_exclusive.is_empty() && + lock->active_exclusive.head()->ctx == lock_data->ctx)) + { + /* + When exclusive lock comes from the same context we can satisfy our + shared lock. This is required for CREATE TABLE ... SELECT ... and + ALTER VIEW ... AS .... + */ + return TRUE; + } + else + return FALSE; + break; + case MDL_EXCLUSIVE: + if (lock_data->state == MDL_PENDING_UPGRADE) + { + /* We are upgrading MDL_SHARED to MDL_EXCLUSIVE. */ + MDL_LOCK_DATA *conf_lock_data; + I_P_List_iterator it(lock->active_shared); + + /* + There should be no active exclusive locks since we own shared lock + on the object. + */ + DBUG_ASSERT(lock->active_exclusive.is_empty() && + lock->active_shared_waiting_upgrade.head() == lock_data); + + while ((conf_lock_data= it++)) + { + /* + When upgrading shared lock to exclusive one we can have other shared + locks for the same object in the same context, e.g. in case when several + instances of TABLE are open. + */ + if (conf_lock_data->ctx != lock_data->ctx) + return FALSE; + } + return TRUE; + } + else + { + return (lock->active_exclusive.is_empty() && + lock->active_shared_waiting_upgrade.is_empty() && + lock->active_shared.is_empty()); + } + break; + default: + DBUG_ASSERT(0); + } + return FALSE; +} + + /** Try to acquire one shared lock. @@ -449,8 +631,7 @@ bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry) pthread_mutex_lock(&LOCK_mdl); - if (lock_data->is_upgradable && - (global_lock.active_shared || global_lock.waiting_shared)) + if (!can_grant_global_lock(lock_data)) { pthread_mutex_unlock(&LOCK_mdl); *retry= TRUE; @@ -461,6 +642,11 @@ bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry) lock_data->key_length))) { lock= get_lock_object(); + /* + Before inserting MDL_LOCK object into hash we should add at least one + MDL_LOCK_DATA to its lists in order to provide key for this element. + Thus we can't merge two branches of the above if-statement. + */ lock->active_shared.push_front(lock_data); lock->lock_data_count= 1; my_hash_insert(&mdl_locks, (uchar*)lock); @@ -471,18 +657,8 @@ bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry) } else { - if ((lock->active_exclusive.is_empty() && - (lock_data->prio == MDL_HIGH_PRIO || - lock->waiting_exclusive.is_empty() && - lock->active_shared_waiting_upgrade.is_empty())) || - (!lock->active_exclusive.is_empty() && - lock->active_exclusive.head()->ctx == lock_data->ctx)) + if (can_grant_lock(lock, lock_data)) { - /* - When exclusive lock comes from the same context we can satisfy our - shared lock. This is required for CREATE TABLE ... SELECT ... and - ALTER VIEW ... AS .... - */ lock->active_shared.push_front(lock_data); lock->lock_data_count++; lock_data->state= MDL_ACQUIRED; @@ -523,7 +699,7 @@ static void release_lock(MDL_LOCK_DATA *lock_data); bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) { - MDL_LOCK_DATA *lock_data, *conf_lock_data; + MDL_LOCK_DATA *lock_data; MDL_LOCK *lock; bool signalled= FALSE; const char *old_msg; @@ -552,6 +728,10 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) lock_data->key_length))) { lock= get_lock_object(); + /* + Again before inserting MDL_LOCK into hash provide key for + it by adding MDL_LOCK_DATA to one of its lists. + */ lock->waiting_exclusive.push_front(lock_data); lock->lock_data_count= 1; my_hash_insert(&mdl_locks, (uchar*)lock); @@ -572,30 +752,28 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) { lock= lock_data->lock; - if (global_lock.active_shared || global_lock.waiting_shared) + if (!can_grant_global_lock(lock_data)) { /* - There is active or pending global shared lock we have + There is an active or pending global shared lock so we have to wait until it goes away. */ signalled= TRUE; break; } - else if (!lock->active_exclusive.is_empty() || - !lock->active_shared_waiting_upgrade.is_empty()) + else if (!can_grant_lock(lock, lock_data)) { - /* - Exclusive MDL owner won't wait on table-level lock the same - applies to shared lock waiting upgrade (in this cases we already - have some table-level lock). - */ - signalled= TRUE; - break; - } - else if ((conf_lock_data= lock->active_shared.head())) - { - signalled= notify_thread_having_shared_lock(thd, - conf_lock_data->ctx->thd); + MDL_LOCK_DATA *conf_lock_data; + I_P_List_iterator it(lock->active_shared); + + signalled= !lock->active_exclusive.is_empty() || + !lock->active_shared_waiting_upgrade.is_empty(); + + while ((conf_lock_data= it++)) + signalled|= + notify_thread_having_shared_lock(thd, conf_lock_data->ctx->thd); + break; } } @@ -674,7 +852,6 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) { - MDL_LOCK_DATA *conf_lock_data; MDL_LOCK *lock; const char *old_msg; THD *thd= context->thd; @@ -700,6 +877,8 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); lock_data->state= MDL_PENDING_UPGRADE; + /* Set type of lock request to the type at which we are aiming. */ + lock_data->type= MDL_EXCLUSIVE; lock->active_shared.remove(lock_data); /* There can be only one upgrader for this lock or we will have deadlock. @@ -711,44 +890,27 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, lock->active_shared_waiting_upgrade.push_front(lock_data); /* - There should be no conflicting global locks since for each upgradable - shared lock we obtain intention exclusive global lock first. + Since we should have been already acquired intention exclusive global lock + this call is only enforcing asserts. */ - DBUG_ASSERT(global_lock.active_shared == 0 && - global_lock.active_intention_exclusive); + DBUG_ASSERT(can_grant_global_lock(lock_data)); while (1) { - bool signalled= FALSE; - bool found_conflict= FALSE; - I_P_List_iterator it(lock->active_shared); + if (can_grant_lock(lock, lock_data)) + break; - DBUG_PRINT("info", ("looking at conflicting locks")); + bool signalled= FALSE; + MDL_LOCK_DATA *conf_lock_data; + I_P_List_iterator it(lock->active_shared); while ((conf_lock_data= it++)) { - /* - We can have other shared locks for the same object in the same context, - e.g. in case when several instances of TABLE are open. - */ if (conf_lock_data->ctx != context) - { - DBUG_PRINT("info", ("found active shared locks")); - found_conflict= TRUE; signalled|= notify_thread_having_shared_lock(thd, conf_lock_data->ctx->thd); - } } - /* - There should be no active exclusive locks since we own shared lock - on the object. - */ - DBUG_ASSERT(lock->active_exclusive.is_empty()); - - if (!found_conflict) - break; - if (signalled) pthread_cond_wait(&COND_mdl, &LOCK_mdl); else @@ -767,6 +929,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, if (thd->killed) { lock_data->state= MDL_ACQUIRED; + lock_data->type= MDL_SHARED; lock->active_shared_waiting_upgrade.remove(lock_data); lock->active_shared.push_front(lock_data); /* Pending requests for shared locks can be satisfied now. */ @@ -778,7 +941,6 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, lock->active_shared_waiting_upgrade.remove(lock_data); lock->active_exclusive.push_front(lock_data); - lock_data->type= MDL_EXCLUSIVE; lock_data->state= MDL_ACQUIRED; if (lock->cached_object) (*lock->cached_object_release_hook)(lock->cached_object); @@ -941,8 +1103,7 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) while ((lock_data= it++)) { DBUG_ASSERT(lock_data->state == MDL_PENDING); - if ((lock_data->is_upgradable || lock_data->type == MDL_EXCLUSIVE) && - (global_lock.active_shared || global_lock.waiting_shared)) + if (!can_grant_global_lock(lock_data)) break; /* To avoid starvation we don't wait if we have pending MDL_EXCLUSIVE lock. @@ -950,9 +1111,7 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) if (lock_data->type == MDL_SHARED && (lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, lock_data->key_length)) && - !(lock->active_exclusive.is_empty() && - lock->active_shared_waiting_upgrade.is_empty() && - lock->waiting_exclusive.is_empty())) + !can_grant_lock(lock, lock_data)) break; } if (!lock_data) From d0a1f640db28448adf1f7cafe8e9d811e80e73da Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 16:59:11 +0300 Subject: [PATCH 036/212] Backport of: ------------------------------------------------------------ revno: 2630.4.24 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Fri 2008-06-06 14:28:58 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. Get rid of upgradability and priority attributes of metadata lock requests by replacing them with two new types of lock requests MDL_SHARED_UPGRADABLE and MDL_SHARED_HIGH_PRIO correspondingly. --- sql/mdl.cc | 97 +++++++++++++++++++++++++------------------------ sql/mdl.h | 54 +++++++-------------------- sql/sql_base.cc | 14 +++++-- sql/sql_show.cc | 2 +- 4 files changed, 75 insertions(+), 92 deletions(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index c63edf143e8..dbf08101159 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -261,8 +261,7 @@ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) by-pointer because of the underlying HASH implementation requires the key to be a contiguous buffer. - The initialized lock request will have MDL_SHARED type and - normal priority. + The initialized lock request will have MDL_SHARED type. Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2 Note that tables and views have the same lock type, since @@ -277,8 +276,6 @@ void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, lock_data->key= key; lock_data->type= MDL_SHARED; lock_data->state= MDL_PENDING; - lock_data->prio= MDL_NORMAL_PRIO; - lock_data->is_upgradable= FALSE; #ifndef DBUG_OFF lock_data->ctx= 0; lock_data->lock= 0; @@ -298,8 +295,7 @@ void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, @param name Name of of object @param root MEM_ROOT on which object should be allocated - @note The allocated lock request will have MDL_SHARED type and - normal priority. + @note The allocated lock request will have MDL_SHARED type. @retval 0 Error @retval non-0 Pointer to an object representing a lock request @@ -364,8 +360,7 @@ void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) we release all the locks acquired so-far but do not free them, since we know that the respective lock requests will be used again. - Also resets lock requests back to their initial state (i.e. - sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO). + Also resets lock requests back to their initial state (i.e. MDL_SHARED). @param context Context to be cleared. */ @@ -378,8 +373,6 @@ void mdl_remove_all_locks(MDL_CONTEXT *context) { /* Reset lock request back to its initial state. */ lock_data->type= MDL_SHARED; - lock_data->prio= MDL_NORMAL_PRIO; - lock_data->is_upgradable= FALSE; #ifndef DBUG_OFF lock_data->ctx= 0; #endif @@ -408,6 +401,16 @@ static void release_lock_object(MDL_LOCK *lock) } +/** + Helper functions which simplifies writing various checks and asserts. +*/ + +static bool is_shared(MDL_LOCK_DATA *lock_data) +{ + return (lock_data->type < MDL_EXCLUSIVE); +} + + /** Check if request for the lock on particular object can be satisfied given current state of the global metadata lock. @@ -430,7 +433,7 @@ static void release_lock_object(MDL_LOCK *lock) Type of request | Correspond. | for indiv. lock | global lock | Active-S Pending-S Active-IS(**) Active-IX ----------------+-------------+-------------------------------------------- - S | IS | + + + + + S, high-prio S | IS | + + + + upgradable S | IX | - - + + X | IX | - - + + S upgraded to X | IX (*) | 0 + + + @@ -451,8 +454,11 @@ static bool can_grant_global_lock(MDL_LOCK_DATA *lock_data) switch (lock_data->type) { case MDL_SHARED: - if (lock_data->is_upgradable && - (global_lock.active_shared || global_lock.waiting_shared)) + case MDL_SHARED_HIGH_PRIO: + return TRUE; + break; + case MDL_SHARED_UPGRADABLE: + if (global_lock.active_shared || global_lock.waiting_shared) { /* We are going to obtain intention exclusive global lock and @@ -512,7 +518,7 @@ static bool can_grant_global_lock(MDL_LOCK_DATA *lock_data) ----------------+--------------------------------------------------------- Current request | Active-S Pending-X Active-X Act-S-pend-upgrade-to-X ----------------+--------------------------------------------------------- - S | + - - (*) - + S, upgradable S | + - - (*) - High-prio S | + + - + X | - + - - S upgraded to X | - (**) + 0 0 @@ -532,8 +538,10 @@ static bool can_grant_lock(MDL_LOCK *lock, MDL_LOCK_DATA *lock_data) switch (lock_data->type) { case MDL_SHARED: + case MDL_SHARED_UPGRADABLE: + case MDL_SHARED_HIGH_PRIO: if ((lock->active_exclusive.is_empty() && - (lock_data->prio == MDL_HIGH_PRIO || + (lock_data->type == MDL_SHARED_HIGH_PRIO || lock->waiting_exclusive.is_empty() && lock->active_shared_waiting_upgrade.is_empty())) || (!lock->active_exclusive.is_empty() && @@ -619,11 +627,12 @@ bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry) MDL_LOCK *lock; *retry= FALSE; - DBUG_ASSERT(lock_data->type == MDL_SHARED && lock_data->state == MDL_PENDING); + DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_PENDING); safe_mutex_assert_not_owner(&LOCK_open); - if (lock_data->ctx->has_global_shared_lock && lock_data->is_upgradable) + if (lock_data->ctx->has_global_shared_lock && + lock_data->type == MDL_SHARED_UPGRADABLE) { my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); return TRUE; @@ -652,7 +661,7 @@ bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry) my_hash_insert(&mdl_locks, (uchar*)lock); lock_data->state= MDL_ACQUIRED; lock_data->lock= lock; - if (lock_data->is_upgradable) + if (lock_data->type == MDL_SHARED_UPGRADABLE) global_lock.active_intention_exclusive++; } else @@ -663,7 +672,7 @@ bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry) lock->lock_data_count++; lock_data->state= MDL_ACQUIRED; lock_data->lock= lock; - if (lock_data->is_upgradable) + if (lock_data->type == MDL_SHARED_UPGRADABLE) global_lock.active_intention_exclusive++; } else @@ -690,8 +699,7 @@ static void release_lock(MDL_LOCK_DATA *lock_data); The context may not have other lock requests. @note In case of failure (for example, if our thread was killed) - resets lock requests back to their initial state (MDL_SHARED - and MDL_NORMAL_PRIO). + resets lock requests back to their initial state (MDL_SHARED) @retval FALSE Success @retval TRUE Failure @@ -804,8 +812,6 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) release_lock(lock_data); /* Return lock request to its initial state. */ lock_data->type= MDL_SHARED; - lock_data->prio= MDL_NORMAL_PRIO; - lock_data->is_upgradable= FALSE; context->locks.remove(lock_data); } /* Pending requests for shared locks can be satisfied now. */ @@ -868,7 +874,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, if (lock_data->type == MDL_EXCLUSIVE) DBUG_RETURN(FALSE); - DBUG_ASSERT(lock_data->is_upgradable); + DBUG_ASSERT(lock_data->type == MDL_SHARED_UPGRADABLE); lock= lock_data->lock; @@ -929,7 +935,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, if (thd->killed) { lock_data->state= MDL_ACQUIRED; - lock_data->type= MDL_SHARED; + lock_data->type= MDL_SHARED_UPGRADABLE; lock->active_shared_waiting_upgrade.remove(lock_data); lock->active_shared.push_front(lock_data); /* Pending requests for shared locks can be satisfied now. */ @@ -1017,8 +1023,7 @@ bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, Acquire global shared metadata lock. Holding this lock will block all requests for exclusive locks - and shared locks which can be potentially upgraded to exclusive - (see MDL_LOCK_DATA::is_upgradable). + and shared locks which can be potentially upgraded to exclusive. @param context Current metadata locking context. @@ -1108,7 +1113,7 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) /* To avoid starvation we don't wait if we have pending MDL_EXCLUSIVE lock. */ - if (lock_data->type == MDL_SHARED && + if (is_shared(lock_data) && (lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, lock_data->key_length)) && !can_grant_lock(lock, lock_data)) @@ -1149,19 +1154,21 @@ static void release_lock(MDL_LOCK_DATA *lock_data) if (lock->cached_object) (*lock->cached_object_release_hook)(lock->cached_object); release_lock_object(lock); - if (lock_data->type == MDL_EXCLUSIVE && lock_data->state == MDL_ACQUIRED || - lock_data->type == MDL_SHARED && lock_data->state == MDL_ACQUIRED && - lock_data->is_upgradable) + if (lock_data->state == MDL_ACQUIRED && + (lock_data->type == MDL_EXCLUSIVE || + lock_data->type == MDL_SHARED_UPGRADABLE)) global_lock.active_intention_exclusive--; } else { switch (lock_data->type) { + case MDL_SHARED_UPGRADABLE: + global_lock.active_intention_exclusive--; + /* Fallthrough. */ case MDL_SHARED: + case MDL_SHARED_HIGH_PRIO: lock->active_shared.remove(lock_data); - if (lock_data->is_upgradable) - global_lock.active_intention_exclusive--; break; case MDL_EXCLUSIVE: if (lock_data->state == MDL_PENDING) @@ -1213,7 +1220,7 @@ void mdl_release_locks(MDL_CONTEXT *context) lists. Allows us to avoid problems in open_tables() in case of back-off */ - if (!(lock_data->type == MDL_SHARED && lock_data->state == MDL_PENDING)) + if (!(is_shared(lock_data) && lock_data->state == MDL_PENDING)) { release_lock(lock_data); lock_data->state= MDL_PENDING; @@ -1224,7 +1231,7 @@ void mdl_release_locks(MDL_CONTEXT *context) /* We will return lock request to its initial state only in mdl_remove_all_locks() since we need to know type of lock - request and if it is upgradable in mdl_wait_for_locks(). + request in mdl_wait_for_locks(). */ } /* Inefficient but will do for a while */ @@ -1242,7 +1249,7 @@ void mdl_release_locks(MDL_CONTEXT *context) @param lock_data Lock to be released @note Resets lock request for lock released back to its initial state - (i.e.sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO). + (i.e. sets type to MDL_SHARED). */ void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) @@ -1258,8 +1265,6 @@ void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) lock_data->state= MDL_PENDING; /* Return lock request to its initial state. */ lock_data->type= MDL_SHARED; - lock_data->prio= MDL_NORMAL_PRIO; - lock_data->is_upgradable= FALSE; context->locks.remove(lock_data); pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); @@ -1318,16 +1323,14 @@ void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); - if (lock_data->type == MDL_SHARED) + if (is_shared(lock_data)) return; lock= lock_data->lock; pthread_mutex_lock(&LOCK_mdl); - if (!lock_data->is_upgradable) - global_lock.active_intention_exclusive--; lock->active_exclusive.remove(lock_data); - lock_data->type= MDL_SHARED; + lock_data->type= MDL_SHARED_UPGRADABLE; lock->active_shared.push_front(lock_data); pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); @@ -1379,10 +1382,11 @@ bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, while ((lock_data= it++) && (lock_data->key_length != key_length || - memcmp(lock_data->key, key, key_length))) + memcmp(lock_data->key, key, key_length) || + !(lock_data->type == MDL_EXCLUSIVE && + lock_data->state == MDL_ACQUIRED))) continue; - return (lock_data && lock_data->type == MDL_EXCLUSIVE && - lock_data->state == MDL_ACQUIRED); + return lock_data; } @@ -1432,8 +1436,7 @@ bool mdl_has_pending_conflicting_lock(MDL_LOCK_DATA *lock_data) { bool result; - DBUG_ASSERT(lock_data->type == MDL_SHARED && - lock_data->state == MDL_ACQUIRED); + DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_ACQUIRED); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); diff --git a/sql/mdl.h b/sql/mdl.h index 12ce2bb9820..f99f38d6285 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -26,9 +26,20 @@ struct MDL_LOCK_DATA; struct MDL_LOCK; struct MDL_CONTEXT; -/** Type of metadata lock request. */ +/** + Type of metadata lock request. -enum enum_mdl_type {MDL_SHARED=0, MDL_EXCLUSIVE}; + - High-priority shared locks differ from ordinary shared locks by + that they ignore pending requests for exclusive locks. + - Upgradable shared locks can be later upgraded to exclusive + (because of that their acquisition involves implicit + acquisition of global intention-exclusive lock). + + @see Comments for can_grant_lock() and can_grant_global_lock() for details. +*/ + +enum enum_mdl_type {MDL_SHARED=0, MDL_SHARED_HIGH_PRIO, + MDL_SHARED_UPGRADABLE, MDL_EXCLUSIVE}; /** States which metadata lock request can have. */ @@ -36,16 +47,6 @@ enum enum_mdl_type {MDL_SHARED=0, MDL_EXCLUSIVE}; enum enum_mdl_state {MDL_PENDING=0, MDL_ACQUIRED, MDL_PENDING_UPGRADE}; -/** - Priority of metadata lock requests. High priority attribute is - applicable only to requests for shared locks and indicates that - such request should ignore pending requests for exclusive locks - and for upgrading of shared locks to exclusive. -*/ - -enum enum_mdl_prio {MDL_NORMAL_PRIO=0, MDL_HIGH_PRIO}; - - /** A pending lock request or a granted metadata lock. A lock is requested or granted based on a fully qualified name and type. E.g. for a table @@ -60,13 +61,6 @@ struct MDL_LOCK_DATA uint key_length; enum enum_mdl_type type; enum enum_mdl_state state; - enum enum_mdl_prio prio; - /** - TRUE -- if shared lock corresponding to this lock request at some - point might be upgraded to an exclusive lock and therefore conflicts - with global shared lock, FALSE -- otherwise. - */ - bool is_upgradable; private: /** @@ -170,28 +164,6 @@ inline void mdl_set_lock_type(MDL_LOCK_DATA *lock_data, enum_mdl_type lock_type) lock_data->type= lock_type; } -/** - Set priority for lock request. High priority can be only set - for shared locks. -*/ - -inline void mdl_set_lock_priority(MDL_LOCK_DATA *lock_data, enum_mdl_prio prio) -{ - DBUG_ASSERT(lock_data->type == MDL_SHARED && lock_data->state == MDL_PENDING); - lock_data->prio= prio; -} - -/** - Mark request for shared lock as upgradable. Can be only applied - to pending locks. -*/ - -inline void mdl_set_upgradable(MDL_LOCK_DATA *lock_data) -{ - DBUG_ASSERT(lock_data->type == MDL_SHARED && lock_data->state == MDL_PENDING); - lock_data->is_upgradable= TRUE; -} - bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry); bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context); bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 8d7b0a3c88c..e1a8e9e79cb 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2822,11 +2822,19 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { bool retry; + /* + There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we + want to be sure that caller doesn't pass us both flags simultaneously. + */ + DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || + !(flags & MYSQL_LOCK_IGNORE_FLUSH)); + if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL && table_list->lock_type >= TL_WRITE_ALLOW_WRITE) - mdl_set_upgradable(mdl_lock_data); - mdl_set_lock_priority(mdl_lock_data, (flags & MYSQL_LOCK_IGNORE_FLUSH) ? - MDL_HIGH_PRIO : MDL_NORMAL_PRIO); + mdl_set_lock_type(mdl_lock_data, MDL_SHARED_UPGRADABLE); + if (flags & MYSQL_LOCK_IGNORE_FLUSH) + mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO); + if (mdl_acquire_shared_lock(mdl_lock_data, &retry)) { if (retry) diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 4d8e482cf04..3d4c0a5aaf7 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3136,7 +3136,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, mdl_init_lock(&mdl_lock_data, mdlkey, 0, db_name->str, table_name->str); table_list.mdl_lock_data= &mdl_lock_data; mdl_add_lock(&thd->mdl_context, &mdl_lock_data); - mdl_set_lock_priority(&mdl_lock_data, MDL_HIGH_PRIO); + mdl_set_lock_type(&mdl_lock_data, MDL_SHARED_HIGH_PRIO); /* TODO: investigate if in this particular situation we can get by From 1b078b3f2df729859807d3905cf3e0b9f1b8d29c Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 17:02:32 +0300 Subject: [PATCH 037/212] Backport of: ------------------------------------------------------------ revno: 2630.4.25 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Fri 2008-06-06 15:32:48 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. Clarified some comments explaining control flow in prepare_for_repair(). --- sql/sql_table.cc | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index b5f7fad36a6..cac4bab27b7 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4397,16 +4397,16 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, if (!(check_opt->sql_flags & TT_USEFRM)) DBUG_RETURN(0); - if (!(table= table_list->table)) /* if open_ltable failed */ + if (!(table= table_list->table)) { + /* + Attempt to do full-blown table open in mysql_admin_table() has failed. + Let us try to open at least a .FRM for this table. + */ char key[MAX_DBKEY_LENGTH]; uint key_length; key_length= create_table_def_key(thd, key, table_list, 0); - /* - TODO: Check that REPAIR's code also conforms to meta-data - locking protocol. Fix if it is not. - */ mdl_lock_data= mdl_alloc_lock(0, table_list->db, table_list->table_name, thd->mem_root); mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); @@ -4486,12 +4486,19 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, if (table_list->table) { - /* If we could open the table, close it */ + /* + Table was successfully open in mysql_admin_table(). Now we need + to close it, but leave it protected by exclusive metadata lock. + */ if (close_cached_table(thd, table)) goto end; table_list->table= 0; } - // After this point we have X mdl lock in both cases + /* + After this point we have an exclusive metadata lock on our table + in both cases when table was successfully open in mysql_admin_table() + and when it was open in prepare_for_repair(). + */ if (my_rename(from, tmp, MYF(MY_WME))) { From 5969dcda21f67b18d2cb443db555d2b1c087bb62 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 17:39:03 +0300 Subject: [PATCH 038/212] Backport of: ---------------------------------------------------------- revno: 2630.4.26 committer: Konstantin Osipov branch nick: mysql-6.0-prelocked_mode-to-push timestamp: Fri 2008-06-06 23:19:04 +0400 message: WL#3726: work on review comments. Remove thd->locked_tables. Always store MYSQL_LOCK instances in thd->lock. Rename thd->prelocked_mode to thd->locked_tables_mode. Use thd->locked_tables_mode to determine if we are under LOCK TABLES. Update the code to not assume that if thd->lock is set, LOCK TABLES mode is off. Review comments. sql/ha_ndbcluster_binlog.cc: Don't unlock the lock under LOCK TABLES (safety). sql/handler.cc: There is no thd->locked_tables any more. Update comments. sql/lock.cc: There is no thd->locked_tables any more. sql/log.cc: Rename thd->prelocked_mode to thd->locked_tables_mode. sql/set_var.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. sql/sp_head.cc: Rename thd->prelocked_mode to thd->locked_tables_mode. sql/sql_base.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. Remove thd->locked_tables. sql/sql_cache.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. sql/sql_class.cc: Avoid code duplication. Do not release the table locks prematurely if we're under LOCK TABLES. Use thd->locked_tables_mode instead of thd->locked_tables. sql/sql_class.h: Remove thd->locked_tables. Make prelocked mode a kind of LOCK TABLES mode. Update comments. sql/sql_cursor.cc: Update comments. sql/sql_insert.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. Rename thd->prelocked_mode to thd->locked_tables_mode. sql/sql_load.cc: Rename thd->prelocked_mode to thd->locked_tables_mode. sql/sql_parse.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. Remove thd->locked_tables. sql/sql_partition.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. sql/sql_rename.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. sql/sql_select.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. sql/sql_table.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. sql/sql_trigger.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. sql/sql_update.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. sql/sql_view.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. storage/myisam/ha_myisam.cc: Use thd->locked_tables_mode to determine if we are under LOCK TABLES. --- sql/ha_ndbcluster_binlog.cc | 10 ++- sql/handler.cc | 13 +-- sql/lock.cc | 2 +- sql/log.cc | 2 +- sql/set_var.cc | 2 +- sql/sp_head.cc | 6 +- sql/sql_base.cc | 165 +++++++++++++++++------------------- sql/sql_cache.cc | 4 +- sql/sql_class.cc | 14 ++- sql/sql_class.h | 74 ++++++++-------- sql/sql_cursor.cc | 2 - sql/sql_insert.cc | 28 ++++-- sql/sql_load.cc | 5 +- sql/sql_parse.cc | 42 ++++----- sql/sql_partition.cc | 2 +- sql/sql_rename.cc | 2 +- sql/sql_select.cc | 4 +- sql/sql_table.cc | 26 +++--- sql/sql_trigger.cc | 8 +- sql/sql_update.cc | 2 +- sql/sql_view.cc | 4 +- storage/myisam/ha_myisam.cc | 4 +- 22 files changed, 208 insertions(+), 213 deletions(-) diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index 14d14db6b7d..6f0e4498d75 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -264,7 +264,7 @@ static void run_query(THD *thd, char *buf, char *end, DBUG_PRINT("query", ("%s", thd->query())); DBUG_ASSERT(!thd->in_sub_stmt); - DBUG_ASSERT(!thd->prelocked_mode); + DBUG_ASSERT(!thd->locked_tables_mode); mysql_parse(thd, thd->query(), thd->query_length(), &found_semicolon); @@ -2429,8 +2429,12 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row) goto add_ndb_binlog_index_err; } - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; + + if (! thd->locked_tables_mode) /* Is always TRUE */ + { + mysql_unlock_tables(thd, thd->lock); + thd->lock= 0; + } thd->options= saved_options; return 0; add_ndb_binlog_index_err: diff --git a/sql/handler.cc b/sql/handler.cc index 9c32171eefd..9366d91b290 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -4527,9 +4527,7 @@ static bool check_table_binlog_row_based(THD *thd, TABLE *table) DESCRIPTION This function will generate and write table maps for all tables - that are locked by the thread 'thd'. Either manually locked - (stored in THD::locked_tables) and automatically locked (stored - in THD::lock) are considered. + that are locked by the thread 'thd'. RETURN VALUE 0 All OK @@ -4537,25 +4535,22 @@ static bool check_table_binlog_row_based(THD *thd, TABLE *table) SEE ALSO THD::lock - THD::locked_tables */ static int write_locked_table_maps(THD *thd) { DBUG_ENTER("write_locked_table_maps"); - DBUG_PRINT("enter", ("thd: 0x%lx thd->lock: 0x%lx thd->locked_tables: 0x%lx " + DBUG_PRINT("enter", ("thd: 0x%lx thd->lock: 0x%lx " "thd->extra_lock: 0x%lx", - (long) thd, (long) thd->lock, - (long) thd->locked_tables, (long) thd->extra_lock)); + (long) thd, (long) thd->lock, (long) thd->extra_lock)); DBUG_PRINT("debug", ("get_binlog_table_maps(): %d", thd->get_binlog_table_maps())); if (thd->get_binlog_table_maps() == 0) { - MYSQL_LOCK *locks[3]; + MYSQL_LOCK *locks[2]; locks[0]= thd->extra_lock; locks[1]= thd->lock; - locks[2]= thd->locked_tables; for (uint i= 0 ; i < sizeof(locks)/sizeof(*locks) ; ++i ) { MYSQL_LOCK const *const lock= locks[i]; diff --git a/sql/lock.cc b/sql/lock.cc index f391b323a59..33c9edcea48 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -754,7 +754,7 @@ TABLE_LIST *mysql_lock_have_duplicate(THD *thd, TABLE_LIST *needle, goto end; /* Get command lock or LOCK TABLES lock. Maybe empty for INSERT DELAYED. */ - if (! (mylock= thd->lock ? thd->lock : thd->locked_tables)) + if (! (mylock= thd->lock)) goto end; /* If we have less than two tables, we cannot have duplicates. */ diff --git a/sql/log.cc b/sql/log.cc index c6802a9a6ed..dd82134ae56 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -4060,7 +4060,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) this will close all tables on the slave. */ bool const end_stmt= - thd->prelocked_mode && thd->lex->requires_prelocking(); + thd->locked_tables_mode && thd->lex->requires_prelocking(); if (thd->binlog_flush_pending_rows_event(end_stmt)) DBUG_RETURN(error); diff --git a/sql/set_var.cc b/sql/set_var.cc index 1490ffd9598..cff85df6e7c 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -4292,7 +4292,7 @@ bool sys_var_opt_readonly::update(THD *thd, set_var *var) DBUG_ENTER("sys_var_opt_readonly::update"); /* Prevent self dead-lock */ - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); DBUG_RETURN(true); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index fdf9909f85e..a0832285742 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1257,7 +1257,7 @@ sp_head::execute(THD *thd) Will write this SP statement into binlog separately (TODO: consider changing the condition to "not inside event union") */ - if (thd->prelocked_mode == NON_PRELOCKED) + if (thd->locked_tables_mode == LTM_NONE) thd->user_var_events_alloc= thd->mem_root; err_status= i->execute(thd, &ip); @@ -1269,7 +1269,7 @@ sp_head::execute(THD *thd) If we've set thd->user_var_events_alloc to mem_root of this SP statement, clean all the events allocated in it. */ - if (thd->prelocked_mode == NON_PRELOCKED) + if (thd->locked_tables_mode == LTM_NONE) { reset_dynamic(&thd->user_var_events); thd->user_var_events_alloc= NULL;//DEBUG @@ -2740,7 +2740,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, thd->query_id= next_query_id(); pthread_mutex_unlock(&LOCK_thread_count); - if (thd->prelocked_mode == NON_PRELOCKED) + if (thd->locked_tables_mode == LTM_NONE) { /* This statement will enter/leave prelocked mode on its own. diff --git a/sql/sql_base.cc b/sql/sql_base.cc index e1a8e9e79cb..09bb05a91c1 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1025,7 +1025,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, DBUG_ASSERT(!have_lock); pthread_mutex_unlock(&LOCK_open); - if (thd->locked_tables) + if (thd->locked_tables_mode) { /* If we are under LOCK TABLES we need to reopen tables without @@ -1141,7 +1141,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, } err_with_reopen: - if (thd->locked_tables) + if (thd->locked_tables_mode) { pthread_mutex_lock(&LOCK_open); /* @@ -1375,7 +1375,6 @@ void close_thread_tables(THD *thd, bool skip_mdl) { TABLE *table; - prelocked_mode_type prelocked_mode= thd->prelocked_mode; DBUG_ENTER("close_thread_tables"); #ifdef EXTRA_DEBUG @@ -1433,11 +1432,12 @@ void close_thread_tables(THD *thd, Reset transaction state, but only if we're not inside a sub-statement of a prelocked statement. */ - if (! prelocked_mode || thd->lex->requires_prelocking()) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES || + thd->lex->requires_prelocking()) thd->transaction.stmt.reset(); } - if (thd->locked_tables || prelocked_mode) + if (thd->locked_tables_mode) { /* Ensure we are calling ha_reset() for all used tables */ @@ -1447,7 +1447,7 @@ void close_thread_tables(THD *thd, We are under simple LOCK TABLES or we're inside a sub-statement of a prelocked statement, so should not do anything else. */ - if (!prelocked_mode || !thd->lex->requires_prelocking()) + if (! thd->lex->requires_prelocking()) DBUG_VOID_RETURN; /* @@ -1455,18 +1455,19 @@ void close_thread_tables(THD *thd, so we have to leave the prelocked mode now with doing implicit UNLOCK TABLES if needed. */ - DBUG_PRINT("info",("thd->prelocked_mode= NON_PRELOCKED")); - thd->prelocked_mode= NON_PRELOCKED; + if (thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) + thd->locked_tables_mode= LTM_LOCK_TABLES; - if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES) + if (thd->locked_tables_mode == LTM_LOCK_TABLES) DBUG_VOID_RETURN; + thd->locked_tables_mode= LTM_NONE; + thd->options&= ~OPTION_TABLE_LOCK; + /* Note that we are leaving prelocked mode so we don't need to care about THD::locked_tables_root. */ - thd->lock= thd->locked_tables; - thd->locked_tables= 0; /* Fallthrough */ } @@ -1501,16 +1502,6 @@ void close_thread_tables(THD *thd, mdl_remove_all_locks(&thd->mdl_context); } - if (prelocked_mode == PRELOCKED) - { - /* - If we are here then we are leaving normal prelocked mode, so it is - good idea to turn off OPTION_TABLE_LOCK flag. - */ - DBUG_ASSERT(thd->lex->requires_prelocking()); - thd->options&= ~(OPTION_TABLE_LOCK); - } - DBUG_VOID_RETURN; } @@ -1951,9 +1942,10 @@ TABLE *find_temporary_table(THD *thd, TABLE_LIST *table_list) Try to locate the table in the list of thd->temporary_tables. If the table is found: - if the table is being used by some outer statement, fail. - - if the table is in thd->locked_tables, unlock it and - remove it from the list of locked tables. Currently only transactional - temporary tables are present in the locked_tables list. + - if the table is locked with LOCK TABLES or by prelocking, + unlock it and remove it from the list of locked tables + (THD::lock). Currently only transactional temporary tables + are locked. - Close the temporary table, remove its .FRM - remove the table from the list of temporary tables @@ -1992,7 +1984,7 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list) If LOCK TABLES list is not empty and contains this table, unlock the table and remove the table from this list. */ - mysql_lock_remove(thd, thd->locked_tables, table, FALSE); + mysql_lock_remove(thd, thd->lock, table, FALSE); close_temporary_table(thd, table, 1, 1); DBUG_RETURN(0); } @@ -2265,7 +2257,7 @@ bool close_cached_table(THD *thd, TABLE *table) DBUG_RETURN(TRUE); /* Close lock if this is not got with LOCK TABLES */ - if (thd->lock) + if (! thd->locked_tables_mode) { mysql_unlock_tables(thd, thd->lock); thd->lock=0; // Start locked threads @@ -2318,8 +2310,8 @@ void unlink_open_table(THD *thd, TABLE *find, bool unlock) if (list->s->table_cache_key.length == key_length && !memcmp(list->s->table_cache_key.str, key, key_length)) { - if (unlock && thd->locked_tables) - mysql_lock_remove(thd, thd->locked_tables, + if (unlock && thd->locked_tables_mode) + mysql_lock_remove(thd, thd->lock, list->parent ? list->parent : list, TRUE); /* Prepare MERGE table for close. Close parent if necessary. */ @@ -2690,7 +2682,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, open not pre-opened tables in pre-locked/LOCK TABLES mode. TODO: move this block into a separate function. */ - if (thd->locked_tables || thd->prelocked_mode) + if (thd->locked_tables_mode) { // Using table locks TABLE *best_table= 0; int best_distance= INT_MIN; @@ -2705,7 +2697,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, */ if (!my_strcasecmp(system_charset_info, table->alias, alias) && table->query_id != thd->query_id && /* skip tables already used */ - !(thd->prelocked_mode && table->query_id) && + (thd->locked_tables_mode == LTM_LOCK_TABLES || + table->query_id == 0) && !table->parent) { int distance= ((int) table->reginfo.lock_type - @@ -2788,7 +2781,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, so we may only end up here if the table did not exist when locked tables list was created. */ - if (thd->prelocked_mode == PRELOCKED) + if (thd->locked_tables_mode == LTM_PRELOCKED) my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias); else my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias); @@ -3329,7 +3322,7 @@ void close_data_files_and_leave_as_placeholders(THD *thd, const char *db, safe_mutex_assert_owner(&LOCK_open); - if (thd->lock) + if (! thd->locked_tables_mode) { /* If we are not under LOCK TABLES we should have only one table @@ -3344,7 +3337,7 @@ void close_data_files_and_leave_as_placeholders(THD *thd, const char *db, if (!strcmp(table->s->table_name.str, table_name) && !strcmp(table->s->db.str, db)) { - if (thd->locked_tables) + if (thd->locked_tables_mode) { if (table->parent) { @@ -3354,11 +3347,11 @@ void close_data_files_and_leave_as_placeholders(THD *thd, const char *db, the parent and close it. OTOH in most cases a MERGE table won't have multiple children with the same db.table_name. */ - mysql_lock_remove(thd, thd->locked_tables, table->parent, TRUE); + mysql_lock_remove(thd, thd->lock, table->parent, TRUE); close_handle_and_leave_table_as_placeholder(table->parent); } else - mysql_lock_remove(thd, thd->locked_tables, table, TRUE); + mysql_lock_remove(thd, thd->lock, table, TRUE); } table->s->version= 0; close_handle_and_leave_table_as_placeholder(table); @@ -3554,7 +3547,7 @@ bool reopen_tables(THD *thd, bool get_locks) if ((lock= mysql_lock_tables(thd, tables, (uint) (tables_ptr - tables), flags, ¬_used))) { - thd->locked_tables=mysql_lock_merge(thd->locked_tables,lock); + thd->lock= mysql_lock_merge(thd->lock, lock); } else { @@ -3587,17 +3580,22 @@ void unlock_locked_tables(THD *thd) DBUG_ASSERT(!thd->in_sub_stmt && !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); - if (thd->locked_tables) - { - thd->lock= thd->locked_tables; - thd->locked_tables=0; - close_thread_tables(thd); - /* - After closing tables we can free memory used for storing lock - request objects for metadata locks - */ - free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE)); - } + /* + Sic: we must be careful to not close open tables if + we're not in LOCK TABLES mode: unlock_locked_tables() is + sometimes called implicitly, expecting no effect on + open tables, e.g. from begin_trans(). + */ + if (thd->locked_tables_mode != LTM_LOCK_TABLES) + return; + + thd->locked_tables_mode= LTM_NONE; + close_thread_tables(thd); + /* + After closing tables we can free memory used for storing lock + request objects for metadata locks + */ + free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE)); } @@ -4494,11 +4492,6 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) prelocking it won't do such precaching and will simply reuse table list which is already built. - If any table has a trigger and start->trg_event_map is non-zero - the final lock will end up in thd->locked_tables, otherwise, the - lock will be placed in thd->lock. See also comments in - st_lex::set_trg_event_type_for_tables(). - RETURN 0 - OK -1 - error @@ -4543,10 +4536,10 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) If we are not already executing prelocked statement and don't have statement for which table list for prelocking is already built, let us cache routines and try to build such table list. - */ - if (!thd->prelocked_mode && !thd->lex->requires_prelocking() && + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && + !thd->lex->requires_prelocking() && thd->lex->uses_stored_routines()) { bool first_no_prelocking, need_prelocking; @@ -4759,7 +4752,8 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) If we lock table for reading we won't update it so there is no need to process its triggers since they never will be activated. */ - if (!thd->prelocked_mode && !thd->lex->requires_prelocking() && + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && + !thd->lex->requires_prelocking() && tables->trg_event_map && tables->table->triggers && tables->lock_type >= TL_WRITE_ALLOW_WRITE) { @@ -4780,7 +4774,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); } - if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables) + if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables_mode) { if (tables->lock_type == TL_WRITE_DEFAULT) tables->table->reginfo.lock_type= thd->update_lock_default; @@ -4803,10 +4797,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) DBUG_PRINT("tcache", ("is parent: %d is child: %d", test(tables->table->child_l), test(tables->parent_l))); - DBUG_PRINT("tcache", ("in lock tables: %d in prelock mode: %d", - test(thd->locked_tables), test(thd->prelocked_mode))); - if (((!thd->locked_tables && !thd->prelocked_mode) || - tables->table->s->tmp_table) && + if ((!thd->locked_tables_mode || tables->table->s->tmp_table) && ((tables->table->child_l && add_merge_table_list(tables)) || (tables->parent_l && @@ -4822,7 +4813,8 @@ process_view_routines: Again we may need cache all routines used by this view and add tables used by them to table list. */ - if (tables->view && !thd->prelocked_mode && + if (tables->view && + thd->locked_tables_mode <= LTM_LOCK_TABLES && !thd->lex->requires_prelocking() && tables->view->uses_stored_routines()) { @@ -4996,7 +4988,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, DBUG_ENTER("open_ltable"); /* should not be used in a prelocked_mode context, see NOTE above */ - DBUG_ASSERT(!thd->prelocked_mode); + DBUG_ASSERT(thd->locked_tables_mode < LTM_PRELOCKED); thd_proc_info(thd, "Opening table"); thd->current_tablenr= 0; @@ -5033,7 +5025,7 @@ retry: table_list->lock_type= lock_type; table_list->table= table; table->grant= table_list->grant; - if (thd->locked_tables) + if (thd->locked_tables_mode) { if (check_lock_and_start_stmt(thd, table, lock_type)) table= 0; @@ -5355,9 +5347,9 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) handling thr_lock gives us. You most always get all needed locks at once. - If query for which we are calling this function marked as requring - prelocking, this function will do implicit LOCK TABLES and change - thd::prelocked_mode accordingly. + If query for which we are calling this function marked as requiring + prelocking, this function will change locked_tables_mode to + LTM_PRELOCKED. RETURN VALUES 0 ok @@ -5374,22 +5366,24 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, We can't meet statement requiring prelocking if we already in prelocked mode. */ - DBUG_ASSERT(!thd->prelocked_mode || !thd->lex->requires_prelocking()); + DBUG_ASSERT(thd->locked_tables_mode <= LTM_LOCK_TABLES || + !thd->lex->requires_prelocking()); *need_reopen= FALSE; if (!tables && !thd->lex->requires_prelocking()) DBUG_RETURN(decide_logging_format(thd, tables)); /* - We need this extra check for thd->prelocked_mode because we want to avoid - attempts to lock tables in substatements. Checking for thd->locked_tables - is not enough in some situations. For example for SP containing + Check for thd->locked_tables_mode to avoid a redundant + and harmful attempt to lock the already locked tables again. + Checking for thd->lock is not enough in some situations. For example, + if a stored function contains "drop table t3; create temporary t3 ..; insert into t3 ...;" - thd->locked_tables may be 0 after drop tables, and without this extra - check insert will try to lock temporary table t3, that will lead - to memory leak... + thd->lock may be 0 after drop tables, whereas locked_tables_mode + is still on. In this situation an attempt to lock temporary + table t3 will lead to a memory leak. */ - if (!thd->locked_tables && !thd->prelocked_mode) + if (! thd->locked_tables_mode) { DBUG_ASSERT(thd->lock == 0); // You must lock everything at once TABLE **start,**ptr; @@ -5443,15 +5437,8 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, We just have done implicit LOCK TABLES, and now we have to emulate first open_and_lock_tables() after it. - Note that "LOCK TABLES" can also be marked as requiring prelocking - (e.g. if one locks view which uses functions). We should not emulate - such open_and_lock_tables() in this case. We also should not set - THD::prelocked_mode or first close_thread_tables() call will do - "UNLOCK TABLES". */ - thd->locked_tables= thd->lock; - thd->lock= 0; - thd->in_lock_tables=0; + thd->in_lock_tables= 0; /* When open_and_lock_tables() is called for a single table out of @@ -5474,8 +5461,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, This was an attempt to enter prelocked mode so there is no need to care about THD::locked_tables_root here. */ - mysql_unlock_tables(thd, thd->locked_tables); - thd->locked_tables= 0; + mysql_unlock_tables(thd, thd->lock); thd->options&= ~(OPTION_TABLE_LOCK); DBUG_RETURN(-1); } @@ -5486,8 +5472,8 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, and was marked as occupied during open_tables() as free for reuse. */ mark_real_tables_as_free_for_reuse(first_not_own); - DBUG_PRINT("info",("prelocked_mode= PRELOCKED")); - thd->prelocked_mode= PRELOCKED; + DBUG_PRINT("info",("locked_tables_mode= PRELOCKED")); + thd->locked_tables_mode= LTM_PRELOCKED; } } else @@ -5512,7 +5498,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, In a stored function or trigger we should ensure that we won't change a table that is already used by the calling statement. */ - if (thd->prelocked_mode && + if (thd->locked_tables_mode >= LTM_PRELOCKED && table->lock_type >= TL_WRITE_ALLOW_WRITE) { for (TABLE* opentab= thd->open_tables; opentab; opentab= opentab->next) @@ -5540,8 +5526,9 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, if (thd->lex->requires_prelocking()) { mark_real_tables_as_free_for_reuse(first_not_own); - DBUG_PRINT("info", ("thd->prelocked_mode= PRELOCKED_UNDER_LOCK_TABLES")); - thd->prelocked_mode= PRELOCKED_UNDER_LOCK_TABLES; + DBUG_PRINT("info", + ("thd->locked_tables_mode= LTM_PRELOCKED_UNDER_LOCK_TABLES")); + thd->locked_tables_mode= LTM_PRELOCKED_UNDER_LOCK_TABLES; } } diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index fb370cbd16a..871efeddd43 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1136,7 +1136,7 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) See also a note on double-check locking usage above. */ - if (thd->locked_tables || query_cache_size == 0) + if (thd->locked_tables_mode || query_cache_size == 0) DBUG_VOID_RETURN; uint8 tables_type= 0; @@ -1364,7 +1364,7 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) See also a note on double-check locking usage above. */ - if (is_disabled() || thd->locked_tables || + if (is_disabled() || thd->locked_tables_mode || thd->variables.query_cache_type == 0 || query_cache_size == 0) goto err; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 9445f092546..1f0c8321608 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -995,11 +995,7 @@ void THD::cleanup(void) ha_rollback(this); xid_cache_delete(&transaction.xid_state); } - if (locked_tables) - { - lock=locked_tables; locked_tables=0; - close_thread_tables(this); - } + unlock_locked_tables(this); #if defined(ENABLED_DEBUG_SYNC) /* End the Debug Sync Facility. See debug_sync.cc. */ @@ -1742,7 +1738,7 @@ bool select_send::send_eof() ha_release_temporary_latches(thd); /* Unlock tables before sending packet to gain some speed */ - if (thd->lock) + if (thd->lock && ! thd->locked_tables_mode) { mysql_unlock_tables(thd, thd->lock); thd->lock=0; @@ -3036,8 +3032,8 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup) */ DBUG_ASSERT(open_tables == 0 && temporary_tables == 0 && handler_tables == 0 && derived_tables == 0 && - lock == 0 && locked_tables == 0 && - prelocked_mode == NON_PRELOCKED && + lock == 0 && + locked_tables_mode == LTM_NONE && m_reprepare_observer == NULL); mdl_context_destroy(&mdl_context); mdl_context_destroy(&handler_mdl_context); @@ -3912,7 +3908,7 @@ int THD::binlog_query(THD::enum_binlog_query_type qtype, char const *query_arg, If we are in prelocked mode, the flushing will be done inside the top-most close_thread_tables(). */ - if (this->prelocked_mode == NON_PRELOCKED) + if (this->locked_tables_mode <= LTM_LOCK_TABLES) if (int error= binlog_flush_pending_rows_event(TRUE)) DBUG_RETURN(error); diff --git a/sql/sql_class.h b/sql/sql_class.h index 0f7d9d9a8d5..0fa3231e458 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -862,12 +862,17 @@ typedef I_List Item_change_list; /** - Type of prelocked mode. - See comment for THD::prelocked_mode for complete description. + Type of locked tables mode. + See comment for THD::locked_tables_mode for complete description. */ -enum prelocked_mode_type {NON_PRELOCKED= 0, PRELOCKED= 1, - PRELOCKED_UNDER_LOCK_TABLES= 2}; +enum enum_locked_tables_mode +{ + LTM_NONE= 0, + LTM_LOCK_TABLES, + LTM_PRELOCKED, + LTM_PRELOCKED_UNDER_LOCK_TABLES +}; /** @@ -920,19 +925,13 @@ public: statement ends. Manual mode comes into play when a user issues a 'LOCK TABLES' statement. In this mode the user can only use the locked tables. - Trying to use any other tables will give an error. The locked tables are - stored in 'locked_tables' member. Manual locking is described in + Trying to use any other tables will give an error. + The locked tables are also stored in this member, however, + thd->locked_tables_mode is turned on. Manual locking is described in the 'LOCK_TABLES' chapter of the MySQL manual. See also lock_tables() for details. */ MYSQL_LOCK *lock; - /* - Tables that were locked with explicit or implicit LOCK TABLES. - (Implicit LOCK TABLES happens when we are prelocking tables for - execution of statement which uses stored routines. See description - THD::prelocked_mode for more info.) - */ - MYSQL_LOCK *locked_tables; /* CREATE-SELECT keeps an extra lock for the table being @@ -942,29 +941,34 @@ public: MYSQL_LOCK *extra_lock; /* - prelocked_mode_type enum and prelocked_mode member are used for - indicating whenever "prelocked mode" is on, and what type of - "prelocked mode" is it. + Enum enum_locked_tables_mode and locked_tables_mode member are + used to indicate whether the so-called "locked tables mode" is on, + and what kind of mode is active. - Prelocked mode is used for execution of queries which explicitly - or implicitly (via views or triggers) use functions, thus may need - some additional tables (mentioned in query table list) for their - execution. + Locked tables mode is used when it's necessary to open and + lock many tables at once, for usage across multiple + (sub-)statements. + This may be necessary either for queries that use stored functions + and triggers, in which case the statements inside functions and + triggers may be executed many times, or for implementation of + LOCK TABLES, in which case the opened tables are reused by all + subsequent statements until a call to UNLOCK TABLES. - First open_tables() call for such query will analyse all functions - used by it and add all additional tables to table its list. It will - also mark this query as requiring prelocking. After that lock_tables() - will issue implicit LOCK TABLES for the whole table list and change - thd::prelocked_mode to non-0. All queries called in functions invoked - by the main query will use prelocked tables. Non-0 prelocked_mode - will also surpress mentioned analysys in those queries thus saving - cycles. Prelocked mode will be turned off once close_thread_tables() - for the main query will be called. - - Note: Since not all "tables" present in table list are really locked - thd::prelocked_mode does not imply thd::locked_tables. + The kind of locked tables mode employed for stored functions and + triggers is also called "prelocked mode". + In this mode, first open_tables() call to open the tables used + in a statement analyses all functions used by the statement + and adds all indirectly used tables to the list of tables to + open and lock. + It also marks the parse tree of the statement as requiring + prelocking. After that, lock_tables() locks the entire list + of tables and changes THD::locked_tables_modeto LTM_PRELOCKED. + All statements executed inside functions or triggers + use the prelocked tables, instead of opening their own ones. + Prelocked mode is turned off automatically once close_thread_tables() + of the main statement is called. */ - prelocked_mode_type prelocked_mode; + enum enum_locked_tables_mode locked_tables_mode; ulong version; uint current_tablenr; @@ -996,8 +1000,8 @@ public: void reset_open_tables_state(THD *thd) { open_tables= temporary_tables= handler_tables= derived_tables= 0; - extra_lock= lock= locked_tables= 0; - prelocked_mode= NON_PRELOCKED; + extra_lock= lock= 0; + locked_tables_mode= LTM_NONE; state_flags= 0U; m_reprepare_observer= NULL; mdl_context_init(&mdl_context, thd); diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc index ffc3fafe55f..098f049704c 100644 --- a/sql/sql_cursor.cc +++ b/sql/sql_cursor.cc @@ -289,7 +289,6 @@ Sensitive_cursor::Sensitive_cursor(THD *thd, select_result *result_arg) Save THD state into cursor. @todo - - XXX: thd->locked_tables is not changed. - What problems can we have with it if cursor is open? - TODO: must be fixed because of the prelocked mode. */ @@ -342,7 +341,6 @@ Sensitive_cursor::post_open(THD *thd) } } /* - XXX: thd->locked_tables is not changed. What problems can we have with it if cursor is open? TODO: must be fixed because of the prelocked mode. */ diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 75ad46f1440..702e1e4f31a 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -441,7 +441,7 @@ void upgrade_lock_type(THD *thd, thr_lock_type *lock_type, */ if (specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE) || thd->variables.max_insert_delayed_threads == 0 || - thd->prelocked_mode || + thd->locked_tables_mode > LTM_LOCK_TABLES || thd->lex->uses_stored_routines()) { *lock_type= TL_WRITE; @@ -611,7 +611,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, never be able to get a lock on the table. QQQ: why not upgrade the lock here instead? */ - if (table_list->lock_type == TL_WRITE_DELAYED && thd->locked_tables && + if (table_list->lock_type == TL_WRITE_DELAYED && + thd->locked_tables_mode && find_locked_table(thd->open_tables, table_list->db, table_list->table_name)) { @@ -741,7 +742,14 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, { if (duplic != DUP_ERROR || ignore) table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); - if (!thd->prelocked_mode) + /** + This is a simple check for the case when the table has a trigger + that reads from it, or when the statement invokes a stored function + that reads from the table being inserted to. + Engines can't handle a bulk insert in parallel with a read form the + same table in the same connection. + */ + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_start_bulk_insert(values_list.elements); } @@ -856,7 +864,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, auto_inc values from the delayed_insert thread as they share TABLE. */ table->file->ha_release_auto_increment(); - if (!thd->prelocked_mode && table->file->ha_end_bulk_insert() && !error) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && + table->file->ha_end_bulk_insert() && !error) { table->file->print_error(my_errno,MYF(0)); error=1; @@ -3076,7 +3085,7 @@ select_insert::prepare(List &values, SELECT_LEX_UNIT *u) lex->current_select->join->select_options|= OPTION_BUFFER_RESULT; } else if (!(lex->current_select->options & OPTION_BUFFER_RESULT) && - !thd->prelocked_mode) + thd->locked_tables_mode <= LTM_LOCK_TABLES) { /* We must not yet prepare the result table if it is the same as one of the @@ -3142,7 +3151,7 @@ int select_insert::prepare2(void) { DBUG_ENTER("select_insert::prepare2"); if (thd->lex->current_select->options & OPTION_BUFFER_RESULT && - !thd->prelocked_mode) + thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_start_bulk_insert((ha_rows) 0); DBUG_RETURN(0); } @@ -3269,7 +3278,8 @@ bool select_insert::send_eof() DBUG_PRINT("enter", ("trans_table=%d, table_type='%s'", trans_table, table->file->table_type())); - error= (!thd->prelocked_mode) ? table->file->ha_end_bulk_insert():0; + error= (thd->locked_tables_mode <= LTM_LOCK_TABLES ? + table->file->ha_end_bulk_insert() : 0); table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); @@ -3349,7 +3359,7 @@ void select_insert::abort() { If we are not in prelocked mode, we end the bulk insert started before. */ - if (!thd->prelocked_mode) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_end_bulk_insert(); /* @@ -3730,7 +3740,7 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); if (info.handle_duplicates == DUP_UPDATE) table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE); - if (!thd->prelocked_mode) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_start_bulk_insert((ha_rows) 0); thd->abort_on_warning= (!info.ignore && (thd->variables.sql_mode & diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 34e899fc536..ef4382c9b4d 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -448,7 +448,7 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, (!table->triggers || !table->triggers->has_delete_triggers())) table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); - if (!thd->prelocked_mode) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_start_bulk_insert((ha_rows) 0); table->copy_blobs=1; @@ -469,7 +469,8 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, error= read_sep_field(thd, info, table_list, fields_vars, set_fields, set_values, read_info, *enclosed, skip_lines, ignore); - if (!thd->prelocked_mode && table->file->ha_end_bulk_insert() && !error) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && + table->file->ha_end_bulk_insert() && !error) { table->file->print_error(my_errno, MYF(0)); error= 1; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 31bd34531e3..95f142bc8a2 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -163,7 +163,7 @@ bool end_active_trans(THD *thd) { DBUG_PRINT("info",("options: 0x%llx", thd->options)); /* Safety if one did "drop table" on locked tables */ - if (!thd->locked_tables) + if (!thd->locked_tables_mode) thd->options&= ~OPTION_TABLE_LOCK; thd->server_status&= ~SERVER_STATUS_IN_TRANS; if (ha_commit(thd)) @@ -2211,7 +2211,7 @@ mysql_execute_command(THD *thd) if (res) break; - if (!thd->locked_tables && lex->protect_against_global_read_lock && + if (!thd->locked_tables_mode && lex->protect_against_global_read_lock && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) break; @@ -2553,7 +2553,7 @@ case SQLCOM_PREPARE: TABLE in the same way. That way we avoid that a new table is created during a gobal read lock. */ - if (!thd->locked_tables && + if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) { res= 1; @@ -2779,7 +2779,8 @@ end_with_restore_list: To prevent that, refuse SLAVE STOP if the client thread has locked tables */ - if (thd->locked_tables || thd->active_transaction() || thd->global_read_lock) + if (thd->locked_tables_mode || + thd->active_transaction() || thd->global_read_lock) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); @@ -2855,7 +2856,7 @@ end_with_restore_list: if (end_active_trans(thd)) goto error; - if (!thd->locked_tables && + if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) { res= 1; @@ -3089,7 +3090,7 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (update_precheck(thd, all_tables)) break; - if (!thd->locked_tables && + if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) goto error; DBUG_ASSERT(select_lex->offset_limit == 0); @@ -3126,7 +3127,7 @@ end_with_restore_list: Protection might have already been risen if its a fall through from the SQLCOM_UPDATE case above. */ - if (!thd->locked_tables && + if (!thd->locked_tables_mode && lex->sql_command == SQLCOM_UPDATE_MULTI && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) goto error; @@ -3230,7 +3231,7 @@ end_with_restore_list: if ((res= insert_precheck(thd, all_tables))) break; - if (!thd->locked_tables && + if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) { res= 1; @@ -3270,7 +3271,7 @@ end_with_restore_list: unit->set_limit(select_lex); - if (! thd->locked_tables && + if (! thd->locked_tables_mode && ! (need_start_waiting= ! wait_if_global_read_lock(thd, 0, 1))) { res= 1; @@ -3340,7 +3341,7 @@ end_with_restore_list: Don't allow this within a transaction because we want to use re-generate table */ - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); @@ -3358,7 +3359,7 @@ end_with_restore_list: DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); - if (!thd->locked_tables && + if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) { res= 1; @@ -3379,7 +3380,7 @@ end_with_restore_list: (TABLE_LIST *)thd->lex->auxiliary_table_list.first; multi_delete *del_result; - if (!thd->locked_tables && + if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) { res= 1; @@ -3526,7 +3527,7 @@ end_with_restore_list: if (check_one_table_access(thd, privilege, all_tables)) goto error; - if (!thd->locked_tables && + if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) goto error; @@ -3615,8 +3616,7 @@ end_with_restore_list: if (thd->variables.query_cache_wlock_invalidate) query_cache.invalidate_locked_for_write(first_table); #endif /*HAVE_QUERY_CACHE*/ - thd->locked_tables=thd->lock; - thd->lock=0; + thd->locked_tables_mode= LTM_LOCK_TABLES; my_ok(thd); } else @@ -3707,7 +3707,7 @@ end_with_restore_list: if (check_access(thd,DROP_ACL,lex->name.str,0,1,0, is_schema_db(lex->name.str))) break; - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); @@ -3746,7 +3746,7 @@ end_with_restore_list: res= 1; break; } - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { res= 1; my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, @@ -3786,7 +3786,7 @@ end_with_restore_list: #endif if (check_access(thd, ALTER_ACL, db->str, 0, 1, 0, is_schema_db(db->str))) break; - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); @@ -4776,7 +4776,7 @@ create_sp_error: xa_state_names[thd->transaction.xid_state.xa_state]); break; } - if (thd->active_transaction() || thd->locked_tables) + if (thd->locked_tables_mode || thd->active_transaction()) { my_error(ER_XAER_OUTSIDE, MYF(0)); break; @@ -7078,7 +7078,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, if we have a write locked table as this would lead to a deadlock when trying to reopen (and re-lock) the table after the flush. */ - if (thd->locked_tables) + if (thd->locked_tables_mode) { my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); return 1; @@ -7103,7 +7103,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, } else { - if (thd && thd->locked_tables) + if (thd && thd->locked_tables_mode) { /* If we are under LOCK TABLES we should have a write diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index edc9055fb39..75ab9a73a5b 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -6216,7 +6216,7 @@ static void release_log_entries(partition_info *part_info) static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) { int err; - if (lpt->thd->locked_tables) + if (lpt->thd->locked_tables_mode) { /* When we have the table locked, it is necessary to reopen the table diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index 857cccde50f..fdf2f5ac155 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -44,7 +44,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) if the user is trying to to do this in a transcation context */ - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index fc1a378c371..5320da45322 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1019,7 +1019,7 @@ JOIN::optimize() error= -1; DBUG_RETURN(1); } - if (const_tables && !thd->locked_tables && + if (const_tables && !thd->locked_tables_mode && !(select_options & SELECT_NO_UNLOCK)) mysql_unlock_some_tables(thd, all_tables, const_tables); if (!conds && outer_join) @@ -6930,7 +6930,7 @@ void JOIN::join_free() We are not using tables anymore Unlock all tables. We may be in an INSERT .... SELECT statement. */ - if (can_unlock && lock && thd->lock && + if (can_unlock && lock && thd->lock && ! thd->locked_tables_mode && !(select_options & SELECT_NO_UNLOCK) && !select_lex->subquery_in_having && (select_lex == (thd->lex->unit.fake_select_lex ? diff --git a/sql/sql_table.cc b/sql/sql_table.cc index cac4bab27b7..dd8bd85aab6 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1786,7 +1786,7 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, if (!drop_temporary) { - if (!thd->locked_tables && + if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) DBUG_RETURN(TRUE); } @@ -1891,7 +1891,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, if (!drop_temporary) { - if (!thd->locked_tables) + if (!thd->locked_tables_mode) { if (lock_table_names(thd, tables)) DBUG_RETURN(1); @@ -1900,7 +1900,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, expel_table_from_cache(0, table->db, table->table_name); pthread_mutex_unlock(&LOCK_open); } - else if (thd->locked_tables) + else { for (table= tables; table; table= table->next_local) if (find_temporary_table(thd, table->db, table->table_name)) @@ -2001,7 +2001,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table_type= table->db_type; if (!drop_temporary) { - if (thd->locked_tables) + if (thd->locked_tables_mode) { if (close_cached_table(thd, table->table)) { @@ -2192,8 +2192,8 @@ err_with_placeholders: locked. Additional check for 'non_temp_tables_count' is to avoid leaving LOCK TABLES mode if we have dropped only temporary tables. */ - if (thd->locked_tables && thd->locked_tables->table_count == 0 && - non_temp_tables_count > 0) + if (thd->locked_tables_mode && + thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0) { unlock_locked_tables(thd); goto end; @@ -6571,7 +6571,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if the user is trying to to do this in a transcation context */ - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); @@ -6620,7 +6620,7 @@ view_err: set of tables from the old table or to open a new TABLE object for an extended list and verify that they belong to locked tables. */ - if (thd->locked_tables && + if (thd->locked_tables_mode && (create_info->used_fields & HA_CREATE_USED_UNION) && (table->s->tmp_table == NO_TMP_TABLE)) { @@ -6847,7 +6847,7 @@ view_err: table_list->table= NULL; // For query cache query_cache_invalidate3(thd, table_list, 0); - if (thd->locked_tables) + if (thd->locked_tables_mode) { /* Under LOCK TABLES we should adjust meta-data locks before finishing @@ -7330,7 +7330,7 @@ view_err: if (table->s->tmp_table != NO_TMP_TABLE) { /* Close lock if this is a transactional table */ - if (thd->lock) + if (thd->lock && ! thd->locked_tables_mode) { mysql_unlock_tables(thd, thd->lock); thd->lock=0; @@ -7467,7 +7467,7 @@ view_err: if (t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG, create_info)) goto err_with_placeholders; - if (thd->locked_tables) + if (thd->locked_tables_mode) { if (new_name == table_name && new_db == db) { @@ -7489,7 +7489,7 @@ view_err: (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); - if (thd->locked_tables && new_name == table_name && new_db == db) + if (thd->locked_tables_mode && new_name == table_name && new_db == db) { thd->in_lock_tables= 1; error= reopen_tables(thd, 1); @@ -7536,7 +7536,7 @@ view_err: table_list->table=0; // For query cache query_cache_invalidate3(thd, table_list, 0); - if (thd->locked_tables) + if (thd->locked_tables_mode) { if ((new_name != table_name || new_db != db)) { diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 61cd9bffa57..9d47b637886 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -383,7 +383,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) LOCK_open is not enough because global read lock is held without holding LOCK_open). */ - if (!thd->locked_tables && + if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) DBUG_RETURN(TRUE); @@ -444,7 +444,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) /* Keep consistent with respect to other DDL statements */ mysql_ha_rm_tables(thd, tables); - if (thd->locked_tables) + if (thd->locked_tables_mode) { /* Under LOCK TABLES we must only accept write locked tables. */ if (!(tables->table= find_write_locked_table(thd->open_tables, tables->db, @@ -493,7 +493,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) table->triggers->drop_trigger(thd, tables, &stmt_query)); /* Under LOCK TABLES we must reopen the table to activate the trigger. */ - if (!result && thd->locked_tables) + if (!result && thd->locked_tables_mode) { /* Make table suitable for reopening */ close_data_files_and_leave_as_placeholders(thd, tables->db, @@ -527,7 +527,7 @@ end: locks. Otherwise call to close_thread_tables() will take care about both TABLE instance created by reopen_name_locked_table() and metadata lock. */ - if (thd->locked_tables && tables && tables->table) + if (thd->locked_tables_mode && tables && tables->table) mdl_downgrade_exclusive_lock(&thd->mdl_context, tables->table->mdl_lock_data); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 5cf6ceaa394..7e033bc963a 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -978,7 +978,7 @@ int mysql_multi_update_prepare(THD *thd) count in open_tables() */ uint table_count= lex->table_count; - const bool using_lock_tables= thd->locked_tables != 0; + const bool using_lock_tables= thd->locked_tables_mode != LTM_NONE; bool original_multiupdate= (thd->lex->sql_command == SQLCOM_UPDATE_MULTI); bool need_reopen= FALSE; DBUG_ENTER("mysql_multi_update_prepare"); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 9382880ba1b..b370fc79b17 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -384,7 +384,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, alteration of views under LOCK TABLES. */ - if (thd->locked_tables) + if (thd->locked_tables_mode) { my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); res= TRUE; @@ -1583,7 +1583,7 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) TABLES we have to simply prohibit dropping of views. */ - if (thd->locked_tables) + if (thd->locked_tables_mode) { my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); DBUG_RETURN(TRUE); diff --git a/storage/myisam/ha_myisam.cc b/storage/myisam/ha_myisam.cc index 9269b331754..28d11e3d5f8 100644 --- a/storage/myisam/ha_myisam.cc +++ b/storage/myisam/ha_myisam.cc @@ -1109,7 +1109,7 @@ int ha_myisam::repair(THD *thd, MI_CHECK ¶m, bool do_optimize) ha_release_temporary_latches(thd); // Don't lock tables if we have used LOCK TABLE - if (!thd->locked_tables && + if (! thd->locked_tables_mode && mi_lock_database(file, table->s->tmp_table ? F_EXTRA_LCK : F_WRLCK)) { mi_check_print_error(¶m,ER(ER_CANT_LOCK),my_errno); @@ -1219,7 +1219,7 @@ int ha_myisam::repair(THD *thd, MI_CHECK ¶m, bool do_optimize) update_state_info(¶m, file, 0); } thd_proc_info(thd, old_proc_info); - if (!thd->locked_tables) + if (! thd->locked_tables_mode) mi_lock_database(file,F_UNLCK); DBUG_RETURN(error ? HA_ADMIN_FAILED : !optimize_done ? HA_ADMIN_ALREADY_DONE : HA_ADMIN_OK); From 466ee7e54013b022cffc41b54b4205800d4cdfe2 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 17:58:31 +0300 Subject: [PATCH 039/212] Backport of: ------------------------------------------------------------ revno: 2630.4.27 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Mon 2008-06-09 14:01:19 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. Changed open_table() to return bool. This allows more easily to distinguish cases when this function succeeds but returns no TABLE instance (in case of view or in case of special kind of open) from cases when we have an error. Pointer to TABLE instance is now always returned in TABLE_LIST::table member. This change allows to get rid of false assumption in open_tables() implementation and makes it more clear. --- sql/mysql_priv.h | 4 +- sql/sql_base.cc | 254 ++++++++++++++++++++++------------------------ sql/sql_insert.cc | 6 +- sql/sql_table.cc | 5 +- 4 files changed, 131 insertions(+), 138 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 94875210d0b..2d043d44afa 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1220,8 +1220,8 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, uint lock_flags); enum enum_open_table_action {OT_NO_ACTION= 0, OT_BACK_OFF_AND_RETRY, OT_DISCOVER, OT_REPAIR}; -TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem, - enum_open_table_action *action, uint flags); +bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem, + enum_open_table_action *action, uint flags); bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, char *cache_key, uint cache_key_length, MEM_ROOT *mem_root, uint flags); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 09bb05a91c1..e21a7aa31e5 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2574,8 +2574,6 @@ void table_share_release_hook(void *share) which will be set according to action which is required to remedy problem appeared during attempt to open table. - If this is a NULL pointer, then the table is not - put in the thread-open-list. flags Bitmap of flags to modify how open works: MYSQL_LOCK_IGNORE_FLUSH - Open table even if someone has done a flush or there is a pending @@ -2597,14 +2595,16 @@ void table_share_release_hook(void *share) "open_type" is TAKE_EXCLUSIVE_MDL. RETURN - NULL Open failed. If refresh is set then one should close - all other tables and retry the open. - # Success. Pointer to TABLE object for open table. + TRUE Open failed. "action" parameter may contain type of action + needed to remedy problem before retrying again. + FALSE Success. Members of TABLE_LIST structure are filled properly (e.g. + TABLE_LIST::table is set for real tables and TABLE_LIST::view is + set for views). */ -TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, - enum_open_table_action *action, uint flags) +bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, + enum_open_table_action *action, uint flags) { reg1 TABLE *table; char key[MAX_DBKEY_LENGTH]; @@ -2622,10 +2622,10 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, /* an open table operation needs a lot of the stack space */ if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias)) - DBUG_RETURN(0); + DBUG_RETURN(TRUE); if (thd->killed) - DBUG_RETURN(0); + DBUG_RETURN(TRUE); key_length= (create_table_def_key(thd, key, table_list, 1) - TMP_TABLE_KEY_EXTRA); @@ -2659,7 +2659,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, (ulong) table->query_id, (uint) thd->server_id, (ulong) thd->variables.pseudo_thread_id)); my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } table->query_id= thd->query_id; thd->thread_specific_used= TRUE; @@ -2672,7 +2672,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (flags & MYSQL_OPEN_TEMPORARY_ONLY) { my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name); - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } /* @@ -2770,7 +2770,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, mem_root, 0)) { DBUG_ASSERT(table_list->view != 0); - DBUG_RETURN(0); // VIEW + DBUG_RETURN(FALSE); // VIEW } } } @@ -2785,7 +2785,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias); else my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias); - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } /* @@ -2809,7 +2809,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, */ mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } else { @@ -2832,7 +2832,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { if (retry) *action= OT_BACK_OFF_AND_RETRY; - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } } @@ -2854,7 +2854,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, /* Someone did a refresh while thread was opening tables */ *action= OT_BACK_OFF_AND_RETRY; pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE) @@ -2867,14 +2867,14 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (!exists) { pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); + DBUG_RETURN(FALSE); } /* Table exists. Let us try to open it. */ } else if (table_list->open_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL) { pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); + DBUG_RETURN(FALSE); } if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock_data))) @@ -2921,28 +2921,14 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); - } - else if (table_list->view) - { - /* - We're trying to open a table for what was a view. - This can only happen during (re-)execution. - At prepared statement prepare the view has been opened and - merged into the statement parse tree. After that, someone - performed a DDL and replaced the view with a base table. - Don't try to open the table inside a prepared statement, - invalidate it instead. - - Note, the assert below is known to fail inside stored - procedures (Bug#27011). - */ - DBUG_ASSERT(thd->m_reprepare_observer); - check_and_update_table_version(thd, table_list, share); - /* Always an error. */ - DBUG_ASSERT(thd->is_error()); - goto err_unlock; + DBUG_RETURN(FALSE); } + /* + Note that situation when we are trying to open a table for what + was a view during previous execution of PS will be handled in by + the caller. Here we should simply open our table even if + TABLE_LIST::view is true. + */ if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) goto err_unlock; @@ -2992,7 +2978,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, *action= OT_BACK_OFF_AND_RETRY; release_table_share(share); pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } /* Force close at once after usage */ thd->version= share->version; @@ -3103,15 +3089,16 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->pos_in_table_list= table_list; table_list->updatable= 1; // It is not derived table nor non-updatable VIEW table->clear_column_bitmaps(); + table_list->table= table; DBUG_ASSERT(table->key_read == 0); - DBUG_RETURN(table); + DBUG_RETURN(FALSE); err_unlock: release_table_share(share); err_unlock2: pthread_mutex_unlock(&LOCK_open); mdl_release_lock(&thd->mdl_context, mdl_lock_data); - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } @@ -4502,6 +4489,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) TABLE_LIST *tables= NULL; enum_open_table_action action; int result=0; + bool error; MEM_ROOT new_frm_mem; /* Also used for indicating that prelocking is need */ TABLE_LIST **query_tables_last_own; @@ -4620,64 +4608,30 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) tables->db, tables->table_name, (long) tables)); (*counter)++; - /* - Not a placeholder: must be a base table or a view, and the table is - not opened yet. Try to open the table. - */ - if (!tables->table) + /* Not a placeholder: must be a base table or a view. Let us open it. */ + DBUG_ASSERT(!tables->table); + + if (tables->prelocking_placeholder) { - if (tables->prelocking_placeholder) - { - /* - For the tables added by the pre-locking code, attempt to open - the table but fail silently if the table does not exist. - The real failure will occur when/if a statement attempts to use - that table. - */ - Prelock_error_handler prelock_handler; - thd->push_internal_handler(& prelock_handler); - tables->table= open_table(thd, tables, &new_frm_mem, &action, flags); - thd->pop_internal_handler(); - safe_to_ignore_table= prelock_handler.safely_trapped_errors(); - } - else - tables->table= open_table(thd, tables, &new_frm_mem, &action, flags); + /* + For the tables added by the pre-locking code, attempt to open + the table but fail silently if the table does not exist. + The real failure will occur when/if a statement attempts to use + that table. + */ + Prelock_error_handler prelock_handler; + thd->push_internal_handler(& prelock_handler); + error= open_table(thd, tables, &new_frm_mem, &action, flags); + thd->pop_internal_handler(); + safe_to_ignore_table= prelock_handler.safely_trapped_errors(); } else - DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx", - tables->db, tables->table_name, - (long) tables->table)); + error= open_table(thd, tables, &new_frm_mem, &action, flags); - if (!tables->table) + free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); + + if (error) { - free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); - - if (tables->view) - { - /* VIEW placeholder */ - (*counter)--; - - /* - tables->next_global list consists of two parts: - 1) Query tables and underlying tables of views. - 2) Tables used by all stored routines that this statement invokes on - execution. - We need to know where the bound between these two parts is. If we've - just opened a view, which was the last table in part #1, and it - has added its base tables after itself, adjust the boundary pointer - accordingly. - */ - if (query_tables_last_own == &(tables->next_global) && - tables->view->query_tables) - query_tables_last_own= tables->view->query_tables_last; - /* - Let us free memory used by 'sroutines' hash here since we never - call destructor for this LEX. - */ - my_hash_free(&tables->view->sroutines); - goto process_view_routines; - } - /* If in a MERGE table open, we need to remove the children list from statement table list before restarting. Otherwise the list @@ -4691,15 +4645,6 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) parent_l->next_global= *parent_l->table->child_last_l; } - /* - FIXME This is a temporary hack. Actually we need check that will - allow us to differentiate between error while opening/creating - table and successful table creation. - ... - */ - if (tables->open_type) - continue; - if (action) { /* @@ -4742,36 +4687,73 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) result= -1; // Fatal error break; } - else + + /* + We can't rely on simple check for TABLE_LIST::view to determine + that this is a view since during re-execution we might reopen + ordinary table in place of view and thus have TABLE_LIST::view + set from repvious execution and TABLE_LIST::table set from + current. + */ + if (!tables->table && tables->view) { + /* VIEW placeholder */ + (*counter)--; + /* - If we are not already in prelocked mode and extended table list is not - yet built and we have trigger for table being opened then we should - cache all routines used by its triggers and add their tables to - prelocking list. - If we lock table for reading we won't update it so there is no need to - process its triggers since they never will be activated. + tables->next_global list consists of two parts: + 1) Query tables and underlying tables of views. + 2) Tables used by all stored routines that this statement invokes on + execution. + We need to know where the bound between these two parts is. If we've + just opened a view, which was the last table in part #1, and it + has added its base tables after itself, adjust the boundary pointer + accordingly. */ - if (thd->locked_tables_mode <= LTM_LOCK_TABLES && - !thd->lex->requires_prelocking() && - tables->trg_event_map && tables->table->triggers && - tables->lock_type >= TL_WRITE_ALLOW_WRITE) + if (query_tables_last_own == &(tables->next_global) && + tables->view->query_tables) + query_tables_last_own= tables->view->query_tables_last; + /* + Let us free memory used by 'sroutines' hash here since we never + call destructor for this LEX. + */ + my_hash_free(&tables->view->sroutines); + goto process_view_routines; + } + + /* + Special types of open can succeed but still don't set + TABLE_LIST::table to anything. + */ + if (tables->open_type && !tables->table) + continue; + + /* + If we are not already in prelocked mode and extended table list is not + yet built and we have trigger for table being opened then we should + cache all routines used by its triggers and add their tables to + prelocking list. + If we lock table for reading we won't update it so there is no need to + process its triggers since they never will be activated. + */ + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && + !thd->lex->requires_prelocking() && + tables->trg_event_map && tables->table->triggers && + tables->lock_type >= TL_WRITE_ALLOW_WRITE) + { + if (!query_tables_last_own) + query_tables_last_own= thd->lex->query_tables_last; + if (sp_cache_routines_and_add_tables_for_triggers(thd, thd->lex, + tables)) { - if (!query_tables_last_own) - query_tables_last_own= thd->lex->query_tables_last; - if (sp_cache_routines_and_add_tables_for_triggers(thd, thd->lex, - tables)) - { - /* - Serious error during reading stored routines from mysql.proc table. - Something's wrong with the table or its contents, and an error has - been emitted; we must abort. - */ - result= -1; - goto err; - } + /* + Serious error during reading stored routines from mysql.proc table. + Something's wrong with the table or its contents, and an error has + been emitted; we must abort. + */ + result= -1; + goto err; } - free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); } if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables_mode) @@ -4985,6 +4967,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, TABLE *table; enum_open_table_action action; bool refresh; + bool error; DBUG_ENTER("open_ltable"); /* should not be used in a prelocked_mode context, see NOTE above */ @@ -4996,7 +4979,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, table_list->required_type= FRMTYPE_TABLE; retry: - while (!(table= open_table(thd, table_list, thd->mem_root, &action, 0)) && + while ((error= open_table(thd, table_list, thd->mem_root, &action, 0)) && action) { /* @@ -5009,8 +4992,14 @@ retry: break; } - if (table) + if (!error) { + /* + We can't have a view or some special "open_type" in this function + so there should be a TABLE instance. + */ + DBUG_ASSERT(table_list->table); + table= table_list->table; if (table->child_l) { /* A MERGE table must not come here. */ @@ -5023,7 +5012,6 @@ retry: } table_list->lock_type= lock_type; - table_list->table= table; table->grant= table_list->grant; if (thd->locked_tables_mode) { @@ -5047,6 +5035,8 @@ retry: } } } + else + table= 0; end: thd_proc_info(thd, 0); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 702e1e4f31a..00bb013218d 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3555,8 +3555,8 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, } else { - if (!(table= open_table(thd, create_table, thd->mem_root, ¬_used2, - MYSQL_OPEN_TEMPORARY_ONLY)) && + if (open_table(thd, create_table, thd->mem_root, ¬_used2, + MYSQL_OPEN_TEMPORARY_ONLY) && !create_info->table_existed) { /* @@ -3566,6 +3566,8 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, */ drop_temporary_table(thd, create_table); } + else + table= create_table->table; } } reenable_binlog(thd); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index dd8bd85aab6..dcfc9b811b6 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -7182,8 +7182,9 @@ view_err: tbl.db= new_db; tbl.table_name= tbl.alias= tmp_name; /* Table is in thd->temporary_tables */ - new_table= open_table(thd, &tbl, thd->mem_root, ¬_used, - MYSQL_LOCK_IGNORE_FLUSH); + (void) open_table(thd, &tbl, thd->mem_root, ¬_used, + MYSQL_LOCK_IGNORE_FLUSH); + new_table= tbl.table; } else { From db3f97c3fb2f0e39d56fc813877ff9d38b61af2f Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 18:04:32 +0300 Subject: [PATCH 040/212] Backport of: ------------------------------------------------------------ revno: 2630.9.1 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w3 timestamp: Sun 2008-06-08 22:13:58 +0400 message: WL#3726 "DDL locking for all metadata objects" After review fixes in progress. Some adjustments to the patch that removes thd->locked_tables sql/sp_head.cc: Fix conditions which were wrongly translated by previous patch. sql/sql_base.cc: close_thread_tables(): Add comment clarifying control flow in case of LTM_LOCK_TABLES mode and statements requiring prelocking. Be consistent with other places where we clear OPTION_TABLE_LOCK flag. lock_tables(): Always set THD::lock to 0 after freeing memory to which it points. --- sql/sp_head.cc | 6 +++--- sql/sql_base.cc | 12 +++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/sql/sp_head.cc b/sql/sp_head.cc index a0832285742..d905ddcda31 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1257,7 +1257,7 @@ sp_head::execute(THD *thd) Will write this SP statement into binlog separately (TODO: consider changing the condition to "not inside event union") */ - if (thd->locked_tables_mode == LTM_NONE) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) thd->user_var_events_alloc= thd->mem_root; err_status= i->execute(thd, &ip); @@ -1269,7 +1269,7 @@ sp_head::execute(THD *thd) If we've set thd->user_var_events_alloc to mem_root of this SP statement, clean all the events allocated in it. */ - if (thd->locked_tables_mode == LTM_NONE) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) { reset_dynamic(&thd->user_var_events); thd->user_var_events_alloc= NULL;//DEBUG @@ -2740,7 +2740,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, thd->query_id= next_query_id(); pthread_mutex_unlock(&LOCK_thread_count); - if (thd->locked_tables_mode == LTM_NONE) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) { /* This statement will enter/leave prelocked mode on its own. diff --git a/sql/sql_base.cc b/sql/sql_base.cc index e21a7aa31e5..8665cebc4ed 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1375,6 +1375,7 @@ void close_thread_tables(THD *thd, bool skip_mdl) { TABLE *table; + bool clear_table_lock_option= FALSE; DBUG_ENTER("close_thread_tables"); #ifdef EXTRA_DEBUG @@ -1446,6 +1447,11 @@ void close_thread_tables(THD *thd, /* We are under simple LOCK TABLES or we're inside a sub-statement of a prelocked statement, so should not do anything else. + + Note that even if we are in LTM_LOCK_TABLES mode and statement + requires prelocking (e.g. when we are closing tables after + failing ot "open" all tables required for statement execution) + we will exit this function a few lines below. */ if (! thd->lex->requires_prelocking()) DBUG_VOID_RETURN; @@ -1462,7 +1468,7 @@ void close_thread_tables(THD *thd, DBUG_VOID_RETURN; thd->locked_tables_mode= LTM_NONE; - thd->options&= ~OPTION_TABLE_LOCK; + clear_table_lock_option= TRUE; /* Note that we are leaving prelocked mode so we don't need @@ -1502,6 +1508,9 @@ void close_thread_tables(THD *thd, mdl_remove_all_locks(&thd->mdl_context); } + if (clear_table_lock_option) + thd->options&= ~OPTION_TABLE_LOCK; + DBUG_VOID_RETURN; } @@ -5452,6 +5461,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, need to care about THD::locked_tables_root here. */ mysql_unlock_tables(thd, thd->lock); + thd->lock= 0; thd->options&= ~(OPTION_TABLE_LOCK); DBUG_RETURN(-1); } From f7ba9dafd5f92863ffd9a0b3ebee7147d73bbb21 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 18:20:43 +0300 Subject: [PATCH 041/212] Backport of: ------------------------------------------------------------ revno: 2630.9.2 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w3 timestamp: Tue 2008-06-10 18:01:56 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. sql/mdl.cc: Changed mdl_acquire_shared_lock() signature to accept MDL_CONTEXT as one of arguments as described in specification. sql/mdl.h: Changed mdl_acquire_shared_lock() signature to accept MDL_CONTEXT as one of arguments as described in specification. sql/sql_base.cc: Changed mdl_acquire_shared_lock() signature to accept MDL_CONTEXT as one of arguments as described in specification. Renamed handle_failed_open_table_attempt() to recover_from_failed_open_table_attempt() as suggested by review. Added comment clarifying why we need to check TABLE::db_stat while looking at TABLE instances open by other threads. sql/sql_show.cc: Changed mdl_acquire_shared_lock() signature to accept MDL_CONTEXT as one of arguments as described in specification. --- sql/mdl.cc | 8 ++++++-- sql/mdl.h | 3 ++- sql/sql_base.cc | 23 ++++++++++++++++------- sql/sql_show.cc | 2 +- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index dbf08101159..d3d067cfb9b 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -611,6 +611,7 @@ static bool can_grant_lock(MDL_LOCK *lock, MDL_LOCK_DATA *lock_data) This function must be called after the lock is added to a context. + @param context [in] Context containing request for lock @param lock_data [in] Lock request object for lock to be acquired @param retry [out] Indicates that conflicting lock exists and another attempt should be made after releasing all current @@ -622,16 +623,19 @@ static bool can_grant_lock(MDL_LOCK *lock, MDL_LOCK_DATA *lock_data) In the latter case "retry" parameter is set to TRUE. */ -bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry) +bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data, + bool *retry) { MDL_LOCK *lock; *retry= FALSE; DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_PENDING); + DBUG_ASSERT(lock_data->ctx == context); + safe_mutex_assert_not_owner(&LOCK_open); - if (lock_data->ctx->has_global_shared_lock && + if (context->has_global_shared_lock && lock_data->type == MDL_SHARED_UPGRADABLE) { my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); diff --git a/sql/mdl.h b/sql/mdl.h index f99f38d6285..b192980ebaa 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -164,7 +164,8 @@ inline void mdl_set_lock_type(MDL_LOCK_DATA *lock_data, enum_mdl_type lock_type) lock_data->type= lock_type; } -bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry); +bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data, + bool *retry); bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context); bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 8665cebc4ed..633184cde79 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2837,7 +2837,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (flags & MYSQL_LOCK_IGNORE_FLUSH) mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO); - if (mdl_acquire_shared_lock(mdl_lock_data, &retry)) + if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry)) { if (retry) *action= OT_BACK_OFF_AND_RETRY; @@ -4044,18 +4044,21 @@ end_with_lock_open: /** - Handle failed attempt ot open table by performing requested action. + Recover from failed attempt ot open table by performing requested action. @param thd Thread context @param table Table list element for table that caused problem @param action Type of action requested by failed open_table() call + @pre This function should be called only with "action" != OT_NO_ACTION. + @retval FALSE - Success. One should try to open tables once again. @retval TRUE - Error */ -static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table, - enum_open_table_action action) +static bool +recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, + enum_open_table_action action) { bool result= FALSE; @@ -4678,7 +4681,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) TABLE_LIST element. Altough currently this assumption is valid it may change in future. */ - if (handle_failed_open_table_attempt(thd, tables, action)) + if (recover_from_failed_open_table_attempt(thd, tables, action)) { result= -1; goto err; @@ -4997,7 +5000,7 @@ retry: might have been acquired successfully. */ close_thread_tables(thd, (action == OT_BACK_OFF_AND_RETRY)); - if (handle_failed_open_table_attempt(thd, table_list, action)) + if (recover_from_failed_open_table_attempt(thd, table_list, action)) break; } @@ -8530,7 +8533,13 @@ bool notify_thread_having_shared_lock(THD *thd, THD *in_use) thd_table ; thd_table= thd_table->next) { - /* TODO With new MDL check for db_stat is probably a legacy */ + /* + Check for TABLE::db_stat is needed since in some places we call + handler::close() for table instance (and set TABLE::db_stat to 0) + and do not remove such instances from the THD::open_tables + for some time, during which other thread can see those instances + (e.g. see partitioning code). + */ if (thd_table->db_stat) signalled|= mysql_lock_abort_for_thread(thd, thd_table); } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 3d4c0a5aaf7..9df31c7c2ad 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3145,7 +3145,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, */ while (1) { - if (mdl_acquire_shared_lock(&mdl_lock_data, &retry)) + if (mdl_acquire_shared_lock(&thd->mdl_context, &mdl_lock_data, &retry)) { if (!retry || mdl_wait_for_locks(&thd->mdl_context)) { From e8a9191e646ab510e14082793fed2d828e6c8679 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 1 Dec 2009 22:13:01 +0300 Subject: [PATCH 042/212] Backport of: ------------------------------------------------------------ revno: 2630.9.3 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w3 timestamp: Wed 2008-06-11 08:33:36 +0400 message: WL#3726 "DDL locking for all metadata objects". After review fixes in progress. Changed close_cached_tables() not to flush all unused TABLE instances when flushing individual table. Renamed expel_table_from_cache() to tdc_remove_table() and added enum parameter to be able more explicitly specify type of removal, rewrote its code to be more efficient. ****** Backport of: ------------------------------------------------------------ revno: 2630.9.4 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w3 timestamp: Wed 2008-06-11 15:53:53 +0400 message: WL#3726 "DDL locking for all metadata objects". After-review fixes in progress. Minor changes in order to improve code readability and simplify debugging. mysql-test/r/ps_ddl.result: Restore the original (correct) behaviour, now that the patch that fixes the regression introduced by the original patch for WL#3726 is fixed. mysql-test/t/ps_ddl.test: Restore the original (correct) behaviour, now that the patch that fixes the regression introduced by the original patch for WL#3726 is fixed sql/mysql_priv.h: Renamed expel_table_from_cache() to tdc_remove_table() and added enum parameter to be able more explicitly specify type of removal. sql/sql_base.cc: Changed close_cached_tables() not to flush all unused TABLE instances when flushing individual table. Renamed expel_table_from_cache() to tdc_remove_table() and added enum parameter to be able more explicitly specify type of removal, rewrote its code to be more efficient. As result removed relink_unused() function which is no longer used. ****** Improved code in close_cached_tables() according to reviewer's comments. sql/sql_delete.cc: Renamed expel_table_from_cache() to tdc_remove_table() and added enum parameter to be able more explicitly specify type of removal. sql/sql_rename.cc: Renamed expel_table_from_cache() to tdc_remove_table() and added enum parameter to be able more explicitly specify type of removal. sql/sql_show.cc: Moved acquisition of high-prio shared metadata lock in which happens in fill_schema_table_from_frm() to separate function. sql/sql_table.cc: Renamed expel_table_from_cache() to tdc_remove_table() and added enum parameter to be able more explicitly specify type of removal. sql/sql_trigger.cc: Renamed expel_table_from_cache() to tdc_remove_table() and added enum parameter to be able more explicitly specify type of removal. --- mysql-test/r/ps_ddl.result | 2 +- mysql-test/t/ps_ddl.test | 2 +- sql/mysql_priv.h | 7 +- sql/sql_base.cc | 152 ++++++++++++++++++++----------------- sql/sql_delete.cc | 3 +- sql/sql_rename.cc | 3 +- sql/sql_show.cc | 79 ++++++++++++++----- sql/sql_table.cc | 4 +- sql/sql_trigger.cc | 2 +- 9 files changed, 155 insertions(+), 99 deletions(-) diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result index af0ffaf4ae5..f411328ed7c 100644 --- a/mysql-test/r/ps_ddl.result +++ b/mysql-test/r/ps_ddl.result @@ -850,7 +850,7 @@ flush table t1; execute stmt; f1() 6 -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); SUCCESS execute stmt; diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test index 6e771d44b3f..1ba193983b2 100644 --- a/mysql-test/t/ps_ddl.test +++ b/mysql-test/t/ps_ddl.test @@ -745,7 +745,7 @@ execute stmt; call p_verify_reprepare_count(1); flush table t1; execute stmt; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); execute stmt; --echo # Test 18-c: dependent VIEW has changed diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 2d043d44afa..b650af8017d 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1543,8 +1543,11 @@ char *generate_partition_syntax(partition_info *part_info, #endif bool notify_thread_having_shared_lock(THD *thd, THD *in_use); -void expel_table_from_cache(THD *leave_thd, const char *db, - const char *table_name); + +enum enum_tdc_remove_table_type {TDC_RT_REMOVE_ALL, TDC_RT_REMOVE_NOT_OWN, + TDC_RT_REMOVE_UNUSED}; +void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, + const char *db, const char *table_name); #define NORMAL_PART_NAME 0 #define TEMP_PART_NAME 1 diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 633184cde79..f7ac1df8b32 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -921,12 +921,17 @@ void free_io_cache(TABLE *table) particular table identified by its share. @param share Table share. + + @pre Caller should have LOCK_open mutex acquired. */ static void kill_delayed_threads_for_table(TABLE_SHARE *share) { I_P_List_iterator it(share->used_tables); TABLE *tab; + + safe_mutex_assert_owner(&LOCK_open); + while ((tab= it++)) { THD *in_use= tab->in_use; @@ -983,6 +988,15 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", refresh_version)); kill_delayed_threads(); + /* + Get rid of all unused TABLE and TABLE_SHARE instances. By doing + this we automatically close all tables which were marked as "old". + */ + while (unused_tables) + free_cache_entry(unused_tables); + /* Free table shares which were not freed implicitly by loop above. */ + while (oldest_unused_share->next) + (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); } else { @@ -993,8 +1007,10 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, if (share) { - share->version= 0; kill_delayed_threads_for_table(share); + /* tdc_remove_table() also sets TABLE_SHARE::version to 0. */ + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db, + table->table_name); found=1; } } @@ -1002,28 +1018,14 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, wait_for_refresh=0; // Nothing to wait for } - /* - Get rid of all unused TABLE and TABLE_SHARE instances. By doing - this we automatically close all tables which were marked as "old". - - FIXME: Do not close all unused TABLE instances when flushing - particular table. - */ - while (unused_tables) - free_cache_entry(unused_tables); - /* Free table shares */ - while (oldest_unused_share->next) - (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); + if (!have_lock) + pthread_mutex_unlock(&LOCK_open); if (!wait_for_refresh) - { - if (!have_lock) - pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(result); - } + /* Code below assume that LOCK_open is released. */ DBUG_ASSERT(!have_lock); - pthread_mutex_unlock(&LOCK_open); if (thd->locked_tables_mode) { @@ -2109,27 +2111,6 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db, } - /* move table first in unused links */ - -static void relink_unused(TABLE *table) -{ - /* Assert that MERGE children are not attached in unused_tables. */ - DBUG_ASSERT(!table->is_children_attached()); - - if (table != unused_tables) - { - table->prev->next=table->next; /* Remove from unused list */ - table->next->prev=table->prev; - table->next=unused_tables; /* Link in unused tables */ - table->prev=unused_tables->prev; - unused_tables->prev->next=table; - unused_tables->prev=table; - unused_tables=table; - check_unused(); - } -} - - /** Prepare an open merge table for close. @@ -2241,7 +2222,8 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, } pthread_mutex_lock(&LOCK_open); - expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str); + tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN, + table->s->db.str, table->s->table_name.str); pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(FALSE); } @@ -4075,7 +4057,7 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, if (mdl_acquire_exclusive_locks(&thd->mdl_context)) return TRUE; pthread_mutex_lock(&LOCK_open); - expel_table_from_cache(0, table->db, table->table_name); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); ha_create_table_from_engine(thd, table->db, table->table_name); pthread_mutex_unlock(&LOCK_open); @@ -4089,7 +4071,7 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, if (mdl_acquire_exclusive_locks(&thd->mdl_context)) return TRUE; pthread_mutex_lock(&LOCK_open); - expel_table_from_cache(0, table->db, table->table_name); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); pthread_mutex_unlock(&LOCK_open); result= auto_repair_table(thd, table); @@ -8549,50 +8531,82 @@ bool notify_thread_having_shared_lock(THD *thd, THD *in_use) /** - Remove all instances of the table from cache assuming that current thread - has exclusive meta-data lock on it (optionally leave instances belonging - to the current thread in cache). + Remove all or some (depending on parameter) instances of TABLE and + TABLE_SHARE from the table definition cache. - @param leave_thd 0 If we should remove all instances - non-0 Pointer to current thread context if we should - leave instances belonging to this thread. - @param db Name of database - @param table_name Name of table + @param thd Thread context + @param remove_type Type of removal: + TDC_RT_REMOVE_ALL - remove all TABLE instances and + TABLE_SHARE instance. There + should be no used TABLE objects + and caller should have exclusive + metadata lock on the table. + TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances + except those that belong to + this thread. There should be + no TABLE objects used by other + threads and caller should have + exclusive metadata lock on the + table. + TDC_RT_REMOVE_UNUSED - remove all unused TABLE + instances (if there are no + used instances will also + remove TABLE_SHARE). + @param db Name of database + @param table_name Name of table @note Unlike remove_table_from_cache() it assumes that table instances are already not used by any (other) thread (this should be achieved by using meta-data locks). */ -void expel_table_from_cache(THD *leave_thd, const char *db, const char *table_name) +void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, + const char *db, const char *table_name) { char key[MAX_DBKEY_LENGTH]; uint key_length; TABLE *table; TABLE_SHARE *share; + safe_mutex_assert_owner(&LOCK_open); + + DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || + mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, + db, table_name)); + key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, key_length))) { - I_P_List_iterator it(share->free_tables); - share->version= 0; - - while ((table= it++)) - relink_unused(table); - } - - /* This may destroy share so we have to do new look-up later. */ - while (unused_tables && !unused_tables->s->version) - free_cache_entry(unused_tables); - - if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, - key_length))) - { - DBUG_ASSERT(leave_thd || share->ref_count == 0); - if (share->ref_count == 0) - my_hash_delete(&table_def_cache, (uchar*) share); + if (share->ref_count) + { + I_P_List_iterator it(share->free_tables); +#ifndef DBUG_OFF + if (remove_type == TDC_RT_REMOVE_ALL) + { + DBUG_ASSERT(share->used_tables.is_empty()); + } + else if (remove_type == TDC_RT_REMOVE_NOT_OWN) + { + I_P_List_iterator it2(share->used_tables); + while ((table= it2++)) + if (table->in_use != thd) + { + DBUG_ASSERT(0); + } + } +#endif + /* + Set share's version to zero in order to ensure that it gets + automatically deleted once it is no longer referenced. + */ + share->version= 0; + while ((table= it++)) + free_cache_entry(table); + } + else + (void) my_hash_delete(&table_def_cache, (uchar*) share); } } @@ -8793,7 +8807,7 @@ int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt) DBUG_RETURN(1); } pthread_mutex_lock(&LOCK_open); - expel_table_from_cache(lpt->thd, lpt->db, lpt->table_name); + tdc_remove_table(lpt->thd, TDC_RT_REMOVE_NOT_OWN, lpt->db, lpt->table_name); pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 612f9d1954d..630cf73076c 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1172,7 +1172,8 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) if (mdl_acquire_exclusive_locks(&thd->mdl_context)) DBUG_RETURN(TRUE); pthread_mutex_lock(&LOCK_open); - expel_table_from_cache(0, table_list->db, table_list->table_name); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, + table_list->table_name); pthread_mutex_unlock(&LOCK_open); } diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index fdf2f5ac155..ca1543b32d8 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -139,7 +139,8 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) pthread_mutex_lock(&LOCK_open); for (ren_table= table_list; ren_table; ren_table= ren_table->next_local) - expel_table_from_cache(0, ren_table->db, ren_table->table_name); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, ren_table->db, + ren_table->table_name); error=0; if ((ren_table=rename_tables(thd,table_list,0))) diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 9df31c7c2ad..f608e233349 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3075,6 +3075,56 @@ uint get_table_open_method(TABLE_LIST *tables, } +/** + Acquire high priority share metadata lock on a table. + + @param thd Thread context. + @param mdl_lock_data Pointer to memory to be used for MDL_LOCK_DATA + object for a lock request. + @param mdlkey Pointer to the buffer for key for the lock request + (should be at least strlen(db) + strlen(name) + 2 + bytes, or, if the lengths are not known, + MAX_DBNAME_LENGTH) + @param table Table list element for the table + + @note This is an auxiliary function to be used in cases when we want to + access table's description by looking up info in TABLE_SHARE without + going through full-blown table open. + @note This function assumes that there are no other metadata lock requests + in the current metadata locking context. + + @retval FALSE Success + @retval TRUE Some error occured (probably thread was killed). +*/ + +static bool +acquire_high_prio_shared_mdl_lock(THD *thd, MDL_LOCK_DATA *mdl_lock_data, + char *mdlkey, TABLE_LIST *table) +{ + bool retry; + + mdl_init_lock(mdl_lock_data, mdlkey, 0, table->db, table->table_name); + table->mdl_lock_data= mdl_lock_data; + mdl_add_lock(&thd->mdl_context, mdl_lock_data); + mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO); + + while (1) + { + if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry)) + { + if (!retry || mdl_wait_for_locks(&thd->mdl_context)) + { + mdl_remove_all_locks(&thd->mdl_context); + return TRUE; + } + continue; + } + break; + } + return FALSE; +} + + /** @brief Fill I_S table with data from FRM file only @@ -3108,7 +3158,6 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; MDL_LOCK_DATA mdl_lock_data; char mdlkey[MAX_DBKEY_LENGTH]; - bool retry; bzero((char*) &table_list, sizeof(TABLE_LIST)); bzero((char*) &tbl, sizeof(TABLE)); @@ -3133,32 +3182,20 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, table_list.db= db_name->str; } - mdl_init_lock(&mdl_lock_data, mdlkey, 0, db_name->str, table_name->str); - table_list.mdl_lock_data= &mdl_lock_data; - mdl_add_lock(&thd->mdl_context, &mdl_lock_data); - mdl_set_lock_type(&mdl_lock_data, MDL_SHARED_HIGH_PRIO); - /* TODO: investigate if in this particular situation we can get by simply obtaining internal lock of data-dictionary (ATM it is LOCK_open) instead of obtaning full-blown metadata lock. */ - while (1) + if (acquire_high_prio_shared_mdl_lock(thd, &mdl_lock_data, mdlkey, + &table_list)) { - if (mdl_acquire_shared_lock(&thd->mdl_context, &mdl_lock_data, &retry)) - { - if (!retry || mdl_wait_for_locks(&thd->mdl_context)) - { - /* - Some error occured or we have been killed while waiting - for conflicting locks to go away, let the caller to handle - the situation. - */ - return 1; - } - continue; - } - break; + /* + Some error occured (most probably we have been killed while + waiting for conflicting locks to go away), let the caller to + handle the situation. + */ + return 1; } key_length= create_table_def_key(thd, key, &table_list, 0); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index dcfc9b811b6..2c91ac69f9f 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1897,7 +1897,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, DBUG_RETURN(1); pthread_mutex_lock(&LOCK_open); for (table= tables; table; table= table->next_local) - expel_table_from_cache(0, table->db, table->table_name); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); pthread_mutex_unlock(&LOCK_open); } else @@ -4345,7 +4345,7 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, if (mdl_acquire_exclusive_locks(&thd->mdl_context)) DBUG_RETURN(TRUE); pthread_mutex_lock(&LOCK_open); - expel_table_from_cache(0, table->db, table->table_name); + tdc_remove_table(0, TDC_RT_REMOVE_UNUSED, table->db, table->table_name); pthread_mutex_unlock(&LOCK_open); if (my_copy(src_path, dst_path, MYF(MY_WME))) diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 9d47b637886..c8f01a56a72 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -469,7 +469,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) goto end; pthread_mutex_lock(&LOCK_open); - expel_table_from_cache(0, tables->db, tables->table_name); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, tables->db, tables->table_name); if (reopen_name_locked_table(thd, tables)) goto end_unlock; From 3104af49cdd1a3c041948b0da17558c49c3edb49 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 2 Dec 2009 18:22:15 +0300 Subject: [PATCH 043/212] Backport of: ---------------------------------------------------------- revno: 2630.10.1 committer: Konstantin Osipov branch nick: mysql-6.0-lock-tables-tidyup timestamp: Wed 2008-06-11 15:49:58 +0400 message: WL#3726, review fixes. Now that we have metadata locks, we don't need to keep a crippled TABLE instance in the table cache to indicate that a table is locked. Remove all code that used this technique. Instead, rely on metadata locks and use the standard open_table() and close_thread_table() to manipulate with the table cache tables. Removes a list of functions that have become unused (see the comment for sql_base.cc for details). Under LOCK TABLES, keep a TABLE_LIST instance for each table that may be temporarily closed. For that, implement an own class for LOCK TABLES mode, Locked_tables_list. This is a pre-requisite patch for WL#4144. This is not exactly a backport: there is no new online ALTER table in Celosia, so the old alter table code was changed to work with the new table cache API. mysql-test/r/lock.result: Update results (WL#3726 post-review patch). mysql-test/r/trigger-compat.result: We take the table from the table cache now, thus no warning. mysql-test/suite/rpl/r/rpl_trigger.result: We take the table from the table cache now, thus no warning. mysql-test/t/lock.test: Additional tests for LOCK TABLES mode (previously not covered by the test suite (WL#3726). sql/field.h: Remove reopen_table(). sql/lock.cc: Remove an obsolete parameter of mysql_lock_remove(). It's not used anywhere now either. sql/mysql_priv.h: Add 4 new open_table() flags. Remove declarations of removed functions. sql/sp_head.cc: Rename thd->mdl_el_root to thd->locked_tables_root. sql/sql_acl.cc: Use the new implementation of unlock_locked_tables(). sql/sql_base.cc: Implement class Locked_tables_list. Implement close_all_tables_for_name(). Rewrite close_cached_tables() to use the new reopen_tables(). Remove reopen_table(), reopen_tables(), reopen_table_entry() (ex. open_unireg_entry()), close_data_files_and_leave_as_placeholders(), close_handle_and_leave_table_as_placeholder(), close_cached_table(), table_def_change_share(), reattach_merge(), reopen_name_locked_table(), unlink_open_table(). Move acquisition of a metadata lock into an own function - open_table_get_mdl_lock(). sql/sql_class.cc: Deploy class Locked_tables_list. sql/sql_class.h: Declare class Locked_tables_list. Keep one instance of this class in class THD. Rename mdl_el_root to locked_tables_root. sql/sql_db.cc: Update a comment. sql/sql_insert.cc: Use the plain open_table() to open a just created table in CREATE TABLE .. SELECT. sql/sql_parse.cc: Use thd->locked_tables_list to enter and leave LTM_LOCK_TABLES mode. sql/sql_partition.cc: Deploy the new method of working with partitioned table locks. sql/sql_servers.cc: Update to the new signature of unlock_locked_tables(). sql/sql_table.cc: In mysql_rm_table_part2(), the branch that removes a table under LOCK TABLES, make sure that the table being dropped is also removed from THD::locked_tables_list. Update ALTER TABLE and CREATE TABLE LIKE implementation to use open_table() and close_all_tables_for_name() instead of reopen_tables(). sql/sql_trigger.cc: Use the new locking way. sql/table.h: Add TABLE::pos_in_locked_tables, which is used only under LOCK TABLES. --- mysql-test/r/lock.result | 45 + mysql-test/r/trigger-compat.result | 2 - mysql-test/suite/rpl/r/rpl_trigger.result | 2 - mysql-test/t/lock.test | 43 + sql/field.h | 1 - sql/lock.cc | 22 +- sql/mysql_priv.h | 38 +- sql/sp_head.cc | 9 +- sql/sql_acl.cc | 3 +- sql/sql_base.cc | 1347 ++++++--------------- sql/sql_class.cc | 7 +- sql/sql_class.h | 63 +- sql/sql_db.cc | 4 +- sql/sql_insert.cc | 12 +- sql/sql_parse.cc | 51 +- sql/sql_partition.cc | 73 +- sql/sql_servers.cc | 3 +- sql/sql_table.cc | 161 +-- sql/sql_trigger.cc | 77 +- sql/table.h | 3 +- 20 files changed, 767 insertions(+), 1199 deletions(-) diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index a60c5c8383f..676a8c41bb6 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -201,3 +201,48 @@ ERROR HY000: Table 't2' was locked with a READ lock and can't be updated UNLOCK TABLES; DROP TABLE t1,t2; End of 5.1 tests. +# +# Ensure that FLUSH TABLES doesn't substitute a base locked table +# with a temporary one. +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +create temporary table t1 (a int); +flush table t1; +drop temporary table t1; +select * from t1; +a +unlock tables; +drop table t1, t2; +# +# Ensure that REPAIR .. USE_FRM works under LOCK TABLES. +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +repair table t1 use_frm; +Table Op Msg_type Msg_text +test.t1 repair status OK +repair table t1 use_frm; +Table Op Msg_type Msg_text +test.t1 repair status OK +select * from t1; +a +select * from t2; +a +repair table t2 use_frm; +Table Op Msg_type Msg_text +test.t2 repair status OK +repair table t2 use_frm; +Table Op Msg_type Msg_text +test.t2 repair status OK +select * from t1; +a +unlock tables; +drop table t1, t2; +# +# End of 6.0 tests. +# diff --git a/mysql-test/r/trigger-compat.result b/mysql-test/r/trigger-compat.result index 14ef4f54f27..14949c227bd 100644 --- a/mysql-test/r/trigger-compat.result +++ b/mysql-test/r/trigger-compat.result @@ -34,8 +34,6 @@ TRIGGER_CATALOG TRIGGER_SCHEMA TRIGGER_NAME EVENT_MANIPULATION EVENT_OBJECT_CATA def mysqltest_db1 wl2818_trg1 INSERT def mysqltest_db1 t1 0 NULL INSERT INTO t2 VALUES(CURRENT_USER()) ROW BEFORE NULL NULL OLD NEW NULL latin1 latin1_swedish_ci latin1_swedish_ci def mysqltest_db1 wl2818_trg2 INSERT def mysqltest_db1 t1 0 NULL INSERT INTO t2 VALUES(CURRENT_USER()) ROW AFTER NULL NULL OLD NEW NULL mysqltest_dfn@localhost latin1 latin1_swedish_ci latin1_swedish_ci DROP TRIGGER wl2818_trg1; -Warnings: -Warning 1454 No definer attribute for trigger 'mysqltest_db1'.'wl2818_trg1'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger. DROP TRIGGER wl2818_trg2; use mysqltest_db1; DROP TABLE t1; diff --git a/mysql-test/suite/rpl/r/rpl_trigger.result b/mysql-test/suite/rpl/r/rpl_trigger.result index 3d7757613a7..01d886c4709 100644 --- a/mysql-test/suite/rpl/r/rpl_trigger.result +++ b/mysql-test/suite/rpl/r/rpl_trigger.result @@ -893,8 +893,6 @@ s @ root@localhost DROP TRIGGER trg1; -Warnings: -Warning 1454 No definer attribute for trigger 'test'.'trg1'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger. DROP TABLE t1; DROP TABLE t2; STOP SLAVE; diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index 856ae020492..7effaaeb20d 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -252,3 +252,46 @@ UNLOCK TABLES; DROP TABLE t1,t2; --echo End of 5.1 tests. + +--echo # +--echo # Ensure that FLUSH TABLES doesn't substitute a base locked table +--echo # with a temporary one. +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +create temporary table t1 (a int); +flush table t1; +drop temporary table t1; +select * from t1; +unlock tables; +drop table t1, t2; + +--echo # +--echo # Ensure that REPAIR .. USE_FRM works under LOCK TABLES. +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +repair table t1 use_frm; +repair table t1 use_frm; +select * from t1; +select * from t2; +repair table t2 use_frm; +repair table t2 use_frm; +select * from t1; +unlock tables; +drop table t1, t2; + + +--echo # +--echo # End of 6.0 tests. +--echo # diff --git a/sql/field.h b/sql/field.h index fb6ca34e88e..3dd18b4ffaa 100644 --- a/sql/field.h +++ b/sql/field.h @@ -478,7 +478,6 @@ public: } /* Hash value */ virtual void hash(ulong *nr, ulong *nr2); - friend bool reopen_table(THD *,struct st_table *,bool); friend int cre_myisam(char * name, register TABLE *form, uint options, ulonglong auto_increment_value); friend class Copy_field; diff --git a/sql/lock.cc b/sql/lock.cc index 33c9edcea48..814eebde337 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -512,28 +512,15 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) /** Try to find the table in the list of locked tables. In case of success, unlock the table and remove it from this list. - - @note This function has a legacy side effect: the table is - unlocked even if it is not found in the locked list. - It's not clear if this side effect is intentional or still - desirable. It might lead to unmatched calls to - unlock_external(). Moreover, a discrepancy can be left - unnoticed by the storage engine, because in - unlock_external() we call handler::external_lock(F_UNLCK) only - if table->current_lock is not F_UNLCK. + If a table has more than one lock instance, removes them all. @param thd thread context @param locked list of locked tables @param table the table to unlock - @param always_unlock specify explicitly if the legacy side - effect is desired. */ -void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, - bool always_unlock) +void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table) { - if (always_unlock == TRUE) - mysql_unlock_some_tables(thd, &table, /* table count */ 1); if (locked) { reg1 uint i; @@ -547,9 +534,8 @@ void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, DBUG_ASSERT(table->lock_position == i); - /* Unlock if not yet unlocked */ - if (always_unlock == FALSE) - mysql_unlock_some_tables(thd, &table, /* table count */ 1); + /* Unlock the table. */ + mysql_unlock_some_tables(thd, &table, /* table count */ 1); /* Decrement table_count in advance, making below expressions easier */ old_tables= --locked->table_count; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index b650af8017d..12d7e46e821 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1225,20 +1225,13 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem, bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, char *cache_key, uint cache_key_length, MEM_ROOT *mem_root, uint flags); -bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list); TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name); TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name); void detach_merge_children(TABLE *table, bool clear_refs); bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, TABLE_LIST *new_child_list, TABLE_LIST **new_last); -bool reopen_table(TABLE *table); -bool reopen_tables(THD *thd, bool get_locks); thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table); -void close_data_files_and_leave_as_placeholders(THD *thd, const char *db, - const char *table_name); -void close_handle_and_leave_table_as_placeholder(TABLE *table); -void unlock_locked_tables(THD *thd); void execute_init_command(THD *thd, sys_var_str *init_command_var, rw_lock_t *var_mutex); extern Field *not_found_field; @@ -1388,12 +1381,12 @@ void add_join_on(TABLE_LIST *b,Item *expr); void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List *using_fields, SELECT_LEX *lex); bool add_proc_to_list(THD *thd, Item *item); -bool close_cached_table(THD *thd, TABLE *table); bool wait_while_table_is_used(THD *thd, TABLE *table, enum ha_extra_function function); -void unlink_open_table(THD *thd, TABLE *find, bool unlock); void drop_open_table(THD *thd, TABLE *table, const char *db_name, const char *table_name); +void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, + bool remove_from_locked_tables); void update_non_unique_table_error(TABLE_LIST *update, const char *operation, TABLE_LIST *duplicate); @@ -1515,7 +1508,6 @@ void close_temporary_table(THD *thd, TABLE *table, bool free_share, void close_temporary(TABLE *table, bool free_share, bool delete_table); bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db, const char *table_name); -void remove_db_from_cache(const char *db); void flush_tables(); bool is_equal(const LEX_STRING *a, const LEX_STRING *b); char *make_default_log_name(char *buff,const char* log_ext); @@ -1561,7 +1553,7 @@ void create_subpartition_name(char *out, const char *in1, typedef struct st_lock_param_type { - TABLE_LIST table_list; + TABLE_LIST *table_list; ulonglong copied; ulonglong deleted; THD *thd; @@ -1572,7 +1564,6 @@ typedef struct st_lock_param_type const char *db; const char *table_name; uchar *pack_frm_data; - enum thr_lock_type old_lock_type; uint key_count; uint db_options; size_t pack_frm_len; @@ -2044,12 +2035,31 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, #define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 #define MYSQL_LOCK_PERF_SCHEMA 0x0010 #define MYSQL_OPEN_TAKE_UPGRADABLE_MDL 0x0020 +/** + Do not try to acquire a metadata lock on the table: we + already have one. +*/ +#define MYSQL_OPEN_HAS_MDL_LOCK 0x0040 +/** + If in locked tables mode, ignore the locked tables and get + a new instance of the table. +*/ +#define MYSQL_OPEN_GET_NEW_TABLE 0x0080 +/** Don't look up the table in the list of temporary tables. */ +#define MYSQL_OPEN_SKIP_TEMPORARY 0x0100 + +/** Please refer to the internals manual. */ +#define MYSQL_OPEN_REOPEN (MYSQL_LOCK_IGNORE_FLUSH |\ + MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |\ + MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\ + MYSQL_OPEN_GET_NEW_TABLE |\ + MYSQL_OPEN_SKIP_TEMPORARY |\ + MYSQL_OPEN_HAS_MDL_LOCK) void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count); -void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, - bool always_unlock); +void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table); void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock); void mysql_lock_downgrade_write(THD *thd, TABLE *table, thr_lock_type new_lock_type); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index d905ddcda31..1d953e773b3 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3985,8 +3985,8 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name, - thd->mdl_el_root ? - thd->mdl_el_root : + thd->locked_tables_root ? + thd->locked_tables_root : thd->mem_root); /* Everyting else should be zeroed */ @@ -4030,8 +4030,9 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->select_lex= lex->current_select; table->cacheable_table= 1; table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name, - thd->mdl_el_root ? thd->mdl_el_root : - thd->mem_root); + thd->locked_tables_root ? + thd->locked_tables_root : thd->mem_root); + lex->add_to_query_tables(table); return table; } diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index b01e7b1049d..aa2c697f221 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -676,7 +676,8 @@ my_bool acl_reload(THD *thd) my_bool return_val= 1; DBUG_ENTER("acl_reload"); - unlock_locked_tables(thd); // Can't have locked tables here + /* Can't have locked tables here. */ + thd->locked_tables_list.unlock_locked_tables(thd); /* To avoid deadlocks we should obtain table locks before diff --git a/sql/sql_base.cc b/sql/sql_base.cc index f7ac1df8b32..61c071d3430 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -116,9 +116,6 @@ static bool table_def_inited= 0; static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, TABLE_SHARE *table_share); -static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, - const char *alias, char *cache_key, - uint cache_key_length); static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry); static bool auto_repair_table(THD *thd, TABLE_LIST *table_list); static void free_cache_entry(TABLE *entry); @@ -405,20 +402,6 @@ static void table_def_unuse_table(TABLE *table) } -/** - Bind used TABLE instance to another table share. - - @note Will go away once we refactor code responsible - for reopening tables under lock tables. -*/ - -static void table_def_change_share(TABLE *table, TABLE_SHARE *new_share) -{ - table->s->used_tables.remove(table); - new_share->used_tables.push_front(table); -} - - /* Get TABLE_SHARE for a table. @@ -726,65 +709,6 @@ static void reference_table_share(TABLE_SHARE *share) } -/** - Close file handle, but leave the table in THD::open_tables list - to allow its future reopening. - - @param table Table handler - - @note THD::killed will be set if we run out of memory - - @note If closing a MERGE child, the calling function has to - take care for closing the parent too, if necessary. - - @todo Get rid of this function once we refactor LOCK TABLES - to keep around TABLE_LIST elements used for opening - of tables until UNLOCK TABLES. -*/ - -void close_handle_and_leave_table_as_placeholder(TABLE *table) -{ - TABLE_SHARE *share, *old_share= table->s; - char *key_buff; - MEM_ROOT *mem_root= &table->mem_root; - DBUG_ENTER("close_handle_and_leave_table_as_lock"); - - DBUG_ASSERT(table->db_stat); - - /* - Make a local copy of the table share and free the current one. - This has to be done to ensure that the table share is removed from - the table defintion cache as soon as the last instance is removed - */ - if (multi_alloc_root(mem_root, - &share, sizeof(*share), - &key_buff, old_share->table_cache_key.length, - NULL)) - { - bzero((char*) share, sizeof(*share)); - share->set_table_cache_key(key_buff, old_share->table_cache_key.str, - old_share->table_cache_key.length); - share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table() - } - - /* - When closing a MERGE parent or child table, detach the children first. - Do not clear child table references to allow for reopen. - */ - if (table->child_l || table->parent) - detach_merge_children(table, FALSE); - table->file->close(); - table->db_stat= 0; // Mark file closed - table_def_change_share(table, share); - release_table_share(table->s); - table->s= share; - table->file->change_table_ptr(table, table->s); - - DBUG_VOID_RETURN; -} - - - /* Create a list for all open tables matching SQL expression @@ -1030,64 +954,31 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, if (thd->locked_tables_mode) { /* - If we are under LOCK TABLES we need to reopen tables without + If we are under LOCK TABLES, we need to reopen the tables without opening a door for any concurrent threads to sneak in and get lock on our tables. To achieve this we use exclusive metadata locks. */ - if (!tables) + TABLE_LIST *tables_to_reopen= (tables ? tables : + thd->locked_tables_list.locked_tables()); + + for (TABLE_LIST *table_list= tables_to_reopen; table_list; + table_list= table_list->next_global) { - for (TABLE *tab= thd->open_tables; tab; tab= tab->next) + /* A check that the table was locked for write is done by the caller. */ + TABLE *table= find_locked_table(thd->open_tables, table_list->db, + table_list->table_name); + + /* May return NULL if this table has already been closed via an alias. */ + if (! table) + continue; + + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) { - /* - Checking TABLE::db_stat is essential in case when we have - several instances of the table open and locked. - */ - if (tab->db_stat) - { - char dbname[NAME_LEN+1]; - char tname[NAME_LEN+1]; - /* - Since close_data_files_and_leave_as_placeholders() frees share's - memroot we need to make copies of database and table names. - */ - strmov(dbname, tab->s->db.str); - strmov(tname, tab->s->table_name.str); - if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN)) - { - result= TRUE; - goto err_with_reopen; - } - pthread_mutex_lock(&LOCK_open); - close_data_files_and_leave_as_placeholders(thd, dbname, tname); - pthread_mutex_unlock(&LOCK_open); - } - } - } - else - { - for (TABLE_LIST *table= tables; table; table= table->next_local) - { - /* This should always succeed thanks to check in caller. */ - TABLE *tab= find_write_locked_table(thd->open_tables, table->db, - table->table_name); - /* - Checking TABLE::db_stat is essential in case when we have - several instances of the table open and locked. - */ - if (tab->db_stat) - { - if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN)) - { - result= TRUE; - goto err_with_reopen; - } - pthread_mutex_lock(&LOCK_open); - close_data_files_and_leave_as_placeholders(thd, table->db, - table->table_name); - pthread_mutex_unlock(&LOCK_open); - } + result= TRUE; + goto err_with_reopen; } + close_all_tables_for_name(thd, table->s, FALSE); } } @@ -1145,16 +1036,12 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, err_with_reopen: if (thd->locked_tables_mode) { - pthread_mutex_lock(&LOCK_open); /* No other thread has the locked tables open; reopen them and get the old locks. This should always succeed (unless some external process has removed the tables) */ - thd->in_lock_tables=1; - result|= reopen_tables(thd, 1); - thd->in_lock_tables=0; - pthread_mutex_unlock(&LOCK_open); + thd->locked_tables_list.reopen_tables(thd); /* Since mdl_downgrade_exclusive_lock() won't do anything with shared metadata lock it is much simplier to go through all open tables rather @@ -1311,6 +1198,8 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table) { for (; table ; table= table->next) { + DBUG_ASSERT(table->pos_in_locked_tables == NULL || + table->pos_in_locked_tables->table == table); if (table->query_id == thd->query_id) { table->query_id= 0; @@ -1355,6 +1244,75 @@ static void close_open_tables(THD *thd) } +/** + Close all open instances of the table but keep the MDL lock, + if any. + + Works both under LOCK TABLES and in the normal mode. + Removes all closed instances of the table from the table cache. + + @param thd thread handle + @param[in] share table share, but is just a handy way to + access the table cache key + + @param[in] remove_from_locked_tables + TRUE if the table is being dropped or renamed. + In that case the documented behaviour is to + implicitly remove the table from LOCK TABLES + list. +*/ + +void +close_all_tables_for_name(THD *thd, TABLE_SHARE *share, + bool remove_from_locked_tables) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length= share->table_cache_key.length; + + memcpy(key, share->table_cache_key.str, key_length); + + safe_mutex_assert_not_owner(&LOCK_open); + /* + We need to hold LOCK_open while changing the open_tables + list, since another thread may work on it. + @sa notify_thread_having_shared_lock() + */ + pthread_mutex_lock(&LOCK_open); + + for (TABLE **prev= &thd->open_tables; *prev; ) + { + TABLE *table= *prev; + + if (table->s->table_cache_key.length == key_length && + !memcmp(table->s->table_cache_key.str, key, key_length)) + { + /* + Does nothing if the table is not locked. + This allows one to use this function after a table + has been unlocked, e.g. in partition management. + */ + mysql_lock_remove(thd, thd->lock, table); + + thd->locked_tables_list.unlink_from_list(thd, + table->pos_in_locked_tables, + remove_from_locked_tables); + + /* Make sure the table is removed from the cache */ + table->s->version= 0; + close_thread_table(thd, prev); + } + else + { + /* Step to next entry in open_tables list. */ + prev= &table->next; + } + } + /* We have been removing tables from the table cache. */ + broadcast_refresh(); + pthread_mutex_unlock(&LOCK_open); +} + + /* Close all tables used by the current substatement, or all tables used by this thread if we are on the upper level. @@ -1995,7 +1953,7 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list) If LOCK TABLES list is not empty and contains this table, unlock the table and remove the table from this list. */ - mysql_lock_remove(thd, thd->lock, table, FALSE); + mysql_lock_remove(thd, thd->lock, table); close_temporary_table(thd, table, 1, 1); DBUG_RETURN(0); } @@ -2111,77 +2069,6 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db, } -/** - Prepare an open merge table for close. - - @param[in] thd thread context - @param[in] table table to prepare - @param[in,out] prev_pp pointer to pointer of previous table - - @detail - If the table is a MERGE parent, just detach the children. - If the table is a MERGE child, close the parent (incl. detach). -*/ - -static void unlink_open_merge(THD *thd, TABLE *table, TABLE ***prev_pp) -{ - DBUG_ENTER("unlink_open_merge"); - - if (table->parent) - { - /* - If MERGE child, close parent too. Closing includes detaching. - - This is used for example in ALTER TABLE t1 RENAME TO t5 under - LOCK TABLES where t1 is a MERGE child: - CREATE TABLE t1 (c1 INT); - CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); - LOCK TABLES t1 WRITE, t2 WRITE; - ALTER TABLE t1 RENAME TO t5; - */ - TABLE *parent= table->parent; - TABLE **prv_p; - - /* Find parent in open_tables list. */ - for (prv_p= &thd->open_tables; - *prv_p && (*prv_p != parent); - prv_p= &(*prv_p)->next) {} - if (*prv_p) - { - /* Special treatment required if child follows parent in list. */ - if (*prev_pp == &parent->next) - *prev_pp= prv_p; - /* - Remove parent from open_tables list and close it. - This includes detaching and hence clearing parent references. - */ - close_thread_table(thd, prv_p); - } - } - else if (table->child_l) - { - /* - When closing a MERGE parent, detach the children first. It is - not necessary to clear the child or parent table reference of - this table because the TABLE is freed. But we need to clear - the child or parent references of the other belonging tables - so that they cannot be moved into the unused_tables chain with - these pointers set. - - This is used for example in ALTER TABLE t2 RENAME TO t5 under - LOCK TABLES where t2 is a MERGE parent: - CREATE TABLE t1 (c1 INT); - CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); - LOCK TABLES t1 WRITE, t2 WRITE; - ALTER TABLE t2 RENAME TO t5; - */ - detach_merge_children(table, TRUE); - } - - DBUG_VOID_RETURN; -} - - /** Force all other threads to stop using the table by upgrading metadata lock on it and remove unused TABLE instances from cache. @@ -2230,119 +2117,22 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, /** - Upgrade metadata lock on the table and close all its instances. + Close a and drop a just created table in CREATE TABLE ... SELECT. - @param thd Thread handler - @param table Table to remove from cache + @param thd Thread handle + @param table TABLE object for the table to be dropped + @param db_name Name of database for this table + @param table_name Name of this table - @retval FALSE Success. - @retval TRUE Failure (e.g. because thread was killed). -*/ - -bool close_cached_table(THD *thd, TABLE *table) -{ - DBUG_ENTER("close_cached_table"); - - /* FIXME: check if we pass proper parameters everywhere. */ - if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) - DBUG_RETURN(TRUE); - - /* Close lock if this is not got with LOCK TABLES */ - if (! thd->locked_tables_mode) - { - mysql_unlock_tables(thd, thd->lock); - thd->lock=0; // Start locked threads - } - - pthread_mutex_lock(&LOCK_open); - /* Close all copies of 'table'. This also frees all LOCK TABLES lock */ - unlink_open_table(thd, table, TRUE); - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(FALSE); -} - - -/** - Remove all instances of table from thread's open list and - table cache. - - @param thd Thread context - @param find Table to remove - @param unlock TRUE - free all locks on tables removed that are - done with LOCK TABLES - FALSE - otherwise - - @note When unlock parameter is FALSE or current thread doesn't have - any tables locked with LOCK TABLES, tables are assumed to be - not locked (for example already unlocked). -*/ - -void unlink_open_table(THD *thd, TABLE *find, bool unlock) -{ - char key[MAX_DBKEY_LENGTH]; - uint key_length= find->s->table_cache_key.length; - TABLE *list, **prev; - DBUG_ENTER("unlink_open_table"); - - safe_mutex_assert_owner(&LOCK_open); - - memcpy(key, find->s->table_cache_key.str, key_length); - /* - Note that we need to hold LOCK_open while changing the - open_tables list. Another thread may work on it. - (See: notify_thread_having_shared_lock()) - Closing a MERGE child before the parent would be fatal if the - other thread tries to abort the MERGE lock in between. - */ - for (prev= &thd->open_tables; *prev; ) - { - list= *prev; - - if (list->s->table_cache_key.length == key_length && - !memcmp(list->s->table_cache_key.str, key, key_length)) - { - if (unlock && thd->locked_tables_mode) - mysql_lock_remove(thd, thd->lock, - list->parent ? list->parent : list, TRUE); - - /* Prepare MERGE table for close. Close parent if necessary. */ - unlink_open_merge(thd, list, &prev); - - /* Remove table from open_tables list. */ - *prev= list->next; - /* Close table. */ - free_cache_entry(list); - } - else - { - /* Step to next entry in open_tables list. */ - prev= &list->next; - } - } - - // Notify any 'refresh' threads - broadcast_refresh(); - DBUG_VOID_RETURN; -} - - -/** - Auxiliary routine which closes and drops open table. - - @param thd Thread handle - @param table TABLE object for table to be dropped - @param db_name Name of database for this table - @param table_name Name of this table - - @note This routine assumes that table to be closed is open only - by calling thread so we needn't wait until other threads - will close the table. Also unless called under implicit or - explicit LOCK TABLES mode it assumes that table to be - dropped is already unlocked. In the former case it will - also remove lock on the table. But one should not rely on - this behaviour as it may change in future. - Currently, however, this function is never called for a - table that was locked with LOCK TABLES. + This routine assumes that the table to be closed is open only + by the calling thread, so we needn't wait until other threads + close the table. It also assumes that the table is first + in thd->open_ables and a data lock on it, if any, has been + released. To sum up, it's tuned to work with + CREATE TABLE ... SELECT and CREATE TABLE .. SELECT only. + Note, that currently CREATE TABLE ... SELECT is not supported + under LOCK TABLES. This function, still, can be called in + prelocked mode, e.g. if we do CREATE TABLE .. SELECT f1(); */ void drop_open_table(THD *thd, TABLE *table, const char *db_name, @@ -2352,13 +2142,14 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name, close_temporary_table(thd, table, 1, 1); else { + DBUG_ASSERT(table == thd->open_tables); + handlerton *table_type= table->s->db_type(); + /* Ensure the table is removed from the cache. */ + table->s->version= 0; + pthread_mutex_lock(&LOCK_open); - /* - unlink_open_table() also tells threads waiting for refresh or close - that something has happened. - */ - unlink_open_table(thd, table, FALSE); + close_thread_table(thd, &thd->open_tables); quick_rm_table(table_type, db_name, table_name, 0); pthread_mutex_unlock(&LOCK_open); } @@ -2409,73 +2200,6 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond) } -/* - Open table for which this thread has exclusive meta-data lock. - - SYNOPSIS - reopen_name_locked_table() - thd Thread handle - table_list TABLE_LIST object for table to be open. - - NOTE - This function assumes that its caller already acquired LOCK_open mutex. - - RETURN VALUE - FALSE - Success - TRUE - Error -*/ - -bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list) -{ - TABLE *table; - TABLE_SHARE *share; - char key[MAX_DBKEY_LENGTH]; - uint key_length; - char *table_name= table_list->table_name; - DBUG_ENTER("reopen_name_locked_table"); - - if (thd->killed) - DBUG_RETURN(TRUE); - - key_length= create_table_def_key(thd, key, table_list, 0); - - if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) - DBUG_RETURN(TRUE); - - if (reopen_table_entry(thd, table, table_list, table_name, key, key_length)) - { - my_free((uchar*)table, MYF(0)); - DBUG_RETURN(TRUE); - } - - share= table->s; - /* - We want to prevent other connections from opening this table until end - of statement as it is likely that modifications of table's metadata are - not yet finished (for example CREATE TRIGGER have to change .TRG file, - or we might want to drop table if CREATE TABLE ... SELECT fails). - This also allows us to assume that no other connection will sneak in - before we will get table-level lock on this table. - */ - share->version=0; - table->in_use = thd; - - table_def_add_used_table(thd, table); - - table->next= thd->open_tables; - thd->open_tables= table; - - table->tablenr=thd->current_tablenr++; - table->used_fields=0; - table->const_table=0; - table->null_row= table->maybe_null= 0; - table->force_index= table->force_index_order= table->force_index_group= 0; - table->status=STATUS_NO_RECORD; - table_list->table= table; - DBUG_RETURN(FALSE); -} - - /** Check that table exists in table definition cache, on disk or in some storage engine. @@ -2554,6 +2278,62 @@ void table_share_release_hook(void *share) } +/* + A helper function that acquires an MDL lock for a table + being opened. +*/ + +static bool +open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, + MDL_LOCK_DATA *mdl_lock_data, + uint flags, + enum_open_table_action *action) +{ + mdl_add_lock(&thd->mdl_context, mdl_lock_data); + + if (table_list->open_type) + { + /* + In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table + may not yet exist. Let's acquire an exclusive lock for that + case. If later it turns out the table existsed, we will + downgrade the lock to shared. Note that, according to the + locking protocol, all exclusive locks must be acquired before + shared locks. This invariant is preserved here and is also + enforced by asserts in metadata locking subsystem. + */ + mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); + if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + return 1; + } + else + { + bool retry; + + /* + There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we + want to be sure that caller doesn't pass us both flags simultaneously. + */ + DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || + !(flags & MYSQL_LOCK_IGNORE_FLUSH)); + + if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL && + table_list->lock_type >= TL_WRITE_ALLOW_WRITE) + mdl_set_lock_type(mdl_lock_data, MDL_SHARED_UPGRADABLE); + if (flags & MYSQL_LOCK_IGNORE_FLUSH) + mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO); + + if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry)) + { + if (retry) + *action= OT_BACK_OFF_AND_RETRY; + return 1; + } + } + return 0; +} + + /* Open a table. @@ -2628,7 +2408,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, same name. This block implements the behaviour. TODO: move this block into a separate function. */ - if (!table_list->skip_temporary) + if (!table_list->skip_temporary && ! (flags & MYSQL_OPEN_SKIP_TEMPORARY)) { for (table= thd->temporary_tables; table ; table=table->next) { @@ -2673,7 +2453,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, open not pre-opened tables in pre-locked/LOCK TABLES mode. TODO: move this block into a separate function. */ - if (thd->locked_tables_mode) + if (thd->locked_tables_mode && + ! (flags & MYSQL_OPEN_GET_NEW_TABLE)) { // Using table locks TABLE *best_table= 0; int best_distance= INT_MIN; @@ -2785,47 +2566,12 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, */ mdl_lock_data= table_list->mdl_lock_data; - mdl_add_lock(&thd->mdl_context, mdl_lock_data); - - if (table_list->open_type) + if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { - /* - In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table - may not yet exist. Let's acquire an exclusive lock for that - case. If later it turns out the table existsed, we will - downgrade the lock to shared. Note that, according to the - locking protocol, all exclusive locks must be acquired before - shared locks. This invariant is preserved here and is also - enforced by asserts in metadata locking subsystem. - */ - mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); - if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + if (open_table_get_mdl_lock(thd, table_list, mdl_lock_data, flags, + action)) DBUG_RETURN(TRUE); } - else - { - bool retry; - - /* - There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we - want to be sure that caller doesn't pass us both flags simultaneously. - */ - DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || - !(flags & MYSQL_LOCK_IGNORE_FLUSH)); - - if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL && - table_list->lock_type >= TL_WRITE_ALLOW_WRITE) - mdl_set_lock_type(mdl_lock_data, MDL_SHARED_UPGRADABLE); - if (flags & MYSQL_LOCK_IGNORE_FLUSH) - mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO); - - if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry)) - { - if (retry) - *action= OT_BACK_OFF_AND_RETRY; - DBUG_RETURN(TRUE); - } - } pthread_mutex_lock(&LOCK_open); @@ -3061,7 +2807,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, MYF(MY_WME)); memcpy((char*) table->alias, alias, length); } - /* These variables are also set in reopen_table() */ table->tablenr=thd->current_tablenr++; table->used_fields=0; table->const_table=0; @@ -3153,427 +2898,264 @@ TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_na } -/* - Reopen an table because the definition has changed. - - SYNOPSIS - reopen_table() - table Table object - - NOTES - The data file for the table is already closed and the share is released - The table has a 'dummy' share that mainly contains database and table name. - - RETURN - 0 ok - 1 error. The old table object is not changed. -*/ - -bool reopen_table(TABLE *table) -{ - TABLE tmp; - bool error= 1; - Field **field; - uint key,part; - TABLE_LIST table_list; - THD *thd= table->in_use; - DBUG_ENTER("reopen_table"); - DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); - - DBUG_ASSERT(table->s->ref_count == 0); - DBUG_ASSERT(!table->sort.io_cache); - DBUG_ASSERT(!table->children_attached); - -#ifdef EXTRA_DEBUG - if (table->db_stat) - sql_print_error("Table %s had a open data handler in reopen_table", - table->alias); -#endif - bzero((char*) &table_list, sizeof(TABLE_LIST)); - table_list.db= table->s->db.str; - table_list.table_name= table->s->table_name.str; - table_list.table= table; - - if (reopen_table_entry(thd, &tmp, &table_list, - table->alias, - table->s->table_cache_key.str, - table->s->table_cache_key.length)) - goto end; - - /* This list copies variables set by open_table */ - tmp.tablenr= table->tablenr; - tmp.used_fields= table->used_fields; - tmp.const_table= table->const_table; - tmp.null_row= table->null_row; - tmp.maybe_null= table->maybe_null; - tmp.status= table->status; - - tmp.s->table_map_id= table->s->table_map_id; - - /* Get state */ - tmp.in_use= thd; - tmp.reginfo.lock_type=table->reginfo.lock_type; - tmp.grant= table->grant; - - /* Replace table in open list */ - tmp.next= table->next; - tmp.prev= table->prev; - - /* Preserve MERGE parent. */ - tmp.parent= table->parent; - /* Fix MERGE child list and check for unchanged union. */ - if ((table->child_l || tmp.child_l) && - fix_merge_after_open(table->child_l, table->child_last_l, - tmp.child_l, tmp.child_last_l)) - { - (void) closefrm(&tmp, 1); // close file, free everything - goto end; - } - tmp.mdl_lock_data= table->mdl_lock_data; - - table_def_change_share(table, tmp.s); - /* Avoid wiping out TABLE's position in new share's used tables list. */ - tmp.share_next= table->share_next; - tmp.share_prev= table->share_prev; - - delete table->triggers; - if (table->file) - (void) closefrm(table, 1); // close file, free everything - - *table= tmp; - table->default_column_bitmaps(); - table->file->change_table_ptr(table, table->s); - - DBUG_ASSERT(table->alias != 0); - for (field=table->field ; *field ; field++) - { - (*field)->table= (*field)->orig_table= table; - (*field)->table_name= &table->alias; - } - for (key=0 ; key < table->s->keys ; key++) - { - for (part=0 ; part < table->key_info[key].usable_key_parts ; part++) - { - table->key_info[key].key_part[part].field->table= table; - table->key_info[key].key_part[part].field->orig_table= table; - } - } - if (table->triggers) - table->triggers->set_table(table); - /* - Do not attach MERGE children here. The children might be reopened - after the parent. Attach children after reopening all tables that - require reopen. See for example reopen_tables(). - */ - - broadcast_refresh(); - error=0; - - end: - DBUG_RETURN(error); -} - +/*********************************************************************** + class Locked_tables_list implementation. Declared in sql_class.h +************************************************************************/ /** - Close all instances of a table open by this thread and replace - them with placeholder in THD::open_tables list for future reopening. + Enter LTM_LOCK_TABLES mode. - @param thd Thread context - @param db Database name for the table to be closed - @param table_name Name of the table to be closed + Enter the LOCK TABLES mode using all the tables that are + currently open and locked in this connection. + Initializes a TABLE_LIST instance for every locked table. - @note This function assumes that if we are not under LOCK TABLES, - then there is only one table open and locked. This means that - the function probably has to be adjusted before it can be used - anywhere outside ALTER TABLE. + @param thd thread handle - @note Must not use TABLE_SHARE::table_name/db of the table being closed, - the strings are used in a loop even after the share may be freed. + @return TRUE if out of memory. */ -void close_data_files_and_leave_as_placeholders(THD *thd, const char *db, - const char *table_name) +bool +Locked_tables_list::init_locked_tables(THD *thd) { - TABLE *table; - DBUG_ENTER("close_data_files_and_leave_as_placeholders"); + DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE); + DBUG_ASSERT(m_locked_tables == NULL); - safe_mutex_assert_owner(&LOCK_open); - - if (! thd->locked_tables_mode) + for (TABLE *table= thd->open_tables; table; table= table->next) { - /* - If we are not under LOCK TABLES we should have only one table - open and locked so it makes sense to remove the lock at once. - */ - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; - } + TABLE_LIST *src_table_list= table->pos_in_table_list; + char *db, *table_name, *alias; + size_t db_len= strlen(src_table_list->db) + 1; + size_t table_name_len= strlen(src_table_list->table_name) + 1; + size_t alias_len= strlen(src_table_list->alias) + 1; + TABLE_LIST *dst_table_list; - for (table=thd->open_tables; table ; table=table->next) - { - if (!strcmp(table->s->table_name.str, table_name) && - !strcmp(table->s->db.str, db)) + if (! multi_alloc_root(&m_locked_tables_root, + &dst_table_list, sizeof(*dst_table_list), + &db, db_len, + &table_name, table_name_len, + &alias, alias_len, + 0)) { - if (thd->locked_tables_mode) - { - if (table->parent) - { - /* - If MERGE child, need to reopen parent too. This means that - the first child to be closed will detach all children from - the parent and close it. OTOH in most cases a MERGE table - won't have multiple children with the same db.table_name. - */ - mysql_lock_remove(thd, thd->lock, table->parent, TRUE); - close_handle_and_leave_table_as_placeholder(table->parent); - } - else - mysql_lock_remove(thd, thd->lock, table, TRUE); - } - table->s->version= 0; - close_handle_and_leave_table_as_placeholder(table); + unlock_locked_tables(0); + return TRUE; } + + /** + Sic: remember the *actual* table level lock type taken, to + acquire the exact same type in reopen_tables(). + E.g. if the table was locked for write, src_table_list->lock_type is + TL_WRITE_DEFAULT, whereas reginfo.lock_type has been updated from + thd->update_lock_default. + */ + dst_table_list->init_one_table(db, table_name, alias, + src_table_list->table->reginfo.lock_type); + dst_table_list->mdl_lock_data= src_table_list->mdl_lock_data; + dst_table_list->table= table; + memcpy(db, src_table_list->db, db_len); + memcpy(table_name, src_table_list->table_name, table_name_len); + memcpy(alias, src_table_list->alias, alias_len); + /* Link last into the list of tables */ + *(dst_table_list->prev_global= m_locked_tables_last)= dst_table_list; + m_locked_tables_last= &dst_table_list->next_global; + table->pos_in_locked_tables= dst_table_list; } - DBUG_VOID_RETURN; + thd->locked_tables_mode= LTM_LOCK_TABLES; + return FALSE; } - /** - Reattach MERGE children after reopen. + Leave LTM_LOCK_TABLES mode if it's been entered. - @param[in] thd thread context - @param[in,out] err_tables_p pointer to pointer of tables in error + Close all locked tables, free memory, and leave the mode. - @return status - @retval FALSE OK, err_tables_p unchanged - @retval TRUE Error, err_tables_p contains table(s) + @note This function is a no-op if we're not in LOCK TABLES. */ -static bool reattach_merge(THD *thd, TABLE **err_tables_p) +void +Locked_tables_list::unlock_locked_tables(THD *thd) + { - TABLE *table; - TABLE *next; - TABLE **prv_p= &thd->open_tables; - bool error= FALSE; - DBUG_ENTER("reattach_merge"); - - for (table= thd->open_tables; table; table= next) - { - next= table->next; - DBUG_PRINT("tcache", ("check table: '%s'.'%s' 0x%lx next: 0x%lx", - table->s->db.str, table->s->table_name.str, - (long) table, (long) next)); - /* Reattach children for MERGE tables with "closed data files" only. */ - if (table->child_l && !table->children_attached) - { - DBUG_PRINT("tcache", ("MERGE parent, attach children")); - if(table->file->extra(HA_EXTRA_ATTACH_CHILDREN)) - { - my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); - error= TRUE; - /* Remove table from open_tables. */ - *prv_p= next; - if (next) - prv_p= &next->next; - /* Stack table on error list. */ - table->next= *err_tables_p; - *err_tables_p= table; - continue; - } - else - { - table->children_attached= TRUE; - DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx", - table->s->db.str, - table->s->table_name.str, (long) table)); - } - } - prv_p= &table->next; - } - DBUG_RETURN(error); -} - - -/** - Reopen all tables with closed data files. - - @param thd Thread context - @param get_locks Should we get locks after reopening tables ? - - @note Since this function can't properly handle prelocking and - create placeholders it should be used in very special - situations like FLUSH TABLES or ALTER TABLE. In general - case one should just repeat open_tables()/lock_tables() - combination when one needs tables to be reopened (for - example see open_and_lock_tables()). - - @note One should have lock on LOCK_open when calling this. - - @return FALSE in case of success, TRUE - otherwise. -*/ - -bool reopen_tables(THD *thd, bool get_locks) -{ - TABLE *table,*next,**prev; - TABLE **tables,**tables_ptr; // For locks - TABLE *err_tables= NULL, *err_tab_tmp; - bool error=0, not_used; - bool merge_table_found= FALSE; - - DBUG_ENTER("reopen_tables"); - - if (!thd->open_tables) - DBUG_RETURN(0); - - safe_mutex_assert_owner(&LOCK_open); - if (get_locks) + if (thd) { + DBUG_ASSERT(!thd->in_sub_stmt && + !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); /* - The ptr is checked later - Do not handle locks of MERGE children. + Sic: we must be careful to not close open tables if + we're not in LOCK TABLES mode: unlock_locked_tables() is + sometimes called implicitly, expecting no effect on + open tables, e.g. from begin_trans(). */ - uint opens=0; - for (table= thd->open_tables; table ; table=table->next) - if (!table->parent) - opens++; - DBUG_PRINT("tcache", ("open tables to lock: %u", opens)); - tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens); - } - else - tables= &thd->open_tables; - tables_ptr =tables; + if (thd->locked_tables_mode != LTM_LOCK_TABLES) + return; - prev= &thd->open_tables; - for (table=thd->open_tables; table ; table=next) - { - uint db_stat=table->db_stat; - next=table->next; - DBUG_PRINT("tcache", ("open table: '%s'.'%s' 0x%lx " - "parent: 0x%lx db_stat: %u", - table->s->db.str, table->s->table_name.str, - (long) table, (long) table->parent, db_stat)); - if (table->child_l && !db_stat) - merge_table_found= TRUE; - if (!tables || (!db_stat && reopen_table(table))) - { - my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); - /* - If we could not allocate 'tables', we may close open tables - here. If a MERGE table is affected, detach the children first. - It is not necessary to clear the child or parent table reference - of this table because the TABLE is freed. But we need to clear - the child or parent references of the other belonging tables so - that they cannot be moved into the unused_tables chain with - these pointers set. - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); - free_cache_entry(table); - error=1; - } - else - { - DBUG_PRINT("tcache", ("opened. need lock: %d", - get_locks && !db_stat && !table->parent)); - *prev= table; - prev= &table->next; - /* Do not handle locks of MERGE children. */ - if (get_locks && !db_stat && !table->parent) - { - *tables_ptr++= table; // need new lock on this - /* - We rely on having exclusive metadata lock on the table to be - able safely re-acquire table locks on it. - */ - DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, - table->s->db.str, - table->s->table_name.str)); - } - } - } - *prev=0; - /* - When all tables are open again, we can re-attach MERGE children to - their parents. All TABLE objects are still present. - */ - DBUG_PRINT("tcache", ("re-attaching MERGE tables: %d", merge_table_found)); - if (!error && merge_table_found && reattach_merge(thd, &err_tables)) - { - while (err_tables) - { - err_tab_tmp= err_tables->next; - free_cache_entry(err_tables); - err_tables= err_tab_tmp; - } - } - DBUG_PRINT("tcache", ("open tables to lock: %u", - (uint) (tables_ptr - tables))); - if (tables != tables_ptr) // Should we get back old locks - { - MYSQL_LOCK *lock; - const uint flags= MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK | - MYSQL_LOCK_IGNORE_FLUSH; - /* - Since we have exclusive metadata locks on tables which we - are reopening we should always get these locks (We won't - wait on table level locks so can't get aborted and we ignore - other threads that set THD::some_tables_deleted by using - MYSQL_LOCK_IGNORE_FLUSH flag). - */ - thd->some_tables_deleted=0; - if ((lock= mysql_lock_tables(thd, tables, (uint) (tables_ptr - tables), - flags, ¬_used))) - { - thd->lock= mysql_lock_merge(thd->lock, lock); - } - else + for (TABLE_LIST *table_list= m_locked_tables; + table_list; table_list= table_list->next_global) { /* - This case should only happen if there is a bug in the reopen logic. - Need to issue error message to have a reply for the application. - Not exactly what happened though, but close enough. + Clear the position in the list, the TABLE object will be + returned to the table cache. */ - my_error(ER_LOCK_DEADLOCK, MYF(0)); - error=1; + table_list->table->pos_in_locked_tables= NULL; } + thd->locked_tables_mode= LTM_NONE; + + close_thread_tables(thd); } - if (get_locks && tables) - { - my_afree((uchar*) tables); - } - broadcast_refresh(); - DBUG_RETURN(error); + /* + After closing tables we can free memory used for storing lock + request for metadata locks and TABLE_LIST elements. + */ + free_root(&m_locked_tables_root, MYF(0)); + m_locked_tables= NULL; + m_locked_tables_last= &m_locked_tables; } /** - Unlock and close tables open and locked by LOCK TABLES statement. + Unlink a locked table from the locked tables list, either + temporarily or permanently. - @param thd Current thread's context. + @param thd thread handle + @param table_list the element of locked tables list. + The implementation assumes that this argument + points to a TABLE_LIST element linked into + the locked tables list. Passing a TABLE_LIST + instance that is not part of locked tables + list will lead to a crash. + @parma remove_from_locked_tables + TRUE if the table is removed from the list + permanently. + + This function is a no-op if we're not under LOCK TABLES. + + @sa Locked_tables_list::reopen_tables() */ -void unlock_locked_tables(THD *thd) -{ - DBUG_ASSERT(!thd->in_sub_stmt && - !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); +void Locked_tables_list::unlink_from_list(THD *thd, + TABLE_LIST *table_list, + bool remove_from_locked_tables) +{ /* - Sic: we must be careful to not close open tables if - we're not in LOCK TABLES mode: unlock_locked_tables() is - sometimes called implicitly, expecting no effect on - open tables, e.g. from begin_trans(). + If mode is not LTM_LOCK_TABLES, we needn't do anything. Moreover, + outside this mode pos_in_locked_tables value is not trustworthy. */ if (thd->locked_tables_mode != LTM_LOCK_TABLES) return; - thd->locked_tables_mode= LTM_NONE; - close_thread_tables(thd); /* - After closing tables we can free memory used for storing lock - request objects for metadata locks + table_list must be set and point to pos_in_locked_tables of some + table. */ - free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE)); + DBUG_ASSERT(table_list->table->pos_in_locked_tables == table_list); + + /* Clear the pointer, the table will be returned to the table cache. */ + table_list->table->pos_in_locked_tables= NULL; + + /* Mark the table as closed in the locked tables list. */ + table_list->table= NULL; + + /* + If the table is being dropped or renamed, remove it from + the locked tables list (implicitly drop the LOCK TABLES lock + on it). + */ + if (remove_from_locked_tables) + { + *table_list->prev_global= table_list->next_global; + if (table_list->next_global == NULL) + m_locked_tables_last= table_list->prev_global; + else + table_list->next_global->prev_global= table_list->prev_global; + } +} + +/** + This is an attempt to recover (somewhat) in case of an error. + If we failed to reopen a closed table, let's unlink it from the + list and forget about it. From a user perspective that would look + as if the server "lost" the lock on one of the locked tables. + + @note This function is a no-op if we're not under LOCK TABLES. +*/ + +void Locked_tables_list::unlink_all_closed_tables() +{ + for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list= + table_list->next_global) + { + if (table_list->table == NULL) + { + /* Unlink from list. */ + *table_list->prev_global= table_list->next_global; + if (table_list->next_global == NULL) + m_locked_tables_last= table_list->prev_global; + else + table_list->next_global->prev_global= table_list->prev_global; + } + } +} + + +/** + Reopen the tables locked with LOCK TABLES and temporarily closed + by a DDL statement or FLUSH TABLES. + + @note This function is a no-op if we're not under LOCK TABLES. + + @return TRUE if an error reopening the tables. May happen in + case of some fatal system error only, e.g. a disk + corruption, out of memory or a serious bug in the + locking. +*/ + +bool +Locked_tables_list::reopen_tables(THD *thd) +{ + enum enum_open_table_action ot_action_unused; + bool lt_refresh_unused; + + for (TABLE_LIST *table_list= m_locked_tables; + table_list; table_list= table_list->next_global) + { + MYSQL_LOCK *lock; + + if (table_list->table) /* The table was not closed */ + continue; + + /* Links into thd->open_tables upon success */ + if (open_table(thd, table_list, thd->mem_root, &ot_action_unused, + MYSQL_OPEN_REOPEN)) + { + unlink_all_closed_tables(); + return TRUE; + } + table_list->table->pos_in_locked_tables= table_list; + /* See also the comment on lock type in init_locked_tables(). */ + table_list->table->reginfo.lock_type= table_list->lock_type; + thd->in_lock_tables= 1; + lock= mysql_lock_tables(thd, &table_list->table, 1, + MYSQL_OPEN_REOPEN, <_refresh_unused); + thd->in_lock_tables= 0; + if (lock) + lock= mysql_lock_merge(thd->lock, lock); + if (lock == NULL) + { + /* + No one's seen this branch work. Recover and report an + error just in case. + */ + pthread_mutex_lock(&LOCK_open); + close_thread_table(thd, &thd->open_tables); + pthread_mutex_unlock(&LOCK_open); + table_list->table= 0; + unlink_all_closed_tables(); + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; + } + thd->lock= lock; + } + return FALSE; } @@ -3759,147 +3341,6 @@ err: } -/** - Load table definition from file and open table while holding exclusive - meta-data lock on it. - - @param thd Thread handle - @param entry Memory for TABLE object to be created - @param table_list TABLE_LIST with db, table_name & belong_to_view - @param alias Alias name - @param cache_key Key for table definition cache - @param cache_key_length Length of cache_key - - @note This auxiliary function is mostly inteded for re-opening table - in situations when we hold exclusive meta-data lock. It is not - intended for normal case in which we have only shared meta-data - lock on the table to be open. - - @note Extra argument for open is taken from thd->open_options. - - @note One must have a lock on LOCK_open as well as exclusive meta-data - lock on the table when calling this function. - - @return FALSE in case of success, TRUE otherwise. -*/ - -static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, - const char *alias, char *cache_key, - uint cache_key_length) -{ - int error; - TABLE_SHARE *share; - uint discover_retry_count= 0; - DBUG_ENTER("reopen_table_entry"); - - safe_mutex_assert_owner(&LOCK_open); - DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, - table_list->db, - table_list->table_name)); - -retry: - if (!(share= get_table_share_with_create(thd, table_list, cache_key, - cache_key_length, - OPEN_VIEW | - table_list->i_s_requested_object, - &error))) - DBUG_RETURN(1); - - if (share->is_view) - { - /* - If parent_l of the table_list is non null then a merge table - has this view as child table, which is not supported. - */ - if (table_list->parent_l) - { - my_error(ER_WRONG_MRG_TABLE, MYF(0)); - goto err; - } - - /* - This table is a view. Validate its metadata version: in particular, - that it was a view when the statement was prepared. - */ - if (check_and_update_table_version(thd, table_list, share)) - goto err; - if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) - goto err; - /* Attempt to reopen view will bring havoc to upper layers anyway. */ - release_table_share(share); - my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, - "BASE TABLE"); - DBUG_RETURN(1); - } - - if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) - goto err; - - while ((error= open_table_from_share(thd, share, alias, - (uint) (HA_OPEN_KEYFILE | - HA_OPEN_RNDFILE | - HA_GET_INDEX | - HA_TRY_READ_ONLY), - (READ_KEYINFO | COMPUTE_TYPES | - EXTRA_RECORD), - thd->open_options, entry, FALSE))) - { - if (error == 7) // Table def changed - { - share->version= 0; // Mark share as old - if (discover_retry_count++) // Retry once - goto err; - - /* - Since we have exclusive metadata lock on the table here the only - practical case when share->ref_count != 1 is when we have several - instances of the table opened by this thread (i.e we are under LOCK - TABLES). - */ - if (share->ref_count != 1) - goto err; - - release_table_share(share); - - if (ha_create_table_from_engine(thd, table_list->db, - table_list->table_name)) - goto err; - - thd->warning_info->clear_warning_info(thd->query_id); - thd->clear_error(); // Clear error message - goto retry; - } - if (!entry->s || !entry->s->crashed) - goto err; - - entry->s->version= 0; - - /* TODO: We don't need to release share here. */ - release_table_share(share); - pthread_mutex_unlock(&LOCK_open); - error= (int)auto_repair_table(thd, table_list); - pthread_mutex_lock(&LOCK_open); - - if (error) - goto err; - - goto retry; - } - - if (open_table_entry_fini(thd, share, entry)) - { - closefrm(entry, 0); - goto err; - } - - DBUG_RETURN(0); - -err: - release_table_share(share); - DBUG_RETURN(1); -} - - /** Finalize the process of TABLE creation by loading table triggers and taking action if a HEAP table content was emptied implicitly. @@ -8791,24 +8232,10 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b) int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt) { - DBUG_ENTER("abort_and_upgrade_locks"); + DBUG_ENTER("abort_and_upgrade_lock"); - lpt->old_lock_type= lpt->table->reginfo.lock_type; - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent : - lpt->table, TRUE); - if (mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context, - lpt->table->mdl_lock_data)) - { - mysql_lock_downgrade_write(lpt->thd, - lpt->table->parent ? lpt->table->parent : - lpt->table, - lpt->old_lock_type); + if (wait_while_table_is_used(lpt->thd, lpt->table, HA_EXTRA_FORCE_REOPEN)) DBUG_RETURN(1); - } - pthread_mutex_lock(&LOCK_open); - tdc_remove_table(lpt->thd, TDC_RT_REMOVE_NOT_OWN, lpt->db, lpt->table_name); - pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 1f0c8321608..d36ea5c52c9 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -469,7 +469,7 @@ THD::THD() debug_sync_control(0), #endif /* defined(ENABLED_DEBUG_SYNC) */ main_warning_info(0), - mdl_el_root(NULL) + locked_tables_root(NULL) { ulong tmp; @@ -574,8 +574,6 @@ THD::THD() thr_lock_owner_init(&main_lock_id, &lock_info); m_internal_handler= NULL; - - init_sql_alloc(&locked_tables_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0); } @@ -995,7 +993,7 @@ void THD::cleanup(void) ha_rollback(this); xid_cache_delete(&transaction.xid_state); } - unlock_locked_tables(this); + locked_tables_list.unlock_locked_tables(this); #if defined(ENABLED_DEBUG_SYNC) /* End the Debug Sync Facility. See debug_sync.cc. */ @@ -1074,7 +1072,6 @@ THD::~THD() #endif free_root(&main_mem_root, MYF(0)); - free_root(&locked_tables_root, MYF(0)); DBUG_VOID_RETURN; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 0fa3231e458..68ae3afe931 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1169,6 +1169,58 @@ private: Internal_error_handler *m_err_handler; }; +/** + Tables that were locked with LOCK TABLES statement. + + Encapsulates a list of TABLE_LIST instances for tables + locked by LOCK TABLES statement, memory root for metadata locks, + and, generally, the context of LOCK TABLES statement. + + In LOCK TABLES mode, the locked tables are kept open between + statements. + Therefore, we can't allocate metadata locks on execution memory + root -- as well as tables, the locks need to stay around till + UNLOCK TABLES is called. + The locks are allocated in the memory root encapsulate in this + class. + + Some SQL commands, like FLUSH TABLE or ALTER TABLE, demand that + the tables they operate on are closed, at least temporarily. + This class encapsulates a list of TABLE_LIST instances, one + for each base table from LOCK TABLES list, + which helps conveniently close the TABLEs when it's necessary + and later reopen them. + + Implemented in sql_base.cc +*/ + +class Locked_tables_list +{ +private: + MEM_ROOT m_locked_tables_root; + TABLE_LIST *m_locked_tables; + TABLE_LIST **m_locked_tables_last; +public: + Locked_tables_list() + :m_locked_tables(NULL), + m_locked_tables_last(&m_locked_tables) + { + init_sql_alloc(&m_locked_tables_root, MEM_ROOT_BLOCK_SIZE, 0); + } + void unlock_locked_tables(THD *thd); + ~Locked_tables_list() + { + unlock_locked_tables(0); + } + bool init_locked_tables(THD *thd); + TABLE_LIST *locked_tables() { return m_locked_tables; } + MEM_ROOT *locked_tables_root() { return &m_locked_tables_root; } + void unlink_from_list(THD *thd, TABLE_LIST *table_list, + bool remove_from_locked_tables); + void unlink_all_closed_tables(); + bool reopen_tables(THD *thd); +}; + /** Storage engine specific thread local data. @@ -1810,6 +1862,8 @@ public: */ Parser_state *m_parser_state; + Locked_tables_list locked_tables_list; + #ifdef WITH_PARTITION_STORAGE_ENGINE partition_info *work_part_info; #endif @@ -1819,8 +1873,13 @@ public: struct st_debug_sync_control *debug_sync_control; #endif /* defined(ENABLED_DEBUG_SYNC) */ - MEM_ROOT *mdl_el_root; - MEM_ROOT locked_tables_root; + /** + Points to the memory root of Locked_tables_list if + we're locking the tables for LOCK TABLES. Otherwise is NULL. + This is necessary to ensure that metadata locks allocated for + tables used in triggers will persist after statement end. + */ + MEM_ROOT *locked_tables_root; THD(); ~THD(); diff --git a/sql/sql_db.cc b/sql/sql_db.cc index b5c51601faf..1b61a96d7ff 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1956,8 +1956,8 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) /* Step7: drop the old database. - remove_db_from_cache(olddb) and query_cache_invalidate(olddb) - are done inside mysql_rm_db(), no needs to execute them again. + query_cache_invalidate(olddb) is done inside mysql_rm_db(), no need + to execute them again. mysql_rm_db() also "unuses" if we drop the current database. */ error= mysql_rm_db(thd, old_db->str, 0, 1); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 00bb013218d..e0199fcdf20 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3542,16 +3542,22 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, create_table)) + enum enum_open_table_action ot_action_unused; + /* + Here we open the destination table, on which we already have + an exclusive metadata lock. + */ + if (open_table(thd, create_table, thd->mem_root, + &ot_action_unused, MYSQL_OPEN_REOPEN)) { + pthread_mutex_lock(&LOCK_open); quick_rm_table(create_info->db_type, create_table->db, table_case_name(create_info, create_table->table_name), 0); + pthread_mutex_unlock(&LOCK_open); } else table= create_table->table; - pthread_mutex_unlock(&LOCK_open); } else { diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 95f142bc8a2..9de07e90560 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -184,7 +184,7 @@ bool begin_trans(THD *thd) return 1; } - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); if (end_active_trans(thd)) error= -1; @@ -3583,7 +3583,7 @@ end_with_restore_list: done FLUSH TABLES WITH READ LOCK + BEGIN. If this assumption becomes false, mysqldump will not work. */ - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); if (thd->options & OPTION_TABLE_LOCK) { end_active_trans(thd); @@ -3594,7 +3594,7 @@ end_with_restore_list: my_ok(thd); break; case SQLCOM_LOCK_TABLES: - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); /* we must end the trasaction first, regardless of anything */ if (end_active_trans(thd)) goto error; @@ -3604,24 +3604,23 @@ end_with_restore_list: if (lex->protect_against_global_read_lock && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) goto error; - thd->in_lock_tables=1; - thd->options|= OPTION_TABLE_LOCK; - alloc_mdl_locks(all_tables, &thd->locked_tables_root); - thd->mdl_el_root= &thd->locked_tables_root; - if (!(res= open_and_lock_tables_derived(thd, all_tables, FALSE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL))) + alloc_mdl_locks(all_tables, thd->locked_tables_list.locked_tables_root()); + + thd->options|= OPTION_TABLE_LOCK; + thd->in_lock_tables=1; + thd->locked_tables_root= thd->locked_tables_list.locked_tables_root(); + + res= (open_and_lock_tables_derived(thd, all_tables, FALSE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || + thd->locked_tables_list.init_locked_tables(thd)); + + thd->in_lock_tables= 0; + thd->locked_tables_root= NULL; + + if (res) { -#ifdef HAVE_QUERY_CACHE - if (thd->variables.query_cache_wlock_invalidate) - query_cache.invalidate_locked_for_write(first_table); -#endif /*HAVE_QUERY_CACHE*/ - thd->locked_tables_mode= LTM_LOCK_TABLES; - my_ok(thd); - } - else - { - /* + /* Need to end the current transaction, so the storage engine (InnoDB) can free its locks if LOCK TABLES locked some tables before finding that it can't lock a table in its list @@ -3630,8 +3629,14 @@ end_with_restore_list: end_active_trans(thd); thd->options&= ~(OPTION_TABLE_LOCK); } - thd->in_lock_tables=0; - thd->mdl_el_root= 0; + else + { +#ifdef HAVE_QUERY_CACHE + if (thd->variables.query_cache_wlock_invalidate) + query_cache.invalidate_locked_for_write(first_table); +#endif /*HAVE_QUERY_CACHE*/ + my_ok(thd); + } break; case SQLCOM_CREATE_DB: { @@ -6534,8 +6539,8 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); ptr->mdl_lock_data= mdl_alloc_lock(0 , ptr->db, ptr->table_name, - thd->mdl_el_root ? thd->mdl_el_root : - thd->mem_root); + thd->locked_tables_root ? + thd->locked_tables_root : thd->mem_root); DBUG_RETURN(ptr); } diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 75ab9a73a5b..f4525db7def 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -4314,7 +4314,6 @@ set_engine_all_partitions(partition_info *part_info, static int fast_end_partition(THD *thd, ulonglong copied, ulonglong deleted, - TABLE *table, TABLE_LIST *table_list, bool is_empty, ALTER_PARTITION_PARAM_TYPE *lpt, bool written_bin_log) @@ -4333,11 +4332,7 @@ static int fast_end_partition(THD *thd, ulonglong copied, error= 1; if (error) - { - /* If error during commit, no need to rollback, it's done. */ - table->file->print_error(error, MYF(0)); - DBUG_RETURN(TRUE); - } + DBUG_RETURN(TRUE); /* The error has been reported */ if ((!is_empty) && (!written_bin_log) && (!thd->lex->no_write_to_binlog)) @@ -6215,30 +6210,13 @@ static void release_log_entries(partition_info *part_info) */ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) { - int err; - if (lpt->thd->locked_tables_mode) - { - /* - When we have the table locked, it is necessary to reopen the table - since all table objects were closed and removed as part of the - ALTER TABLE of partitioning structure. - */ - pthread_mutex_lock(&LOCK_open); - lpt->thd->in_lock_tables= 1; - err= reopen_tables(lpt->thd, 1); - lpt->thd->in_lock_tables= 0; - if (err) - { - /* - Issue a warning since we weren't able to regain the lock again. - We also need to unlink table from thread's open list and from - table_cache - */ - unlink_open_table(lpt->thd, lpt->table, FALSE); - sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE"); - } - pthread_mutex_unlock(&LOCK_open); - } + THD *thd= lpt->thd; + + close_all_tables_for_name(thd, lpt->table->s, FALSE); + lpt->table= 0; + lpt->table_list->table= 0; + if (thd->locked_tables_list.reopen_tables(thd)) + sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE"); } /* @@ -6252,17 +6230,37 @@ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) { + TABLE_SHARE *share= lpt->table->s; THD *thd= lpt->thd; - const char *db= lpt->db; - const char *table_name= lpt->table_name; + TABLE *table; DBUG_ENTER("alter_close_tables"); /* - We need to also unlock tables and close all handlers. - We set lock to zero to ensure we don't do this twice - and we set db_stat to zero to ensure we don't close twice. + We must keep LOCK_open while manipulating with thd->open_tables. + Another thread may be working on it. */ pthread_mutex_lock(&LOCK_open); - close_data_files_and_leave_as_placeholders(thd, db, table_name); + /* + We can safely remove locks for all tables with the same name: + later they will all be closed anyway in + alter_partition_lock_handling(). + */ + for (table= thd->open_tables; table ; table= table->next) + { + if (!strcmp(table->s->table_name.str, share->table_name.str) && + !strcmp(table->s->db.str, share->db.str)) + { + mysql_lock_remove(thd, thd->lock, table); + table->file->close(); + table->db_stat= 0; // Mark file closed + /* + Ensure that we won't end up with a crippled table instance + in the table cache if an error occurs before we reach + alter_partition_lock_handling() and the table is closed + by close_thread_tables() instead. + */ + table->s->version= 0; + } + } pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } @@ -6429,6 +6427,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, DBUG_ENTER("fast_alter_partition_table"); lpt->thd= thd; + lpt->table_list= table_list; lpt->part_info= part_info; lpt->alter_info= alter_info; lpt->create_info= create_info; @@ -6745,7 +6744,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, user */ DBUG_RETURN(fast_end_partition(thd, lpt->copied, lpt->deleted, - table, table_list, FALSE, NULL, + table_list, FALSE, NULL, written_bin_log)); err: close_thread_tables(thd); diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index 5251a50cab9..6d25b0c2b59 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -224,7 +224,8 @@ bool servers_reload(THD *thd) bool return_val= TRUE; DBUG_ENTER("servers_reload"); - unlock_locked_tables(thd); // Can't have locked tables here + /* Can't have locked tables here */ + thd->locked_tables_list.unlock_locked_tables(thd); DBUG_PRINT("info", ("locking servers_cache")); rw_wrlock(&THR_LOCK_servers); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 2c91ac69f9f..52596f417b2 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1966,7 +1966,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, case -1: DBUG_ASSERT(thd->in_sub_stmt); error= 1; - goto err_with_placeholders; + goto err; default: // temporary table not found error= 0; @@ -2003,18 +2003,19 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, { if (thd->locked_tables_mode) { - if (close_cached_table(thd, table->table)) + if (wait_while_table_is_used(thd, table->table, HA_EXTRA_FORCE_REOPEN)) { error= -1; - goto err_with_placeholders; + goto err; } + close_all_tables_for_name(thd, table->table->s, TRUE); table->table= 0; } if (thd->killed) { error= -1; - goto err_with_placeholders; + goto err; } alias= (lower_case_table_names == 2) ? table->alias : table->table_name; /* remove .frm file and engine files */ @@ -2178,7 +2179,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ } } -err_with_placeholders: +err: if (!drop_temporary) { /* @@ -2195,7 +2196,7 @@ err_with_placeholders: if (thd->locked_tables_mode && thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0) { - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); goto end; } for (table= tables; table; table= table->next_local) @@ -4315,6 +4316,7 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, HA_CHECK_OPT *check_opt) { MDL_LOCK_DATA *mdl_lock_data= 0; + enum enum_open_table_action ot_action_unused; DBUG_ENTER("prepare_for_restore"); if (table->table) // do not overwrite existing tables on restore @@ -4360,16 +4362,16 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, DBUG_RETURN(send_check_errmsg(thd, table, "restore", "Failed generating table from .frm file")); } + table->mdl_lock_data= mdl_lock_data; } /* Now we should be able to open the partially restored table to finish the restore in the handler later on */ - pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table)) + if (open_table(thd, table, thd->mem_root, + &ot_action_unused, MYSQL_OPEN_REOPEN)) { - pthread_mutex_unlock(&LOCK_open); if (mdl_lock_data) mdl_release_lock(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(send_check_errmsg(thd, table, "restore", @@ -4377,7 +4379,6 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, } /* A MERGE table must not come here. */ DBUG_ASSERT(!table->table || !table->table->child_l); - pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } @@ -4392,7 +4393,10 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, const char **ext; MY_STAT stat_info; MDL_LOCK_DATA *mdl_lock_data; + enum enum_open_table_action ot_action_unused; DBUG_ENTER("prepare_for_repair"); + uint reopen_for_repair_flags= (MYSQL_LOCK_IGNORE_FLUSH | + MYSQL_OPEN_HAS_MDL_LOCK); if (!(check_opt->sql_flags & TT_USEFRM)) DBUG_RETURN(0); @@ -4428,8 +4432,9 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); // Out of memory } - table= &tmp_table; pthread_mutex_unlock(&LOCK_open); + table= &tmp_table; + table_list->mdl_lock_data= mdl_lock_data; } else { @@ -4490,8 +4495,9 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, Table was successfully open in mysql_admin_table(). Now we need to close it, but leave it protected by exclusive metadata lock. */ - if (close_cached_table(thd, table)) + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto end; + close_all_tables_for_name(thd, table_list->table->s, FALSE); table_list->table= 0; } /* @@ -4519,21 +4525,23 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, goto end; } + if (thd->locked_tables_list.reopen_tables(thd)) + goto end; + /* Now we should be able to open the partially repaired table to finish the repair in the handler later on. */ - pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table_list)) + if (open_table(thd, table_list, thd->mem_root, + &ot_action_unused, reopen_for_repair_flags)) { - pthread_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed to open partially repaired table"); goto end; } - pthread_mutex_unlock(&LOCK_open); end: + thd->locked_tables_list.unlink_all_closed_tables(); if (table == &tmp_table) { pthread_mutex_lock(&LOCK_open); @@ -5334,6 +5342,11 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, db, table_name, reg_ext, 0); if (!access(dst_path, F_OK)) goto table_exists; + /* + Make the metadata lock available to open_table() called to + reopen the table down the road. + */ + table->mdl_lock_data= target_lock_data; } DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000);); @@ -5463,20 +5476,16 @@ binlog: char buf[2048]; String query(buf, sizeof(buf), system_charset_info); query.length(0); // Have to zero it since constructor doesn't - + enum enum_open_table_action ot_action_unused; /* Here we open the destination table, on which we already have - exclusive metada lock. This is needed for store_create_info() - to work. The table will be closed by unlink_open_table() at - the end of this function. + exclusive metadata lock. This is needed for store_create_info() + to work. The table will be closed by close_thread_table() at + the end of this branch. */ - pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table)) - { - pthread_mutex_unlock(&LOCK_open); + if (open_table(thd, table, thd->mem_root, &ot_action_unused, + MYSQL_OPEN_REOPEN)) goto err; - } - pthread_mutex_unlock(&LOCK_open); int result __attribute__((unused))= store_create_info(thd, table, &query, @@ -5485,8 +5494,14 @@ binlog: DBUG_ASSERT(result == 0); // store_create_info() always return 0 write_bin_log(thd, TRUE, query.ptr(), query.length()); + DBUG_ASSERT(thd->open_tables == table->table); pthread_mutex_lock(&LOCK_open); - unlink_open_table(thd, table->table, FALSE); + /* + When opening the table, we ignored the locked tables + (MYSQL_OPEN_GET_NEW_TABLE). Now we can close the table without + risking to close some locked table. + */ + close_thread_table(thd, &thd->open_tables); pthread_mutex_unlock(&LOCK_open); } else // Case 1 @@ -6789,13 +6804,14 @@ view_err: /* Then do a 'simple' rename of the table. First we need to close all instances of 'source' table. - Note that if close_cached_table() returns error here (i.e. if + Note that if wait_while_table_is_used() returns error here (i.e. if this thread was killed) then it must be that previous step of - simple rename did nothing and therefore we can safely reture + simple rename did nothing and therefore we can safely return without additional clean-up. */ - if (close_cached_table(thd, table)) + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto err; + close_all_tables_for_name(thd, table->s, TRUE); /* Then, we want check once again that target table does not exist. Actually the order of these two steps does not matter since @@ -7384,11 +7400,12 @@ view_err: if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME)) goto err_new_table_cleanup; - pthread_mutex_lock(&LOCK_open); - close_data_files_and_leave_as_placeholders(thd, db, table_name); + close_all_tables_for_name(thd, table->s, + new_name != table_name || new_db != db); error=0; + table_list->table= table= 0; /* Safety */ save_old_db_type= old_db_type; /* @@ -7410,6 +7427,7 @@ view_err: /* This type cannot happen in regular ALTER. */ new_db_type= old_db_type= NULL; } + pthread_mutex_lock(&LOCK_open); if (mysql_rename_table(old_db_type, db, table_name, db, old_name, FN_TO_IS_TMP)) { @@ -7433,10 +7451,15 @@ view_err: FN_FROM_IS_TMP); } + if (! error) + (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); + + pthread_mutex_unlock(&LOCK_open); + if (error) { /* This shouldn't happen. But let us play it safe. */ - goto err_with_placeholders; + goto err_with_mdl; } if (need_copy_table == ALTER_TABLE_METADATA_ONLY) @@ -7446,6 +7469,7 @@ view_err: To do this we need to obtain a handler object for it. NO need to tamper with MERGE tables. The real open is done later. */ + enum enum_open_table_action ot_action_unused; TABLE *t_table; if (new_name != table_name || new_db != db) { @@ -7454,51 +7478,39 @@ view_err: table_list->table_name_length= strlen(new_name); table_list->db= new_db; table_list->db_length= strlen(new_db); - if (reopen_name_locked_table(thd, table_list)) - goto err_with_placeholders; - t_table= table_list->table; + table_list->mdl_lock_data= target_lock_data; } else { - if (reopen_table(table)) - goto err_with_placeholders; - t_table= table; + /* + Under LOCK TABLES, we have a different mdl_lock_data + points to a different instance than the one set initially + to request the lock. + */ + table_list->mdl_lock_data= mdl_lock_data; } - /* Tell the handler that a new frm file is in place. */ - if (t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG, - create_info)) - goto err_with_placeholders; - if (thd->locked_tables_mode) + if (open_table(thd, table_list, thd->mem_root, + &ot_action_unused, MYSQL_OPEN_REOPEN)) { - if (new_name == table_name && new_db == db) - { - /* - We are going to reopen table down on the road, so we have to restore - state of the TABLE object which we used for obtaining of handler - object to make it suitable for reopening. - */ - DBUG_ASSERT(t_table == table); - close_handle_and_leave_table_as_placeholder(table); - } - else - { - /* Unlink the new name from the list of locked tables. */ - unlink_open_table(thd, t_table, FALSE); - } + goto err_with_mdl; } - } + t_table= table_list->table; - (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); + /* Tell the handler that a new frm file is in place. */ + error= t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG, + create_info); + + DBUG_ASSERT(thd->open_tables == t_table); + pthread_mutex_lock(&LOCK_open); + close_thread_table(thd, &thd->open_tables); + pthread_mutex_unlock(&LOCK_open); + table_list->table= 0; - if (thd->locked_tables_mode && new_name == table_name && new_db == db) - { - thd->in_lock_tables= 1; - error= reopen_tables(thd, 1); - thd->in_lock_tables= 0; if (error) - goto err_with_placeholders; + goto err_with_mdl; } - pthread_mutex_unlock(&LOCK_open); + if (thd->locked_tables_list.reopen_tables(thd)) + goto err_with_mdl; thd_proc_info(thd, "end"); @@ -7541,9 +7553,6 @@ view_err: { if ((new_name != table_name || new_db != db)) { - pthread_mutex_lock(&LOCK_open); - unlink_open_table(thd, table, FALSE); - pthread_mutex_unlock(&LOCK_open); mdl_release_lock(&thd->mdl_context, target_lock_data); mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); } @@ -7607,14 +7616,14 @@ err: mdl_release_lock(&thd->mdl_context, target_lock_data); DBUG_RETURN(TRUE); -err_with_placeholders: +err_with_mdl: /* An error happened while we were holding exclusive name metadata lock - on table being altered. To be safe under LOCK TABLES we should remove - placeholders from the list of open tables and relese metadata lock. + on table being altered. To be safe under LOCK TABLES we should + remove all references to the altered table from the list of locked + tables and release the exclusive metadata lock. */ - unlink_open_table(thd, table, FALSE); - pthread_mutex_unlock(&LOCK_open); + thd->locked_tables_list.unlink_all_closed_tables(); if (target_lock_data) mdl_release_lock(&thd->mdl_context, target_lock_data); mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index c8f01a56a72..9a42dd189e7 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -328,6 +328,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) bool result= TRUE; String stmt_query; bool need_start_waiting= FALSE; + bool lock_upgrade_done= FALSE; DBUG_ENTER("mysql_create_or_drop_trigger"); @@ -450,72 +451,54 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) if (!(tables->table= find_write_locked_table(thd->open_tables, tables->db, tables->table_name))) goto end; - /* - Ensure that table is opened only by this thread and that no other - statement will open this table. - */ - if (wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN)) - goto end; - - pthread_mutex_lock(&LOCK_open); + /* Later on we will need it to downgrade the lock */ + tables->mdl_lock_data= tables->table->mdl_lock_data; } else { - /* - Obtain exlusive meta-data lock on the table and remove TABLE - instances from cache. - */ - if (lock_table_names(thd, tables)) + tables->table= open_n_lock_single_table(thd, tables, + TL_WRITE_ALLOW_READ, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL); + if (! tables->table) goto end; - - pthread_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, tables->db, tables->table_name); - - if (reopen_name_locked_table(thd, tables)) - goto end_unlock; + tables->table->use_all_columns(); } table= tables->table; + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto end; + + lock_upgrade_done= TRUE; + if (!table->triggers) { if (!create) { my_error(ER_TRG_DOES_NOT_EXIST, MYF(0)); - goto end_unlock; + goto end; } if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table))) - goto end_unlock; + goto end; } + pthread_mutex_lock(&LOCK_open); result= (create ? table->triggers->create_trigger(thd, tables, &stmt_query): table->triggers->drop_trigger(thd, tables, &stmt_query)); - - /* Under LOCK TABLES we must reopen the table to activate the trigger. */ - if (!result && thd->locked_tables_mode) - { - /* Make table suitable for reopening */ - close_data_files_and_leave_as_placeholders(thd, tables->db, - tables->table_name); - thd->in_lock_tables= 1; - if (reopen_tables(thd, 1)) - { - /* To be safe remove this table from the set of LOCKED TABLES */ - unlink_open_table(thd, tables->table, FALSE); - - /* - Ignore reopen_tables errors for now. It's better not leave master/slave - in a inconsistent state. - */ - thd->clear_error(); - } - thd->in_lock_tables= 0; - } - -end_unlock: pthread_mutex_unlock(&LOCK_open); + if (result) + goto end; + + close_all_tables_for_name(thd, table->s, FALSE); + /* + Reopen the table if we were under LOCK TABLES. + Ignore the return value for now. It's better to + keep master/slave in consistent state. + */ + thd->locked_tables_list.reopen_tables(thd); + end: if (!result) { @@ -525,11 +508,11 @@ end: /* If we are under LOCK TABLES we should restore original state of meta-data locks. Otherwise call to close_thread_tables() will take care about both - TABLE instance created by reopen_name_locked_table() and metadata lock. + TABLE instance created by open_n_lock_single_table() and metadata lock. */ - if (thd->locked_tables_mode && tables && tables->table) + if (thd->locked_tables_mode && tables && lock_upgrade_done) mdl_downgrade_exclusive_lock(&thd->mdl_context, - tables->table->mdl_lock_data); + tables->mdl_lock_data); if (need_start_waiting) start_waiting_global_read_lock(thd); diff --git a/sql/table.h b/sql/table.h index 48caf962894..432523e5db2 100644 --- a/sql/table.h +++ b/sql/table.h @@ -632,7 +632,6 @@ private: TABLE *share_next, **share_prev; friend struct TABLE_share; - friend bool reopen_table(TABLE *table); public: @@ -679,6 +678,8 @@ public: /* Table's triggers, 0 if there are no of them */ Table_triggers_list *triggers; TABLE_LIST *pos_in_table_list;/* Element referring to this table */ + /* Position in thd->locked_table_list under LOCK TABLES */ + TABLE_LIST *pos_in_locked_tables; ORDER *group; const char *alias; /* alias or table name */ uchar *null_flags; From 7585067a47d2c745c6a8ddc2a06fdbbd4611edec Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 2 Dec 2009 18:33:51 +0300 Subject: [PATCH 044/212] Backport of: ---------------------------------------------------------- revno: 2630.4.30 Konstantin Osipov 2008-06-11 Fix a potential cause of test failures. sql/sql_base.cc: 0 may not be equal to (char*) 0 on 64 bit pointer/32 bit int machines. --- sql/sql_base.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 61c071d3430..b1d2ec41367 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2934,7 +2934,7 @@ Locked_tables_list::init_locked_tables(THD *thd) &db, db_len, &table_name, table_name_len, &alias, alias_len, - 0)) + NullS)) { unlock_locked_tables(0); return TRUE; From b7e8b0164446432df2673b9bc628ac74c1b30b76 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 2 Dec 2009 18:37:10 +0300 Subject: [PATCH 045/212] Backport of: ---------------------------------------------------------- revno: 2630.4.31 committer: Konstantin Osipov branch nick: mysql-6.0-3726 timestamp: Thu 2008-06-12 06:23:08 +0400 message: Extend the signature of TABLE_LIST::init_one_table() to initialize lengths This is part of WL#3726 post-review fixes. --- sql/event_db_repository.cc | 8 ++++---- sql/sql_base.cc | 21 +++++++++++---------- sql/table.h | 4 ++++ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index b17785a6be7..e9ee54ff8aa 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -554,7 +554,7 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE_LIST tables; DBUG_ENTER("Event_db_repository::open_event_table"); - tables.init_one_table("mysql", "event", "event", lock_type); + tables.init_one_table("mysql", 5, "event", 5, "event", lock_type); alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) @@ -1109,7 +1109,7 @@ Event_db_repository::check_system_tables(THD *thd) /* Check mysql.db */ - tables.init_one_table("mysql", "db", "db", TL_READ); + tables.init_one_table("mysql", 5, "db", 2, "db", TL_READ); alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) @@ -1127,7 +1127,7 @@ Event_db_repository::check_system_tables(THD *thd) close_thread_tables(thd); } /* Check mysql.user */ - tables.init_one_table("mysql", "user", "user", TL_READ); + tables.init_one_table("mysql", 5, "user", 4, "user", TL_READ); alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) @@ -1148,7 +1148,7 @@ Event_db_repository::check_system_tables(THD *thd) close_thread_tables(thd); } /* Check mysql.event */ - tables.init_one_table("mysql", "event", "event", TL_READ); + tables.init_one_table("mysql", 5, "event", 5, "event", TL_READ); alloc_mdl_locks(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index b1d2ec41367..5227c5e9c72 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2924,16 +2924,16 @@ Locked_tables_list::init_locked_tables(THD *thd) { TABLE_LIST *src_table_list= table->pos_in_table_list; char *db, *table_name, *alias; - size_t db_len= strlen(src_table_list->db) + 1; - size_t table_name_len= strlen(src_table_list->table_name) + 1; - size_t alias_len= strlen(src_table_list->alias) + 1; + size_t db_len= src_table_list->db_length; + size_t table_name_len= src_table_list->table_name_length; + size_t alias_len= strlen(src_table_list->alias); TABLE_LIST *dst_table_list; if (! multi_alloc_root(&m_locked_tables_root, &dst_table_list, sizeof(*dst_table_list), - &db, db_len, - &table_name, table_name_len, - &alias, alias_len, + &db, db_len + 1, + &table_name, table_name_len + 1, + &alias, alias_len + 1, NullS)) { unlock_locked_tables(0); @@ -2947,13 +2947,14 @@ Locked_tables_list::init_locked_tables(THD *thd) TL_WRITE_DEFAULT, whereas reginfo.lock_type has been updated from thd->update_lock_default. */ - dst_table_list->init_one_table(db, table_name, alias, + dst_table_list->init_one_table(db, db_len, table_name, table_name_len, + alias, src_table_list->table->reginfo.lock_type); dst_table_list->mdl_lock_data= src_table_list->mdl_lock_data; dst_table_list->table= table; - memcpy(db, src_table_list->db, db_len); - memcpy(table_name, src_table_list->table_name, table_name_len); - memcpy(alias, src_table_list->alias, alias_len); + memcpy(db, src_table_list->db, db_len + 1); + memcpy(table_name, src_table_list->table_name, table_name_len + 1); + memcpy(alias, src_table_list->alias, alias_len + 1); /* Link last into the list of tables */ *(dst_table_list->prev_global= m_locked_tables_last)= dst_table_list; m_locked_tables_last= &dst_table_list->next_global; diff --git a/sql/table.h b/sql/table.h index 432523e5db2..c30b6e7ad80 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1120,13 +1120,17 @@ struct TABLE_LIST simple_open_and_lock_tables */ inline void init_one_table(const char *db_name_arg, + size_t db_length_arg, const char *table_name_arg, + size_t table_name_length_arg, const char *alias_arg, enum thr_lock_type lock_type_arg) { bzero((char*) this, sizeof(*this)); db= (char*) db_name_arg; + db_length= db_length_arg; table_name= (char*) table_name_arg; + table_name_length= table_name_length_arg; alias= (char*) alias_arg; lock_type= lock_type_arg; } From e3b3907c4f6f6dbfd650272e7c65880d04e5789e Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 2 Dec 2009 19:15:40 +0300 Subject: [PATCH 046/212] Backport of: ------------------------------------------------------------ revno: 2630.4.32 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Thu 2008-06-19 16:39:58 +0400 message: WL#3726 "DDL locking for all metadata objects". After-review fixes in progress. Ensure that metadata locking subsystem properly handles out-of-memory conditions. Clarified MDL interface by separating release of locks and removal of lock requests from the context. sql/lock.cc: mdl_release_lock(), mdl_acquire_exclusive_locks() and mdl_try_acquire_exclusive_lock() are no longer responsible for removal of metadata lock requests from the context. One should explicitly call mdl_remove_all_locks() and mdl_remove_lock() to do this. sql/mdl.cc: Ensured that metadata locking subsystem properly handles out-of-memory conditions. Introduced new MDL_INITIALIZED state for metadata lock request which is used in all cases when lock is not acquired and we have not associated request with object respesenting lock. MDL_PENDING is now only used for requests for exclusive locks which are added to the MDL_LOCK::waiting_exclusive queue. mdl_release_lock(), mdl_acquire_exclusive_locks() and mdl_try_acquire_exclusive_lock() are no longer responsible for removal of metadata lock requests from the context. One should explicitly call mdl_remove_all_locks() and newly introduced mdl_remove_lock() to do this. Also renamed mdl_release_all_locks_for_name() to emphasize that it also actually removes lock requests from the context. Finally mdl_try_acquire_exclusive_lock() is now returs information about encountered lock conflict in separate out parameter since its return value is used for distinguishing between error (e.g. due to OOM) and success. sql/mdl.h: Introduced new MDL_INITIALIZED state for metadata lock request which is used in all cases when lock is not acquired and we have not associated request with object respesenting lock. MDL_PENDING is now only used for requests for exclusive locks which are added to the MDL_LOCK::waiting_exclusive queue. mdl_release_lock(), mdl_acquire_exclusive_locks() and mdl_try_acquire_exclusive_lock() are no longer responsible for removal of metadata lock requests from the context. One should explicitly call mdl_remove_all_locks() and newly introduced mdl_remove_lock() to do this. Also renamed mdl_release_all_locks_for_name() to emphasize that it also actually removes lock requests from the context. Finally mdl_try_acquire_exclusive_lock() is now returs information about encountered lock conflict in separate out parameter since its return value is used for distinguishing between error (e.g. due to OOM) and success. sql/sql_base.cc: mdl_release_lock(), mdl_acquire_exclusive_locks() and mdl_try_acquire_exclusive_lock() are no longer responsible for removal of metadata lock requests from the context. One should explicitly call mdl_remove_all_locks() and mdl_remove_lock() to do this. Also adjusted open_table() to ensure that it releases/removes metadata locks in case of error after adding/acquiring them (unless keeping these lock requests is required for recovering action). sql/sql_delete.cc: mdl_release_lock(), mdl_acquire_exclusive_locks() and mdl_try_acquire_exclusive_lock() are no longer responsible for removal of metadata lock requests from the context. One should explicitly call mdl_remove_all_locks() and mdl_remove_lock() to do this. sql/sql_handler.cc: mdl_release_lock(), mdl_acquire_exclusive_locks() and mdl_try_acquire_exclusive_lock() are no longer responsible for removal of metadata lock requests from the context. One should explicitly call mdl_remove_all_locks() and mdl_remove_lock() to do this. sql/sql_show.cc: mdl_release_lock(), mdl_acquire_exclusive_locks() and mdl_try_acquire_exclusive_lock() are no longer responsible for removal of metadata lock requests from the context. One should explicitly call mdl_remove_all_locks() and mdl_remove_lock() to do this. sql/sql_table.cc: Renamed mdl_release_all_locks_for_name() to emphasize that it also actually removes lock requests from the context. mdl_release_lock(), mdl_acquire_exclusive_locks() and mdl_try_acquire_exclusive_lock() are no longer responsible for removal of metadata lock requests from the context. One should explicitly call mdl_remove_all_locks() and mdl_remove_lock() to do this. Finally mdl_try_acquire_exclusive_lock() is now returs information about encountered lock conflict in separate out parameter since its return value is used for distinguishing between error (e.g. due to OOM) and success. --- sql/lock.cc | 2 +- sql/mdl.cc | 183 ++++++++++++++++++++++++++++----------------- sql/mdl.h | 13 ++-- sql/sql_base.cc | 25 ++++++- sql/sql_delete.cc | 9 +++ sql/sql_handler.cc | 1 + sql/sql_show.cc | 1 + sql/sql_table.cc | 49 ++++++++++-- 8 files changed, 199 insertions(+), 84 deletions(-) diff --git a/sql/lock.cc b/sql/lock.cc index 814eebde337..199ab354c22 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -965,7 +965,7 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list) lock_table->mdl_lock_data= mdl_lock_data; } if (mdl_acquire_exclusive_locks(&thd->mdl_context)) - return 1; + goto end; return 0; end: diff --git a/sql/mdl.cc b/sql/mdl.cc index d3d067cfb9b..afe3f0eaa7b 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -257,7 +257,7 @@ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) Stores the database name, object name and the type in the key buffer. Initializes mdl_el to point to the key. - We can't simply initialize mdl_el with type, db and name + We can't simply initialize MDL_LOCK_DATA with type, db and name by-pointer because of the underlying HASH implementation requires the key to be a contiguous buffer. @@ -275,7 +275,7 @@ void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, lock_data->key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; lock_data->key= key; lock_data->type= MDL_SHARED; - lock_data->state= MDL_PENDING; + lock_data->state= MDL_INITIALIZED; #ifndef DBUG_OFF lock_data->ctx= 0; lock_data->lock= 0; @@ -336,7 +336,7 @@ MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name, void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) { DBUG_ENTER("mdl_add_lock"); - DBUG_ASSERT(lock_data->state == MDL_PENDING); + DBUG_ASSERT(lock_data->state == MDL_INITIALIZED); DBUG_ASSERT(!lock_data->ctx); lock_data->ctx= context; context->locks.push_front(lock_data); @@ -344,6 +344,36 @@ void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) } +/** + Remove a lock request from the list of lock requests of the context. + + Disassociates a lock request from the given context. + + @param context The MDL context to remove the lock from. + @param lock_data The lock request to be removed. + + @pre The lock request being removed should correspond to lock which + was released or was not acquired. + + @note Resets lock request for lock released back to its initial state + (i.e. sets type to MDL_SHARED). +*/ + +void mdl_remove_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) +{ + DBUG_ENTER("mdl_remove_lock"); + DBUG_ASSERT(lock_data->state == MDL_INITIALIZED); + DBUG_ASSERT(context == lock_data->ctx); + /* Reset lock request back to its initial state. */ + lock_data->type= MDL_SHARED; +#ifndef DBUG_OFF + lock_data->ctx= 0; +#endif + context->locks.remove(lock_data); + DBUG_VOID_RETURN; +} + + /** Clear all lock requests in the context (clear the context). @@ -629,7 +659,7 @@ bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data, MDL_LOCK *lock; *retry= FALSE; - DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_PENDING); + DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_INITIALIZED); DBUG_ASSERT(lock_data->ctx == context); @@ -654,7 +684,11 @@ bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data, if (!(lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, lock_data->key_length))) { - lock= get_lock_object(); + if (!(lock= get_lock_object())) + { + pthread_mutex_unlock(&LOCK_mdl); + return TRUE; + } /* Before inserting MDL_LOCK object into hash we should add at least one MDL_LOCK_DATA to its lists in order to provide key for this element. @@ -662,7 +696,12 @@ bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data, */ lock->active_shared.push_front(lock_data); lock->lock_data_count= 1; - my_hash_insert(&mdl_locks, (uchar*)lock); + if (my_hash_insert(&mdl_locks, (uchar*)lock)) + { + release_lock_object(lock); + pthread_mutex_unlock(&LOCK_mdl); + return TRUE; + } lock_data->state= MDL_ACQUIRED; lock_data->lock= lock; if (lock_data->type == MDL_SHARED_UPGRADABLE) @@ -702,9 +741,6 @@ static void release_lock(MDL_LOCK_DATA *lock_data); @param context A context containing requests for exclusive locks The context may not have other lock requests. - @note In case of failure (for example, if our thread was killed) - resets lock requests back to their initial state (MDL_SHARED) - @retval FALSE Success @retval TRUE Failure */ @@ -735,25 +771,32 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) while ((lock_data= it++)) { DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE && - lock_data->state == MDL_PENDING); + lock_data->state == MDL_INITIALIZED); if (!(lock= (MDL_LOCK *) my_hash_search(&mdl_locks, (uchar*)lock_data->key, lock_data->key_length))) { - lock= get_lock_object(); + if (!(lock= get_lock_object())) + goto err; /* Again before inserting MDL_LOCK into hash provide key for it by adding MDL_LOCK_DATA to one of its lists. */ lock->waiting_exclusive.push_front(lock_data); lock->lock_data_count= 1; - my_hash_insert(&mdl_locks, (uchar*)lock); + if (my_hash_insert(&mdl_locks, (uchar*)lock)) + { + release_lock_object(lock); + goto err; + } lock_data->lock= lock; + lock_data->state= MDL_PENDING; } else { lock->waiting_exclusive.push_front(lock_data); lock->lock_data_count++; lock_data->lock= lock; + lock_data->state= MDL_PENDING; } } @@ -806,23 +849,7 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime); } if (thd->killed) - { - /* Remove our pending lock requests from the locks. */ - it.rewind(); - while ((lock_data= it++)) - { - DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE && - lock_data->state == MDL_PENDING); - release_lock(lock_data); - /* Return lock request to its initial state. */ - lock_data->type= MDL_SHARED; - context->locks.remove(lock_data); - } - /* Pending requests for shared locks can be satisfied now. */ - pthread_cond_broadcast(&COND_mdl); - thd->exit_cond(old_msg); - return TRUE; - } + goto err; } it.rewind(); while ((lock_data= it++)) @@ -839,6 +866,22 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ thd->exit_cond(old_msg); return FALSE; + +err: + /* + Remove our pending lock requests from the locks. + Ignore those lock requests which were not made MDL_PENDING. + */ + it.rewind(); + while ((lock_data= it++) && lock_data->state == MDL_PENDING) + { + release_lock(lock_data); + lock_data->state= MDL_INITIALIZED; + } + /* May be some pending requests for shared locks can be satisfied now. */ + pthread_cond_broadcast(&COND_mdl); + thd->exit_cond(old_msg); + return TRUE; } @@ -976,50 +1019,56 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, @param context [in] The context containing the lock request @param lock [in] The lock request + @param conflict [out] Indicates that conflicting lock exists - @retval FALSE the lock was granted - @retval TRUE there were conflicting locks. + @retval TRUE Failure either conflicting lock exists or some error + occured (probably OOM). + @retval FALSE Success, lock was acquired. FIXME: Compared to lock_table_name_if_not_cached() it gives sligthly more false negatives. */ bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data) + MDL_LOCK_DATA *lock_data, + bool *conflict) { MDL_LOCK *lock; DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE && - lock_data->state == MDL_PENDING); + lock_data->state == MDL_INITIALIZED); safe_mutex_assert_not_owner(&LOCK_open); + *conflict= FALSE; + pthread_mutex_lock(&LOCK_mdl); if (!(lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, lock_data->key_length))) { - lock= get_lock_object(); + if (!(lock= get_lock_object())) + goto err; lock->active_exclusive.push_front(lock_data); lock->lock_data_count= 1; - my_hash_insert(&mdl_locks, (uchar*)lock); + if (my_hash_insert(&mdl_locks, (uchar*)lock)) + { + release_lock_object(lock); + goto err; + } lock_data->state= MDL_ACQUIRED; lock_data->lock= lock; - lock= 0; global_lock.active_intention_exclusive++; + pthread_mutex_unlock(&LOCK_mdl); + return FALSE; } + + /* There is some lock for the object. */ + *conflict= TRUE; + +err: pthread_mutex_unlock(&LOCK_mdl); - - /* - FIXME: We can't leave pending MDL_EXCLUSIVE lock request in the list since - for such locks we assume that they have MDL_LOCK_DATA::lock properly set. - Long term we should clearly define relation between lock types, - presence in the context lists and MDL_LOCK_DATA::lock values. - */ - if (lock) - context->locks.remove(lock_data); - - return lock; + return TRUE; } @@ -1111,11 +1160,12 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) it.rewind(); while ((lock_data= it++)) { - DBUG_ASSERT(lock_data->state == MDL_PENDING); + DBUG_ASSERT(lock_data->state == MDL_INITIALIZED); if (!can_grant_global_lock(lock_data)) break; /* - To avoid starvation we don't wait if we have pending MDL_EXCLUSIVE lock. + To avoid starvation we don't wait if we have a conflict against + request for MDL_EXCLUSIVE lock. */ if (is_shared(lock_data) && (lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, @@ -1149,6 +1199,9 @@ static void release_lock(MDL_LOCK_DATA *lock_data) DBUG_PRINT("enter", ("db=%s name=%s", lock_data->key + 4, lock_data->key + 4 + strlen(lock_data->key + 4) + 1)); + DBUG_ASSERT(lock_data->state == MDL_PENDING || + lock_data->state == MDL_ACQUIRED); + lock= lock_data->lock; if (lock->has_one_lock_data()) { @@ -1184,7 +1237,6 @@ static void release_lock(MDL_LOCK_DATA *lock_data) } break; default: - /* TODO Really? How about problems during lock upgrade ? */ DBUG_ASSERT(0); } lock->lock_data_count--; @@ -1224,10 +1276,10 @@ void mdl_release_locks(MDL_CONTEXT *context) lists. Allows us to avoid problems in open_tables() in case of back-off */ - if (!(is_shared(lock_data) && lock_data->state == MDL_PENDING)) + if (lock_data->state != MDL_INITIALIZED) { release_lock(lock_data); - lock_data->state= MDL_PENDING; + lock_data->state= MDL_INITIALIZED; #ifndef DBUG_OFF lock_data->lock= 0; #endif @@ -1247,13 +1299,10 @@ void mdl_release_locks(MDL_CONTEXT *context) /** Release a lock. - Removes the lock from the context. @param context Context containing lock in question @param lock_data Lock to be released - @note Resets lock request for lock released back to its initial state - (i.e. sets type to MDL_SHARED). */ void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) @@ -1263,13 +1312,9 @@ void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) pthread_mutex_lock(&LOCK_mdl); release_lock(lock_data); #ifndef DBUG_OFF - lock_data->ctx= 0; lock_data->lock= 0; #endif - lock_data->state= MDL_PENDING; - /* Return lock request to its initial state. */ - lock_data->type= MDL_SHARED; - context->locks.remove(lock_data); + lock_data->state= MDL_INITIALIZED; pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); } @@ -1277,17 +1322,15 @@ void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) /** Release all locks in the context which correspond to the same name/ - object as this lock request. + object as this lock request, remove lock requests from the context. @param context Context containing locks in question @param lock_data One of the locks for the name/object for which all locks should be released. - - @see mdl_release_lock() */ -void mdl_release_all_locks_for_name(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data) +void mdl_release_and_remove_all_locks_for_name(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data) { MDL_LOCK *lock; I_P_List_iterator it(context->locks); @@ -1306,7 +1349,10 @@ void mdl_release_all_locks_for_name(MDL_CONTEXT *context, { DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); if (lock_data->lock == lock) + { mdl_release_lock(context, lock_data); + mdl_remove_lock(context, lock_data); + } } } @@ -1418,9 +1464,10 @@ bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, int4store(key, type); key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; - while ((lock_data= it++) && (lock_data->key_length != key_length || - memcmp(lock_data->key, key, key_length) || - lock_data->state == MDL_PENDING)) + while ((lock_data= it++) && + (lock_data->key_length != key_length || + memcmp(lock_data->key, key, key_length) || + lock_data->state != MDL_ACQUIRED)) continue; return lock_data; diff --git a/sql/mdl.h b/sql/mdl.h index b192980ebaa..92bd83038e5 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -44,7 +44,8 @@ enum enum_mdl_type {MDL_SHARED=0, MDL_SHARED_HIGH_PRIO, /** States which metadata lock request can have. */ -enum enum_mdl_state {MDL_PENDING=0, MDL_ACQUIRED, MDL_PENDING_UPGRADE}; +enum enum_mdl_state {MDL_INITIALIZED=0, MDL_PENDING, + MDL_ACQUIRED, MDL_PENDING_UPGRADE}; /** @@ -152,6 +153,7 @@ void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name, MEM_ROOT *root); void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); +void mdl_remove_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); void mdl_remove_all_locks(MDL_CONTEXT *context); /** @@ -160,7 +162,7 @@ void mdl_remove_all_locks(MDL_CONTEXT *context); inline void mdl_set_lock_type(MDL_LOCK_DATA *lock_data, enum_mdl_type lock_type) { - DBUG_ASSERT(lock_data->state == MDL_PENDING); + DBUG_ASSERT(lock_data->state == MDL_INITIALIZED); lock_data->type= lock_type; } @@ -170,14 +172,15 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context); bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data); + MDL_LOCK_DATA *lock_data, + bool *conflict); bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context); bool mdl_wait_for_locks(MDL_CONTEXT *context); void mdl_release_locks(MDL_CONTEXT *context); -void mdl_release_all_locks_for_name(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data); +void mdl_release_and_remove_all_locks_for_name(MDL_CONTEXT *context, + MDL_LOCK_DATA *lock_data); void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 5227c5e9c72..6c344ad1d4e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2278,9 +2278,9 @@ void table_share_release_hook(void *share) } -/* - A helper function that acquires an MDL lock for a table - being opened. +/** + A helper function that acquires an MDL lock for a table + being opened. */ static bool @@ -2304,7 +2304,10 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, */ mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + { + mdl_remove_lock(&thd->mdl_context, mdl_lock_data); return 1; + } } else { @@ -2327,6 +2330,8 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, { if (retry) *action= OT_BACK_OFF_AND_RETRY; + else + mdl_remove_lock(&thd->mdl_context, mdl_lock_data); return 1; } } @@ -2833,7 +2838,11 @@ err_unlock: release_table_share(share); err_unlock2: pthread_mutex_unlock(&LOCK_open); - mdl_release_lock(&thd->mdl_context, mdl_lock_data); + if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) + { + mdl_release_lock(&thd->mdl_context, mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + } DBUG_RETURN(TRUE); } @@ -3497,7 +3506,10 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, mdl_set_lock_type(table->mdl_lock_data, MDL_EXCLUSIVE); mdl_add_lock(&thd->mdl_context, table->mdl_lock_data); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + { + mdl_remove_lock(&thd->mdl_context, table->mdl_lock_data); return TRUE; + } pthread_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); ha_create_table_from_engine(thd, table->db, table->table_name); @@ -3506,18 +3518,23 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, thd->warning_info->clear_warning_info(thd->query_id); thd->clear_error(); // Clear error message mdl_release_lock(&thd->mdl_context, table->mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, table->mdl_lock_data); break; case OT_REPAIR: mdl_set_lock_type(table->mdl_lock_data, MDL_EXCLUSIVE); mdl_add_lock(&thd->mdl_context, table->mdl_lock_data); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + { + mdl_remove_lock(&thd->mdl_context, table->mdl_lock_data); return TRUE; + } pthread_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); pthread_mutex_unlock(&LOCK_open); result= auto_repair_table(thd, table); mdl_release_lock(&thd->mdl_context, table->mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, table->mdl_lock_data); break; default: DBUG_ASSERT(0); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 630cf73076c..4827d48e1c5 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1170,7 +1170,10 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); mdl_add_lock(&thd->mdl_context, mdl_lock_data); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + { + mdl_remove_lock(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(TRUE); + } pthread_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, table_list->table_name); @@ -1200,12 +1203,18 @@ end: my_ok(thd); // This should return record count } if (mdl_lock_data) + { mdl_release_lock(&thd->mdl_context, mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + } } else if (error) { if (mdl_lock_data) + { mdl_release_lock(&thd->mdl_context, mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + } } DBUG_RETURN(error); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index a2c1f0e3782..9b30d8cec12 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -150,6 +150,7 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) } pthread_mutex_unlock(&LOCK_open); mdl_release_lock(&thd->handler_mdl_context, mdl_lock_data); + mdl_remove_lock(&thd->handler_mdl_context, mdl_lock_data); } else if (tables->table) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index f608e233349..c50d74412bf 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3250,6 +3250,7 @@ err_unlock: err: mdl_release_lock(&thd->mdl_context, &mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, &mdl_lock_data); thd->clear_error(); return res; } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 52596f417b2..f6eafaacf61 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -2208,7 +2208,8 @@ err: and locked and therefore have to remove several metadata lock requests associated with them. */ - mdl_release_all_locks_for_name(&thd->mdl_context, table->mdl_lock_data); + mdl_release_and_remove_all_locks_for_name(&thd->mdl_context, + table->mdl_lock_data); } } } @@ -4080,13 +4081,27 @@ static bool lock_table_name_if_not_cached(THD *thd, const char *db, const char *table_name, MDL_LOCK_DATA **lock_data) { + bool conflict; + if (!(*lock_data= mdl_alloc_lock(0, db, table_name, thd->mem_root))) return TRUE; mdl_set_lock_type(*lock_data, MDL_EXCLUSIVE); mdl_add_lock(&thd->mdl_context, *lock_data); - if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock_data)) + if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock_data, + &conflict)) { - *lock_data= 0; + /* + To simplify our life under LOCK TABLES we remove unsatisfied + lock request from the context. + */ + mdl_remove_lock(&thd->mdl_context, *lock_data); + if (!conflict) + { + /* Probably OOM. */ + return TRUE; + } + else + *lock_data= 0; } return FALSE; } @@ -4157,7 +4172,10 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, unlock: if (target_lock_data) + { mdl_release_lock(&thd->mdl_context, target_lock_data); + mdl_remove_lock(&thd->mdl_context, target_lock_data); + } pthread_mutex_lock(&LOCK_lock_db); if (!--creating_table && creating_database) pthread_cond_signal(&COND_refresh); @@ -4416,7 +4434,10 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); mdl_add_lock(&thd->mdl_context, mdl_lock_data); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + { + mdl_remove_lock(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(0); + } pthread_mutex_lock(&LOCK_open); if (!(share= (get_table_share(thd, table_list, key, key_length, 0, @@ -4550,7 +4571,10 @@ end: } /* In case of a temporary table there will be no metadata lock. */ if (error && mdl_lock_data) + { mdl_release_lock(&thd->mdl_context, mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + } DBUG_RETURN(error); } @@ -5518,7 +5542,10 @@ binlog: err: if (target_lock_data) + { mdl_release_lock(&thd->mdl_context, target_lock_data); + mdl_remove_lock(&thd->mdl_context, target_lock_data); + } DBUG_RETURN(res); } @@ -6876,7 +6903,9 @@ view_err: if (new_name != table_name || new_db != db) { mdl_release_lock(&thd->mdl_context, target_lock_data); - mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, target_lock_data); + mdl_release_and_remove_all_locks_for_name(&thd->mdl_context, + mdl_lock_data); } else mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_data); @@ -7554,7 +7583,9 @@ view_err: if ((new_name != table_name || new_db != db)) { mdl_release_lock(&thd->mdl_context, target_lock_data); - mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, target_lock_data); + mdl_release_and_remove_all_locks_for_name(&thd->mdl_context, + mdl_lock_data); } else mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_data); @@ -7613,7 +7644,10 @@ err: thd->abort_on_warning= save_abort_on_warning; } if (target_lock_data) + { mdl_release_lock(&thd->mdl_context, target_lock_data); + mdl_remove_lock(&thd->mdl_context, target_lock_data); + } DBUG_RETURN(TRUE); err_with_mdl: @@ -7625,8 +7659,11 @@ err_with_mdl: */ thd->locked_tables_list.unlink_all_closed_tables(); if (target_lock_data) + { mdl_release_lock(&thd->mdl_context, target_lock_data); - mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); + mdl_remove_lock(&thd->mdl_context, target_lock_data); + } + mdl_release_and_remove_all_locks_for_name(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(TRUE); } /* mysql_alter_table */ From 124cda8a0a783180b280935fd4b62a2b7d6dc6c4 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 2 Dec 2009 19:31:57 +0300 Subject: [PATCH 047/212] Backport of: ------------------------------------------------------------ revno: 2630.4.33 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Fri 2008-06-20 17:11:20 +0400 message: WL#3726 "DDL locking for all metadata objects". After-review fixes in progress. Minimized dependency of mdl.cc on other modules (particularly made it independant of mysql_priv.h) in order to be able write unit tests for metadata locking subsystem. sql/ha_ndbcluster_binlog.cc: Use newly introduced MAX_MDLKEY_LENGTH constant for allocating buffer for object key for metadata locking subsystem. sql/log_event.cc: Use newly introduced MAX_MDLKEY_LENGTH constant for allocating buffer for object key for metadata locking subsystem. sql/mdl.cc: Removed dependency on THD class (and thus on mysql_priv.h) by using direct access to members of st_my_thread_var instead of accessing THD::killed/enter_cond()/exit_cond(). sql/mdl.h: Added MAX_MDLKEY_LENGTH constant to be used for allocating buffers for key for metadata locking subsystem. Added declarations of server kernel functions used by metadata locking subsystem to mdl.h in order to decrease dependency of mdl.cc on other files. sql/mysql_priv.h: Moved declaration of notify_thread_having_shared_lock() to the mdl.h (also renamed it to make clear in metadata locking code that it is a callback to SQL-layer). sql/sql_base.cc: Renamed notify_thread_having_shared_lock() to make it clear in metadata locking subsystem code that it is a callback to SQL layer. sql/sql_handler.cc: Use newly introduced MAX_MDLKEY_LENGTH constant for allocating buffer for object key for metadata locking subsystem. sql/sql_show.cc: Use newly introduced MAX_MDLKEY_LENGTH constant for allocating buffer for object key for metadata locking subsystem. --- sql/ha_ndbcluster_binlog.cc | 2 +- sql/log_event.cc | 2 +- sql/mdl.cc | 136 ++++++++++++++++++++++++------------ sql/mdl.h | 19 +++++ sql/mysql_priv.h | 2 - sql/sql_base.cc | 8 +-- sql/sql_handler.cc | 2 +- sql/sql_show.cc | 4 +- 8 files changed, 120 insertions(+), 55 deletions(-) diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index 6f0e4498d75..b9ea87aec52 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -141,7 +141,7 @@ static Uint64 *p_latest_trans_gci= 0; static TABLE *ndb_binlog_index= 0; static TABLE_LIST binlog_tables; static MDL_LOCK_DATA binlog_mdl_lock_data; -static char binlog_mdlkey[MAX_DBKEY_LENGTH]; +static char binlog_mdlkey[MAX_MDLKEY_LENGTH]; /* Helper functions diff --git a/sql/log_event.cc b/sql/log_event.cc index 92de9933181..b3f6fd58f1a 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -8074,7 +8074,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) &db_mem, (uint) NAME_LEN + 1, &tname_mem, (uint) NAME_LEN + 1, &mdl_lock_data, sizeof(MDL_LOCK_DATA), - &mdlkey, MAX_DBKEY_LENGTH, + &mdlkey, MAX_MDLKEY_LENGTH, NullS))) DBUG_RETURN(HA_ERR_OUT_OF_MEM); diff --git a/sql/mdl.cc b/sql/mdl.cc index afe3f0eaa7b..0a663ad2e11 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -15,13 +15,9 @@ -/* - TODO: Remove this dependency on mysql_priv.h. It's not - trivial step at the moment since currently we access to - some of THD members and use some of its methods here. -*/ -#include "mysql_priv.h" #include "mdl.h" +#include +#include /** @@ -308,7 +304,7 @@ MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name, char *key; if (!multi_alloc_root(root, &lock_data, sizeof(MDL_LOCK_DATA), &key, - MAX_DBKEY_LENGTH, NULL)) + MAX_MDLKEY_LENGTH, NULL)) return NULL; mdl_init_lock(lock_data, key, type, db, name); @@ -441,6 +437,58 @@ static bool is_shared(MDL_LOCK_DATA *lock_data) } +/** + Helper functions and macros to be used for killable waiting in metadata + locking subsystem. + + @sa THD::enter_cond()/exit_cond()/killed. + + @note We can't use THD::enter_cond()/exit_cond()/killed directly here + since this will make metadata subsystem dependant on THD class + and thus prevent us from writing unit tests for it. And usage of + wrapper functions to access THD::killed/enter_cond()/exit_cond() + will probably introduce too much overhead. +*/ + +#define MDL_ENTER_COND(A, B) mdl_enter_cond(A, B, __func__, __FILE__, __LINE__) + +static inline const char* mdl_enter_cond(MDL_CONTEXT *context, + st_my_thread_var *mysys_var, + const char *calling_func, + const char *calling_file, + const unsigned int calling_line) +{ + safe_mutex_assert_owner(&LOCK_mdl); + + mysys_var->current_mutex= &LOCK_mdl; + mysys_var->current_cond= &COND_mdl; + + return set_thd_proc_info(context->thd, "Waiting for table", + calling_func, calling_file, calling_line); +} + +#define MDL_EXIT_COND(A, B, C) mdl_exit_cond(A, B, C, __func__, __FILE__, __LINE__) + +static inline void mdl_exit_cond(MDL_CONTEXT *context, + st_my_thread_var *mysys_var, + const char* old_msg, + const char *calling_func, + const char *calling_file, + const unsigned int calling_line) +{ + DBUG_ASSERT(&LOCK_mdl == mysys_var->current_mutex); + + pthread_mutex_unlock(&LOCK_mdl); + pthread_mutex_lock(&mysys_var->mutex); + mysys_var->current_mutex= 0; + mysys_var->current_cond= 0; + pthread_mutex_unlock(&mysys_var->mutex); + + (void) set_thd_proc_info(context->thd, old_msg, calling_func, + calling_file, calling_line); +} + + /** Check if request for the lock on particular object can be satisfied given current state of the global metadata lock. @@ -752,9 +800,7 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) bool signalled= FALSE; const char *old_msg; I_P_List_iterator it(context->locks); - THD *thd= context->thd; - - DBUG_ASSERT(thd == current_thd); + st_my_thread_var *mysys_var= my_thread_var; safe_mutex_assert_not_owner(&LOCK_open); @@ -766,7 +812,7 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) pthread_mutex_lock(&LOCK_mdl); - old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); + old_msg= MDL_ENTER_COND(context, mysys_var); while ((lock_data= it++)) { @@ -826,8 +872,11 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) !lock->active_shared_waiting_upgrade.is_empty(); while ((conf_lock_data= it++)) + { signalled|= - notify_thread_having_shared_lock(thd, conf_lock_data->ctx->thd); + mysql_notify_thread_having_shared_lock(context->thd, + conf_lock_data->ctx->thd); + } break; } @@ -848,7 +897,7 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) set_timespec(abstime, 10); pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime); } - if (thd->killed) + if (mysys_var->abort) goto err; } it.rewind(); @@ -863,8 +912,8 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) (*lock->cached_object_release_hook)(lock->cached_object); lock->cached_object= NULL; } - /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ - thd->exit_cond(old_msg); + /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ + MDL_EXIT_COND(context, mysys_var, old_msg); return FALSE; err: @@ -880,7 +929,7 @@ err: } /* May be some pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); - thd->exit_cond(old_msg); + MDL_EXIT_COND(context, mysys_var, old_msg); return TRUE; } @@ -907,12 +956,10 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, { MDL_LOCK *lock; const char *old_msg; - THD *thd= context->thd; + st_my_thread_var *mysys_var= my_thread_var; DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive"); - DBUG_ASSERT(thd == current_thd); - safe_mutex_assert_not_owner(&LOCK_open); DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); @@ -927,7 +974,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, pthread_mutex_lock(&LOCK_mdl); - old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); + old_msg= MDL_ENTER_COND(context, mysys_var); lock_data->state= MDL_PENDING_UPGRADE; /* Set type of lock request to the type at which we are aiming. */ @@ -960,8 +1007,11 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, while ((conf_lock_data= it++)) { if (conf_lock_data->ctx != context) - signalled|= notify_thread_having_shared_lock(thd, - conf_lock_data->ctx->thd); + { + signalled|= + mysql_notify_thread_having_shared_lock(context->thd, + conf_lock_data->ctx->thd); + } } if (signalled) @@ -979,7 +1029,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, DBUG_PRINT("info", ("Failed to wake-up from table-level lock ... sleeping")); pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime); } - if (thd->killed) + if (mysys_var->abort) { lock_data->state= MDL_ACQUIRED; lock_data->type= MDL_SHARED_UPGRADABLE; @@ -987,7 +1037,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, lock->active_shared.push_front(lock_data); /* Pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); - thd->exit_cond(old_msg); + MDL_EXIT_COND(context, mysys_var, old_msg); DBUG_RETURN(TRUE); } } @@ -999,8 +1049,8 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, (*lock->cached_object_release_hook)(lock->cached_object); lock->cached_object= 0; - /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ - thd->exit_cond(old_msg); + /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ + MDL_EXIT_COND(context, mysys_var, old_msg); DBUG_RETURN(FALSE); } @@ -1086,32 +1136,31 @@ err: bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) { - THD *thd= context->thd; + st_my_thread_var *mysys_var= my_thread_var; const char *old_msg; safe_mutex_assert_not_owner(&LOCK_open); - DBUG_ASSERT(thd == current_thd); DBUG_ASSERT(!context->has_global_shared_lock); pthread_mutex_lock(&LOCK_mdl); global_lock.waiting_shared++; - old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); + old_msg= MDL_ENTER_COND(context, mysys_var); - while (!thd->killed && global_lock.active_intention_exclusive) + while (!mysys_var->abort && global_lock.active_intention_exclusive) pthread_cond_wait(&COND_mdl, &LOCK_mdl); global_lock.waiting_shared--; - if (thd->killed) + if (mysys_var->abort) { - /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ - thd->exit_cond(old_msg); + /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ + MDL_EXIT_COND(context, mysys_var, old_msg); return TRUE; } global_lock.active_shared++; context->has_global_shared_lock= TRUE; - /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ - thd->exit_cond(old_msg); + /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ + MDL_EXIT_COND(context, mysys_var, old_msg); return FALSE; } @@ -1137,12 +1186,11 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) MDL_LOCK *lock; I_P_List_iterator it(context->locks); const char *old_msg; - THD *thd= context->thd; + st_my_thread_var *mysys_var= my_thread_var; safe_mutex_assert_not_owner(&LOCK_open); - DBUG_ASSERT(thd == current_thd); - while (!thd->killed) + while (!mysys_var->abort) { /* We have to check if there are some HANDLERs open by this thread @@ -1156,7 +1204,7 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) */ mysql_ha_flush(context->thd); pthread_mutex_lock(&LOCK_mdl); - old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table"); + old_msg= MDL_ENTER_COND(context, mysys_var); it.rewind(); while ((lock_data= it++)) { @@ -1179,10 +1227,10 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) break; } pthread_cond_wait(&COND_mdl, &LOCK_mdl); - /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */ - thd->exit_cond(old_msg); + /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ + MDL_EXIT_COND(context, mysys_var, old_msg); } - return thd->killed; + return mysys_var->abort; } @@ -1422,7 +1470,7 @@ void mdl_release_global_shared_lock(MDL_CONTEXT *context) bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, const char *db, const char *name) { - char key[MAX_DBKEY_LENGTH]; + char key[MAX_MDLKEY_LENGTH]; uint key_length; MDL_LOCK_DATA *lock_data; I_P_List_iterator it(context->locks); @@ -1456,7 +1504,7 @@ bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, const char *name) { - char key[MAX_DBKEY_LENGTH]; + char key[MAX_MDLKEY_LENGTH]; uint key_length; MDL_LOCK_DATA *lock_data; I_P_List_iterator it(context->locks); diff --git a/sql/mdl.h b/sql/mdl.h index 92bd83038e5..b4b84a9ab24 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -19,6 +19,7 @@ #include "sql_plist.h" #include #include +#include class THD; @@ -148,6 +149,9 @@ void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); void mdl_context_merge(MDL_CONTEXT *target, MDL_CONTEXT *source); +/** Maximal length of key for metadata locking subsystem. */ +#define MAX_MDLKEY_LENGTH (4 + NAME_LEN + 1 + NAME_LEN + 1) + void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, const char *db, const char *name); MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name, @@ -237,4 +241,19 @@ void* mdl_get_cached_object(MDL_LOCK_DATA *lock_data); void mdl_set_cached_object(MDL_LOCK_DATA *lock_data, void *cached_object, mdl_cached_object_release_hook release_hook); + +/* + Functions in the server's kernel used by metadata locking subsystem. +*/ + +extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use); +extern void mysql_ha_flush(THD *thd); +extern "C" const char *set_thd_proc_info(THD *thd, const char *info, + const char *calling_function, + const char *calling_file, + const unsigned int calling_line); +#ifndef DBUG_OFF +extern pthread_mutex_t LOCK_open; +#endif + #endif diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 12d7e46e821..2f15ecb2b71 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1534,8 +1534,6 @@ char *generate_partition_syntax(partition_info *part_info, Alter_info *alter_info); #endif -bool notify_thread_having_shared_lock(THD *thd, THD *in_use); - enum enum_tdc_remove_table_type {TDC_RT_REMOVE_ALL, TDC_RT_REMOVE_NOT_OWN, TDC_RT_REMOVE_UNUSED}; void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 6c344ad1d4e..7803bd0ba11 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1275,7 +1275,7 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, /* We need to hold LOCK_open while changing the open_tables list, since another thread may work on it. - @sa notify_thread_having_shared_lock() + @sa mysql_notify_thread_having_shared_lock() */ pthread_mutex_lock(&LOCK_open); @@ -1455,7 +1455,7 @@ void close_thread_tables(THD *thd, /* Note that we need to hold LOCK_open while changing the open_tables list. Another thread may work on it. - (See: notify_thread_having_shared_lock()) + (See: mysql_notify_thread_having_shared_lock()) Closing a MERGE child before the parent would be fatal if the other thread tries to abort the MERGE lock in between. */ @@ -7956,7 +7956,7 @@ void flush_tables() rest of the server is broken. */ -bool notify_thread_having_shared_lock(THD *thd, THD *in_use) +bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use) { bool signalled= FALSE; if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && @@ -8501,7 +8501,7 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup) /* Note that we need to hold LOCK_open while changing the open_tables list. Another thread may work on it. - (See: notify_thread_having_shared_lock()) + (See: mysql_notify_thread_having_shared_lock()) Closing a MERGE child before the parent would be fatal if the other thread tries to abort the MERGE lock in between. */ diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 9b30d8cec12..49d6cbaa447 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -247,7 +247,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) &name, (uint) namelen, &alias, (uint) aliaslen, &mdl_lock_data, sizeof(MDL_LOCK_DATA), - &mdlkey, MAX_DBKEY_LENGTH, + &mdlkey, MAX_MDLKEY_LENGTH, NullS))) { DBUG_PRINT("exit",("ERROR")); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index c50d74412bf..a77ba6264a3 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3084,7 +3084,7 @@ uint get_table_open_method(TABLE_LIST *tables, @param mdlkey Pointer to the buffer for key for the lock request (should be at least strlen(db) + strlen(name) + 2 bytes, or, if the lengths are not known, - MAX_DBNAME_LENGTH) + MAX_MDLKEY_LENGTH) @param table Table list element for the table @note This is an auxiliary function to be used in cases when we want to @@ -3157,7 +3157,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, uint key_length; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; MDL_LOCK_DATA mdl_lock_data; - char mdlkey[MAX_DBKEY_LENGTH]; + char mdlkey[MAX_MDLKEY_LENGTH]; bzero((char*) &table_list, sizeof(TABLE_LIST)); bzero((char*) &tbl, sizeof(TABLE)); From 77cbfd85ee3f6969dcbe528a2f97ec12c9cf99dc Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 2 Dec 2009 23:47:23 +0300 Subject: [PATCH 048/212] Backport of: ---------------------------------------------------------- revno: 2630.4.35 committer: Konstantin Osipov branch nick: mysql-6.0-3726 timestamp: Wed 2008-06-25 16:44:00 +0400 message: Fix a MyISAM-specific bug in the new implementation of LOCK TABLES (WL#3726). If more than one instance of a MyISAM table are open in the same connection, all of them must share the same status_param. Otherwise, unlock of a table may lead to lost records. See also comments in thr_lock.c. include/thr_lock.h: Declare thr_lock_merge_status(). mysql-test/r/lock.result: Update test results (WL#3726). mysql-test/t/lock.test: Add a test case for the situation when the same table is locked twice by LOCK TABLES, and only one instance is updated. mysys/thr_lock.c: Move the code that makes sure all status_params of the same table are shared into a separate function. sql/lock.cc: Make sure that status_param is shared when a table is reopened. --- include/thr_lock.h | 2 ++ mysql-test/r/lock.result | 18 +++++++++++++++++ mysql-test/t/lock.test | 18 +++++++++++++++++ mysys/thr_lock.c | 42 ++++++++++++++++++++++++++++++++++------ sql/lock.cc | 2 ++ 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/include/thr_lock.h b/include/thr_lock.h index fb70c57c0e7..d5a7cd57ea7 100644 --- a/include/thr_lock.h +++ b/include/thr_lock.h @@ -160,6 +160,8 @@ void thr_unlock(THR_LOCK_DATA *data); enum enum_thr_lock_result thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner); void thr_multi_unlock(THR_LOCK_DATA **data,uint count); +void +thr_lock_merge_status(THR_LOCK_DATA **data, uint count); void thr_abort_locks(THR_LOCK *lock, my_bool upgrade_lock); my_bool thr_abort_locks_for_thread(THR_LOCK *lock, my_thread_id thread); void thr_print_locks(void); /* For debugging */ diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index 676a8c41bb6..092c376b34a 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -244,5 +244,23 @@ a unlock tables; drop table t1, t2; # +# Ensure that mi_copy_status is called for two instances +# of the same table when it is reopened after a flush. +# +drop table if exists t1; +drop view if exists v1; +create table t1 (c1 int); +create view v1 as select * from t1; +lock tables t1 write, v1 write; +flush table t1; +insert into t1 values (33); +flush table t1; +select * from t1; +c1 +33 +unlock tables; +drop table t1; +drop view v1; +# # End of 6.0 tests. # diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index 7effaaeb20d..51900be4df8 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -291,6 +291,24 @@ select * from t1; unlock tables; drop table t1, t2; +--echo # +--echo # Ensure that mi_copy_status is called for two instances +--echo # of the same table when it is reopened after a flush. +--echo # +--disable_warnings +drop table if exists t1; +drop view if exists v1; +--enable_warnings +create table t1 (c1 int); +create view v1 as select * from t1; +lock tables t1 write, v1 write; +flush table t1; +insert into t1 values (33); +flush table t1; +select * from t1; +unlock tables; +drop table t1; +drop view v1; --echo # --echo # End of 6.0 tests. diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index 1d724de641c..1288dc36a90 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -1026,12 +1026,43 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) (long) pos[0]->lock, pos[0]->type); fflush(stdout); #endif } - /* - Ensure that all get_locks() have the same status - If we lock the same table multiple times, we must use the same - status_param! - */ + thr_lock_merge_status(data, count); + DBUG_RETURN(THR_LOCK_SUCCESS); +} + + +/** + Ensure that all locks for a given table have the same + status_param. + + This is a MyISAM and possibly Maria specific crutch. MyISAM + engine stores data file length, record count and other table + properties in status_param member of handler. When a table is + locked, connection-local copy is made from a global copy + (myisam_share) by mi_get_status(). When a table is unlocked, + the changed status is transferred back to the global share by + mi_update_status(). + + One thing MyISAM doesn't do is to ensure that when the same + table is opened twice in a connection all instances share the + same status_param. This is necessary, however: for one, to keep + all instances of a connection "on the same page" with regard to + the current state of the table. For other, unless this is done, + myisam_share will always get updated from the last unlocked + instance (in mi_update_status()), and when this instance was not + the one that was used to update data, records may be lost. + + For each table, this function looks up the last lock_data in the + list of acquired locks, and makes sure that all other instances + share status_param with it. +*/ + +void +thr_lock_merge_status(THR_LOCK_DATA **data, uint count) +{ #if !defined(DONT_USE_RW_LOCKS) + THR_LOCK_DATA **pos= data; + THR_LOCK_DATA **end= data + count; if (count > 1) { THR_LOCK_DATA *last_lock= end[-1]; @@ -1073,7 +1104,6 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) } while (pos != data); } #endif - DBUG_RETURN(THR_LOCK_SUCCESS); } /* free all locks */ diff --git a/sql/lock.cc b/sql/lock.cc index 199ab354c22..d19f4b46821 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -687,6 +687,8 @@ MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b) /* Delete old, not needed locks */ my_free((uchar*) a,MYF(0)); my_free((uchar*) b,MYF(0)); + + thr_lock_merge_status(sql_lock->locks, sql_lock->lock_count); DBUG_RETURN(sql_lock); } From 9148af5cdbd6baacc7721c8e6fd40465e9cdce1e Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 00:08:51 +0300 Subject: [PATCH 049/212] Backport of: ---------------------------------------------------------- revno: 2630.4.36 committer: Konstantin Osipov branch nick: mysql-6.0-3726 timestamp: Wed 2008-06-25 17:03:37 +0400 message: As per discussion with Serg and Dmitri, remove the code that is trying to emulate LOCK TABLES when locking tables for prelocking. This code was worked around by the engines, and has no effect. We may still have to do something to ensure that statement-based replication is consistent in MVCC engines, or other engines that downgrade table level locks, but this will have to be done somehow differently. sql/sql_base.cc: Do not try to emulate LOCK TABLES when locking tables for pre-locking. --- sql/sql_base.cc | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 7803bd0ba11..17b4360c8f9 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1335,7 +1335,6 @@ void close_thread_tables(THD *thd, bool skip_mdl) { TABLE *table; - bool clear_table_lock_option= FALSE; DBUG_ENTER("close_thread_tables"); #ifdef EXTRA_DEBUG @@ -1428,7 +1427,6 @@ void close_thread_tables(THD *thd, DBUG_VOID_RETURN; thd->locked_tables_mode= LTM_NONE; - clear_table_lock_option= TRUE; /* Note that we are leaving prelocked mode so we don't need @@ -1467,10 +1465,6 @@ void close_thread_tables(THD *thd, { mdl_remove_all_locks(&thd->mdl_context); } - - if (clear_table_lock_option) - thd->options&= ~OPTION_TABLE_LOCK; - DBUG_VOID_RETURN; } @@ -4842,8 +4836,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, /* We have to emulate LOCK TABLES if we are statement needs prelocking. */ if (thd->lex->requires_prelocking()) { - thd->in_lock_tables=1; - thd->options|= OPTION_TABLE_LOCK; /* A query that modifies autoinc column in sub-statement can make the master and slave inconsistent. @@ -4863,14 +4855,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, if (! (thd->lock= mysql_lock_tables(thd, start, (uint) (ptr - start), flags, need_reopen))) - { - if (thd->lex->requires_prelocking()) - { - thd->options&= ~(OPTION_TABLE_LOCK); - thd->in_lock_tables=0; - } DBUG_RETURN(-1); - } if (thd->lex->requires_prelocking() && thd->lex->sql_command != SQLCOM_LOCK_TABLES) @@ -4880,10 +4865,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, We just have done implicit LOCK TABLES, and now we have to emulate first open_and_lock_tables() after it. - */ - thd->in_lock_tables= 0; - - /* When open_and_lock_tables() is called for a single table out of a table list, the 'next_global' chain is temporarily broken. We may not find 'first_not_own' before the end of the "list". @@ -4906,7 +4887,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, */ mysql_unlock_tables(thd, thd->lock); thd->lock= 0; - thd->options&= ~(OPTION_TABLE_LOCK); DBUG_RETURN(-1); } } From e91294243f9905356a31d1a9944b2bea9cbfe859 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 00:24:10 +0300 Subject: [PATCH 050/212] Backport of: ------------------------------------------------------------ revno: 2630.4.37 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Wed 2008-06-25 20:28:57 +0400 message: Fix build failure of mysql-6.0-3726 tree on Windows. The failure was caused by the fact that sql/mdl.cc was using __func__ macro without including sql_profile.h header which contained definition of this macro for those platforms that miss it. This patch solves this problem by moving this define to include/my_global.h which makes it available in modules which don't/can't include sql/mysql_priv.h. This is a patch that is part of WL#3726. include/my_global.h: Moved definition of __func__ macro from sql/sql_profile.h to include/my_global.h to make it accessible from those modules of the server which don't include mysql_priv.h. sql/sql_profile.cc: Removed _unknown_func_ const variable since it is no longer used by __func__ macro. sql/sql_profile.h: Moved definition of __func__ macro from sql/sql_profile.h to include/my_global.h to make it accessible from those modules of the server which don't include mysql_priv.h. --- include/my_global.h | 19 +++++++++++++++++++ sql/sql_profile.cc | 3 --- sql/sql_profile.h | 8 -------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/include/my_global.h b/include/my_global.h index 815be622b16..0f2aea62438 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -1574,6 +1574,25 @@ inline void operator delete[](void*, void*) { /* Do nothing */ } #define bool In_C_you_should_use_my_bool_instead() #endif +/* Provide __func__ macro definition for platforms that miss it. */ +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#elif defined(_MSC_VER) +# if _MSC_VER < 1300 +# define __func__ "" +# else +# define __func__ __FUNCTION__ +# endif +#elif defined(__BORLANDC__) +# define __func__ __FUNC__ +#else +# define __func__ "" +#endif + #ifndef HAVE_RINT /** All integers up to this number can be represented exactly as double precision diff --git a/sql/sql_profile.cc b/sql/sql_profile.cc index 69e5bc3cbb4..0f67c865d45 100644 --- a/sql/sql_profile.cc +++ b/sql/sql_profile.cc @@ -38,9 +38,6 @@ #define MAX_QUERY_LENGTH 300 -/* Reserved for systems that can't record the function name in source. */ -const char * const _unknown_func_ = ""; - /** Connects Information_Schema and Profiling. */ diff --git a/sql/sql_profile.h b/sql/sql_profile.h index bffe1cb576b..e9c5686efab 100644 --- a/sql/sql_profile.h +++ b/sql/sql_profile.h @@ -16,14 +16,6 @@ #ifndef _SQL_PROFILE_H #define _SQL_PROFILE_H -#ifndef __func__ -#ifdef __FUNCTION__ -#define __func__ __FUNCTION__ -#else -#define __func__ "unknown function" -#endif -#endif - extern ST_FIELD_INFO query_profile_statistics_info[]; int fill_query_profile_statistics_info(THD *thd, TABLE_LIST *tables, Item *cond); int make_profile_table_for_show(THD *thd, ST_SCHEMA_TABLE *schema_table); From 7f628ac2ddf0cd70ccfb43d48792f45943d6ca48 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 02:09:22 +0300 Subject: [PATCH 051/212] Backport of: ---------------------------------------------------------- revno: 2630.4.38 committer: Konstantin Osipov branch nick: mysql-6.0-4144 timestamp: Wed 2008-06-25 22:07:06 +0400 message: WL#4144 - Lock MERGE engine children. Committing a version of the patch merged with WL#3726 on behalf of Ingo. Step #1: Move locking from parent to children. MERGE children are now left in the query list of tables after inserted there in open_tables(). So they are locked by lock_tables() as all other tables are. The MERGE parent does not store locks any more. It appears in a MYSQL_LOCK with zero lock data. This is kind of a "dummy" lock. All other lock handling is also done directly on the children. To protect against parent or child modifications during LOCK TABLES, the children are detached after every statement and attached before every statement, even under LOCK TABLES. The children table list is removed from the query list of tables on every detach and on close of the parent. Step #2: Move MERGE specific functionality from SQL layer into table handler. Functionality moved from SQL layer (mainly sql_base.cc) to the table handler (ha_myisammrg.cc). Unnecessary code is removed from the SQL layer. Step #3: Moved all MERGE specific members from TABLE to ha_myisammrg. Moved members from TABLE to ha_myisammrg. Renamed some mebers. Fixed comments. Step #4: Valgrind and coverage testing Valgrind did not uncover new problems. Added purecov comments. Added a new test for DATA/INDEX DIRECTORY options. Changed handling of ::reset() for non-attached children. Fixed the merge-big test. Step #5: Fixed crashes detected during review Changed detection when to attach/detach. Added new tests. Backport also the fix for Bug#44040 "MySQL allows creating a MERGE table upon VIEWs but crashes when using it" include/my_base.h: WL#4144 - Lock MERGE engine children Added HA_EXTRA_ADD_CHILDREN_LIST and HA_EXTRA_IS_ATTACHED_CHILDREN for MERGE table support mysql-test/r/merge.result: WL#4144 - Lock MERGE engine children Fixed test result. mysql-test/t/disabled.def: Enable merge.test, which now is working again (WL#4144). mysql-test/t/merge-big.test: Fix the messages for wait_condition (merge with WL#3726). mysql-test/t/merge.test: WL#4144 - Lock MERGE engine children Fixed one test to meet coding standards for tests (upper case keywords, engine names as in SHOW ENGINES). Fixed error codes. Added a test for DATA/INDEX DIRECTORY. mysys/thr_lock.c: WL#4144 - Lock MERGE engine children Added purecov comments. sql/ha_partition.cc: WL#4144 - Lock MERGE engine children Added MERGE specific extra operations to ha_partition::extra(). Extended comments. Changed function comment to doxygen style. Fixed nomenclature: 'parameter' -> 'operation'. sql/mysql_priv.h: WL#4144 - Lock MERGE engine children Removed declarations for removed functions. sql/sql_base.cc: WL#4144 - Lock MERGE engine children Leave the children in the query list of tables after open_tables(). Set proper back links (prev_global). Attach MERGE children before and detach them after every statement. Even under LOCK TABLES. Remove children from the query list when they are detached. Remove lock forwarding from children to parent. Moved MERGE specific functions to ha_myisammrg.cc. Added purecov comments. Backport the fix for Bug#44040 "MySQL allows creating a MERGE table upon VIEWs but crashes when using it" sql/sql_table.cc: WL#4144 - Lock MERGE engine children Changed detection of MERGE tables. sql/table.cc: WL#4144 - Lock MERGE engine children Moved is_children_attached() method from TABLE to ha_myisammrg. sql/table.h: WL#4144 - Lock MERGE engine children Moved all MERGE specific members from TABLE to ha_myisammrg. storage/myisammrg/ha_myisammrg.cc: WL#4144 - Lock MERGE engine children Set proper back links in the child list (prev_global). Added a function for removal of the child list from the query list. Remove children from the query list when the parent is closed. Make parent lock handling a dummy (zero locks). Moved MERGE specific functionality from SQL layer to here. Moved all MERGE specific members from TABLE to ha_myisammrg. Renamed children list pointers. Added initialization and free for the children list mem_root. Fixed comments. Added purecov comments. storage/myisammrg/ha_myisammrg.h: WL#4144 - Lock MERGE engine children Added method add_children_list(). Moved all MERGE specific members from TABLE to ha_myisammrg. Renamed children list pointers. Added a mem_root for the children list. storage/myisammrg/myrg_extra.c: WL#4144 - Lock MERGE engine children Changed handling of ::reset() for non-attached children. --- include/my_base.h | 5 +- mysql-test/r/merge.result | 285 +++++++++++++- mysql-test/t/disabled.def | 1 - mysql-test/t/merge-big.test | 4 +- mysql-test/t/merge.test | 326 +++++++++++++++- mysys/thr_lock.c | 2 + sql/ha_partition.cc | 68 ++-- sql/mysql_priv.h | 3 - sql/sql_base.cc | 493 ++++-------------------- sql/sql_table.cc | 8 +- sql/table.cc | 18 - sql/table.h | 8 - storage/myisammrg/ha_myisammrg.cc | 620 +++++++++++++++++++----------- storage/myisammrg/ha_myisammrg.h | 4 + storage/myisammrg/myrg_extra.c | 11 +- 15 files changed, 1123 insertions(+), 733 deletions(-) diff --git a/include/my_base.h b/include/my_base.h index 70bd9b5e073..0b8ea79f965 100644 --- a/include/my_base.h +++ b/include/my_base.h @@ -191,10 +191,11 @@ enum ha_extra_function { /* Inform handler that we will do a rename */ HA_EXTRA_PREPARE_FOR_RENAME, /* - Orders MERGE handler to attach or detach its child tables. Used at - begin and end of a statement. + Special actions for MERGE tables. */ + HA_EXTRA_ADD_CHILDREN_LIST, HA_EXTRA_ATTACH_CHILDREN, + HA_EXTRA_IS_ATTACHED_CHILDREN, HA_EXTRA_DETACH_CHILDREN }; diff --git a/mysql-test/r/merge.result b/mysql-test/r/merge.result index 83152a0dd8d..479645744ec 100644 --- a/mysql-test/r/merge.result +++ b/mysql-test/r/merge.result @@ -578,23 +578,23 @@ select max(b) from t1 where a = 2; max(b) 1 drop table t3,t1,t2; -create table t1 (a int not null); -create table t2 (a int not null); -insert into t1 values (1); -insert into t2 values (2); -create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2); -select * from t3; +CREATE TABLE t1 (c1 INT NOT NULL); +CREATE TABLE t2 (c1 INT NOT NULL); +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); +CREATE TEMPORARY TABLE t3 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t1,t2); +SELECT * FROM t3; ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist -create temporary table t4 (a int not null); -create temporary table t5 (a int not null); -insert into t4 values (1); -insert into t5 values (2); -create temporary table t6 (a int not null) ENGINE=MERGE UNION=(t4,t5); -select * from t6; -a -1 -2 -drop table t6, t3, t1, t2, t4, t5; +CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL); +CREATE TEMPORARY TABLE t5 (c1 INT NOT NULL); +INSERT INTO t4 VALUES (4); +INSERT INTO t5 VALUES (5); +CREATE TEMPORARY TABLE t6 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t4,t5); +SELECT * FROM t6; +c1 +4 +5 +DROP TABLE t6, t3, t1, t2, t4, t5; create temporary table t1 (a int not null); create temporary table t2 (a int not null); insert into t1 values (1); @@ -1258,7 +1258,7 @@ c1 LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE; ALTER TABLE t2 RENAME TO t5; SELECT * FROM t3 ORDER BY c1; -ERROR HY000: Table 't3' was not locked with LOCK TABLES +ERROR HY000: Table 't2' was not locked with LOCK TABLES ALTER TABLE t5 RENAME TO t2; ERROR HY000: Table 't5' was not locked with LOCK TABLES UNLOCK TABLES; @@ -1330,9 +1330,9 @@ LOCK TABLES t1 WRITE, t2 WRITE; INSERT INTO t1 VALUES (1); DROP TABLE t1; SELECT * FROM t2; -ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist +ERROR HY000: Table 't1' was not locked with LOCK TABLES SELECT * FROM t1; -ERROR 42S02: Table 'test.t1' doesn't exist +ERROR HY000: Table 't1' was not locked with LOCK TABLES UNLOCK TABLES; DROP TABLE t2; # @@ -2256,3 +2256,250 @@ deallocate prepare stmt; # drop table t_parent; set @@global.table_definition_cache=@save_table_definition_cache; +DROP DATABASE IF EXISTS mysql_test1; +CREATE DATABASE mysql_test1; +CREATE TABLE t1 ... DATA DIRECTORY=... INDEX DIRECTORY=... +CREATE TABLE mysql_test1.t2 ... DATA DIRECTORY=... INDEX DIRECTORY=... +CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,mysql_test1.t2) +INSERT_METHOD=LAST; +INSERT INTO t1 VALUES (1); +INSERT INTO mysql_test1.t2 VALUES (2); +SELECT * FROM m1; +c1 +1 +2 +DROP TABLE t1, mysql_test1.t2, m1; +DROP DATABASE mysql_test1; +CREATE TABLE t1 (c1 INT); +CREATE TABLE t2 (c1 INT); +INSERT INTO t1 (c1) VALUES (1); +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) INSERT_METHOD=FIRST; +CREATE TABLE t3 (c1 INT); +INSERT INTO t3 (c1) VALUES (1); +CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM t3); +CREATE VIEW v1 AS SELECT foo.c1 c1, f1() c2, bar.c1 c3, f1() c4 +FROM tm1 foo, tm1 bar, t3; +SELECT * FROM v1; +c1 c2 c3 c4 +1 1 1 1 +DROP FUNCTION f1; +DROP VIEW v1; +DROP TABLE tm1, t1, t2, t3; +CREATE TEMPORARY TABLE t1 (c1 INT); +CREATE TEMPORARY TABLE t2 (c1 INT); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) +INSERT_METHOD=FIRST; +CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM tm1); +INSERT INTO tm1 (c1) VALUES (1); +SELECT f1() FROM (SELECT 1) AS c1; +f1() +1 +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2; +CREATE FUNCTION f1() RETURNS INT +BEGIN +CREATE TEMPORARY TABLE t1 (c1 INT); +CREATE TEMPORARY TABLE t2 (c1 INT); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2); +INSERT INTO t1 (c1) VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +SELECT f1() FROM (SELECT 1 UNION SELECT 1) c1; +f1() +1 +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2; +CREATE TEMPORARY TABLE t1 (c1 INT); +INSERT INTO t1 (c1) VALUES (1); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); +CREATE FUNCTION f1() RETURNS INT +BEGIN +CREATE TEMPORARY TABLE t2 (c1 INT); +ALTER TEMPORARY TABLE tm1 UNION=(t1,t2); +INSERT INTO t2 (c1) VALUES (2); +RETURN (SELECT MAX(c1) FROM tm1); +END| +ERROR 0A000: ALTER VIEW is not allowed in stored procedures +DROP TABLE tm1, t1; +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +DROP TABLE tm1, t1; +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO tm1 VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +SELECT f1(); +f1() +1 +DROP FUNCTION f1; +DROP TABLE tm1, t1; +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +LOCK TABLE tm1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +UNLOCK TABLES; +DROP TABLE tm1, t1; +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO tm1 VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +LOCK TABLE tm1 WRITE; +SELECT f1(); +f1() +1 +UNLOCK TABLES; +DROP FUNCTION f1; +DROP TABLE tm1, t1; +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +CREATE TRIGGER t2_ai AFTER INSERT ON t2 +FOR EACH ROW INSERT INTO tm1 VALUES(11); +LOCK TABLE t2 WRITE; +INSERT INTO t2 VALUES (2); +SELECT * FROM tm1; +c1 +11 +SELECT * FROM t2; +c1 +2 +UNLOCK TABLES; +DROP TRIGGER t2_ai; +DROP TABLE tm1, t1, t2; +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +DROP TABLE tm1, t1; +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO tm1 VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +SELECT f1(); +f1() +1 +DROP FUNCTION f1; +DROP TABLE tm1, t1; +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +CREATE TABLE t9 (c1 INT) ENGINE=MyISAM; +LOCK TABLE t9 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +UNLOCK TABLES; +DROP TABLE tm1, t1, t9; +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO tm1 VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +CREATE TABLE t9 (c1 INT) ENGINE=MyISAM; +LOCK TABLE t9 WRITE; +SELECT f1(); +f1() +1 +UNLOCK TABLES; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t9; +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TRIGGER t2_ai AFTER INSERT ON t2 +FOR EACH ROW INSERT INTO tm1 VALUES(11); +LOCK TABLE t2 WRITE; +INSERT INTO t2 VALUES (2); +SELECT * FROM tm1; +c1 +11 +SELECT * FROM t2; +c1 +2 +UNLOCK TABLES; +DROP TRIGGER t2_ai; +DROP TABLE tm1, t1, t2; +# +# Don't select MERGE child when trying to get prelocked table. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +CREATE TRIGGER tm1_ai AFTER INSERT ON tm1 +FOR EACH ROW INSERT INTO t1 VALUES(11); +LOCK TABLE tm1 WRITE, t1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +11 +UNLOCK TABLES; +LOCK TABLE t1 WRITE, tm1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +11 +1 +11 +UNLOCK TABLES; +DROP TRIGGER tm1_ai; +DROP TABLE tm1, t1; +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t3 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t4 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t5 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5) +INSERT_METHOD=LAST; +CREATE TRIGGER t2_au AFTER UPDATE ON t2 +FOR EACH ROW INSERT INTO t3 VALUES(33); +CREATE FUNCTION f1() RETURNS INT +RETURN (SELECT MAX(c1) FROM t4); +LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE; +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(2); +INSERT INTO t3 VALUES(3); +INSERT INTO t4 VALUES(4); +INSERT INTO t5 VALUES(5); +UPDATE t2, tm1 SET t2.c1=f1(); +FLUSH TABLES; +FLUSH TABLES; +UNLOCK TABLES; +SELECT * FROM tm1; +c1 +1 +4 +3 +33 +4 +5 +DROP TRIGGER t2_au; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2, t3, t4, t5; +End of 6.0 tests diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def index bb2749e902b..ad7617b9403 100644 --- a/mysql-test/t/disabled.def +++ b/mysql-test/t/disabled.def @@ -15,4 +15,3 @@ partition_innodb_builtin : Bug#32430 2009-09-25 mattiasj Waiting for push of Inn partition_innodb_plugin : Bug#32430 2009-09-25 mattiasj Waiting for push of Innodb changes innodb-autoinc : Bug#48482 2009-11-02 svoj innodb-autoinc.test fails with results difference rpl_killed_ddl : Bug#45520: rpl_killed_ddl fails sporadically in pb2 -merge : WL#4144 diff --git a/mysql-test/t/merge-big.test b/mysql-test/t/merge-big.test index b687973c9d1..33bd93791f1 100644 --- a/mysql-test/t/merge-big.test +++ b/mysql-test/t/merge-big.test @@ -51,7 +51,7 @@ connection default; #--sleep 8 #SELECT ID,STATE,INFO FROM INFORMATION_SCHEMA.PROCESSLIST; let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE ID = $con1_id AND STATE = 'Locked'; + WHERE ID = $con1_id AND STATE = 'Table lock'; --source include/wait_condition.inc #SELECT NOW(); --echo # Kick INSERT out of thr_multi_lock(). @@ -61,7 +61,7 @@ FLUSH TABLES; #--sleep 8 #SELECT ID,STATE,INFO FROM INFORMATION_SCHEMA.PROCESSLIST; let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE ID = $con1_id AND STATE = 'Waiting for table'; + WHERE ID = $con1_id AND STATE = 'Table lock'; --source include/wait_condition.inc #SELECT NOW(); --echo # Unlock and close table and wait for con1 to close too. diff --git a/mysql-test/t/merge.test b/mysql-test/t/merge.test index 63ad5a1e97c..f7ce6ba700b 100644 --- a/mysql-test/t/merge.test +++ b/mysql-test/t/merge.test @@ -216,20 +216,20 @@ drop table t3,t1,t2; # # temporary merge tables # -create table t1 (a int not null); -create table t2 (a int not null); -insert into t1 values (1); -insert into t2 values (2); -create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2); +CREATE TABLE t1 (c1 INT NOT NULL); +CREATE TABLE t2 (c1 INT NOT NULL); +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); +CREATE TEMPORARY TABLE t3 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t1,t2); --error ER_WRONG_MRG_TABLE -select * from t3; -create temporary table t4 (a int not null); -create temporary table t5 (a int not null); -insert into t4 values (1); -insert into t5 values (2); -create temporary table t6 (a int not null) ENGINE=MERGE UNION=(t4,t5); -select * from t6; -drop table t6, t3, t1, t2, t4, t5; +SELECT * FROM t3; +CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL); +CREATE TEMPORARY TABLE t5 (c1 INT NOT NULL); +INSERT INTO t4 VALUES (4); +INSERT INTO t5 VALUES (5); +CREATE TEMPORARY TABLE t6 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t4,t5); +SELECT * FROM t6; +DROP TABLE t6, t3, t1, t2, t4, t5; # # Bug#19627 - temporary merge table locking # MERGE table and its children must match in temporary type. @@ -556,7 +556,7 @@ CREATE TABLE t1(a INT); SELECT * FROM tm1; CHECK TABLE tm1; CREATE TABLE t2(a BLOB); ---error 1168 +--error ER_WRONG_MRG_TABLE SELECT * FROM tm1; CHECK TABLE tm1; ALTER TABLE t2 MODIFY a INT; @@ -969,9 +969,9 @@ CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1) LOCK TABLES t1 WRITE, t2 WRITE; INSERT INTO t1 VALUES (1); DROP TABLE t1; ---error 1168 +--error ER_TABLE_NOT_LOCKED SELECT * FROM t2; ---error ER_NO_SUCH_TABLE +--error ER_TABLE_NOT_LOCKED SELECT * FROM t1; UNLOCK TABLES; DROP TABLE t2; @@ -1407,6 +1407,7 @@ FLUSH TABLES m1, t1; UNLOCK TABLES; DROP TABLE t1, m1; + # # Bug#35068 - Assertion fails when reading from i_s.tables # and there is incorrect merge table @@ -1694,3 +1695,296 @@ while ($1) --enable_query_log drop table t_parent; set @@global.table_definition_cache=@save_table_definition_cache; + +# +# WL#4144 - Lock MERGE engine children +# +# Test DATA/INDEX DIRECTORY +# +--disable_warnings +DROP DATABASE IF EXISTS mysql_test1; +--enable_warnings +CREATE DATABASE mysql_test1; +--disable_query_log +# data/index directory don't work in HAVE_purify builds. Disable +# build-dependent warnings. +--disable_warnings +--echo CREATE TABLE t1 ... DATA DIRECTORY=... INDEX DIRECTORY=... +eval CREATE TABLE t1 (c1 INT) + DATA DIRECTORY='$MYSQLTEST_VARDIR/tmp' + INDEX DIRECTORY='$MYSQLTEST_VARDIR/tmp'; +--echo CREATE TABLE mysql_test1.t2 ... DATA DIRECTORY=... INDEX DIRECTORY=... +eval CREATE TABLE mysql_test1.t2 (c1 INT) + DATA DIRECTORY='$MYSQLTEST_VARDIR/tmp' + INDEX DIRECTORY='$MYSQLTEST_VARDIR/tmp'; +--enable_query_log +--enable_warnings +CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,mysql_test1.t2) + INSERT_METHOD=LAST; +INSERT INTO t1 VALUES (1); +INSERT INTO mysql_test1.t2 VALUES (2); +SELECT * FROM m1; +#--copy_file $MYSQLTEST_VARDIR/master-data/test/m1.MRG /tmp/mysql-test-m1.MRG +DROP TABLE t1, mysql_test1.t2, m1; +DROP DATABASE mysql_test1; +# +# Review detected Crash #1. Detaching main tables while in sub statement. +# +CREATE TABLE t1 (c1 INT); +CREATE TABLE t2 (c1 INT); +INSERT INTO t1 (c1) VALUES (1); +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) INSERT_METHOD=FIRST; +CREATE TABLE t3 (c1 INT); +INSERT INTO t3 (c1) VALUES (1); +CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM t3); +CREATE VIEW v1 AS SELECT foo.c1 c1, f1() c2, bar.c1 c3, f1() c4 + FROM tm1 foo, tm1 bar, t3; +SELECT * FROM v1; +DROP FUNCTION f1; +DROP VIEW v1; +DROP TABLE tm1, t1, t2, t3; +# +# Review detected Crash #2. Trying to attach temporary table twice. +# +CREATE TEMPORARY TABLE t1 (c1 INT); +CREATE TEMPORARY TABLE t2 (c1 INT); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) + INSERT_METHOD=FIRST; +CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM tm1); +INSERT INTO tm1 (c1) VALUES (1); +SELECT f1() FROM (SELECT 1) AS c1; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2; +# +# Review suggested test. DDL in a stored function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + CREATE TEMPORARY TABLE t1 (c1 INT); + CREATE TEMPORARY TABLE t2 (c1 INT); + CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2); + INSERT INTO t1 (c1) VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +SELECT f1() FROM (SELECT 1 UNION SELECT 1) c1; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2; +# +CREATE TEMPORARY TABLE t1 (c1 INT); +INSERT INTO t1 (c1) VALUES (1); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); +DELIMITER |; +--error ER_SP_BADSTATEMENT +CREATE FUNCTION f1() RETURNS INT +BEGIN + CREATE TEMPORARY TABLE t2 (c1 INT); + ALTER TEMPORARY TABLE tm1 UNION=(t1,t2); + INSERT INTO t2 (c1) VALUES (2); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +DROP TABLE tm1, t1; +# +# Base table. No LOCK TABLES, no functions/triggers. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +DROP TABLE tm1, t1; +# +# Base table. No LOCK TABLES, sub-statement that is run inside a function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO tm1 VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +SELECT f1(); +DROP FUNCTION f1; +DROP TABLE tm1, t1; +# +# Base table. LOCK TABLES, no functions/triggers. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +LOCK TABLE tm1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +UNLOCK TABLES; +DROP TABLE tm1, t1; +# +# Base table. LOCK TABLES, sub-statement that is run inside a function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO tm1 VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +LOCK TABLE tm1 WRITE; +SELECT f1(); +UNLOCK TABLES; +DROP FUNCTION f1; +DROP TABLE tm1, t1; +# +# Base table. LOCK TABLES statement that locks a table that has a trigger +# that inserts into a merge table, so an attempt is made to lock tables +# of a sub-statement. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +CREATE TRIGGER t2_ai AFTER INSERT ON t2 + FOR EACH ROW INSERT INTO tm1 VALUES(11); +LOCK TABLE t2 WRITE; +INSERT INTO t2 VALUES (2); +SELECT * FROM tm1; +SELECT * FROM t2; +UNLOCK TABLES; +DROP TRIGGER t2_ai; +DROP TABLE tm1, t1, t2; +# +# Temporary. No LOCK TABLES, no functions/triggers. +# +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +DROP TABLE tm1, t1; +# +# Temporary. No LOCK TABLES, sub-statement that is run inside a function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO tm1 VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +SELECT f1(); +DROP FUNCTION f1; +DROP TABLE tm1, t1; +# +# Temporary. LOCK TABLES, no functions/triggers. +# +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +CREATE TABLE t9 (c1 INT) ENGINE=MyISAM; +LOCK TABLE t9 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +UNLOCK TABLES; +DROP TABLE tm1, t1, t9; +# +# Temporary. LOCK TABLES, sub-statement that is run inside a function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO tm1 VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +CREATE TABLE t9 (c1 INT) ENGINE=MyISAM; +LOCK TABLE t9 WRITE; +SELECT f1(); +UNLOCK TABLES; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t9; +# +# Temporary. LOCK TABLES statement that locks a table that has a trigger +# that inserts into a merge table, so an attempt is made to lock tables +# of a sub-statement. +# +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TRIGGER t2_ai AFTER INSERT ON t2 + FOR EACH ROW INSERT INTO tm1 VALUES(11); +LOCK TABLE t2 WRITE; +INSERT INTO t2 VALUES (2); +SELECT * FROM tm1; +SELECT * FROM t2; +UNLOCK TABLES; +DROP TRIGGER t2_ai; +DROP TABLE tm1, t1, t2; +--echo # +--echo # Don't select MERGE child when trying to get prelocked table. +--echo # +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +CREATE TRIGGER tm1_ai AFTER INSERT ON tm1 + FOR EACH ROW INSERT INTO t1 VALUES(11); +LOCK TABLE tm1 WRITE, t1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +UNLOCK TABLES; +LOCK TABLE t1 WRITE, tm1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +UNLOCK TABLES; +DROP TRIGGER tm1_ai; +DROP TABLE tm1, t1; + +# Don't resurrect chopped off prelocked tables. +# The problem is not visible by test results; only by debugging. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t3 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t4 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t5 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5) + INSERT_METHOD=LAST; +CREATE TRIGGER t2_au AFTER UPDATE ON t2 + FOR EACH ROW INSERT INTO t3 VALUES(33); +CREATE FUNCTION f1() RETURNS INT + RETURN (SELECT MAX(c1) FROM t4); +LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE; +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(2); +INSERT INTO t3 VALUES(3); +INSERT INTO t4 VALUES(4); +INSERT INTO t5 VALUES(5); + connect (con1,localhost,root,,); + send UPDATE t2, tm1 SET t2.c1=f1(); +connection default; +# Force reopen in other thread. +#sleep 1; +FLUSH TABLES; +#sleep 1; +FLUSH TABLES; +#sleep 1; +UNLOCK TABLES; + connection con1; + reap; + disconnect con1; +connection default; +SELECT * FROM tm1; +DROP TRIGGER t2_au; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2, t3, t4, t5; + + + +--echo End of 6.0 tests diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index 1288dc36a90..e92f68965d8 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -631,6 +631,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, { if (lock->write.data->type == TL_WRITE_ONLY) { + /* purecov: begin tested */ /* Allow lock owner to bypass TL_WRITE_ONLY. */ if (!thr_lock_owner_equal(data->owner, lock->write.data->owner)) { @@ -639,6 +640,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, result= THR_LOCK_ABORTED; /* Can't wait for this one */ goto end; } + /* purecov: end */ } /* diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 1514651c1c5..8760b398ec9 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -5265,34 +5265,35 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, } -/* - General function to prepare handler for certain behavior +/** + General function to prepare handler for certain behavior. - SYNOPSIS - extra() + @param[in] operation operation to execute operation Operation type for extra call - RETURN VALUE - >0 Error code - 0 Success + @return status + @retval 0 success + @retval >0 error code + + @detail - DESCRIPTION extra() is called whenever the server wishes to send a hint to the storage engine. The MyISAM engine implements the most hints. We divide the parameters into the following categories: - 1) Parameters used by most handlers - 2) Parameters used by some non-MyISAM handlers - 3) Parameters used only by MyISAM - 4) Parameters only used by temporary tables for query processing - 5) Parameters only used by MyISAM internally - 6) Parameters not used at all - 7) Parameters only used by federated tables for query processing - 8) Parameters only used by NDB + 1) Operations used by most handlers + 2) Operations used by some non-MyISAM handlers + 3) Operations used only by MyISAM + 4) Operations only used by temporary tables for query processing + 5) Operations only used by MyISAM internally + 6) Operations not used at all + 7) Operations only used by federated tables for query processing + 8) Operations only used by NDB + 9) Operations only used by MERGE The partition handler need to handle category 1), 2) and 3). - 1) Parameters used by most handlers + 1) Operations used by most handlers ----------------------------------- HA_EXTRA_RESET: This option is used by most handlers and it resets the handler state @@ -5331,7 +5332,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, ensure disk based tables are flushed at end of query execution. Currently is never used. - 2) Parameters used by some non-MyISAM handlers + 2) Operations used by some non-MyISAM handlers ---------------------------------------------- HA_EXTRA_KEYREAD_PRESERVE_FIELDS: This is a strictly InnoDB feature that is more or less undocumented. @@ -5350,7 +5351,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, SQL constructs. Not used by MyISAM. - 3) Parameters used only by MyISAM + 3) Operations used only by MyISAM --------------------------------- HA_EXTRA_NORMAL: Only used in MyISAM to reset quick mode, not implemented by any other @@ -5481,7 +5482,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, Only used by MyISAM, called when altering table, closing tables to enforce a reopen of the table files. - 4) Parameters only used by temporary tables for query processing + 4) Operations only used by temporary tables for query processing ---------------------------------------------------------------- HA_EXTRA_RESET_STATE: Same as reset() except that buffers are not released. If there is @@ -5512,7 +5513,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, tables used in query processing. Not handled by partition handler. - 5) Parameters only used by MyISAM internally + 5) Operations only used by MyISAM internally -------------------------------------------- HA_EXTRA_REINIT_CACHE: This call reinitializes the READ CACHE described above if there is one @@ -5547,19 +5548,19 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, HA_EXTRA_CHANGE_KEY_TO_UNIQUE: Only used by MyISAM, never called. - 6) Parameters not used at all + 6) Operations not used at all ----------------------------- HA_EXTRA_KEY_CACHE: HA_EXTRA_NO_KEY_CACHE: This parameters are no longer used and could be removed. - 7) Parameters only used by federated tables for query processing + 7) Operations only used by federated tables for query processing ---------------------------------------------------------------- HA_EXTRA_INSERT_WITH_UPDATE: Inform handler that an "INSERT...ON DUPLICATE KEY UPDATE" will be executed. This condition is unset by HA_EXTRA_NO_IGNORE_DUP_KEY. - 8) Parameters only used by NDB + 8) Operations only used by NDB ------------------------------ HA_EXTRA_DELETE_CANNOT_BATCH: HA_EXTRA_UPDATE_CANNOT_BATCH: @@ -5567,6 +5568,14 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, and should perform them immediately. This may be needed when table has AFTER DELETE/UPDATE triggers which access to subject table. These flags are reset by the handler::extra(HA_EXTRA_RESET) call. + + 9) Operations only used by MERGE + ------------------------------ + HA_EXTRA_ADD_CHILDREN_LIST: + HA_EXTRA_ATTACH_CHILDREN: + HA_EXTRA_IS_ATTACHED_CHILDREN: + HA_EXTRA_DETACH_CHILDREN: + Special actions for MERGE tables. Ignore. */ int ha_partition::extra(enum ha_extra_function operation) @@ -5659,12 +5668,21 @@ int ha_partition::extra(enum ha_extra_function operation) /* Category 7), used by federated handlers */ case HA_EXTRA_INSERT_WITH_UPDATE: DBUG_RETURN(loop_extra(operation)); - /* Category 8) Parameters only used by NDB */ + /* Category 8) Operations only used by NDB */ case HA_EXTRA_DELETE_CANNOT_BATCH: case HA_EXTRA_UPDATE_CANNOT_BATCH: { /* Currently only NDB use the *_CANNOT_BATCH */ break; + } + /* Category 9) Operations only used by MERGE */ + case HA_EXTRA_ADD_CHILDREN_LIST: + case HA_EXTRA_ATTACH_CHILDREN: + case HA_EXTRA_IS_ATTACHED_CHILDREN: + case HA_EXTRA_DETACH_CHILDREN: + { + /* Special actions for MERGE tables. Ignore. */ + break; } /* http://dev.mysql.com/doc/refman/5.1/en/partitioning-limitations.html diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 2f15ecb2b71..dc3f59dfec0 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1228,9 +1228,6 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name); TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name); -void detach_merge_children(TABLE *table, bool clear_refs); -bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, - TABLE_LIST *new_child_list, TABLE_LIST **new_last); thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table); void execute_init_command(THD *thd, sys_var_str *init_command_var, rw_lock_t *var_mutex); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 17b4360c8f9..6751dc763be 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -164,6 +164,11 @@ static void check_unused(void) I_P_List_iterator it(share->free_tables); while ((entry= it++)) { + /* We must not have TABLEs in the free list that have their file closed. */ + DBUG_ASSERT(entry->db_stat && entry->file); + /* Merge children should be detached from a merge parent */ + DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); + if (entry->in_use) { DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */ @@ -372,6 +377,10 @@ static void table_def_use_table(THD *thd, TABLE *table) /* Add table to list of used tables for this share. */ table->s->used_tables.push_front(table); table->in_use= thd; + /* The ex-unused table must be fully functional. */ + DBUG_ASSERT(table->db_stat && table->file); + /* The children must be detached from the table. */ + DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); } @@ -813,9 +822,6 @@ static void free_cache_entry(TABLE *table) { DBUG_ENTER("free_cache_entry"); - /* Assert that MERGE children are not attached before final close. */ - DBUG_ASSERT(!table->is_children_attached()); - /* This should be done before releasing table share. */ table_def_remove_table(table); @@ -1137,14 +1143,11 @@ static void mark_temp_tables_as_free_for_reuse(THD *thd) { table->query_id= 0; table->file->ha_reset(); - /* - Detach temporary MERGE children from temporary parent to allow new - attach at next open. Do not do the detach, if close_thread_tables() - is called from a sub-statement. The temporary table might still be - used in the top-level statement. - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); + + /* Detach temporary MERGE children from temporary parent. */ + DBUG_ASSERT(table->file); + table->file->extra(HA_EXTRA_DETACH_CHILDREN); + /* Reset temporary table lock type to it's default value (TL_WRITE). @@ -1344,6 +1347,20 @@ void close_thread_tables(THD *thd, table->s->table_name.str, (long) table)); #endif + /* Detach MERGE children after every statement. Even under LOCK TABLES. */ + for (table= thd->open_tables; table; table= table->next) + { + /* Table might be in use by some outer statement. */ + DBUG_PRINT("tcache", ("table: '%s' query_id: %lu", + table->s->table_name.str, (ulong) table->query_id)); + if (thd->locked_tables_mode <= LTM_LOCK_TABLES || + table->query_id == thd->query_id) + { + DBUG_ASSERT(table->file); + table->file->extra(HA_EXTRA_DETACH_CHILDREN); + } + } + /* We are assuming here that thd->derived_tables contains ONLY derived tables for this substatement. i.e. instead of approach which uses @@ -1481,12 +1498,6 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) safe_mutex_assert_owner(&LOCK_open); *table_ptr=table->next; - /* - When closing a MERGE parent or child table, detach the children first. - Clear child table references to force new assignment at next open. - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); table->mdl_lock_data= 0; if (table->needs_reopen() || @@ -1497,8 +1508,9 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) } else { - /* Assert that MERGE children are not attached in unused_tables. */ - DBUG_ASSERT(!table->is_children_attached()); + /* Avoid to have MERGE tables with attached children in unused_tables. */ + DBUG_ASSERT(table->file); + table->file->extra(HA_EXTRA_DETACH_CHILDREN); /* Free memory and reset for next loop */ free_field_buffers_larger_than(table,MAX_TDC_BLOB_SIZE); @@ -1964,19 +1976,6 @@ void close_temporary_table(THD *thd, TABLE *table, table->s->db.str, table->s->table_name.str, (long) table, table->alias)); - /* - When closing a MERGE parent or child table, detach the children - first. Clear child table references as MERGE table cannot be - reopened after final close of one of its tables. - - This is necessary here because it is sometimes called with attached - tables and without prior close_thread_tables(). E.g. in - mysql_alter_table(), mysql_rm_table_part2(), mysql_truncate(), - drop_open_table(). - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); - if (table->prev) { table->prev->next= table->next; @@ -2462,15 +2461,10 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (table->s->table_cache_key.length == key_length && !memcmp(table->s->table_cache_key.str, key, key_length)) { - /* - When looking for a usable TABLE, ignore MERGE children, as they - belong to their parent and cannot be used explicitly. - */ if (!my_strcasecmp(system_charset_info, table->alias, alias) && table->query_id != thd->query_id && /* skip tables already used */ (thd->locked_tables_mode == LTM_LOCK_TABLES || - table->query_id == 0) && - !table->parent) + table->query_id == 0)) { int distance= ((int) table->reginfo.lock_type - (int) table_list->lock_type); @@ -2622,6 +2616,16 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (share->is_view) { + /* + If parent_l of the table_list is non null then a merge table + has this view as child table, which is not supported. + */ + if (table_list->parent_l) + { + my_error(ER_WRONG_MRG_TABLE, MYF(0)); + goto err_unlock; + } + /* This table is a view. Validate its metadata version: in particular, that it was a view when the statement was prepared. @@ -2826,6 +2830,9 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->clear_column_bitmaps(); table_list->table= table; DBUG_ASSERT(table->key_read == 0); + /* Tables may be reused in a sub statement. */ + if (table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)) + table->file->extra(HA_EXTRA_DETACH_CHILDREN); DBUG_RETURN(FALSE); err_unlock: @@ -3537,340 +3544,6 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, } -/** - @brief Add list of MERGE children to a TABLE_LIST list. - - @param[in] tlist the parent TABLE_LIST object just opened - - @return status - @retval 0 OK - @retval != 0 Error - - @detail - When a MERGE parent table has just been opened, insert the - TABLE_LIST chain from the MERGE handle into the table list used for - opening tables for this statement. This lets the children be opened - too. -*/ - -static int add_merge_table_list(TABLE_LIST *tlist) -{ - TABLE *parent= tlist->table; - TABLE_LIST *child_l; - DBUG_ENTER("add_merge_table_list"); - DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); - - /* Must not call this with attached children. */ - DBUG_ASSERT(!parent->children_attached); - /* Must not call this with children list in place. */ - DBUG_ASSERT(tlist->next_global != parent->child_l); - /* Prevent inclusion of another MERGE table. Could make infinite recursion. */ - if (tlist->parent_l) - { - my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), tlist->alias); - DBUG_RETURN(1); - } - - /* Fix children.*/ - for (child_l= parent->child_l; ; child_l= child_l->next_global) - { - /* - Note: child_l->table may still be set if this parent was taken - from the unused_tables chain. Ignore this fact here. The - reference will be replaced by the handler in - ::extra(HA_EXTRA_ATTACH_CHILDREN). - */ - - /* Set lock type. */ - child_l->lock_type= tlist->lock_type; - - /* Set parent reference. */ - child_l->parent_l= tlist; - - /* Break when this was the last child. */ - if (&child_l->next_global == parent->child_last_l) - break; - } - - /* Insert children into the table list. */ - *parent->child_last_l= tlist->next_global; - tlist->next_global= parent->child_l; - - /* - Do not fix the prev_global pointers. We will remove the - chain soon anyway. - */ - - DBUG_RETURN(0); -} - - -/** - @brief Attach MERGE children to the parent. - - @param[in] tlist the child TABLE_LIST object just opened - - @return status - @retval 0 OK - @retval != 0 Error - - @note - This is called when the last MERGE child has just been opened, let - the handler attach the MyISAM tables to the MERGE table. Remove - MERGE TABLE_LIST chain from the statement list so that it cannot be - changed or freed. -*/ - -static int attach_merge_children(TABLE_LIST *tlist) -{ - TABLE *parent= tlist->parent_l->table; - int error; - DBUG_ENTER("attach_merge_children"); - DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); - - /* Must not call this with attached children. */ - DBUG_ASSERT(!parent->children_attached); - /* Must call this with children list in place. */ - DBUG_ASSERT(tlist->parent_l->next_global == parent->child_l); - - /* Attach MyISAM tables to MERGE table. */ - error= parent->file->extra(HA_EXTRA_ATTACH_CHILDREN); - - /* - Remove children from the table list. Even in case of an error. - This should prevent tampering with them. - */ - tlist->parent_l->next_global= *parent->child_last_l; - - /* - Do not fix the last childs next_global pointer. It is needed for - stepping to the next table in the enclosing loop in open_tables(). - Do not fix prev_global pointers. We did not set them. - */ - - if (error) - { - DBUG_PRINT("error", ("attaching MERGE children failed: %d", my_errno)); - parent->file->print_error(error, MYF(0)); - DBUG_RETURN(1); - } - - parent->children_attached= TRUE; - DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); - - /* - Note that we have the cildren in the thd->open_tables list at this - point. - */ - - DBUG_RETURN(0); -} - - -/** - @brief Detach MERGE children from the parent. - - @note - Call this before the first table of a MERGE table (parent or child) - is closed. - - When closing thread tables at end of statement, both parent and - children are in thd->open_tables and will be closed. In most cases - the children will be closed before the parent. They are opened after - the parent and thus stacked into thd->open_tables before it. - - To avoid that we touch a closed children in any way, we must detach - the children from the parent when the first belonging table is - closed (parent or child). - - All references to the children should be removed on handler level - and optionally on table level. - - @note - Assure that you call it for a MERGE parent or child only. - Either table->child_l or table->parent must be set. - - @param[in] table the TABLE object of the parent - @param[in] clear_refs if to clear TABLE references - this must be true when called from - close_thread_tables() to enable fresh - open in open_tables() - it must be false when called in preparation - for reopen_tables() -*/ - -void detach_merge_children(TABLE *table, bool clear_refs) -{ - TABLE_LIST *child_l; - TABLE *parent= table->child_l ? table : table->parent; - bool first_detach; - DBUG_ENTER("detach_merge_children"); - /* - Either table->child_l or table->parent must be set. Parent must have - child_l set. - */ - DBUG_ASSERT(parent && parent->child_l); - DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx clear_refs: %d", - table->s->db.str, table->s->table_name.str, - (long) table, clear_refs)); - DBUG_PRINT("myrg", ("parent: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); - - /* - In a open_tables() loop it can happen that not all tables have their - children attached yet. Also this is called for every child and the - parent from close_thread_tables(). - */ - if ((first_detach= parent->children_attached)) - { - (void) parent->file->extra(HA_EXTRA_DETACH_CHILDREN); - parent->children_attached= FALSE; - DBUG_PRINT("myrg", ("detached parent: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); - } - else - DBUG_PRINT("myrg", ("parent is already detached")); - - if (clear_refs) - { - /* In any case clear the own parent reference. (***) */ - table->parent= NULL; - - /* - On the first detach, clear all references. If this table is the - parent, we still may need to clear the child references. The first - detach might not have done this. - */ - if (first_detach || (table == parent)) - { - /* Clear TABLE references to force new assignment at next open. */ - for (child_l= parent->child_l; ; child_l= child_l->next_global) - { - /* - Do not DBUG_ASSERT(child_l->table); open_tables might be - incomplete. - - Clear the parent reference of the children only on the first - detach. The children might already be closed. They will clear - it themseves when this function is called for them with - 'clear_refs' true. See above "(***)". - */ - if (first_detach && child_l->table) - child_l->table->parent= NULL; - - /* Clear the table reference to force new assignment at next open. */ - child_l->table= NULL; - - /* Break when this was the last child. */ - if (&child_l->next_global == parent->child_last_l) - break; - } - } - } - - DBUG_VOID_RETURN; -} - - -/** - @brief Fix MERGE children after open. - - @param[in] old_child_list first list member from original table - @param[in] old_last pointer to &next_global of last list member - @param[in] new_child_list first list member from freshly opened table - @param[in] new_last pointer to &next_global of last list member - - @return mismatch - @retval FALSE OK, no mismatch - @retval TRUE Error, lists mismatch - - @detail - Main action is to copy TABLE reference for each member of original - child list to new child list. After a fresh open these references - are NULL. Assign the old children to the new table. Some of them - might also be reopened or will be reopened soon. - - Other action is to verify that the table definition with respect to - the UNION list did not change. - - @note - This function terminates the child list if the respective '*_last' - pointer is non-NULL. Do not call it from a place where the list is - embedded in another list and this would break it. - - Terminating the list is required for example in the first - reopen_table() after open_tables(). open_tables() requires the end - of the list not to be terminated because other tables could follow - behind the child list. - - If a '*_last' pointer is NULL, the respective list is assumed to be - NULL terminated. -*/ - -bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, - TABLE_LIST *new_child_list, TABLE_LIST **new_last) -{ - bool mismatch= FALSE; - DBUG_ENTER("fix_merge_after_open"); - DBUG_PRINT("myrg", ("old last addr: 0x%lx new last addr: 0x%lx", - (long) old_last, (long) new_last)); - - /* Terminate the lists for easier check of list end. */ - if (old_last) - *old_last= NULL; - if (new_last) - *new_last= NULL; - - for (;;) - { - DBUG_PRINT("myrg", ("old list item: 0x%lx new list item: 0x%lx", - (long) old_child_list, (long) new_child_list)); - /* Break if one of the list is at its end. */ - if (!old_child_list || !new_child_list) - break; - /* Old table has references to child TABLEs. */ - DBUG_ASSERT(old_child_list->table); - /* New table does not yet have references to child TABLEs. */ - DBUG_ASSERT(!new_child_list->table); - DBUG_PRINT("myrg", ("old table: '%s'.'%s' new table: '%s'.'%s'", - old_child_list->db, old_child_list->table_name, - new_child_list->db, new_child_list->table_name)); - /* Child db.table names must match. */ - if (strcmp(old_child_list->table_name, new_child_list->table_name) || - strcmp(old_child_list->db, new_child_list->db)) - break; - /* - Copy TABLE reference. Child TABLE objects are still in place - though not necessarily open yet. - */ - DBUG_PRINT("myrg", ("old table ref: 0x%lx replaces new table ref: 0x%lx", - (long) old_child_list->table, - (long) new_child_list->table)); - new_child_list->table= old_child_list->table; - /* Step both lists. */ - old_child_list= old_child_list->next_global; - new_child_list= new_child_list->next_global; - } - DBUG_PRINT("myrg", ("end of list, mismatch: %d", mismatch)); - /* - If the list pointers are not both NULL after the loop, then the - lists differ. If the are both identical, but not NULL, then they - have at least one table in common and hence the rest of the list - would be identical too. But in this case the loop woul run until the - list end, where both pointers would become NULL. - */ - if (old_child_list != new_child_list) - mismatch= TRUE; - if (mismatch) - my_error(ER_TABLE_DEF_CHANGED, MYF(0)); - - DBUG_RETURN(mismatch); -} - - /* Return a appropriate read lock type given a table object. @@ -4079,19 +3752,6 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) if (error) { - /* - If in a MERGE table open, we need to remove the children list - from statement table list before restarting. Otherwise the list - will be inserted another time. - */ - if (tables->parent_l) - { - TABLE_LIST *parent_l= tables->parent_l; - /* The parent table should be correctly open at this point. */ - DBUG_ASSERT(parent_l->table); - parent_l->next_global= *parent_l->table->child_last_l; - } - if (action) { /* @@ -4132,7 +3792,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) } result= -1; // Fatal error - break; + goto err; } /* @@ -4222,16 +3882,15 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) goto err; } - /* Attach MERGE children if not locked already. */ - DBUG_PRINT("tcache", ("is parent: %d is child: %d", - test(tables->table->child_l), - test(tables->parent_l))); - if ((!thd->locked_tables_mode || tables->table->s->tmp_table) && - ((tables->table->child_l && - add_merge_table_list(tables)) || - (tables->parent_l && - (&tables->next_global == tables->parent_l->table->child_last_l) && - attach_merge_children(tables)))) + /* + After opening a MERGE table add the children to the query list of + tables, so that they are opened too. + Note that placeholders don't have the handler open. + */ + /* MERGE tables need to access parent and child TABLE_LISTs. */ + DBUG_ASSERT(tables->table->pos_in_table_list == tables); + /* Non-MERGE tables ignore this call. */ + if (tables->table->file->extra(HA_EXTRA_ADD_CHILDREN_LIST)) { result= -1; goto err; @@ -4263,6 +3922,29 @@ process_view_routines: } } + /* + After successful open of all tables, including MERGE parents and + children, attach the children to their parents. At end of statement, + the children are detached. Attaching and detaching are always done, + even under LOCK TABLES. + */ + for (tables= *start; tables; tables= tables->next_global) + { + TABLE *tbl= tables->table; + + /* Schema tables may not have a TABLE object here. */ + if (tbl && tbl->file->ht->db_type == DB_TYPE_MRG_MYISAM) + { + /* MERGE tables need to access parent and child TABLE_LISTs. */ + DBUG_ASSERT(tbl->pos_in_table_list == tables); + if (tbl->file->extra(HA_EXTRA_ATTACH_CHILDREN)) + { + result= -1; + goto err; + } + } + } + err: thd_proc_info(thd, 0); free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block @@ -4272,17 +3954,6 @@ process_view_routines: if (result && tables) { - /* - Some functions determine success as (tables->table != NULL). - tables->table is in thd->open_tables. It won't go lost. If the - error happens on a MERGE child, clear the parents TABLE reference. - */ - if (tables->parent_l) - { - if (tables->parent_l->next_global == tables->parent_l->table->child_l) - tables->parent_l->next_global= *tables->parent_l->table->child_last_l; - tables->parent_l->table= NULL; - } tables->table= NULL; } DBUG_PRINT("tcache", ("returning: %d", result)); @@ -4447,7 +4118,7 @@ retry: */ DBUG_ASSERT(table_list->table); table= table_list->table; - if (table->child_l) + if (table->file->ht->db_type == DB_TYPE_MRG_MYISAM) { /* A MERGE table must not come here. */ /* purecov: begin tested */ @@ -4896,7 +4567,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, and was marked as occupied during open_tables() as free for reuse. */ mark_real_tables_as_free_for_reuse(first_not_own); - DBUG_PRINT("info",("locked_tables_mode= PRELOCKED")); + DBUG_PRINT("info",("locked_tables_mode= LTM_PRELOCKED")); thd->locked_tables_mode= LTM_PRELOCKED; } } @@ -8481,9 +8152,7 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup) /* Note that we need to hold LOCK_open while changing the open_tables list. Another thread may work on it. - (See: mysql_notify_thread_having_shared_lock()) - Closing a MERGE child before the parent would be fatal if the - other thread tries to abort the MERGE lock in between. + (See: notify_thread_having_shared_lock()) */ while (thd->open_tables) found_old_table|= close_thread_table(thd, &thd->open_tables); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index f6eafaacf61..289cc77fb27 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4396,7 +4396,8 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, "Failed to open partially restored table")); } /* A MERGE table must not come here. */ - DBUG_ASSERT(!table->table || !table->table->child_l); + DBUG_ASSERT(!table->table || + table->table->file->ht->db_type != DB_TYPE_MRG_MYISAM); DBUG_RETURN(0); } @@ -4463,7 +4464,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, } /* A MERGE table must not come here. */ - DBUG_ASSERT(!table->child_l); + DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM); /* REPAIR TABLE ... USE_FRM for temporary tables makes little sense. @@ -7270,7 +7271,8 @@ view_err: } else { - if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + if (!table->s->tmp_table && + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto err_new_table_cleanup; thd_proc_info(thd, "manage keys"); alter_table_manage_keys(table, table->file->indexes_are_disabled(), diff --git a/sql/table.cc b/sql/table.cc index 3943dc8e50b..dd39d05733e 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4555,24 +4555,6 @@ void TABLE::mark_columns_needed_for_insert() } -/** - @brief Check if this is part of a MERGE table with attached children. - - @return status - @retval TRUE children are attached - @retval FALSE no MERGE part or children not attached - - @detail - A MERGE table consists of a parent TABLE and zero or more child - TABLEs. Each of these TABLEs is called a part of a MERGE table. -*/ - -bool TABLE::is_children_attached(void) -{ - return((child_l && children_attached) || - (parent && parent->children_attached)); -} - /* Cleanup this table for re-execution. diff --git a/sql/table.h b/sql/table.h index c30b6e7ad80..918fb8dfff0 100644 --- a/sql/table.h +++ b/sql/table.h @@ -635,11 +635,6 @@ private: public: - /* For the below MERGE related members see top comment in ha_myisammrg.cc */ - TABLE *parent; /* Set in MERGE child. Ptr to parent */ - TABLE_LIST *child_l; /* Set in MERGE parent. List of children */ - TABLE_LIST **child_last_l; /* Set in MERGE parent. End of list */ - THD *in_use; /* Which thread uses this */ Field **field; /* Pointer to fields */ @@ -809,8 +804,6 @@ public: my_bool insert_or_update; /* Can be used by the handler */ my_bool alias_name_used; /* true if table_name is alias */ my_bool get_fields_in_item_tree; /* Signal to fix_field */ - /* If MERGE children attached to parent. See top comment in ha_myisammrg.cc */ - my_bool children_attached; REGINFO reginfo; /* field connections */ MEM_ROOT mem_root; @@ -861,7 +854,6 @@ public: */ inline bool needs_reopen() { return s->version != refresh_version; } - bool is_children_attached(void); }; diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index b2181636492..3d1b2b13c1e 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -33,40 +33,29 @@ and hence through open_tables(). When the parent appears in the list of tables to open, the initial open of the handler does nothing but read the meta file and collect a list of TABLE_LIST objects for the - children. This list is attached to the parent TABLE object as - TABLE::child_l. The end of the children list is saved in - TABLE::child_last_l. + children. This list is attached to the handler object as + ha_myisammrg::children_l. The end of the children list is saved in + ha_myisammrg::children_last_l. - Back in open_tables(), add_merge_table_list() is called. It updates - each list member with the lock type and a back pointer to the parent - TABLE_LIST object TABLE_LIST::parent_l. The list is then inserted in - the list of tables to open, right behind the parent. Consequently, - open_tables() opens the children, one after the other. The TABLE - references of the TABLE_LIST objects are implicitly set to the open - tables. The children are opened as independent MyISAM tables, right as - if they are used by the SQL statement. + Back in open_tables(), handler::extra(HA_EXTRA_ADD_CHILDREN_LIST) is + called. It updates each list member with the lock type and a back + pointer to the parent TABLE_LIST object TABLE_LIST::parent_l. The list + is then inserted in the list of tables to open, right behind the + parent. Consequently, open_tables() opens the children, one after the + other. The TABLE references of the TABLE_LIST objects are implicitly + set to the open tables by open_table(). The children are opened as + independent MyISAM tables, right as if they are used by the SQL + statement. - TABLE_LIST::parent_l is required to find the parent 1. when the last - child has been opened and children are to be attached, and 2. when an - error happens during child open and the child list must be removed - from the queuery list. In these cases the current child does not have - TABLE::parent set or does not have a TABLE at all respectively. - - When the last child is open, attach_merge_children() is called. It - removes the list of children from the open list. Then the children are - "attached" to the parent. All required references between parent and + When all tables from the statement query list are open, + handler::extra(HA_EXTRA_ATTACH_CHILDREN) is called. It "attaches" the + children to the parent. All required references between parent and children are set up. The MERGE storage engine sets up an array with references to the low-level MyISAM table objects (MI_INFO). It remembers the state of the table in MYRG_INFO::children_attached. - Every child TABLE::parent references the parent TABLE object. That way - TABLE objects belonging to a MERGE table can be identified. - TABLE::parent is required because the parent and child TABLE objects - can live longer than the parent TABLE_LIST object. So the path - child->pos_in_table_list->parent_l->table can be broken. - If necessary, the compatibility of parent and children is checked. This check is necessary when any of the objects are reopend. This is detected by comparing the current table def version against the @@ -80,14 +69,20 @@ myisammrg_attach_children_callback() sets it ot TRUE if a table def version mismatches the remembered child def version. - Finally the parent TABLE::children_attached is set. + The children chain remains in the statement query list until the table + is closed or the children are detached. This is done so that the + children are locked by lock_tables(). + + At statement end the children are detached. At the next statement + begin the open-add-attach sequence repeats. There is no exception for + LOCK TABLES. The fresh establishment of the parent-child relationship + before every statement catches numerous cases of ALTER/FLUSH/DROP/etc + of parent or children during LOCK TABLES. --- On parent open the storage engine structures are allocated and initialized. They stay with the open table until its final close. - - */ #ifdef USE_PRAGMA_IMPLEMENTATION @@ -118,7 +113,10 @@ static handler *myisammrg_create_handler(handlerton *hton, ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg) :handler(hton, table_arg), file(0), is_cloned(0) -{} +{ + init_sql_alloc(&children_mem_root, max(4 * sizeof(TABLE_LIST), FN_REFLEN) + + ALLOC_ROOT_MIN_BLOCK_SIZE, 0); +} /** @@ -126,7 +124,9 @@ ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg) */ ha_myisammrg::~ha_myisammrg(void) -{} +{ + free_root(&children_mem_root, MYF(0)); +} static const char *ha_myisammrg_exts[] = { @@ -178,45 +178,48 @@ const char *ha_myisammrg::index_type(uint key_number) /** - @brief Callback function for open of a MERGE parent table. - - @detail This function adds a TABLE_LIST object for a MERGE child table - to a list of tables of the parent TABLE object. It is called for - each child table. - - The list of child TABLE_LIST objects is kept in the TABLE object of - the parent for the whole life time of the MERGE table. It is - inserted in the statement list behind the MERGE parent TABLE_LIST - object when the MERGE table is opened. It is removed from the - statement list after the last child is opened. - - All memeory used for the child TABLE_LIST objects and the strings - referred by it are taken from the parent TABLE::mem_root. Thus they - are all freed implicitly at the final close of the table. - - TABLE::child_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global - # # ^ # ^ - # # | # | - # # +--------- TABLE_LIST::prev_global - # # | - # |<--- TABLE_LIST::prev_global | - # | - TABLE::child_last_l -----------------------------------------+ + Callback function for open of a MERGE parent table. @param[in] callback_param data pointer as given to myrg_parent_open() + this is used to pass the handler handle @param[in] filename file name of MyISAM table without extension. @return status @retval 0 OK @retval != 0 Error + + @detail + + This function adds a TABLE_LIST object for a MERGE child table to a + list of tables in the parent handler object. It is called for each + child table. + + The list of child TABLE_LIST objects is kept in the handler object + of the parent for the whole life time of the MERGE table. It is + inserted in the statement query list behind the MERGE parent + TABLE_LIST object when the MERGE table is opened. It is removed from + the statement query list at end of statement or at children detach. + + All memory used for the child TABLE_LIST objects and the strings + referred by it are taken from the parent + ha_myisammrg::children_mem_root. Thus they are all freed implicitly at + the final close of the table. + + children_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global + # # ^ # ^ + # # | # | + # # +--------- TABLE_LIST::prev_global + # # | + # |<--- TABLE_LIST::prev_global | + # | + children_last_l -----------------------------------------+ */ static int myisammrg_parent_open_callback(void *callback_param, const char *filename) { - ha_myisammrg *ha_myrg; - TABLE *parent; + ha_myisammrg *ha_myrg= (ha_myisammrg*) callback_param; TABLE_LIST *child_l; const char *db; const char *table_name; @@ -242,11 +245,8 @@ static int myisammrg_parent_open_callback(void *callback_param, dirlen-= db - dir_path; /* This is now the length of 'db'. */ DBUG_PRINT("myrg", ("open: '%s'.'%s'", db, table_name)); - ha_myrg= (ha_myisammrg*) callback_param; - parent= ha_myrg->table_ptr(); - /* Get a TABLE_LIST object. */ - if (!(child_l= (TABLE_LIST*) alloc_root(&parent->mem_root, + if (!(child_l= (TABLE_LIST*) alloc_root(&ha_myrg->children_mem_root, sizeof(TABLE_LIST)))) { /* purecov: begin inspected */ @@ -258,68 +258,231 @@ static int myisammrg_parent_open_callback(void *callback_param, /* Set database (schema) name. */ child_l->db_length= dirlen; - child_l->db= strmake_root(&parent->mem_root, db, dirlen); + child_l->db= strmake_root(&ha_myrg->children_mem_root, db, dirlen); /* Set table name. */ child_l->table_name_length= strlen(table_name); - child_l->table_name= strmake_root(&parent->mem_root, table_name, + child_l->table_name= strmake_root(&ha_myrg->children_mem_root, table_name, child_l->table_name_length); /* Convert to lowercase if required. */ if (lower_case_table_names && child_l->table_name_length) + { + /* purecov: begin tested */ child_l->table_name_length= my_casedn_str(files_charset_info, child_l->table_name); + /* purecov: end */ + } /* Set alias. */ child_l->alias= child_l->table_name; - /* - FIXME: Actually we should use some other mem-root here. - To be fixed once Ingo pushes his patch for WL4144. - */ - alloc_mdl_locks(child_l, &parent->mem_root); - /* Initialize table map to 'undefined'. */ child_l->init_child_def_version(); - /* Link TABLE_LIST object into the parent list. */ - if (!parent->child_last_l) + /* Link TABLE_LIST object into the children list. */ + if (ha_myrg->children_last_l) + child_l->prev_global= ha_myrg->children_last_l; + else { - /* Initialize parent->child_last_l when handling first child. */ - parent->child_last_l= &parent->child_l; + /* Initialize ha_myrg->children_last_l when handling first child. */ + ha_myrg->children_last_l= &ha_myrg->children_l; } - *parent->child_last_l= child_l; - child_l->prev_global= parent->child_last_l; - parent->child_last_l= &child_l->next_global; + *ha_myrg->children_last_l= child_l; + ha_myrg->children_last_l= &child_l->next_global; DBUG_RETURN(0); } /** - @brief Callback function for attaching a MERGE child table. + Open a MERGE parent table, but not its children. - @detail This function retrieves the MyISAM table handle from the - next child table. It is called for each child table. + @param[in] name MERGE table path name + @param[in] mode read/write mode, unused + @param[in] test_if_locked open flags - @param[in] callback_param data pointer as given to - myrg_attach_children() + @return status + @retval 0 OK + @retval -1 Error, my_errno gives reason + + @detail + This function initializes the MERGE storage engine structures + and adds a child list of TABLE_LIST to the parent handler. +*/ + +int ha_myisammrg::open(const char *name, int mode __attribute__((unused)), + uint test_if_locked) +{ + DBUG_ENTER("ha_myisammrg::open"); + DBUG_PRINT("myrg", ("name: '%s' table: 0x%lx", name, (long) table)); + DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked)); + + /* Must not be used when table is open. */ + DBUG_ASSERT(!this->file); + + /* Save for later use. */ + this->test_if_locked= test_if_locked; + + /* In case this handler was open and closed before, free old data. */ + free_root(&this->children_mem_root, MYF(MY_MARK_BLOCKS_FREE)); + + /* + Initialize variables that are used, modified, and/or set by + myisammrg_parent_open_callback(). + 'children_l' is the head of the children chain. + 'children_last_l' points to the end of the children chain. + 'my_errno' is set by myisammrg_parent_open_callback() in + case of an error. + */ + children_l= NULL; + children_last_l= NULL; + my_errno= 0; + + /* retrieve children table list. */ + if (is_cloned) + { + /* + Open and attaches the MyISAM tables,that are under the MERGE table + parent, on the MyISAM storage engine interface directly within the + MERGE engine. The new MyISAM table instances, as well as the MERGE + clone itself, are not visible in the table cache. This is not a + problem because all locking is handled by the original MERGE table + from which this is cloned of. + */ + if (!(file= myrg_open(name, table->db_stat, HA_OPEN_IGNORE_IF_LOCKED))) + { + DBUG_PRINT("error", ("my_errno %d", my_errno)); + DBUG_RETURN(my_errno ? my_errno : -1); + } + + file->children_attached= TRUE; + + info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST); + } + else if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this))) + { + /* purecov: begin inspected */ + DBUG_PRINT("error", ("my_errno %d", my_errno)); + DBUG_RETURN(my_errno ? my_errno : -1); + /* purecov: end */ + } + DBUG_PRINT("myrg", ("MYRG_INFO: 0x%lx child tables: %u", + (long) file, file->tables)); + DBUG_RETURN(0); +} + + +/** + Add list of MERGE children to a TABLE_LIST chain. + + @return status + @retval 0 OK + @retval != 0 Error + + @detail + When a MERGE parent table has just been opened, insert the + TABLE_LIST chain from the MERGE handle into the table list used for + opening tables for this statement. This lets the children be opened + too. +*/ + +int ha_myisammrg::add_children_list(void) +{ + TABLE_LIST *parent_l= this->table->pos_in_table_list; + TABLE_LIST *child_l; + THD *thd= current_thd; + DBUG_ENTER("ha_myisammrg::add_children_list"); + DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", this->table->s->db.str, + this->table->s->table_name.str, (long) this->table)); + + /* Must call this with open table. */ + DBUG_ASSERT(this->file); + + /* Ignore this for empty MERGE tables (UNION=()). */ + if (!this->file->tables) + { + DBUG_PRINT("myrg", ("empty merge table union")); + goto end; + } + + /* Must not call this with attached children. */ + DBUG_ASSERT(!this->file->children_attached); + + /* Must not call this with children list in place. */ + DBUG_ASSERT(parent_l->next_global != this->children_l); + + /* + Prevent inclusion of another MERGE table, which could make infinite + recursion. + */ + if (parent_l->parent_l) + { + my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), parent_l->alias); + DBUG_RETURN(1); + } + + /* Fix children. */ + DBUG_ASSERT(this->children_l); + for (child_l= this->children_l; ; child_l= child_l->next_global) + { + DBUG_ASSERT(!child_l->table); + + /* Set lock type. */ + child_l->lock_type= parent_l->lock_type; + + /* Set parent reference. Used to detect MERGE in children list. */ + child_l->parent_l= parent_l; + + /* Copy select_lex. Used in unique_table() at least. */ + child_l->select_lex= parent_l->select_lex; + + child_l->mdl_lock_data= NULL; /* Safety, if alloc_mdl_locks fails. */ + + /* Break when this was the last child. */ + if (&child_l->next_global == this->children_last_l) + break; + } + + alloc_mdl_locks(children_l, + thd->locked_tables_root ? thd->locked_tables_root : + thd->mem_root); + + /* Insert children into the table list. */ + if (parent_l->next_global) + parent_l->next_global->prev_global= this->children_last_l; + *this->children_last_l= parent_l->next_global; + parent_l->next_global= this->children_l; + this->children_l->prev_global= &parent_l->next_global; + +end: + DBUG_RETURN(0); +} + + +/** + Callback function for attaching a MERGE child table. + + @param[in] callback_param data pointer as given to myrg_attach_children() + this is used to pass the handler handle @return pointer to open MyISAM table structure @retval !=NULL OK, returning pointer @retval NULL, my_errno == 0 Ok, no more child tables @retval NULL, my_errno != 0 error + + @detail + This function retrieves the MyISAM table handle from the + next child table. It is called for each child table. */ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) { - ha_myisammrg *ha_myrg; - TABLE *parent; + ha_myisammrg *ha_myrg= (ha_myisammrg*) callback_param; + TABLE *parent= ha_myrg->table_ptr(); TABLE *child; TABLE_LIST *child_l; MI_INFO *myisam; DBUG_ENTER("myisammrg_attach_children_callback"); my_errno= 0; - ha_myrg= (ha_myisammrg*) callback_param; - parent= ha_myrg->table_ptr(); /* Get child list item. */ child_l= ha_myrg->next_child_attach; @@ -329,13 +492,11 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) DBUG_RETURN(NULL); } child= child_l->table; - DBUG_PRINT("myrg", ("child table: '%s'.'%s' 0x%lx", child->s->db.str, - child->s->table_name.str, (long) child)); /* Prepare for next child. Used as child_l in next call to this function. We cannot rely on a NULL-terminated chain. */ - if (&child_l->next_global == parent->child_last_l) + if (&child_l->next_global == ha_myrg->children_last_l) { DBUG_PRINT("myrg", ("attaching last child")); ha_myrg->next_child_attach= NULL; @@ -343,9 +504,6 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) else ha_myrg->next_child_attach= child_l->next_global; - /* Set parent reference. */ - child->parent= parent; - /* Do a quick compatibility check. The table def version is set when the table share is created. The child def version is copied @@ -394,63 +552,6 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) } -/** - @brief Open a MERGE parent table, not its children. - - @detail This function initializes the MERGE storage engine structures - and adds a child list of TABLE_LIST to the parent TABLE. - - @param[in] name MERGE table path name - @param[in] mode read/write mode, unused - @param[in] test_if_locked open flags - - @return status - @retval 0 OK - @retval -1 Error, my_errno gives reason -*/ - -int ha_myisammrg::open(const char *name, int mode __attribute__((unused)), - uint test_if_locked) -{ - DBUG_ENTER("ha_myisammrg::open"); - DBUG_PRINT("myrg", ("name: '%s' table: 0x%lx", name, (long) table)); - DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked)); - - /* Save for later use. */ - this->test_if_locked= test_if_locked; - - /* retrieve children table list. */ - my_errno= 0; - if (is_cloned) - { - /* - Open and attaches the MyISAM tables,that are under the MERGE table - parent, on the MyISAM storage engine interface directly within the - MERGE engine. The new MyISAM table instances, as well as the MERGE - clone itself, are not visible in the table cache. This is not a - problem because all locking is handled by the original MERGE table - from which this is cloned of. - */ - if (!(file= myrg_open(table->s->normalized_path.str, table->db_stat, - HA_OPEN_IGNORE_IF_LOCKED))) - { - DBUG_PRINT("error", ("my_errno %d", my_errno)); - DBUG_RETURN(my_errno ? my_errno : -1); - } - - file->children_attached= TRUE; - - info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST); - } - else if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this))) - { - DBUG_PRINT("error", ("my_errno %d", my_errno)); - DBUG_RETURN(my_errno ? my_errno : -1); - } - DBUG_PRINT("myrg", ("MYRG_INFO: 0x%lx", (long) file)); - DBUG_RETURN(0); -} - /** Returns a cloned instance of the current handler. @@ -502,22 +603,24 @@ handler *ha_myisammrg::clone(MEM_ROOT *mem_root) /** - @brief Attach children to a MERGE table. + Attach children to a MERGE table. - @detail Let the storage engine attach its children through a callback + @return status + @retval 0 OK + @retval != 0 Error, my_errno gives reason + + @detail + Let the storage engine attach its children through a callback function. Check table definitions for consistency. - @note Special thd->open_options may be in effect. We can make use of + @note + Special thd->open_options may be in effect. We can make use of them in attach. I.e. we use HA_OPEN_FOR_REPAIR to report the names of mismatching child tables. We cannot transport these options in ha_myisammrg::test_if_locked because they may change after the parent is opened. The parent is kept open in the table cache over multiple statements and can be used by other threads. Open options can change over time. - - @return status - @retval 0 OK - @retval != 0 Error, my_errno gives reason */ int ha_myisammrg::attach_children(void) @@ -532,8 +635,27 @@ int ha_myisammrg::attach_children(void) DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str, table->s->table_name.str, (long) table)); DBUG_PRINT("myrg", ("test_if_locked: %u", this->test_if_locked)); + + /* Must call this with open table. */ + DBUG_ASSERT(this->file); + + /* + A MERGE table with no children (empty union) is always seen as + attached internally. + */ + if (!this->file->tables) + { + DBUG_PRINT("myrg", ("empty merge table union")); + goto end; + } + DBUG_PRINT("myrg", ("child tables: %u", this->file->tables)); + + /* Must not call this with attached children. */ DBUG_ASSERT(!this->file->children_attached); + /* Must call this with children list in place. */ + DBUG_ASSERT(this->table->pos_in_table_list->next_global == this->children_l); + /* Initialize variables that are used, modified, and/or set by myisammrg_attach_children_callback(). @@ -546,7 +668,7 @@ int ha_myisammrg::attach_children(void) 'my_errno' is set by myisammrg_attach_children_callback() in case of an error. */ - next_child_attach= table->child_l; + next_child_attach= this->children_l; need_compat_check= FALSE; my_errno= 0; @@ -555,8 +677,8 @@ int ha_myisammrg::attach_children(void) myisammrg_attach_children_callback, this, (my_bool *) &need_compat_check)) { - DBUG_PRINT("error", ("my_errno %d", my_errno)); - DBUG_RETURN(my_errno ? my_errno : -1); + error= my_errno; + goto err; } DBUG_PRINT("myrg", ("calling myrg_extrafunc")); myrg_extrafunc(file, query_cache_invalidate_by_MyISAM_filename_ref); @@ -583,7 +705,11 @@ int ha_myisammrg::attach_children(void) DBUG_PRINT("error",("reclength: %lu mean_rec_length: %lu", table->s->reclength, stats.mean_rec_length)); if (test_if_locked & HA_OPEN_FOR_REPAIR) + { + /* purecov: begin inspected */ myrg_print_wrong_table(file->open_tables->table->filename); + /* purecov: end */ + } error= HA_ERR_WRONG_MRG_TABLE_DEF; goto err; } @@ -614,20 +740,23 @@ int ha_myisammrg::attach_children(void) my_free((uchar*) recinfo, MYF(0)); goto err; } + /* purecov: begin inspected */ myrg_print_wrong_table(u_table->table->filename); + /* purecov: end */ } } my_free((uchar*) recinfo, MYF(0)); if (error == HA_ERR_WRONG_MRG_TABLE_DEF) - goto err; + goto err; /* purecov: inspected */ /* All checks passed so far. Now update child def version. */ - for (child_l= table->child_l; ; child_l= child_l->next_global) + DBUG_ASSERT(this->children_l); + for (child_l= this->children_l; ; child_l= child_l->next_global) { child_l->set_child_def_version( child_l->table->s->get_table_def_version()); - if (&child_l->next_global == table->child_last_l) + if (&child_l->next_global == this->children_last_l) break; } } @@ -640,50 +769,115 @@ int ha_myisammrg::attach_children(void) goto err; } #endif + + end: DBUG_RETURN(0); err: - myrg_detach_children(file); + DBUG_PRINT("error", ("attaching MERGE children failed: %d", error)); + print_error(error, MYF(0)); + detach_children(); DBUG_RETURN(my_errno= error); } /** - @brief Detach all children from a MERGE table. - - @note Detach must not touch the children in any way. - They may have been closed at ths point already. - All references to the children should be removed. + Detach all children from a MERGE table and from the query list of tables. @return status @retval 0 OK @retval != 0 Error, my_errno gives reason + + @note + Detach must not touch the child TABLE objects in any way. + They may have been closed at ths point already. + All references to the children should be removed. */ int ha_myisammrg::detach_children(void) { + TABLE_LIST *child_l; DBUG_ENTER("ha_myisammrg::detach_children"); - DBUG_ASSERT(this->file && this->file->children_attached); + + /* Must call this with open table. */ + DBUG_ASSERT(this->file); + + /* A MERGE table with no children (empty union) cannot be detached. */ + if (!this->file->tables) + { + DBUG_PRINT("myrg", ("empty merge table union")); + goto end; + } + + /* Clear TABLE references. */ + DBUG_ASSERT(this->children_l); + for (child_l= this->children_l; ; child_l= child_l->next_global) + { + /* + Do not DBUG_ASSERT(child_l->table); open_tables might be + incomplete. + + Clear the table reference. + */ + child_l->table= NULL; + + /* Break when this was the last child. */ + if (&child_l->next_global == this->children_last_l) + break; + } + + /* + Remove children from the table list. This won't fail if called + twice. The list is terminated after removal. + + If the parent is LEX::query_tables_own_last and pre-locked tables + follow (tables used by stored functions or triggers), the children + are inserted behind the parent and before the pre-locked tables. But + we do not adjust LEX::query_tables_own_last. The pre-locked tables + could have chopped off the list by clearing + *LEX::query_tables_own_last. This did also chop off the children. If + we would copy the reference from *this->children_last_l in this + case, we would put the chopped off pre-locked tables back to the + list. So we refrain from copying it back, if the destination has + been set to NULL meanwhile. + */ + if (this->children_l->prev_global && *this->children_l->prev_global) + *this->children_l->prev_global= *this->children_last_l; + if (*this->children_last_l) + (*this->children_last_l)->prev_global= this->children_l->prev_global; + + /* Terminate child list. So it cannot be tried to remove again. */ + *this->children_last_l= NULL; + this->children_l->prev_global= NULL; + + if (!this->file->children_attached) + { + DBUG_PRINT("myrg", ("merge children are already detached")); + goto end; + } if (myrg_detach_children(this->file)) { /* purecov: begin inspected */ - DBUG_PRINT("error", ("my_errno %d", my_errno)); + print_error(my_errno, MYF(0)); DBUG_RETURN(my_errno ? my_errno : -1); /* purecov: end */ } + + end: DBUG_RETURN(0); } /** - @brief Close a MERGE parent table, not its children. - - @note The children are expected to be closed separately by the caller. + Close a MERGE parent table, but not its children. @return status @retval 0 OK @retval != 0 Error, my_errno gives reason + + @note + The children are expected to be closed separately by the caller. */ int ha_myisammrg::close(void) @@ -691,11 +885,12 @@ int ha_myisammrg::close(void) int rc; DBUG_ENTER("ha_myisammrg::close"); /* - Children must not be attached here. Unless the MERGE table has no - children or the handler instance has been cloned. In these cases - children_attached is always true. + There are cases where children are not explicitly detached before + close. detach_children() protects itself against double detach. */ - DBUG_ASSERT(!this->file->children_attached || !this->file->tables || this->is_cloned); + if (!is_cloned) + detach_children(); + rc= myrg_close(file); file= 0; DBUG_RETURN(rc); @@ -973,13 +1168,23 @@ int ha_myisammrg::info(uint flag) int ha_myisammrg::extra(enum ha_extra_function operation) { - if (operation == HA_EXTRA_ATTACH_CHILDREN) + if (operation == HA_EXTRA_ADD_CHILDREN_LIST) + { + int rc= add_children_list(); + return(rc); + } + else if (operation == HA_EXTRA_ATTACH_CHILDREN) { int rc= attach_children(); if (!rc) (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL return(rc); } + else if (operation == HA_EXTRA_IS_ATTACHED_CHILDREN) + { + /* For the upper layer pretend empty MERGE union is never attached. */ + return(file && file->tables && file->children_attached); + } else if (operation == HA_EXTRA_DETACH_CHILDREN) { /* @@ -1000,6 +1205,7 @@ int ha_myisammrg::extra(enum ha_extra_function operation) int ha_myisammrg::reset(void) { + /* This is normally called with detached children. */ return myrg_reset(file); } @@ -1015,21 +1221,24 @@ int ha_myisammrg::extra_opt(enum ha_extra_function operation, ulong cache_size) int ha_myisammrg::external_lock(THD *thd, int lock_type) { - DBUG_ASSERT(this->file->children_attached); - return myrg_lock_database(file,lock_type); + /* + This can be called with no children attached. E.g. FLUSH TABLES + unlocks and re-locks tables under LOCK TABLES, but it does not open + them first. So they are detached all the time. But locking of the + children should work anyway because thd->open_tables is not changed + during FLUSH TABLES. + + If this handler instance has been cloned, we still must call + myrg_lock_database(). + */ + if (is_cloned) + return myrg_lock_database(file, lock_type); + return 0; } uint ha_myisammrg::lock_count(void) const { - /* - Return the real lock count even if the children are not attached. - This method is used for allocating memory. If we would return 0 - to another thread (e.g. doing FLUSH TABLE), and attach the children - before the other thread calls store_lock(), then we would return - more locks in store_lock() than we claimed by lock_count(). The - other tread would overrun its memory. - */ - return file->tables; + return 0; } @@ -1037,37 +1246,6 @@ THR_LOCK_DATA **ha_myisammrg::store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type) { - MYRG_TABLE *open_table; - - /* - This method can be called while another thread is attaching the - children. If the processor reorders instructions or write to memory, - 'children_attached' could be set before 'open_tables' has all the - pointers to the children. Use of a mutex here and in - myrg_attach_children() forces consistent data. - */ - pthread_mutex_lock(&this->file->mutex); - - /* - When MERGE table is open, but not yet attached, other threads - could flush it, which means call mysql_lock_abort_for_thread() - on this threads TABLE. 'children_attached' is FALSE in this - situaton. Since the table is not locked, return no lock data. - */ - if (!this->file->children_attached) - goto end; /* purecov: tested */ - - for (open_table=file->open_tables ; - open_table != file->end_table ; - open_table++) - { - *(to++)= &open_table->table->lock; - if (lock_type != TL_IGNORE && open_table->table->lock.type == TL_UNLOCK) - open_table->table->lock.type=lock_type; - } - - end: - pthread_mutex_unlock(&this->file->mutex); return to; } @@ -1156,7 +1334,7 @@ int ha_myisammrg::create(const char *name, register TABLE *form, /* Allocate a table_names array in thread mem_root. */ if (!(table_names= (const char**) thd->alloc((create_info->merge_list.elements+1) * sizeof(char*)))) - DBUG_RETURN(HA_ERR_OUT_OF_MEM); + DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */ /* Create child path names. */ for (pos= table_names; tables; tables= tables->next_local) @@ -1266,7 +1444,7 @@ bool ha_myisammrg::check_if_incompatible_data(HA_CREATE_INFO *info, int ha_myisammrg::check(THD* thd, HA_CHECK_OPT* check_opt) { - return HA_ADMIN_OK; + return this->file->children_attached ? HA_ADMIN_OK : HA_ADMIN_CORRUPT; } diff --git a/storage/myisammrg/ha_myisammrg.h b/storage/myisammrg/ha_myisammrg.h index 790aa15e90a..c3803eb584b 100644 --- a/storage/myisammrg/ha_myisammrg.h +++ b/storage/myisammrg/ha_myisammrg.h @@ -28,6 +28,9 @@ class ha_myisammrg: public handler my_bool is_cloned; /* This instance has been cloned */ public: + MEM_ROOT children_mem_root; /* mem root for children list */ + TABLE_LIST *children_l; /* children list */ + TABLE_LIST **children_last_l; /* children list end */ TABLE_LIST *next_child_attach; /* next child to attach */ uint test_if_locked; /* flags from ::open() */ bool need_compat_check; /* if need compatibility check */ @@ -60,6 +63,7 @@ class ha_myisammrg: public handler { return ulonglong2double(stats.data_file_length) / IO_SIZE + file->tables; } int open(const char *name, int mode, uint test_if_locked); + int add_children_list(void); int attach_children(void); int detach_children(void); virtual handler *clone(MEM_ROOT *mem_root); diff --git a/storage/myisammrg/myrg_extra.c b/storage/myisammrg/myrg_extra.c index 3d14f6a56e6..0b9c138a188 100644 --- a/storage/myisammrg/myrg_extra.c +++ b/storage/myisammrg/myrg_extra.c @@ -75,12 +75,17 @@ int myrg_reset(MYRG_INFO *info) MYRG_TABLE *file; DBUG_ENTER("myrg_reset"); - if (!info->children_attached) - DBUG_RETURN(1); info->cache_in_use=0; info->current_table=0; info->last_used_table= info->open_tables; - + + /* + This is normally called with detached children. + Return OK as this is the normal case. + */ + if (!info->children_attached) + DBUG_RETURN(0); + for (file=info->open_tables ; file != info->end_table ; file++) { int error; From 31baae3f9c7bf76f0f1e0fdf8f81932d8cf7a91f Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 14:12:53 +0300 Subject: [PATCH 052/212] Backport of: ------------------------------------------------------------ revno: 2630.4.39 committer: Dmitry Lenev branch nick: mysql-6.0-3726-w2 timestamp: Thu 2008-06-26 13:08:27 +0400 message: Fix warnings about passing pointer to not fully-initialized THD object to constructor of base Open_tables_state classe, which appeared on Windows and were introduced by one of the patches implementing WL#3726 "DDL locking for all metadata objects". sql/sql_class.cc: Moved code preparing Open_tables_state instance for operations which open/lock/close tables from class constructor to init_open_tables_state() method. This allows us to move such initialization of base Open_table_state instance in THD class constructor from base classes initialization section to constructor's body and thus to get rid of warnings about about passing pointer to not fully-initialized THD object to base class constructor. sql/sql_class.h: Moved code preparing Open_tables_state instance for operations which open/lock/close tables from class constructor to init_open_tables_state() method. This allows us to move such initialization of base Open_table_state instance in THD class constructor from base classes initialization section to constructor's body and thus to get rid of warnings about about passing pointer to not fully-initialized THD object to base class constructor. --- sql/sql_class.cc | 11 ++++------- sql/sql_class.h | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/sql/sql_class.cc b/sql/sql_class.cc index d36ea5c52c9..ff1b03102a6 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -202,12 +202,6 @@ bool foreign_key_prefix(Key *a, Key *b) ** Thread specific functions ****************************************************************************/ -Open_tables_state::Open_tables_state(THD *thd, ulong version_arg) - :version(version_arg), state_flags(0U) -{ - reset_open_tables_state(thd); -} - /* The following functions form part of the C plugin API */ @@ -440,7 +434,7 @@ bool Drop_table_error_handler::handle_condition(THD *thd, THD::THD() :Statement(&main_lex, &main_mem_root, CONVENTIONAL_EXECUTION, /* statement id */ 0), - Open_tables_state(this, refresh_version), rli_fake(0), + rli_fake(0), lock_id(&main_lock_id), user_time(0), in_sub_stmt(0), sql_log_bin_toplevel(false), @@ -542,6 +536,9 @@ THD::THD() command=COM_CONNECT; *scramble= '\0'; + /* Call to init() below requires fully initialized Open_tables_state. */ + init_open_tables_state(this, refresh_version); + init(); #if defined(ENABLED_PROFILING) profiling.set_thd(this); diff --git a/sql/sql_class.h b/sql/sql_class.h index 68ae3afe931..a5236201724 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -984,13 +984,22 @@ public: MDL_CONTEXT mdl_context; MDL_CONTEXT handler_mdl_context; - /* - This constructor serves for creation of Open_tables_state instances - which are used as backup storage. + /** + This constructor initializes Open_tables_state instance which can only + be used as backup storage. To prepare Open_tables_state instance for + operations which open/lock/close tables (e.g. open_table()) one has to + call init_open_tables_state(). */ Open_tables_state() : state_flags(0U) { } - Open_tables_state(THD *thd, ulong version_arg); + /** + Prepare Open_tables_state instance for operations dealing with tables. + */ + void init_open_tables_state(THD *thd, ulong version_arg) + { + reset_open_tables_state(thd); + version= version_arg; + } void set_open_tables_state(Open_tables_state *state) { From eef538ab962c444d9f4728e046fcf34638104c6d Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 14:32:29 +0300 Subject: [PATCH 053/212] Backport of (WL#3726) ------------------------------------------------------------ revno: 2630.13.4 committer: Dmitry Lenev branch nick: mysql-6.0-runtime timestamp: Mon 2008-07-07 19:51:20 +0400 message: Fixed outdated comment describing mdl_init_lock() function. --- sql/mdl.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index 0a663ad2e11..f28778a1a83 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -245,8 +245,9 @@ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) @param lock_data Pointer to an MDL_LOCK_DATA object to initialize @param key_buff Pointer to the buffer for key for the lock request - (should be at least strlen(db) + strlen(name) - + 2 bytes, or, if the lengths are not known, MAX_DBNAME_LENGTH) + (should be at least 4+ strlen(db) + 1 + strlen(name) + + 1 bytes, or, if the lengths are not known, + MAX_MDLKEY_LENGTH) @param type Id of type of object to be locked @param db Name of database to which the object belongs @param name Name of of the object From 2d4de82f29d79563d0bfe57818a70c397322cc42 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 14:41:29 +0300 Subject: [PATCH 054/212] WL#3726 "DDL locking", post-review fixes. ---------------------------------------------------------- revno: 2630.2.23 committer: Konstantin Osipov branch nick: mysql-6.0-runtime timestamp: Fri 2008-06-27 21:15:11 +0400 message: Add an assert that we never call COMMIT or ROLLBACK while having a table lock. sql/sql_parse.cc: We should never call COMMIT or ROLLBACK with a table lock, except when under LOCK TABLES. --- sql/sql_parse.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 55ff9b5c835..62046dddcbd 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3989,12 +3989,16 @@ end_with_restore_list: my_ok(thd); break; case SQLCOM_COMMIT: + DBUG_ASSERT(thd->lock == NULL || + thd->locked_tables_mode == LTM_LOCK_TABLES); if (end_trans(thd, lex->tx_release ? COMMIT_RELEASE : lex->tx_chain ? COMMIT_AND_CHAIN : COMMIT)) goto error; my_ok(thd); break; case SQLCOM_ROLLBACK: + DBUG_ASSERT(thd->lock == NULL || + thd->locked_tables_mode == LTM_LOCK_TABLES); if (end_trans(thd, lex->tx_release ? ROLLBACK_RELEASE : lex->tx_chain ? ROLLBACK_AND_CHAIN : ROLLBACK)) goto error; From ec14bfc746998935c8a1b143095a0ed4d44b967c Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 14:47:05 +0300 Subject: [PATCH 055/212] Backport of: ---------------------------------------------------------- revno: 2630.2.7 committer: Konstantin Osipov branch nick: mysql-6.0-runtime timestamp: Wed 2008-06-04 15:18:52 +0400 message: Fix a code regression (not observable externally) that I introduced in the fix for Bug#26141 (backporting as part of all patches related to WL#3726) --- sql/sp.cc | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/sql/sp.cc b/sql/sp.cc index f6bfba71726..e6999b480f2 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -1693,6 +1693,12 @@ sp_cache_routines_and_add_tables_aux(THD *thd, LEX *lex, int type= rt->key.str[0]; sp_head *sp; + /* + Triggers can't happen here: their bodies are always processed + in sp_cache_routines_and_add_tables_for_triggers(). + */ + DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE); + if (!(sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ? &thd->sp_func_cache : &thd->sp_proc_cache), &name))) @@ -1836,11 +1842,6 @@ int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex, TABLE_LIST *table) { - int ret= 0; - - Sroutine_hash_entry **last_cached_routine_ptr= - (Sroutine_hash_entry **)lex->sroutines_list.next; - if (static_cast(table->lock_type) >= static_cast(TL_WRITE_ALLOW_WRITE)) { @@ -1857,21 +1858,29 @@ sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex, add_used_routine(lex, thd->stmt_arena, &trigger->m_sroutines_key, table->belong_to_view)) { + int ret; + /* Sic: excludes the trigger key from processing */ + Sroutine_hash_entry **last_cached_routine_ptr= + (Sroutine_hash_entry **)lex->sroutines_list.next; + trigger->add_used_tables_to_table_list(thd, &lex->query_tables_last, table->belong_to_view); trigger->propagate_attributes(lex); sp_update_stmt_used_routines(thd, lex, &trigger->m_sroutines, table->belong_to_view); + + ret= sp_cache_routines_and_add_tables_aux(thd, lex, + *last_cached_routine_ptr, + FALSE); + if (ret) + return ret; } } } } } - ret= sp_cache_routines_and_add_tables_aux(thd, lex, - *last_cached_routine_ptr, - FALSE); - return ret; + return 0; } From 3543d2556d57194f8f88f9981f67fbdf5f0b97ba Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 18:47:20 +0300 Subject: [PATCH 056/212] Backport of: ------------------------------------------------------------ revno: 2630.13.16 committer: Davi Arnaut branch nick: WL#4284 timestamp: Sat 2008-07-26 13:38:20 -0300 message: WL#4284: Transactional DDL locking SQL statements' effect on transactions. Currently the MySQL server and its storage engines are not capable of rolling back operations that define or modify data structures (also known as DDL statements) or operations that alter any of the system tables (the mysql database). Allowing these group of statements to participate in transactions is unfeasible at this time (since rollback has no effect whatsoever on them) and goes against the design of our metadata locking subsystem. The solution is to issue implicit commits before and after those statements execution. This effectively confines each of those statements to its own special transaction and ensures that metadata locks taken during this special transaction are not leaked into posterior statements/transactions. mysql-test/include/commit.inc: Alter table rename was not committing the normal transaction at the end of its execution, and as a consequence, the commit was being issued in the next DDL command (rename table) that happened to end the active transaction. Other changes are to take into account the implicit commits issued before and after the DDL command execution. mysql-test/include/implicit_commit_helper.inc: Add auxiliary test that shows if a statement issued a implicit commit. mysql-test/r/commit_1innodb.result: Update test case result. mysql-test/r/implicit_commit.result: Test implicit commit behavior of some SQL commands. mysql-test/t/implicit_commit.test: Test implicit commit behavior of some SQL commands. sql/events.cc: Transaction is now ended before the command execution. sql/mysql_priv.h: Add flags array for server commands and remove historical left over. sql/sql_class.h: Add flags to control when to issue implicit commits before and after a command execution. sql/sql_delete.cc: A implicit commit is issued at the end of truncate statements. sql/sql_parse.cc: Mark commands that need implicit commits before and after their executions. The implicit commits of the statement and the normal transaction are now issued regardless of the user access privileges. sql/sql_table.cc: A implicit commit is now issued before admin commands. tests/mysql_client_test.c: Test that COM_REFRESH issues a implicit commit. --- mysql-test/include/commit.inc | 8 +- mysql-test/include/implicit_commit_helper.inc | 5 + mysql-test/r/commit_1innodb.result | 8 +- mysql-test/r/implicit_commit.result | 1061 +++++++++++++++ mysql-test/t/implicit_commit.test | 1162 +++++++++++++++++ sql/events.cc | 30 - sql/mysql_priv.h | 2 +- sql/sql_class.h | 47 + sql/sql_delete.cc | 13 +- sql/sql_parse.cc | 245 ++-- sql/sql_table.cc | 2 - tests/mysql_client_test.c | 54 + 12 files changed, 2484 insertions(+), 153 deletions(-) create mode 100644 mysql-test/include/implicit_commit_helper.inc create mode 100644 mysql-test/r/implicit_commit.result create mode 100644 mysql-test/t/implicit_commit.test diff --git a/mysql-test/include/commit.inc b/mysql-test/include/commit.inc index d91ba8291fd..d5f10a6d78e 100644 --- a/mysql-test/include/commit.inc +++ b/mysql-test/include/commit.inc @@ -725,15 +725,15 @@ call p_verify_status_increment(4, 4, 4, 4); alter table t3 add column (b int); call p_verify_status_increment(2, 0, 2, 0); alter table t3 rename t4; -call p_verify_status_increment(2, 2, 2, 2); +call p_verify_status_increment(4, 4, 4, 4); rename table t4 to t3; -call p_verify_status_increment(2, 2, 2, 2); +call p_verify_status_increment(0, 0, 0, 0); truncate table t3; call p_verify_status_increment(4, 4, 4, 4); create view v1 as select * from t2; -call p_verify_status_increment(1, 0, 1, 0); +call p_verify_status_increment(2, 0, 2, 0); check table t1; -call p_verify_status_increment(3, 0, 3, 0); +call p_verify_status_increment(2, 0, 2, 0); --echo # Sic: after this bug is fixed, CHECK leaves no pending transaction commit; call p_verify_status_increment(0, 0, 0, 0); diff --git a/mysql-test/include/implicit_commit_helper.inc b/mysql-test/include/implicit_commit_helper.inc new file mode 100644 index 00000000000..5e87b2db079 --- /dev/null +++ b/mysql-test/include/implicit_commit_helper.inc @@ -0,0 +1,5 @@ +INSERT INTO db1.trans (a) VALUES (1); +--disable_result_log +eval $statement; +--enable_result_log +CALL db1.test_if_commit(); diff --git a/mysql-test/r/commit_1innodb.result b/mysql-test/r/commit_1innodb.result index 51c4ac3002c..bbff677ab3f 100644 --- a/mysql-test/r/commit_1innodb.result +++ b/mysql-test/r/commit_1innodb.result @@ -841,11 +841,11 @@ call p_verify_status_increment(2, 0, 2, 0); SUCCESS alter table t3 rename t4; -call p_verify_status_increment(2, 2, 2, 2); +call p_verify_status_increment(4, 4, 4, 4); SUCCESS rename table t4 to t3; -call p_verify_status_increment(2, 2, 2, 2); +call p_verify_status_increment(0, 0, 0, 0); SUCCESS truncate table t3; @@ -853,13 +853,13 @@ call p_verify_status_increment(4, 4, 4, 4); SUCCESS create view v1 as select * from t2; -call p_verify_status_increment(1, 0, 1, 0); +call p_verify_status_increment(2, 0, 2, 0); SUCCESS check table t1; Table Op Msg_type Msg_text test.t1 check status OK -call p_verify_status_increment(3, 0, 3, 0); +call p_verify_status_increment(2, 0, 2, 0); SUCCESS # Sic: after this bug is fixed, CHECK leaves no pending transaction diff --git a/mysql-test/r/implicit_commit.result b/mysql-test/r/implicit_commit.result new file mode 100644 index 00000000000..8c330550a3b --- /dev/null +++ b/mysql-test/r/implicit_commit.result @@ -0,0 +1,1061 @@ +SET GLOBAL EVENT_SCHEDULER = OFF; +SET BINLOG_FORMAT = STATEMENT; +CREATE DATABASE db1; +USE db1; +CREATE TABLE t1 (a INT, KEY a(a)) ENGINE=INNODB; +INSERT INTO t1 VALUES (1),(2),(3),(4),(5); +CREATE TABLE t3 (a INT) ENGINE=MyISAM; +INSERT INTO t3 SELECT * FROM t1; +CREATE TABLE trans (a INT) ENGINE=INNODB; +CREATE PROCEDURE test_if_commit() +BEGIN +ROLLBACK; +SELECT IF (COUNT(*) > 0, "YES", "NO") AS "IMPLICIT COMMIT" FROM trans; +DELETE FROM trans; +COMMIT; +END| +SET AUTOCOMMIT = FALSE; +# +# SQLCOM_SELECT +# +INSERT INTO db1.trans (a) VALUES (1); +select 1 as res from t1 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_TABLE LIKE +# +INSERT INTO db1.trans (a) VALUES (1); +create table t2 like t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE +# +INSERT INTO db1.trans (a) VALUES (1); +show create table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_TABLE +# +INSERT INTO db1.trans (a) VALUES (1); +drop table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CREATE_TABLE TEMPORARY +# +INSERT INTO db1.trans (a) VALUES (1); +create temporary table t2 as select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_TABLE TEMPORARY +# +INSERT INTO db1.trans (a) VALUES (1); +drop temporary table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_TABLE +# +INSERT INTO db1.trans (a) VALUES (1); +create table t2 as select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_UPDATE +# +INSERT INTO db1.trans (a) VALUES (1); +update t2 set a=a+1 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_INSERT +# +INSERT INTO db1.trans (a) VALUES (1); +insert into t2 set a=((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_INSERT_SELECT +# +INSERT INTO db1.trans (a) VALUES (1); +insert into t2 select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_REPLACE +# +INSERT INTO db1.trans (a) VALUES (1); +replace t2 set a=((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_REPLACE_SELECT +# +INSERT INTO db1.trans (a) VALUES (1); +replace t2 select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DELETE +# +INSERT INTO db1.trans (a) VALUES (1); +delete from t2 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DELETE_MULTI +# +INSERT INTO db1.trans (a) VALUES (1); +delete t2, t3 from t2, t3 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_UPDATE_MULTI +# +select * from t2; +a +INSERT INTO db1.trans (a) VALUES (1); +update t2, t3 set t3.a=t2.a, t2.a=null where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_LOAD +# +create table t4 (a varchar(100)); +INSERT INTO db1.trans (a) VALUES (1); +load data infile '../../std_data/words.dat' into table t4; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +drop table t4; +# +# SQLCOM_SHOW_DATABASES +# +INSERT INTO db1.trans (a) VALUES (1); +show databases where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_TABLES +# +INSERT INTO db1.trans (a) VALUES (1); +show tables where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_FIELDS +# +INSERT INTO db1.trans (a) VALUES (1); +show fields from t1 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_KEYS +# +INSERT INTO db1.trans (a) VALUES (1); +show keys from t1 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_VARIABLES +# +INSERT INTO db1.trans (a) VALUES (1); +show variables where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS +# +INSERT INTO db1.trans (a) VALUES (1); +show status where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_ENGINE_MUTEX +# +INSERT INTO db1.trans (a) VALUES (1); +show engine all mutex; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_PROCESSLIST +# +INSERT INTO db1.trans (a) VALUES (1); +show processlist; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_ENGINE_LOGS +# +INSERT INTO db1.trans (a) VALUES (1); +show engine all logs; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_ENGINE_STATUS +# +INSERT INTO db1.trans (a) VALUES (1); +show engine all status; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_CHARSETS +# +INSERT INTO db1.trans (a) VALUES (1); +show charset where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_COLLATIONS +# +INSERT INTO db1.trans (a) VALUES (1); +show collation where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_TABLE_STATUS +# +INSERT INTO db1.trans (a) VALUES (1); +show table status where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_TRIGGERS +# +INSERT INTO db1.trans (a) VALUES (1); +show triggers where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_OPEN_TABLES +# +INSERT INTO db1.trans (a) VALUES (1); +show open tables where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS_PROC +# +INSERT INTO db1.trans (a) VALUES (1); +show procedure status where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS_FUNC +# +INSERT INTO db1.trans (a) VALUES (1); +show function status where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SET_OPTION +# +INSERT INTO db1.trans (a) VALUES (1); +set @a=((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DO +# +INSERT INTO db1.trans (a) VALUES (1); +do ((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CALL +# +create procedure p1(a int) begin end; +INSERT INTO db1.trans (a) VALUES (1); +call p1((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +drop procedure p1; +# +# SQLCOM_CREATE_VIEW +# +INSERT INTO db1.trans (a) VALUES (1); +create view v1 as select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_VIEW +# +INSERT INTO db1.trans (a) VALUES (1); +alter view v1 as select 2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_DROP_VIEW +# +INSERT INTO db1.trans (a) VALUES (1); +drop view v1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CREATE_INDEX +# +INSERT INTO db1.trans (a) VALUES (1); +create index idx1 on t1(a); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_DROP_INDEX +# +INSERT INTO db1.trans (a) VALUES (1); +drop index idx1 on t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_TABLE +# +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 add column b int; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 change b c int; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 drop column c; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_TABLE TEMPORARY +# +create temporary table t4 (a int); +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 add column b int; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 change b c int; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 drop column c; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +drop table t4; +# +# SQLCOM_TRUNCATE +# +insert into t2 select * from t1; +INSERT INTO db1.trans (a) VALUES (1); +truncate table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +insert into t2 select * from t1; +# +# SQLCOM_TRUNCATE TEMPORARY +# +create temporary table t4 as select * from t1; +INSERT INTO db1.trans (a) VALUES (1); +truncate table t4; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +drop temporary table t4; +# +# SQLCOM_SHOW_MASTER_STAT +# +INSERT INTO db1.trans (a) VALUES (1); +show master status; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_SLAVE_STAT +# +INSERT INTO db1.trans (a) VALUES (1); +show slave status; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_GRANT +# +INSERT INTO db1.trans (a) VALUES (1); +grant all on test.t1 to mysqltest_2@localhost with grant option; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_REVOKE +# +INSERT INTO db1.trans (a) VALUES (1); +revoke select on test.t1 from mysqltest_2@localhost; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_REVOKE_ALL +# +INSERT INTO db1.trans (a) VALUES (1); +revoke all on test.t1 from mysqltest_2@localhost; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +drop user mysqltest_2@localhost; +# +# SQLCOM_SHOW_GRANTS +# +INSERT INTO db1.trans (a) VALUES (1); +show grants; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +INSERT INTO db1.trans (a) VALUES (1); +show grants for current_user(); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_LOCK_TABLES +# +INSERT INTO db1.trans (a) VALUES (1); +lock tables t1 write, trans write; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_UNLOCK_TABLES +# +INSERT INTO db1.trans (a) VALUES (1); +unlock tables; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CREATE_DB +# +INSERT INTO db1.trans (a) VALUES (1); +create database db2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CHANGE_DB +# +create table db2.t1 (a int); +insert into db2.t1 values (1); +commit; +INSERT INTO db1.trans (a) VALUES (1); +use db2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_CREATE_DB +# +INSERT INTO db1.trans (a) VALUES (1); +show create database db2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_ALTER_DB +# +# +# SQLCOM_ALTER_DB_UPGRADE +# +# +# SQLCOM_DROP_DB +# +use db1; +INSERT INTO db1.trans (a) VALUES (1); +drop database db2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_REPAIR +# +INSERT INTO db1.trans (a) VALUES (1); +repair table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +repair table t2 use_frm; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_OPTIMIZE +# +INSERT INTO db1.trans (a) VALUES (1); +optimize table t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CHECK +# +INSERT INTO db1.trans (a) VALUES (1); +check table t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +check table t1 extended; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ASSIGN_TO_KEYCACHE +# +set global keycache.key_buffer_size=128*1024; +INSERT INTO db1.trans (a) VALUES (1); +cache index t3 in keycache; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +set global keycache.key_buffer_size=0; +# +# SQLCOM_PRELOAD_KEYS +# +INSERT INTO db1.trans (a) VALUES (1); +load index into cache t3; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_FLUSH +# +INSERT INTO db1.trans (a) VALUES (1); +flush local privileges; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +flush privileges; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_KILL +# +# +# SQLCOM_ANALYZE +# +INSERT INTO db1.trans (a) VALUES (1); +analyze table t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ROLLBACK +# +INSERT INTO db1.trans (a) VALUES (1); +rollback; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_ROLLBACK_TO_SAVEPOINT +# +# +# SQLCOM_COMMIT +# +INSERT INTO db1.trans (a) VALUES (1); +commit; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SAVEPOINT +# +INSERT INTO db1.trans (a) VALUES (1); +savepoint sp1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_RELEASE_SAVEPOINT +# +# +# SQLCOM_SLAVE_START +# +# +# SQLCOM_SLAVE_STOP +# +# +# SQLCOM_BEGIN +# +INSERT INTO db1.trans (a) VALUES (1); +begin; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CHANGE_MASTER +# +# +# SQLCOM_RENAME_TABLE +# +INSERT INTO db1.trans (a) VALUES (1); +rename table t3 to t4; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +rename table t4 to t3; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_RESET +# +# +# SQLCOM_PURGE +# +# +# SQLCOM_PURGE_BEFORE +# +# +# SQLCOM_SHOW_BINLOGS +# +# +# SQLCOM_HA_OPEN +# +INSERT INTO db1.trans (a) VALUES (1); +handler t1 open as ha1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_HA_READ +# +INSERT INTO db1.trans (a) VALUES (1); +handler ha1 read a first; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_HA_CLOSE +# +INSERT INTO db1.trans (a) VALUES (1); +handler ha1 close; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_SLAVE_HOSTS +# +INSERT INTO db1.trans (a) VALUES (1); +show slave hosts; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_BINLOG_EVENTS +# +INSERT INTO db1.trans (a) VALUES (1); +show binlog events; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_NEW_MASTER +# +# +# SQLCOM_SHOW_WARNS +# +INSERT INTO db1.trans (a) VALUES (1); +show warnings; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_EMPTY_QUERY +# +# +# SQLCOM_SHOW_ERRORS +# +INSERT INTO db1.trans (a) VALUES (1); +show errors; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STORAGE_ENGINES +# +INSERT INTO db1.trans (a) VALUES (1); +show engines; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_PRIVILEGES +# +INSERT INTO db1.trans (a) VALUES (1); +show privileges; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_HELP +# +INSERT INTO db1.trans (a) VALUES (1); +help 'foo'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_USER +# +INSERT INTO db1.trans (a) VALUES (1); +create user trxusr1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_RENAME_USER +# +INSERT INTO db1.trans (a) VALUES (1); +rename user 'trxusr1' to 'trxusr2'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_DROP_USER +# +INSERT INTO db1.trans (a) VALUES (1); +drop user trxusr2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CHECKSUM +# +INSERT INTO db1.trans (a) VALUES (1); +checksum table t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_PROCEDURE +# +INSERT INTO db1.trans (a) VALUES (1); +create procedure p1(a int) begin end; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_PROCEDURE +# +INSERT INTO db1.trans (a) VALUES (1); +alter procedure p1 comment 'foobar'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE_PROC +# +INSERT INTO db1.trans (a) VALUES (1); +show create procedure p1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS_PROC +# +INSERT INTO db1.trans (a) VALUES (1); +show procedure status; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_PROC_CODE +# +INSERT INTO db1.trans (a) VALUES (1); +show procedure code p1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_PROCEDURE +# +INSERT INTO db1.trans (a) VALUES (1); +drop procedure p1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CREATE_FUNCTION +# +# +# SQLCOM_DROP_FUNCTION +# +# +# SQLCOM_CREATE_SPFUNCTION +# +INSERT INTO db1.trans (a) VALUES (1); +create function f1() returns int return 69; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_FUNCTION +# +INSERT INTO db1.trans (a) VALUES (1); +alter function f1 comment 'comment'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE_FUNC +# +INSERT INTO db1.trans (a) VALUES (1); +show create function f1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS_FUNC +# +INSERT INTO db1.trans (a) VALUES (1); +show function status like '%f%'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_FUNC_CODE +# +INSERT INTO db1.trans (a) VALUES (1); +show function code f1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_PREPARE +# +INSERT INTO db1.trans (a) VALUES (1); +prepare stmt1 from "insert into t1 values (5)"; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_EXECUTE +# +INSERT INTO db1.trans (a) VALUES (1); +execute stmt1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DEALLOCATE_PREPARE +# +INSERT INTO db1.trans (a) VALUES (1); +deallocate prepare stmt1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_TRIGGER +# +INSERT INTO db1.trans (a) VALUES (1); +create trigger trg1 before insert on t1 for each row set @a:=1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE_TRIGGER +# +INSERT INTO db1.trans (a) VALUES (1); +show create trigger trg1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_TRIGGER +# +INSERT INTO db1.trans (a) VALUES (1); +drop trigger trg1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_XA_START +# +# +# SQLCOM_XA_END +# +# +# SQLCOM_XA_PREPARE +# +# +# SQLCOM_XA_COMMIT +# +# +# SQLCOM_XA_ROLLBACK +# +# +# SQLCOM_XA_RECOVER +# +# +# SQLCOM_ALTER_TABLESPACE +# +# +# SQLCOM_INSTALL_PLUGIN +# +# +# SQLCOM_SHOW_PLUGINS +# +# +# SQLCOM_UNINSTALL_PLUGIN +# +# +# SQLCOM_SHOW_AUTHORS +# +INSERT INTO db1.trans (a) VALUES (1); +show authors; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_BINLOG_BASE64_EVENT +# +# +# SQLCOM_SHOW_CONTRIBUTORS +# +INSERT INTO db1.trans (a) VALUES (1); +show contributors; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_SERVER +# +# +# SQLCOM_ALTER_SERVER +# +# +# SQLCOM_DROP_SERVER +# +# +# SQLCOM_CREATE_EVENT +# +INSERT INTO db1.trans (a) VALUES (1); +create event ev1 on schedule every 1 second do insert into t1 values (6); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_EVENT +# +INSERT INTO db1.trans (a) VALUES (1); +alter event ev1 rename to ev2 disable; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE_EVENT +# +INSERT INTO db1.trans (a) VALUES (1); +show create event ev2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_EVENTS +# +INSERT INTO db1.trans (a) VALUES (1); +show events; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_EVENT +# +INSERT INTO db1.trans (a) VALUES (1); +drop event ev2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_BACKUP +# +# +# SQLCOM_SHOW_ARCHIVE +# +# +# SQLCOM_RESTORE +# +# +# SQLCOM_BACKUP_TEST +# +# +# SQLCOM_SHOW_PROFILE +# +INSERT INTO db1.trans (a) VALUES (1); +show profile memory; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_PROFILES +# +INSERT INTO db1.trans (a) VALUES (1); +show profiles; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +USE test; +DROP DATABASE db1; +End of tests diff --git a/mysql-test/t/implicit_commit.test b/mysql-test/t/implicit_commit.test new file mode 100644 index 00000000000..ed451877c77 --- /dev/null +++ b/mysql-test/t/implicit_commit.test @@ -0,0 +1,1162 @@ +source include/have_innodb.inc; +source include/have_log_bin.inc; + +SET GLOBAL EVENT_SCHEDULER = OFF; +SET BINLOG_FORMAT = STATEMENT; + +LET $OLD_DB= `SELECT DATABASE()`; + +CREATE DATABASE db1; +USE db1; +CREATE TABLE t1 (a INT, KEY a(a)) ENGINE=INNODB; +INSERT INTO t1 VALUES (1),(2),(3),(4),(5); +CREATE TABLE t3 (a INT) ENGINE=MyISAM; +INSERT INTO t3 SELECT * FROM t1; +CREATE TABLE trans (a INT) ENGINE=INNODB; + +DELIMITER |; + +CREATE PROCEDURE test_if_commit() +BEGIN + ROLLBACK; + SELECT IF (COUNT(*) > 0, "YES", "NO") AS "IMPLICIT COMMIT" FROM trans; + DELETE FROM trans; + COMMIT; +END| + +DELIMITER ;| + +SET AUTOCOMMIT = FALSE; + +--echo # +--echo # SQLCOM_SELECT +--echo # + +let $statement= + select 1 as res from t1 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_TABLE LIKE +--echo # + +let $statement= + create table t2 like t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE +--echo # + +let $statement= + show create table t2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_TABLE +--echo # + +let $statement= + drop table t2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_TABLE TEMPORARY +--echo # + +let $statement= + create temporary table t2 as select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_TABLE TEMPORARY +--echo # + +let $statement= + drop temporary table t2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_TABLE +--echo # + +let $statement= + create table t2 as select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_UPDATE +--echo # + +let $statement= + update t2 set a=a+1 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_INSERT +--echo # + +let $statement= + insert into t2 set a=((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_INSERT_SELECT +--echo # + +let $statement= + insert into t2 select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REPLACE +--echo # + +let $statement= + replace t2 set a=((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REPLACE_SELECT +--echo # + +let $statement= + replace t2 select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DELETE +--echo # + +let $statement= + delete from t2 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DELETE_MULTI +--echo # + +let $statement= + delete t2, t3 from t2, t3 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_UPDATE_MULTI +--echo # + +select * from t2; +let $statement= + update t2, t3 set t3.a=t2.a, t2.a=null where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_LOAD +--echo # + +create table t4 (a varchar(100)); + +let $statement= + load data infile '../../std_data/words.dat' into table t4; +source include/implicit_commit_helper.inc; + +drop table t4; + +--echo # +--echo # SQLCOM_SHOW_DATABASES +--echo # + +let $statement= + show databases where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_TABLES +--echo # + +let $statement= + show tables where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_FIELDS +--echo # + +let $statement= + show fields from t1 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_KEYS +--echo # + +let $statement= + show keys from t1 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_VARIABLES +--echo # + +let $statement= + show variables where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS +--echo # + +let $statement= + show status where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_ENGINE_MUTEX +--echo # + +let $statement= + show engine all mutex; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_PROCESSLIST +--echo # + +let $statement= + show processlist; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_ENGINE_LOGS +--echo # + +let $statement= + show engine all logs; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_ENGINE_STATUS +--echo # + +let $statement= + show engine all status; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CHARSETS +--echo # + +let $statement= + show charset where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_COLLATIONS +--echo # + +let $statement= + show collation where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_TABLE_STATUS +--echo # + +let $statement= + show table status where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_TRIGGERS +--echo # + +let $statement= + show triggers where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_OPEN_TABLES +--echo # + +let $statement= + show open tables where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS_PROC +--echo # + +let $statement= + show procedure status where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS_FUNC +--echo # + +let $statement= + show function status where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SET_OPTION +--echo # + +let $statement= + set @a=((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DO +--echo # + +let $statement= + do ((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CALL +--echo # + +create procedure p1(a int) begin end; + +let $statement= + call p1((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +drop procedure p1; + +--echo # +--echo # SQLCOM_CREATE_VIEW +--echo # + +let $statement= + create view v1 as select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_VIEW +--echo # + +let $statement= + alter view v1 as select 2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_VIEW +--echo # + +let $statement= + drop view v1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_INDEX +--echo # + +let $statement= + create index idx1 on t1(a); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_INDEX +--echo # + +let $statement= + drop index idx1 on t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_TABLE +--echo # + +let $statement= + alter table t1 add column b int; +source include/implicit_commit_helper.inc; + +let $statement= + alter table t1 change b c int; +source include/implicit_commit_helper.inc; + +let $statement= + alter table t1 drop column c; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_TABLE TEMPORARY +--echo # + +create temporary table t4 (a int); + +let $statement= + alter table t1 add column b int; +source include/implicit_commit_helper.inc; + +let $statement= + alter table t1 change b c int; +source include/implicit_commit_helper.inc; + +let $statement= + alter table t1 drop column c; +source include/implicit_commit_helper.inc; + +drop table t4; + +--echo # +--echo # SQLCOM_TRUNCATE +--echo # + +insert into t2 select * from t1; +let $statement= + truncate table t2; +source include/implicit_commit_helper.inc; +insert into t2 select * from t1; + +--echo # +--echo # SQLCOM_TRUNCATE TEMPORARY +--echo # + +create temporary table t4 as select * from t1; +let $statement= + truncate table t4; +source include/implicit_commit_helper.inc; +drop temporary table t4; + +--echo # +--echo # SQLCOM_SHOW_MASTER_STAT +--echo # + +let $statement= + show master status; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_SLAVE_STAT +--echo # + +let $statement= + show slave status; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_GRANT +--echo # + +let $statement= + grant all on test.t1 to mysqltest_2@localhost with grant option; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REVOKE +--echo # +let $statement= + revoke select on test.t1 from mysqltest_2@localhost; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REVOKE_ALL +--echo # + +let $statement= + revoke all on test.t1 from mysqltest_2@localhost; +source include/implicit_commit_helper.inc; + +drop user mysqltest_2@localhost; + +--echo # +--echo # SQLCOM_SHOW_GRANTS +--echo # + +let $statement= + show grants; +source include/implicit_commit_helper.inc; + +let $statement= + show grants for current_user(); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_LOCK_TABLES +--echo # + +let $statement= + lock tables t1 write, trans write; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_UNLOCK_TABLES +--echo # + +let $statement= + unlock tables; +source include/implicit_commit_helper.inc; + +# +# Missing test for lock tables transactional. +# + +--echo # +--echo # SQLCOM_CREATE_DB +--echo # + +let $statement= + create database db2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CHANGE_DB +--echo # + +create table db2.t1 (a int); +insert into db2.t1 values (1); +commit; + +let $statement= + use db2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_DB +--echo # + +let $statement= + show create database db2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_DB +--echo # + +#let $statement= +# alter database db2 character set koi8r; +#source include/implicit_commit_helper.inc; + +#let $statement= +# alter database db2 collate cp1251_general_cs; +#source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_DB_UPGRADE +--echo # + +#let $statement= +# alter database `#mysql50#db3` upgrade data directory name; +#source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_DB +--echo # + +use db1; + +let $statement= + drop database db2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REPAIR +--echo # + +let $statement= + repair table t2; +source include/implicit_commit_helper.inc; + +let $statement= + repair table t2 use_frm; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_OPTIMIZE +--echo # + +let $statement= + optimize table t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CHECK +--echo # + +let $statement= + check table t1; +source include/implicit_commit_helper.inc; + +let $statement= + check table t1 extended; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ASSIGN_TO_KEYCACHE +--echo # + +set global keycache.key_buffer_size=128*1024; + +let $statement= + cache index t3 in keycache; +source include/implicit_commit_helper.inc; + +set global keycache.key_buffer_size=0; + +--echo # +--echo # SQLCOM_PRELOAD_KEYS +--echo # + +let $statement= + load index into cache t3; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_FLUSH +--echo # + +let $statement= + flush local privileges; +source include/implicit_commit_helper.inc; + +let $statement= + flush privileges; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_KILL +--echo # + +--echo # +--echo # SQLCOM_ANALYZE +--echo # + +let $statement= + analyze table t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ROLLBACK +--echo # + +let $statement= + rollback; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ROLLBACK_TO_SAVEPOINT +--echo # + + +--echo # +--echo # SQLCOM_COMMIT +--echo # + +let $statement= + commit; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SAVEPOINT +--echo # + +let $statement= + savepoint sp1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_RELEASE_SAVEPOINT +--echo # + +--echo # +--echo # SQLCOM_SLAVE_START +--echo # + +--echo # +--echo # SQLCOM_SLAVE_STOP +--echo # + +--echo # +--echo # SQLCOM_BEGIN +--echo # + +let $statement= + begin; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CHANGE_MASTER +--echo # + +--echo # +--echo # SQLCOM_RENAME_TABLE +--echo # + +let $statement= + rename table t3 to t4; +source include/implicit_commit_helper.inc; + +let $statement= + rename table t4 to t3; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_RESET +--echo # + +--echo # +--echo # SQLCOM_PURGE +--echo # + +--echo # +--echo # SQLCOM_PURGE_BEFORE +--echo # + +--echo # +--echo # SQLCOM_SHOW_BINLOGS +--echo # + +--echo # +--echo # SQLCOM_HA_OPEN +--echo # + +let $statement= + handler t1 open as ha1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_HA_READ +--echo # + +let $statement= + handler ha1 read a first; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_HA_CLOSE +--echo # + +let $statement= + handler ha1 close; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_SLAVE_HOSTS +--echo # + +let $statement= + show slave hosts; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_BINLOG_EVENTS +--echo # + +let $statement= + show binlog events; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_NEW_MASTER +--echo # + +--echo # +--echo # SQLCOM_SHOW_WARNS +--echo # + +let $statement= + show warnings; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_EMPTY_QUERY +--echo # + +--echo # +--echo # SQLCOM_SHOW_ERRORS +--echo # + +let $statement= + show errors; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STORAGE_ENGINES +--echo # + +let $statement= + show engines; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_PRIVILEGES +--echo # + +let $statement= + show privileges; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_HELP +--echo # + +let $statement= + help 'foo'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_USER +--echo # + +let $statement= + create user trxusr1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_RENAME_USER +--echo # + +let $statement= + rename user 'trxusr1' to 'trxusr2'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_USER +--echo # + +let $statement= + drop user trxusr2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CHECKSUM +--echo # + +let $statement= + checksum table t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_PROCEDURE +--echo # + +let $statement= + create procedure p1(a int) begin end; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_PROCEDURE +--echo # + +let $statement= + alter procedure p1 comment 'foobar'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_PROC +--echo # + +let $statement= + show create procedure p1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS_PROC +--echo # + +let $statement= + show procedure status; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_PROC_CODE +--echo # + +# +# Available only on servers with debugging support. +# + +--disable_abort_on_error +let $statement= + show procedure code p1; +source include/implicit_commit_helper.inc; +--enable_abort_on_error + +--echo # +--echo # SQLCOM_DROP_PROCEDURE +--echo # + +let $statement= + drop procedure p1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_FUNCTION +--echo # + +--echo # +--echo # SQLCOM_DROP_FUNCTION +--echo # + +--echo # +--echo # SQLCOM_CREATE_SPFUNCTION +--echo # + +let $statement= + create function f1() returns int return 69; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_FUNCTION +--echo # + +let $statement= + alter function f1 comment 'comment'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_FUNC +--echo # + +let $statement= + show create function f1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS_FUNC +--echo # + +let $statement= + show function status like '%f%'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_FUNC_CODE +--echo # + +# +# Available only on servers with debugging support. +# + +--disable_abort_on_error +let $statement= + show function code f1; +source include/implicit_commit_helper.inc; +--enable_abort_on_error + +--echo # +--echo # SQLCOM_PREPARE +--echo # + +let $statement= + prepare stmt1 from "insert into t1 values (5)"; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_EXECUTE +--echo # + +let $statement= + execute stmt1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DEALLOCATE_PREPARE +--echo # + +let $statement= + deallocate prepare stmt1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_TRIGGER +--echo # + +let $statement= + create trigger trg1 before insert on t1 for each row set @a:=1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_TRIGGER +--echo # + +let $statement= + show create trigger trg1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_TRIGGER +--echo # + +let $statement= + drop trigger trg1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_XA_START +--echo # + +--echo # +--echo # SQLCOM_XA_END +--echo # + +--echo # +--echo # SQLCOM_XA_PREPARE +--echo # + +--echo # +--echo # SQLCOM_XA_COMMIT +--echo # + +--echo # +--echo # SQLCOM_XA_ROLLBACK +--echo # + +--echo # +--echo # SQLCOM_XA_RECOVER +--echo # + +--echo # +--echo # SQLCOM_ALTER_TABLESPACE +--echo # + +--echo # +--echo # SQLCOM_INSTALL_PLUGIN +--echo # + +--echo # +--echo # SQLCOM_SHOW_PLUGINS +--echo # + +--echo # +--echo # SQLCOM_UNINSTALL_PLUGIN +--echo # + +--echo # +--echo # SQLCOM_SHOW_AUTHORS +--echo # + +let $statement= + show authors; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_BINLOG_BASE64_EVENT +--echo # + +--echo # +--echo # SQLCOM_SHOW_CONTRIBUTORS +--echo # + +let $statement= + show contributors; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_SERVER +--echo # + +--echo # +--echo # SQLCOM_ALTER_SERVER +--echo # + +--echo # +--echo # SQLCOM_DROP_SERVER +--echo # + +--echo # +--echo # SQLCOM_CREATE_EVENT +--echo # + +let $statement= + create event ev1 on schedule every 1 second do insert into t1 values (6); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_EVENT +--echo # + +let $statement= + alter event ev1 rename to ev2 disable; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_EVENT +--echo # + +let $statement= + show create event ev2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_EVENTS +--echo # + +let $statement= + show events; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_EVENT +--echo # + +let $statement= + drop event ev2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_BACKUP +--echo # + +#create database backup_db; +# +#let $statement= +# backup database db1 to 'backup_db1.ba'; +#source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_ARCHIVE +--echo # + +# +# --error ER_NOT_ALLOWED_COMMAND +# +#let $statement= +# show backup 'backup_db1.ba'; +#source include/implicit_commit_helper.inc; +# + +--echo # +--echo # SQLCOM_RESTORE +--echo # + +#let $statement= +# restore from 'backup_db1.ba'; +#source include/implicit_commit_helper.inc; + +#--remove_file $MYSQLTEST_VARDIR/master-data/backup_db1.ba +# +#drop database backup_db; + +--echo # +--echo # SQLCOM_BACKUP_TEST +--echo # + +# BACKUP_TEST + +--echo # +--echo # SQLCOM_SHOW_PROFILE +--echo # + +let $statement= + show profile memory; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_PROFILES +--echo # + +let $statement= + show profiles; +source include/implicit_commit_helper.inc; + +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +eval USE $OLD_DB; +DROP DATABASE db1; + +--echo End of tests diff --git a/sql/events.cc b/sql/events.cc index f7ff2b0ccf1..8c9e60e73e8 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -391,15 +391,6 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, int ret; DBUG_ENTER("Events::create_event"); - /* - Let's commit the transaction first - MySQL manual specifies - that a DDL issues an implicit commit, and it doesn't say "successful - DDL", so that an implicit commit is a property of any successfully - parsed DDL statement. - */ - if (end_active_trans(thd)) - DBUG_RETURN(TRUE); - if (check_if_system_tables_error()) DBUG_RETURN(TRUE); @@ -512,13 +503,6 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, DBUG_ENTER("Events::update_event"); - /* - For consistency, implicit COMMIT should be the first thing in the - execution chain. - */ - if (end_active_trans(thd)) - DBUG_RETURN(TRUE); - if (check_if_system_tables_error()) DBUG_RETURN(TRUE); @@ -635,20 +619,6 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) int ret; DBUG_ENTER("Events::drop_event"); - /* - In MySQL, DDL must always commit: since mysql.* tables are - non-transactional, we must modify them outside a transaction - to not break atomicity. - But the second and more important reason to commit here - regardless whether we're actually changing mysql.event table - or not is replication: end_active_trans syncs the binary log, - and unless we run DDL in it's own transaction it may simply - never appear on the slave in case the outside transaction - rolls back. - */ - if (end_active_trans(thd)) - DBUG_RETURN(TRUE); - if (check_if_system_tables_error()) DBUG_RETURN(TRUE); diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 38a55e9ec4c..72bc20238fc 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -2014,8 +2014,8 @@ extern struct my_option my_long_options[]; extern const LEX_STRING view_type; extern scheduler_functions thread_scheduler; extern TYPELIB thread_handling_typelib; -extern uint8 uc_update_queries[SQLCOM_END+1]; extern uint sql_command_flags[]; +extern uint server_command_flags[]; extern TYPELIB log_output_typelib; /* optional things, have_* variables */ diff --git a/sql/sql_class.h b/sql/sql_class.h index 3a6da64ca4a..bef8dff5759 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3152,6 +3152,36 @@ public: joins are currently prohibited in these statements. */ #define CF_REEXECUTION_FRAGILE (1U << 5) +/** + Implicitly commit before the SQL statement is executed. + + Statements marked with this flag will cause any active + transaction to end (commit) before proceeding with the + command execution. + + This flag should be set for statements that probably can't + be rolled back or that do not expect any previously metadata + locked tables. +*/ +#define CF_IMPLICT_COMMIT_BEGIN (1U << 6) +/** + Implicitly commit after the SQL statement. + + Statements marked with this flag are automatically committed + at the end of the statement. + + This flag should be set for statements that will implicitly + open and take metadata locks on system tables that should not + be carried for the whole duration of a active transaction. +*/ +#define CF_IMPLICIT_COMMIT_END (1U << 7) +/** + CF_IMPLICT_COMMIT_BEGIN and CF_IMPLICIT_COMMIT_END are used + to ensure that the active transaction is implicitly committed + before and after every DDL statement and any statement that + modifies our currently non-transactional system tables. +*/ +#define CF_AUTO_COMMIT_TRANS (CF_IMPLICT_COMMIT_BEGIN | CF_IMPLICIT_COMMIT_END) /** Diagnostic statement. @@ -3163,6 +3193,23 @@ public: */ #define CF_DIAGNOSTIC_STMT (1U << 8) +/* Bits in server_command_flags */ + +/** + Skip the increase of the global query id counter. Commonly set for + commands that are stateless (won't cause any change on the server + internal states). +*/ +#define CF_SKIP_QUERY_ID (1U << 0) + +/** + Skip the increase of the number of statements that clients have + sent to the server. Commonly used for commands that will cause + a statement to be executed but the statement might have not been + sent by the user (ie: stored procedure). +*/ +#define CF_SKIP_QUESTIONS (1U << 1) + /* Functions in sql_class.cc */ void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 4827d48e1c5..c0162605e2f 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1062,9 +1062,18 @@ static bool mysql_truncate_by_delete(THD *thd, TABLE_LIST *table_list) table_list->lock_type= TL_WRITE; mysql_init_select(thd->lex); thd->clear_current_stmt_binlog_row_based(); + /* Delete all rows from table */ error= mysql_delete(thd, table_list, NULL, NULL, HA_POS_ERROR, LL(0), TRUE); - ha_autocommit_or_rollback(thd, error); - end_trans(thd, error ? ROLLBACK : COMMIT); + /* + All effects of a TRUNCATE TABLE operation are rolled back if a row by row + deletion fails. Otherwise, operation is automatically committed at the end. + */ + if (error) + { + DBUG_ASSERT(thd->stmt_da->is_error()); + ha_autocommit_or_rollback(thd, TRUE); + end_active_trans(thd); + } thd->current_stmt_binlog_row_based= save_binlog_row_based; DBUG_RETURN(error); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 62046dddcbd..660241e8e2c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -221,6 +221,50 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) } +/* + Implicitly commit a active transaction if statement requires so. + + @param thd Thread handle. + @param mask Bitmask used for the SQL command match. + +*/ +static bool opt_implicit_commit(THD *thd, uint mask) +{ + LEX *lex= thd->lex; + bool res= FALSE, skip= FALSE; + DBUG_ENTER("opt_implicit_commit"); + + if (!(sql_command_flags[lex->sql_command] & mask)) + DBUG_RETURN(FALSE); + + switch (lex->sql_command) { + case SQLCOM_DROP_TABLE: + skip= lex->drop_temporary; + break; + case SQLCOM_ALTER_TABLE: + case SQLCOM_CREATE_TABLE: + /* If CREATE TABLE of non-temporary table, do implicit commit */ + skip= (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE); + break; + case SQLCOM_SET_OPTION: + skip= lex->autocommit ? FALSE : TRUE; + break; + default: + break; + } + + if (!skip) + { + /* Commit or rollback the statement transaction. */ + ha_autocommit_or_rollback(thd, thd->is_error()); + /* Commit the normal transaction if one is active. */ + res= end_active_trans(thd); + } + + DBUG_RETURN(res); +} + + /** Mark all commands that somehow changes a table. @@ -235,26 +279,44 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) */ uint sql_command_flags[SQLCOM_END+1]; +uint server_command_flags[COM_END+1]; void init_update_queries(void) { - bzero((uchar*) &sql_command_flags, sizeof(sql_command_flags)); + /* Initialize the server command flags array. */ + memset(server_command_flags, 0, sizeof(server_command_flags)); - sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA; + server_command_flags[COM_STATISTICS]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS; + server_command_flags[COM_PING]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS; + server_command_flags[COM_STMT_PREPARE]= CF_SKIP_QUESTIONS; + server_command_flags[COM_STMT_CLOSE]= CF_SKIP_QUESTIONS; + server_command_flags[COM_STMT_RESET]= CF_SKIP_QUESTIONS; + + /* Initialize the sql command flags array. */ + memset(sql_command_flags, 0, sizeof(sql_command_flags)); + + sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; @@ -273,7 +335,8 @@ void init_update_queries(void) sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE | + CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; @@ -337,9 +400,29 @@ void init_update_queries(void) The following admin table operations are allowed on log tables. */ - sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_OPTIMIZE]= CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND; + sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_OPTIMIZE]= CF_WRITE_LOGS_COMMAND | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND | + CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_CREATE_USER]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_USER]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_RENAME_USER]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_GRANT]= CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_PRELOAD_KEYS]= CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_FLUSH]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CHECK]= CF_AUTO_COMMIT_TRANS; } @@ -903,28 +986,14 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->set_time(); pthread_mutex_lock(&LOCK_thread_count); thd->query_id= global_query_id; - - switch( command ) { - /* Ignore these statements. */ - case COM_STATISTICS: - case COM_PING: - break; - /* Only increase id on these statements but don't count them. */ - case COM_STMT_PREPARE: - case COM_STMT_CLOSE: - case COM_STMT_RESET: + if (!(server_command_flags[command] & CF_SKIP_QUERY_ID)) next_query_id(); - break; - /* Increase id and count all other statements. */ - default: - statistic_increment(thd->status_var.questions, &LOCK_status); - next_query_id(); - } - thread_running++; - /* TODO: set thd->lex->sql_command to SQLCOM_END here */ pthread_mutex_unlock(&LOCK_thread_count); + if (!(server_command_flags[command] & CF_SKIP_QUESTIONS)) + statistic_increment(thd->status_var.questions, &LOCK_status); + /** Clear the set of flags that are expected to be cleared at the beginning of each command. @@ -1277,6 +1346,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, bool not_used; status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]); ulong options= (ulong) (uchar) packet[0]; + if (end_active_trans(thd)) + break; if (check_global_access(thd,RELOAD_ACL)) break; general_log_print(thd, command, NullS); @@ -1296,13 +1367,16 @@ bool dispatch_command(enum enum_server_command command, THD *thd, res= reload_acl_and_cache(NULL, options | REFRESH_FAST, NULL, ¬_used); my_pthread_setspecific_ptr(THR_THD, thd); - if (!res) - my_ok(thd); - break; + if (res) + break; } + else #endif - if (!reload_acl_and_cache(thd, options, NULL, ¬_used)) - my_ok(thd); + if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) + break; + if (end_active_trans(thd)) + break; + my_ok(thd); break; } #ifndef EMBEDDED_LIBRARY @@ -2037,10 +2111,20 @@ mysql_execute_command(THD *thd) #ifdef HAVE_REPLICATION } /* endif unlikely slave */ #endif + status_var_increment(thd->status_var.com_stat[lex->sql_command]); DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table == FALSE); - + + /* + End a active transaction so that this command will have it's + own transaction and will also sync the binary log. If a DDL is + not run in it's own transaction it may simply never appear on + the slave in case the outside transaction rolls back. + */ + if (opt_implicit_commit(thd, CF_IMPLICT_COMMIT_BEGIN)) + goto error; + switch (lex->sql_command) { case SQLCOM_SHOW_EVENTS: @@ -2315,15 +2399,6 @@ case SQLCOM_PREPARE: } case SQLCOM_CREATE_TABLE: { - /* If CREATE TABLE of non-temporary table, do implicit commit */ - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - if (end_active_trans(thd)) - { - res= -1; - break; - } - } DBUG_ASSERT(first_table == all_tables && first_table != 0); bool link_to_local; // Skip first table, which is the table we are creating @@ -2576,8 +2651,6 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_one_table_access(thd, INDEX_ACL, all_tables)) goto error; /* purecov: inspected */ - if (end_active_trans(thd)) - goto error; /* Currently CREATE INDEX or DROP INDEX cause a full table rebuild and thus classify as slow administrative statements just like @@ -2690,9 +2763,6 @@ end_with_restore_list: WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), "INDEX DIRECTORY"); create_info.data_file_name= create_info.index_file_name= NULL; - /* ALTER TABLE ends previous transaction */ - if (end_active_trans(thd)) - goto error; if (!thd->locked_tables_mode && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) @@ -2738,7 +2808,7 @@ end_with_restore_list: goto error; } - if (end_active_trans(thd) || mysql_rename_tables(thd, first_table, 0)) + if (mysql_rename_tables(thd, first_table, 0)) goto error; break; } @@ -3168,11 +3238,6 @@ end_with_restore_list: break; } case SQLCOM_TRUNCATE: - if (end_active_trans(thd)) - { - res= -1; - break; - } DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_one_table_access(thd, DROP_ACL, all_tables)) goto error; @@ -3280,8 +3345,6 @@ end_with_restore_list: { if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - if (end_active_trans(thd)) - goto error; } else { @@ -3380,9 +3443,6 @@ end_with_restore_list: { List *lex_var_list= &lex->var_list; - if (lex->autocommit && end_active_trans(thd)) - goto error; - if ((check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE) || open_and_lock_tables(thd, all_tables))) goto error; @@ -3485,11 +3545,6 @@ end_with_restore_list: prepared statement- safe. */ HA_CREATE_INFO create_info(lex->create_info); - if (end_active_trans(thd)) - { - res= -1; - break; - } char *alias; if (!(alias=thd->strmake(lex->name.str, lex->name.length)) || check_db_name(&lex->name)) @@ -3522,11 +3577,6 @@ end_with_restore_list: } case SQLCOM_DROP_DB: { - if (end_active_trans(thd)) - { - res= -1; - break; - } if (check_db_name(&lex->name)) { my_error(ER_WRONG_DB_NAME, MYF(0), lex->name.str); @@ -3563,11 +3613,6 @@ end_with_restore_list: case SQLCOM_ALTER_DB_UPGRADE: { LEX_STRING *db= & lex->name; - if (end_active_trans(thd)) - { - res= 1; - break; - } #ifdef HAVE_REPLICATION if (thd->slave_thread && (!rpl_filter->db_ok(db->str) || @@ -3730,8 +3775,6 @@ end_with_restore_list: if (check_access(thd, INSERT_ACL, "mysql", 0, 1, 1, 0) && check_global_access(thd,CREATE_USER_ACL)) break; - if (end_active_trans(thd)) - goto error; /* Conditionally writes to binlog */ if (!(res= mysql_create_user(thd, lex->users_list))) my_ok(thd); @@ -3742,8 +3785,6 @@ end_with_restore_list: if (check_access(thd, DELETE_ACL, "mysql", 0, 1, 1, 0) && check_global_access(thd,CREATE_USER_ACL)) break; - if (end_active_trans(thd)) - goto error; /* Conditionally writes to binlog */ if (!(res= mysql_drop_user(thd, lex->users_list))) my_ok(thd); @@ -3754,8 +3795,6 @@ end_with_restore_list: if (check_access(thd, UPDATE_ACL, "mysql", 0, 1, 1, 0) && check_global_access(thd,CREATE_USER_ACL)) break; - if (end_active_trans(thd)) - goto error; /* Conditionally writes to binlog */ if (!(res= mysql_rename_user(thd, lex->users_list))) my_ok(thd); @@ -3763,8 +3802,6 @@ end_with_restore_list: } case SQLCOM_REVOKE_ALL: { - if (end_active_trans(thd)) - goto error; if (check_access(thd, UPDATE_ACL, "mysql", 0, 1, 1, 0) && check_global_access(thd,CREATE_USER_ACL)) break; @@ -3776,9 +3813,6 @@ end_with_restore_list: case SQLCOM_REVOKE: case SQLCOM_GRANT: { - if (end_active_trans(thd)) - goto error; - if (check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL, first_table ? first_table->db : select_lex->db, first_table ? &first_table->grant.privilege : 0, @@ -4134,9 +4168,6 @@ end_with_restore_list: is_schema_db(lex->sphead->m_db.str))) goto create_sp_error; - if (end_active_trans(thd)) - goto create_sp_error; - name= lex->sphead->name(&namelen); #ifdef HAVE_DLOPEN if (lex->sphead->m_type == TYPE_ENUM_FUNCTION) @@ -4360,8 +4391,6 @@ create_sp_error: lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0)) goto error; - if (end_active_trans(thd)) - goto error; memcpy(&lex->sp_chistics, &chistics, sizeof(lex->sp_chistics)); if ((sp->m_type == TYPE_ENUM_FUNCTION) && !trust_function_creators && mysql_bin_log.is_open() && @@ -4566,16 +4595,12 @@ create_sp_error: Note: SQLCOM_CREATE_VIEW also handles 'ALTER VIEW' commands as specified through the thd->lex->create_view_mode flag. */ - if (end_active_trans(thd)) - goto error; - res= mysql_create_view(thd, first_table, thd->lex->create_view_mode); break; } case SQLCOM_DROP_VIEW: { - if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE) - || end_active_trans(thd)) + if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* Conditionally writes to binlog. */ res= mysql_drop_view(thd, first_table, thd->lex->drop_mode); @@ -4583,9 +4608,6 @@ create_sp_error: } case SQLCOM_CREATE_TRIGGER: { - if (end_active_trans(thd)) - goto error; - /* Conditionally writes to binlog. */ res= mysql_create_or_drop_trigger(thd, all_tables, 1); @@ -4593,9 +4615,6 @@ create_sp_error: } case SQLCOM_DROP_TRIGGER: { - if (end_active_trans(thd)) - goto error; - /* Conditionally writes to binlog. */ res= mysql_create_or_drop_trigger(thd, all_tables, 0); break; @@ -4922,6 +4941,12 @@ finish: */ start_waiting_global_read_lock(thd); } + + /* If commit fails, we should be able to reset the OK status. */ + thd->stmt_da->can_overwrite_status= TRUE; + opt_implicit_commit(thd, CF_IMPLICIT_COMMIT_END); + thd->stmt_da->can_overwrite_status= FALSE; + DBUG_RETURN(res || thd->is_error()); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 18605916789..9f486768043 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4565,8 +4565,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, int result_code; DBUG_ENTER("mysql_admin_table"); - if (end_active_trans(thd)) - DBUG_RETURN(1); field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2)); item->maybe_null = 1; field_list.push_back(item = new Item_empty_string("Op", 10)); diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 02241dd92f6..c9edd210c32 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -18436,6 +18436,59 @@ static void test_bug36004() DBUG_VOID_RETURN; } +/** + Test that COM_REFRESH issues a implicit commit. +*/ + +static void test_wl4284_1() +{ + int rc; + MYSQL_ROW row; + MYSQL_RES *result; + + DBUG_ENTER("test_wl4284_1"); + myheader("test_wl4284_1"); + + /* set AUTOCOMMIT to OFF */ + rc= mysql_autocommit(mysql, FALSE); + myquery(rc); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS trans"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE TABLE trans (a INT) ENGINE= InnoDB"); + myquery(rc); + + rc= mysql_query(mysql, "INSERT INTO trans VALUES(1)"); + myquery(rc); + + rc= mysql_refresh(mysql, REFRESH_GRANT | REFRESH_TABLES); + myquery(rc); + + rc= mysql_rollback(mysql); + myquery(rc); + + rc= mysql_query(mysql, "SELECT * FROM trans"); + myquery(rc); + + result= mysql_use_result(mysql); + mytest(result); + + row= mysql_fetch_row(result); + mytest(row); + + mysql_free_result(result); + + /* set AUTOCOMMIT to OFF */ + rc= mysql_autocommit(mysql, FALSE); + myquery(rc); + + rc= mysql_query(mysql, "DROP TABLE trans"); + myquery(rc); + + DBUG_VOID_RETURN; +} + static void test_bug38486(void) { @@ -19197,6 +19250,7 @@ static struct my_tests_st my_tests[]= { { "test_wl4166_3", test_wl4166_3 }, { "test_wl4166_4", test_wl4166_4 }, { "test_bug36004", test_bug36004 }, + { "test_wl4284_1", test_wl4284_1 }, { "test_wl4435", test_wl4435 }, { "test_wl4435_2", test_wl4435_2 }, { "test_bug38486", test_bug38486 }, From 6a5bbd4bdc5535d524b7e1f009fd81b2cc21544c Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 18:52:21 +0300 Subject: [PATCH 057/212] Backport of: ------------------------------------------------------------ revno: 2630.13.17 committer: Davi Arnaut branch nick: 4284-6.0 timestamp: Sun 2008-07-27 10:14:46 -0300 message: Post-merge fixes: Remove dependency on binlog, require not embedded as test uses the event scheduler and disable abort on error for syntax only available on servers built with debugging support. This is a patch in scope of WL#4284 "Transactional DDL locking" mysql-test/t/implicit_commit.test: Remove dependency on binlog, require not embedded as test uses the event scheduler and disable abort on error for syntax only available on servers built with debugging support. --- mysql-test/t/implicit_commit.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/t/implicit_commit.test b/mysql-test/t/implicit_commit.test index ed451877c77..d8ffd6e9452 100644 --- a/mysql-test/t/implicit_commit.test +++ b/mysql-test/t/implicit_commit.test @@ -1,5 +1,5 @@ source include/have_innodb.inc; -source include/have_log_bin.inc; +source include/not_embedded.inc; SET GLOBAL EVENT_SCHEDULER = OFF; SET BINLOG_FORMAT = STATEMENT; From c52d4830bf167cfe0b536be28a0adca121ae5d72 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 21:37:38 +0300 Subject: [PATCH 058/212] Backport of: ------------------------------------------------------------ revno: 2630.22.3 committer: Davi Arnaut branch nick: 4284-6.0 timestamp: Thu 2008-08-07 22:33:43 -0300 message: WL#4284: Transactional DDL locking Make transaction management more modular through a new interface. The overall objective of this change is to provide groundwork for the design of transactional DDL locking by cleaning up the transaction high level API to better distinguish operations implicit and explicit, and single statement transaction from operations on the normal transaction. Having a a high-level interface for transaction management provides a better base for implementing transactional concepts that are not always tied to storage engines and also makes it easier to interect with other higher level modules of the server. client/Makefile.am: Add new file to the build. libmysqld/CMakeLists.txt: Add new file to the build. libmysqld/Makefile.am: Add new file to the build. sql/CMakeLists.txt: Add new file to the build. sql/Makefile.am: Add new file to the build. sql/handler.cc: Remove multiplexer commit or rollback function. Most callers already have enough information to decided whether to rollback or commit. Having plain and well named functions makes it easier to read and understand code. sql/handler.h: Remove wrapper function as the low level transaction functions shouldn't be called directly anymore. sql/log_event.cc: Rename transaction management functions to the new names. sql/log_event_old.cc: Rename transaction management functions to the new names. sql/mysql_priv.h: Remove obsolete functions for implicit and explicit commit. sql/rpl_injector.cc: Rename transaction management functions to the new names. sql/rpl_rli.cc: Rename transaction management functions to the new names. sql/set_var.cc: Rename transaction management functions to the new names. sql/slave.cc: Rename transaction management functions to the new names. sql/sql_base.cc: Rename transaction management functions to the new names. sql/sql_class.cc: Rename transaction management functions to the new names. sql/sql_delete.cc: Rename transaction management functions to the new names. sql/sql_do.cc: Rename transaction management functions to the new names. sql/sql_insert.cc: Rename transaction management functions to the new names. sql/sql_parse.cc: Rename transaction management functions to the new names. sql/sql_partition.cc: Rename transaction management functions to the new names. sql/sql_table.cc: Rename transaction management functions to the new names. sql/transaction.cc: Implement wrapper functions to differentiate operations on the single statement transaction from the ones operating on the normal transaction. sql/transaction.h: Export new functions for dealing with transaction commands. --- client/Makefile.am | 3 +- libmysqld/CMakeLists.txt | 2 +- libmysqld/Makefile.am | 3 +- sql/CMakeLists.txt | 1 + sql/Makefile.am | 4 +- sql/handler.cc | 66 +--- sql/handler.h | 7 +- sql/log_event.cc | 10 +- sql/log_event_old.cc | 3 +- sql/mysql_priv.h | 9 - sql/rpl_injector.cc | 7 +- sql/rpl_rli.cc | 5 +- sql/set_var.cc | 3 +- sql/slave.cc | 3 +- sql/sql_base.cc | 3 +- sql/sql_class.cc | 4 +- sql/sql_delete.cc | 5 +- sql/sql_do.cc | 3 +- sql/sql_insert.cc | 9 +- sql/sql_parse.cc | 512 +++---------------------------- sql/sql_partition.cc | 5 +- sql/sql_table.cc | 39 +-- sql/transaction.cc | 648 +++++++++++++++++++++++++++++++++++++++ sql/transaction.h | 46 +++ 24 files changed, 819 insertions(+), 581 deletions(-) create mode 100644 sql/transaction.cc create mode 100644 sql/transaction.h diff --git a/client/Makefile.am b/client/Makefile.am index ccd0d8aada0..cfc322c2531 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -107,7 +107,8 @@ sql_src=log_event.h mysql_priv.h rpl_constants.h \ rpl_utility.h rpl_tblmap.h rpl_tblmap.cc \ log_event.cc my_decimal.h my_decimal.cc \ log_event_old.h log_event_old.cc \ - rpl_record_old.h rpl_record_old.cc + rpl_record_old.h rpl_record_old.cc \ + transaction.h strings_src=decimal.c link_sources: diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 655082f0304..053f277ffbb 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -133,7 +133,7 @@ SET(LIBMYSQLD_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/partition_info.cc ../sql/sql_connect.cc ../sql/scheduler.cc ../sql/event_parse_data.cc ../sql/sql_signal.cc ../sql/rpl_handler.cc - ../sql/mdl.cc + ../sql/mdl.cc ../sql/transaction.cc ${GEN_SOURCES} ${LIB_SOURCES}) diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index 3a7fa934778..824145a462a 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -75,8 +75,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \ parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \ rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \ - debug_sync.cc \ - sql_tablespace.cc \ + debug_sync.cc sql_tablespace.cc transaction.cc \ rpl_injector.cc my_user.c partition_info.cc \ sql_servers.cc event_parse_data.cc sql_signal.cc \ rpl_handler.cc mdl.cc diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index a40cf04f37c..38c6adf755b 100755 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -77,6 +77,7 @@ SET (SQL_SOURCE sql_connect.cc scheduler.cc sql_profile.cc event_parse_data.cc sql_signal.cc rpl_handler.cc mdl.cc + transaction.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h ${PROJECT_SOURCE_DIR}/include/mysqld_error.h diff --git a/sql/Makefile.am b/sql/Makefile.am index 95864dfbb1b..a7d897be3f0 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -113,7 +113,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ sql_partition.h partition_info.h partition_element.h \ contributors.h sql_servers.h sql_signal.h records.h \ sql_prepare.h rpl_handler.h replication.h mdl.h \ - sql_plist.h + sql_plist.h transaction.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ @@ -159,7 +159,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc \ sql_servers.cc event_parse_data.cc sql_signal.cc \ - rpl_handler.cc mdl.cc + rpl_handler.cc mdl.cc transaction.cc nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c diff --git a/sql/handler.cc b/sql/handler.cc index 2ccb87591dc..66b0450d8d6 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -27,6 +27,7 @@ #include "rpl_handler.h" #include "rpl_filter.h" #include +#include "transaction.h" #include #include "probes_mysql.h" @@ -888,16 +889,16 @@ void ha_close_connection(THD* thd) a transaction in a given engine is read-write and will not involve the two-phase commit protocol! - At the end of a statement, server call - ha_autocommit_or_rollback() is invoked. This call in turn - invokes handlerton::prepare() for every involved engine. - Prepare is followed by a call to handlerton::commit_one_phase() - If a one-phase commit will suffice, handlerton::prepare() is not - invoked and the server only calls handlerton::commit_one_phase(). - At statement commit, the statement-related read-write engine - flag is propagated to the corresponding flag in the normal - transaction. When the commit is complete, the list of registered - engines is cleared. + At the end of a statement, server call trans_commit_stmt is + invoked. This call in turn invokes handlerton::prepare() + for every involved engine. Prepare is followed by a call + to handlerton::commit_one_phase() If a one-phase commit + will suffice, handlerton::prepare() is not invoked and + the server only calls handlerton::commit_one_phase(). + At statement commit, the statement-related read-write + engine flag is propagated to the corresponding flag in the + normal transaction. When the commit is complete, the list + of registered engines is cleared. Rollback is handled in a similar fashion. @@ -908,7 +909,7 @@ void ha_close_connection(THD* thd) do not "register" in thd->transaction lists, and thus do not modify the transaction state. Besides, each DDL in MySQL is prefixed with an implicit normal transaction commit - (a call to end_active_trans()), and thus leaves nothing + (a call to trans_commit_implicit()), and thus leaves nothing to modify. However, as it has been pointed out with CREATE TABLE .. SELECT, some DDL statements can start a *new* transaction. @@ -1370,47 +1371,6 @@ int ha_rollback_trans(THD *thd, bool all) DBUG_RETURN(error); } -/** - This is used to commit or rollback a single statement depending on - the value of error. - - @note - Note that if the autocommit is on, then the following call inside - InnoDB will commit or rollback the whole transaction (= the statement). The - autocommit mechanism built into InnoDB is based on counting locks, but if - the user has used LOCK TABLES then that mechanism does not know to do the - commit. -*/ -int ha_autocommit_or_rollback(THD *thd, int error) -{ - DBUG_ENTER("ha_autocommit_or_rollback"); - - if (thd->transaction.stmt.ha_list) - { - if (!error) - { - if (ha_commit_trans(thd, 0)) - error=1; - } - else - { - (void) ha_rollback_trans(thd, 0); - if (thd->transaction_rollback_request && !thd->in_sub_stmt) - (void) ha_rollback(thd); - } - - thd->variables.tx_isolation=thd->session_tx_isolation; - } - else - { - if (!error) - RUN_HOOK(transaction, after_commit, (thd, FALSE)); - else - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); - } - DBUG_RETURN(error); -} - struct xahton_st { XID *xid; @@ -3496,7 +3456,7 @@ int ha_enable_transaction(THD *thd, bool on) So, let's commit an open transaction (if any) now. */ if (!(error= ha_commit_trans(thd, 0))) - error= end_trans(thd, COMMIT); + error= trans_commit_implicit(thd); } DBUG_RETURN(error); } diff --git a/sql/handler.h b/sql/handler.h index 7e64a08700f..1bcf6a73b33 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1945,10 +1945,6 @@ extern TYPELIB tx_isolation_typelib; extern TYPELIB myisam_stats_method_typelib; extern ulong total_ha, total_ha_2pc; - /* Wrapper functions */ -#define ha_commit(thd) (ha_commit_trans((thd), TRUE)) -#define ha_rollback(thd) (ha_rollback_trans((thd), TRUE)) - /* lookups */ handlerton *ha_default_handlerton(THD *thd); plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name); @@ -2025,13 +2021,12 @@ int ha_release_temporary_latches(THD *thd); int ha_start_consistent_snapshot(THD *thd); int ha_commit_or_rollback_by_xid(XID *xid, bool commit); int ha_commit_one_phase(THD *thd, bool all); +int ha_commit_trans(THD *thd, bool all); int ha_rollback_trans(THD *thd, bool all); int ha_prepare(THD *thd); int ha_recover(HASH *commit_list); /* transactions: these functions never call handlerton functions directly */ -int ha_commit_trans(THD *thd, bool all); -int ha_autocommit_or_rollback(THD *thd, int error); int ha_enable_transaction(THD *thd, bool on); /* savepoints */ diff --git a/sql/log_event.cc b/sql/log_event.cc index b3f6fd58f1a..9c3a2ce2131 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -31,6 +31,7 @@ #include "rpl_filter.h" #include "rpl_utility.h" #include "rpl_record.h" +#include "transaction.h" #include #endif /* MYSQL_CLIENT */ @@ -3231,7 +3232,7 @@ Default database: '%s'. Query: '%s'", them back here. */ if (expected_error && expected_error == actual_error) - ha_autocommit_or_rollback(thd, TRUE); + trans_rollback_stmt(thd); } /* If we expected a non-zero error code and get nothing and, it is a concurrency @@ -3240,7 +3241,8 @@ Default database: '%s'. Query: '%s'", else if (expected_error && !actual_error && (concurrency_error_code(expected_error) || ignored_error_code(expected_error))) - ha_autocommit_or_rollback(thd, TRUE); + trans_rollback_stmt(thd); + /* Other cases: mostly we expected no error and get one. */ @@ -5315,7 +5317,7 @@ int Xid_log_event::do_apply_event(Relay_log_info const *rli) /* For a slave Xid_log_event is COMMIT */ general_log_print(thd, COM_QUERY, "COMMIT /* implicit, from Xid_log_event */"); - return end_trans(thd, COMMIT); + return trans_commit(thd); } Log_event::enum_skip_reason @@ -7607,7 +7609,7 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd) are involved, commit the transaction and flush the pending event to the binlog. */ - error= ha_autocommit_or_rollback(thd, 0); + error= trans_commit_stmt(thd); /* Now what if this is not a transactional engine? we still need to diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 3dcf19f6b32..90ea8b6cefe 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -6,6 +6,7 @@ #endif #include "log_event_old.h" #include "rpl_record_old.h" +#include "transaction.h" #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) @@ -1795,7 +1796,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) are involved, commit the transaction and flush the pending event to the binlog. */ - if ((error= ha_autocommit_or_rollback(thd, 0))) + if ((error= trans_commit_stmt(thd))) rli->report(ERROR_LEVEL, error, "Error in %s event: commit of row events failed, " "table `%s`.`%s`", diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 72bc20238fc..3f6ed2b1cb0 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -883,15 +883,6 @@ bool parse_sql(THD *thd, Parser_state *parser_state, Object_creation_ctx *creation_ctx); -enum enum_mysql_completiontype { - ROLLBACK_RELEASE=-2, ROLLBACK=1, ROLLBACK_AND_CHAIN=7, - COMMIT_RELEASE=-1, COMMIT=0, COMMIT_AND_CHAIN=6 -}; - -bool begin_trans(THD *thd); -bool end_active_trans(THD *thd); -int end_trans(THD *thd, enum enum_mysql_completiontype completion); - Item *negate_expression(THD *thd, Item *expr); /* log.cc */ diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index 684655d1c3b..738341cc034 100644 --- a/sql/rpl_injector.cc +++ b/sql/rpl_injector.cc @@ -15,6 +15,7 @@ #include "mysql_priv.h" #include "rpl_injector.h" +#include "transaction.h" /* injector::transaction - member definitions @@ -35,7 +36,7 @@ injector::transaction::transaction(MYSQL_BIN_LOG *log, THD *thd) m_start_pos.m_file_name= my_strdup(log_info.log_file_name, MYF(0)); m_start_pos.m_file_pos= log_info.pos; - begin_trans(m_thd); + trans_begin(m_thd); thd->set_current_stmt_binlog_row_based(); } @@ -81,8 +82,8 @@ int injector::transaction::commit() is committed by committing the statement transaction explicitly. */ - ha_autocommit_or_rollback(m_thd, 0); - end_trans(m_thd, COMMIT); + trans_commit_stmt(m_thd); + trans_commit(m_thd); DBUG_RETURN(0); } diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 42cd12bd66c..0a703475aa7 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -20,6 +20,7 @@ #include // For MY_STAT #include "sql_repl.h" // For check_binlog_magic #include "rpl_utility.h" +#include "transaction.h" static int count_relay_log_space(Relay_log_info* rli); @@ -1183,8 +1184,8 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) */ if (error) { - ha_autocommit_or_rollback(thd, 1); // if a "statement transaction" - end_trans(thd, ROLLBACK); // if a "real transaction" + trans_rollback_stmt(thd); // if a "statement transaction" + trans_rollback(thd); // if a "real transaction" } m_table_map.clear_tables(); slave_close_thread_tables(thd); diff --git a/sql/set_var.cc b/sql/set_var.cc index 82ecb6b71cf..266cdd9ad6d 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -61,6 +61,7 @@ #include #include "events.h" +#include "transaction.h" /* WITH_NDBCLUSTER_STORAGE_ENGINE */ #ifdef WITH_NDBCLUSTER_STORAGE_ENGINE @@ -3190,7 +3191,7 @@ static bool set_option_autocommit(THD *thd, set_var *var) */ if (var->save_result.ulong_value != 0 && (thd->options & OPTION_NOT_AUTOCOMMIT) && - ha_commit(thd)) + trans_commit(thd)) return 1; if (var->save_result.ulong_value != 0) diff --git a/sql/slave.cc b/sql/slave.cc index 99a7fc8d3eb..ed722305b29 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -34,6 +34,7 @@ #include "sql_repl.h" #include "rpl_filter.h" #include "repl_failsafe.h" +#include "transaction.h" #include #include #include @@ -2430,7 +2431,7 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) else { exec_res= 0; - end_trans(thd, ROLLBACK); + trans_rollback(thd); /* chance for concurrent connection to get more locks */ safe_sleep(thd, min(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE), (CHECK_KILLED_FUNC)sql_slave_killed, (void*)rli); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index a84891c0ab7..0bd1acaa08e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -22,6 +22,7 @@ #include "sp_head.h" #include "sp.h" #include "sql_trigger.h" +#include "transaction.h" #include "sql_prepare.h" #include #include @@ -1402,7 +1403,7 @@ void close_thread_tables(THD *thd, if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) { thd->stmt_da->can_overwrite_status= TRUE; - ha_autocommit_or_rollback(thd, thd->is_error()); + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); thd->stmt_da->can_overwrite_status= FALSE; /* diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ff1b03102a6..de2ef98e993 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -42,6 +42,7 @@ #include "sp_rcontext.h" #include "sp_cache.h" +#include "transaction.h" #include "debug_sync.h" /* @@ -987,7 +988,8 @@ void THD::cleanup(void) } #endif { - ha_rollback(this); + transaction.xid_state.xa_state= XA_NOTR; + trans_rollback(this); xid_cache_delete(&transaction.xid_state); } locked_tables_list.unlock_locked_tables(this); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index c0162605e2f..13c331af95c 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -23,6 +23,7 @@ #include "sql_select.h" #include "sp_head.h" #include "sql_trigger.h" +#include "transaction.h" /** Implement DELETE SQL word. @@ -1071,8 +1072,8 @@ static bool mysql_truncate_by_delete(THD *thd, TABLE_LIST *table_list) if (error) { DBUG_ASSERT(thd->stmt_da->is_error()); - ha_autocommit_or_rollback(thd, TRUE); - end_active_trans(thd); + trans_rollback_stmt(thd); + trans_rollback(thd); } thd->current_stmt_binlog_row_based= save_binlog_row_based; DBUG_RETURN(error); diff --git a/sql/sql_do.cc b/sql/sql_do.cc index 8406a9eaf45..0f3a7e1ecef 100644 --- a/sql/sql_do.cc +++ b/sql/sql_do.cc @@ -17,6 +17,7 @@ /* Execute DO statement */ #include "mysql_priv.h" +#include "transaction.h" bool mysql_do(THD *thd, List &values) { @@ -36,7 +37,7 @@ bool mysql_do(THD *thd, List &values) will clear the error and the rollback in the end of dispatch_command() won't work. */ - ha_autocommit_or_rollback(thd, thd->is_error()); + trans_rollback_stmt(thd); thd->clear_error(); // DO always is OK } my_ok(thd); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index e0199fcdf20..16e9c1ded38 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -61,6 +61,7 @@ #include "sql_show.h" #include "slave.h" #include "rpl_mi.h" +#include "transaction.h" #ifndef EMBEDDED_LIBRARY static bool delayed_get_table(THD *thd, TABLE_LIST *table_list); @@ -2539,7 +2540,7 @@ pthread_handler_t handle_delayed_insert(void *arg) */ di->table->file->ha_release_auto_increment(); mysql_unlock_tables(thd, lock); - ha_autocommit_or_rollback(thd, 0); + trans_commit_stmt(thd); di->group_count=0; pthread_mutex_lock(&di->mutex); } @@ -2559,7 +2560,7 @@ pthread_handler_t handle_delayed_insert(void *arg) first call to ha_*_row() instead. Remove code that are used to cover for the case outlined above. */ - ha_autocommit_or_rollback(thd, 1); + trans_rollback_stmt(thd); DBUG_LEAVE; } @@ -3863,8 +3864,8 @@ bool select_create::send_eof() */ if (!table->s->tmp_table) { - ha_autocommit_or_rollback(thd, 0); - end_active_trans(thd); + trans_commit_stmt(thd); + trans_commit_implicit(thd); } table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 660241e8e2c..2df66a44fa6 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -28,6 +28,7 @@ #include "sp_cache.h" #include "events.h" #include "sql_trigger.h" +#include "transaction.h" #include "sql_prepare.h" #include "probes_mysql.h" @@ -88,113 +89,6 @@ const char *xa_state_names[]={ "NON-EXISTING", "ACTIVE", "IDLE", "PREPARED", "ROLLBACK ONLY" }; -/** - Mark a XA transaction as rollback-only if the RM unilaterally - rolled back the transaction branch. - - @note If a rollback was requested by the RM, this function sets - the appropriate rollback error code and transits the state - to XA_ROLLBACK_ONLY. - - @return TRUE if transaction was rolled back or if the transaction - state is XA_ROLLBACK_ONLY. FALSE otherwise. -*/ -static bool xa_trans_rolled_back(XID_STATE *xid_state) -{ - if (xid_state->rm_error) - { - switch (xid_state->rm_error) { - case ER_LOCK_WAIT_TIMEOUT: - my_error(ER_XA_RBTIMEOUT, MYF(0)); - break; - case ER_LOCK_DEADLOCK: - my_error(ER_XA_RBDEADLOCK, MYF(0)); - break; - default: - my_error(ER_XA_RBROLLBACK, MYF(0)); - } - xid_state->xa_state= XA_ROLLBACK_ONLY; - } - - return (xid_state->xa_state == XA_ROLLBACK_ONLY); -} - -/** - Rollback work done on behalf of at ransaction branch. -*/ -static bool xa_trans_rollback(THD *thd) -{ - /* - Resource Manager error is meaningless at this point, as we perform - explicit rollback request by user. We must reset rm_error before - calling ha_rollback(), so thd->transaction.xid structure gets reset - by ha_rollback()/THD::transaction::cleanup(). - */ - thd->transaction.xid_state.rm_error= 0; - - bool status= test(ha_rollback(thd)); - - thd->options&= ~(ulong) OPTION_BEGIN; - thd->transaction.all.modified_non_trans_table= FALSE; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state= XA_NOTR; - - return status; -} - -bool end_active_trans(THD *thd) -{ - int error=0; - DBUG_ENTER("end_active_trans"); - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - DBUG_RETURN(1); - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - DBUG_RETURN(1); - } - if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN | - OPTION_TABLE_LOCK)) - { - DBUG_PRINT("info",("options: 0x%llx", thd->options)); - /* Safety if one did "drop table" on locked tables */ - if (!thd->locked_tables_mode) - thd->options&= ~OPTION_TABLE_LOCK; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - if (ha_commit(thd)) - error=1; - } - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - DBUG_RETURN(error); -} - - -bool begin_trans(THD *thd) -{ - int error=0; - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - return 1; - } - - thd->locked_tables_list.unlock_locked_tables(thd); - - if (end_active_trans(thd)) - error= -1; - else - { - thd->options|= OPTION_BEGIN; - thd->server_status|= SERVER_STATUS_IN_TRANS; - } - return error; -} #ifdef HAVE_REPLICATION /** @@ -256,9 +150,9 @@ static bool opt_implicit_commit(THD *thd, uint mask) if (!skip) { /* Commit or rollback the statement transaction. */ - ha_autocommit_or_rollback(thd, thd->is_error()); + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); /* Commit the normal transaction if one is active. */ - res= end_active_trans(thd); + res= trans_commit_implicit(thd); } DBUG_RETURN(res); @@ -691,80 +585,6 @@ void cleanup_items(Item *item) DBUG_VOID_RETURN; } -/** - Ends the current transaction and (maybe) begin the next. - - @param thd Current thread - @param completion Completion type - - @retval - 0 OK -*/ - -int end_trans(THD *thd, enum enum_mysql_completiontype completion) -{ - bool do_release= 0; - int res= 0; - DBUG_ENTER("end_trans"); - - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - DBUG_RETURN(1); - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - DBUG_RETURN(1); - } - switch (completion) { - case COMMIT: - /* - We don't use end_active_trans() here to ensure that this works - even if there is a problem with the OPTION_AUTO_COMMIT flag - (Which of course should never happen...) - */ - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - res= ha_commit(thd); - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - break; - case COMMIT_RELEASE: - do_release= 1; /* fall through */ - case COMMIT_AND_CHAIN: - res= end_active_trans(thd); - if (!res && completion == COMMIT_AND_CHAIN) - res= begin_trans(thd); - break; - case ROLLBACK_RELEASE: - do_release= 1; /* fall through */ - case ROLLBACK: - case ROLLBACK_AND_CHAIN: - { - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - if (ha_rollback(thd)) - res= -1; - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - if (!res && (completion == ROLLBACK_AND_CHAIN)) - res= begin_trans(thd); - break; - } - default: - res= -1; - my_error(ER_UNKNOWN_COM_ERROR, MYF(0)); - DBUG_RETURN(-1); - } - - if (res < 0) - my_error(thd->killed_errno(), MYF(0)); - else if ((res == 0) && do_release) - thd->killed= THD::KILL_CONNECTION; - - DBUG_RETURN(res); -} - #ifndef EMBEDDED_LIBRARY /** @@ -1346,7 +1166,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, bool not_used; status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]); ulong options= (ulong) (uchar) packet[0]; - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) break; if (check_global_access(thd,RELOAD_ACL)) break; @@ -1374,7 +1194,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #endif if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) break; - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) break; my_ok(thd); break; @@ -1532,7 +1352,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, /* If commit fails, we should be able to reset the OK status. */ thd->stmt_da->can_overwrite_status= TRUE; - ha_autocommit_or_rollback(thd, thd->is_error()); + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); thd->stmt_da->can_overwrite_status= FALSE; thd->transaction.stmt.reset(); @@ -3485,7 +3305,7 @@ end_with_restore_list: thd->locked_tables_list.unlock_locked_tables(thd); if (thd->options & OPTION_TABLE_LOCK) { - end_active_trans(thd); + trans_commit_implicit(thd); thd->options&= ~(OPTION_TABLE_LOCK); } if (thd->global_read_lock) @@ -3495,7 +3315,7 @@ end_with_restore_list: case SQLCOM_LOCK_TABLES: thd->locked_tables_list.unlock_locked_tables(thd); /* we must end the trasaction first, regardless of anything */ - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) goto error; if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) @@ -3524,8 +3344,8 @@ end_with_restore_list: can free its locks if LOCK TABLES locked some tables before finding that it can't lock a table in its list */ - ha_autocommit_or_rollback(thd, 1); - end_active_trans(thd); + trans_rollback_stmt(thd); + trans_commit_implicit(thd); thd->options&= ~(OPTION_TABLE_LOCK); } else @@ -4007,132 +3827,50 @@ end_with_restore_list: break; case SQLCOM_BEGIN: - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (begin_trans(thd)) + if (trans_begin(thd, lex->start_transaction_opt)) goto error; - if (lex->start_transaction_opt & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) - { - if (ha_start_consistent_snapshot(thd)) - goto error; - } my_ok(thd); break; case SQLCOM_COMMIT: DBUG_ASSERT(thd->lock == NULL || thd->locked_tables_mode == LTM_LOCK_TABLES); - if (end_trans(thd, lex->tx_release ? COMMIT_RELEASE : - lex->tx_chain ? COMMIT_AND_CHAIN : COMMIT)) + if (trans_commit(thd)) goto error; + /* Begin transaction with the same isolation level. */ + if (lex->tx_chain && trans_begin(thd)) + goto error; + /* Disconnect the current client connection. */ + if (lex->tx_release) + thd->killed= THD::KILL_CONNECTION; my_ok(thd); break; case SQLCOM_ROLLBACK: DBUG_ASSERT(thd->lock == NULL || thd->locked_tables_mode == LTM_LOCK_TABLES); - if (end_trans(thd, lex->tx_release ? ROLLBACK_RELEASE : - lex->tx_chain ? ROLLBACK_AND_CHAIN : ROLLBACK)) + if (trans_rollback(thd)) goto error; + /* Begin transaction with the same isolation level. */ + if (lex->tx_chain && trans_begin(thd)) + goto error; + /* Disconnect the current client connection. */ + if (lex->tx_release) + thd->killed= THD::KILL_CONNECTION; my_ok(thd); break; case SQLCOM_RELEASE_SAVEPOINT: - { - SAVEPOINT *sv; - for (sv=thd->transaction.savepoints; sv; sv=sv->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)sv->name, sv->length) == 0) - break; - } - if (sv) - { - if (ha_release_savepoint(thd, sv)) - res= TRUE; // cannot happen - else - my_ok(thd); - thd->transaction.savepoints=sv->prev; - } - else - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str); + if (trans_release_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; - } case SQLCOM_ROLLBACK_TO_SAVEPOINT: - { - SAVEPOINT *sv; - for (sv=thd->transaction.savepoints; sv; sv=sv->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)sv->name, sv->length) == 0) - break; - } - if (sv) - { - if (ha_rollback_to_savepoint(thd, sv)) - res= TRUE; // cannot happen - else - { - if (((thd->options & OPTION_KEEP_LOG) || - thd->transaction.all.modified_non_trans_table) && - !thd->slave_thread) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARNING_NOT_COMPLETE_ROLLBACK, - ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); - my_ok(thd); - } - thd->transaction.savepoints=sv; - } - else - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str); + if (trans_rollback_to_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; - } case SQLCOM_SAVEPOINT: - if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || - thd->in_sub_stmt) || !opt_using_transactions) - my_ok(thd); - else - { - SAVEPOINT **sv, *newsv; - for (sv=&thd->transaction.savepoints; *sv; sv=&(*sv)->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)(*sv)->name, (*sv)->length) == 0) - break; - } - if (*sv) /* old savepoint of the same name exists */ - { - newsv=*sv; - ha_release_savepoint(thd, *sv); // it cannot fail - *sv=(*sv)->prev; - } - else if ((newsv=(SAVEPOINT *) alloc_root(&thd->transaction.mem_root, - savepoint_alloc_size)) == 0) - { - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - break; - } - newsv->name=strmake_root(&thd->transaction.mem_root, - lex->ident.str, lex->ident.length); - newsv->length=lex->ident.length; - /* - if we'll get an error here, don't add new savepoint to the list. - we'll lose a little bit of memory in transaction mem_root, but it'll - be free'd when transaction ends anyway - */ - if (ha_savepoint(thd, newsv)) - res= TRUE; - else - { - newsv->prev=thd->transaction.savepoints; - thd->transaction.savepoints=newsv; - my_ok(thd); - } - } + if (trans_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; case SQLCOM_CREATE_PROCEDURE: case SQLCOM_CREATE_SPFUNCTION: @@ -4456,7 +4194,7 @@ create_sp_error: lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) goto error; - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) goto error; #ifndef NO_EMBEDDED_ACCESS_CHECKS if (sp_automatic_privileges && !opt_noacl && @@ -4620,185 +4358,29 @@ create_sp_error: break; } case SQLCOM_XA_START: - if (thd->transaction.xid_state.xa_state == XA_IDLE && - thd->lex->xa_opt == XA_RESUME) - { - if (! thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - thd->transaction.xid_state.xa_state=XA_ACTIVE; - my_ok(thd); - break; - } - if (thd->lex->xa_opt != XA_NONE) - { // JOIN is not supported yet. TODO - my_error(ER_XAER_INVAL, MYF(0)); - break; - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (thd->locked_tables_mode || thd->active_transaction()) - { - my_error(ER_XAER_OUTSIDE, MYF(0)); - break; - } - if (xid_cache_search(thd->lex->xid)) - { - my_error(ER_XAER_DUPID, MYF(0)); - break; - } - DBUG_ASSERT(thd->transaction.xid_state.xid.is_null()); - thd->transaction.xid_state.xa_state=XA_ACTIVE; - thd->transaction.xid_state.rm_error= 0; - thd->transaction.xid_state.xid.set(thd->lex->xid); - xid_cache_insert(&thd->transaction.xid_state); - thd->transaction.all.modified_non_trans_table= FALSE; - thd->options= ((thd->options & ~(OPTION_KEEP_LOG)) | OPTION_BEGIN); - thd->server_status|= SERVER_STATUS_IN_TRANS; + if (trans_xa_start(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_END: - /* fake it */ - if (thd->lex->xa_opt != XA_NONE) - { // SUSPEND and FOR MIGRATE are not supported yet. TODO - my_error(ER_XAER_INVAL, MYF(0)); - break; - } - if (thd->transaction.xid_state.xa_state != XA_ACTIVE) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - if (xa_trans_rolled_back(&thd->transaction.xid_state)) - break; - thd->transaction.xid_state.xa_state=XA_IDLE; + if (trans_xa_end(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_PREPARE: - if (thd->transaction.xid_state.xa_state != XA_IDLE) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - if (ha_prepare(thd)) - { - my_error(ER_XA_RBROLLBACK, MYF(0)); - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state=XA_NOTR; - break; - } - thd->transaction.xid_state.xa_state=XA_PREPARED; + if (trans_xa_prepare(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_COMMIT: - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - XID_STATE *xs=xid_cache_search(thd->lex->xid); - if (!xs || xs->in_thd) - my_error(ER_XAER_NOTA, MYF(0)); - else if (xa_trans_rolled_back(xs)) - { - ha_commit_or_rollback_by_xid(thd->lex->xid, 0); - xid_cache_delete(xs); - break; - } - else - { - ha_commit_or_rollback_by_xid(thd->lex->xid, 1); - xid_cache_delete(xs); - my_ok(thd); - } - break; - } - if (xa_trans_rolled_back(&thd->transaction.xid_state)) - { - xa_trans_rollback(thd); - break; - } - if (thd->transaction.xid_state.xa_state == XA_IDLE && - thd->lex->xa_opt == XA_ONE_PHASE) - { - int r; - if ((r= ha_commit(thd))) - my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); - } - else if (thd->transaction.xid_state.xa_state == XA_PREPARED && - thd->lex->xa_opt == XA_NONE) - { - if (wait_if_global_read_lock(thd, 0, 0)) - { - ha_rollback(thd); - my_error(ER_XAER_RMERR, MYF(0)); - } - else - { - if (ha_commit_one_phase(thd, 1)) - my_error(ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); - start_waiting_global_read_lock(thd); - } - } - else - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state=XA_NOTR; + if (trans_xa_commit(thd)) + goto error; + my_ok(thd); break; case SQLCOM_XA_ROLLBACK: - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - XID_STATE *xs=xid_cache_search(thd->lex->xid); - if (!xs || xs->in_thd) - my_error(ER_XAER_NOTA, MYF(0)); - else - { - bool ok= !xa_trans_rolled_back(xs); - ha_commit_or_rollback_by_xid(thd->lex->xid, 0); - xid_cache_delete(xs); - if (ok) - my_ok(thd); - } - break; - } - if (thd->transaction.xid_state.xa_state != XA_IDLE && - thd->transaction.xid_state.xa_state != XA_PREPARED && - thd->transaction.xid_state.xa_state != XA_ROLLBACK_ONLY) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (xa_trans_rollback(thd)) - my_error(ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); + if (trans_xa_rollback(thd)) + goto error; + my_ok(thd); break; case SQLCOM_XA_RECOVER: res= mysql_xa_recover(thd); diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index f4525db7def..e0461533dde 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -50,6 +50,7 @@ #include #include #include "my_md5.h" +#include "transaction.h" #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" @@ -4327,8 +4328,8 @@ static int fast_end_partition(THD *thd, ulonglong copied, if (!is_empty) query_cache_invalidate3(thd, table_list, 0); - error= ha_autocommit_or_rollback(thd, 0); - if (end_active_trans(thd)) + error= trans_commit_stmt(thd); + if (trans_commit_implicit(thd)) error= 1; if (error) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 9f486768043..d6a592c4799 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -22,6 +22,7 @@ #include "sp_head.h" #include "sql_trigger.h" #include "sql_show.h" +#include "transaction.h" #ifdef __WIN__ #include @@ -4668,8 +4669,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, DBUG_PRINT("admin", ("calling prepare_func")); switch ((*prepare_func)(thd, table, check_opt)) { case 1: // error, message written to net - ha_autocommit_or_rollback(thd, 1); - end_trans(thd, ROLLBACK); + trans_rollback_stmt(thd); + trans_rollback(thd); close_thread_tables(thd); DBUG_PRINT("admin", ("simple error, admin next table")); continue; @@ -4740,8 +4741,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY), table_name); protocol->store(buff, length, system_charset_info); - ha_autocommit_or_rollback(thd, 0); - end_trans(thd, COMMIT); + trans_commit_stmt(thd); + trans_commit(thd); close_thread_tables(thd); lex->reset_query_tables_list(FALSE); table->table=0; // For query cache @@ -4790,7 +4791,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, HA_ADMIN_NEEDS_ALTER)) { DBUG_PRINT("admin", ("recreating table")); - ha_autocommit_or_rollback(thd, 1); + trans_rollback_stmt(thd); close_thread_tables(thd); tmp_disable_binlog(thd); // binlogging is done by caller if wanted result_code= mysql_recreate_table(thd, table); @@ -4910,7 +4911,7 @@ send_result_message: system_charset_info); if (protocol->write()) goto err; - ha_autocommit_or_rollback(thd, 0); + trans_commit_stmt(thd); close_thread_tables(thd); DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); TABLE_LIST *save_next_local= table->next_local, @@ -4927,7 +4928,7 @@ send_result_message: */ if (thd->stmt_da->is_ok()) thd->stmt_da->reset_diagnostics_area(); - ha_autocommit_or_rollback(thd, 0); + trans_commit_stmt(thd); close_thread_tables(thd); if (!result_code) // recreation went ok { @@ -5022,8 +5023,8 @@ send_result_message: query_cache_invalidate3(thd, table->table, 0); } } - ha_autocommit_or_rollback(thd, 0); - end_trans(thd, COMMIT); + trans_commit_stmt(thd); + trans_commit_implicit(thd); close_thread_tables(thd); table->table=0; // For query cache if (protocol->write()) @@ -5034,8 +5035,8 @@ send_result_message: DBUG_RETURN(FALSE); err: - ha_autocommit_or_rollback(thd, 1); - end_trans(thd, ROLLBACK); + trans_rollback_stmt(thd); + trans_rollback(thd); close_thread_tables(thd); // Shouldn't be needed if (table) table->table=0; @@ -5550,15 +5551,15 @@ mysql_discard_or_import_tablespace(THD *thd, query_cache_invalidate3(thd, table_list, 0); /* The ALTER TABLE is always in its own transaction */ - error = ha_autocommit_or_rollback(thd, 0); - if (end_active_trans(thd)) + error= trans_commit_stmt(thd); + if (trans_commit_implicit(thd)) error=1; if (error) goto err; write_bin_log(thd, FALSE, thd->query(), thd->query_length()); err: - ha_autocommit_or_rollback(thd, error); + trans_rollback_stmt(thd); thd->tablespace_op=FALSE; if (error == 0) @@ -7208,8 +7209,8 @@ view_err: thd_proc_info(thd, "manage keys"); alter_table_manage_keys(table, table->file->indexes_are_disabled(), alter_info->keys_onoff); - error= ha_autocommit_or_rollback(thd, 0); - if (end_active_trans(thd)) + error= trans_commit_stmt(thd); + if (trans_commit_implicit(thd)) error= 1; } thd->count_cuted_fields= CHECK_FIELD_IGNORE; @@ -7297,7 +7298,7 @@ view_err: /* Need to commit before a table is unlocked (NDB requirement). */ DBUG_PRINT("info", ("Committing before unlocking table")); - if (ha_autocommit_or_rollback(thd, 0) || end_active_trans(thd)) + if (trans_commit_stmt(thd) || trans_commit_implicit(thd)) goto err_new_table_cleanup; committed= 1; } @@ -7803,9 +7804,9 @@ copy_data_between_tables(TABLE *from,TABLE *to, Ensure that the new table is saved properly to disk so that we can do a rename */ - if (ha_autocommit_or_rollback(thd, 0)) + if (trans_commit_stmt(thd)) error=1; - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) error=1; err: diff --git a/sql/transaction.cc b/sql/transaction.cc new file mode 100644 index 00000000000..f4a616ead6b --- /dev/null +++ b/sql/transaction.cc @@ -0,0 +1,648 @@ +/* Copyright (C) 2008 Sun/MySQL + + 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 Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "mysql_priv.h" +#include "transaction.h" +#include "rpl_handler.h" + +/* Conditions under which the transaction state must not change. */ +static bool trans_check(THD *thd) +{ + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_check"); + + if (unlikely(thd->in_sub_stmt)) + my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); + if (xa_state != XA_NOTR) + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + else + DBUG_RETURN(FALSE); + + DBUG_RETURN(TRUE); +} + + +/** + Mark a XA transaction as rollback-only if the RM unilaterally + rolled back the transaction branch. + + @note If a rollback was requested by the RM, this function sets + the appropriate rollback error code and transits the state + to XA_ROLLBACK_ONLY. + + @return TRUE if transaction was rolled back or if the transaction + state is XA_ROLLBACK_ONLY. FALSE otherwise. +*/ +static bool xa_trans_rolled_back(XID_STATE *xid_state) +{ + if (xid_state->rm_error) + { + switch (xid_state->rm_error) { + case ER_LOCK_WAIT_TIMEOUT: + my_error(ER_XA_RBTIMEOUT, MYF(0)); + break; + case ER_LOCK_DEADLOCK: + my_error(ER_XA_RBDEADLOCK, MYF(0)); + break; + default: + my_error(ER_XA_RBROLLBACK, MYF(0)); + } + xid_state->xa_state= XA_ROLLBACK_ONLY; + } + + return (xid_state->xa_state == XA_ROLLBACK_ONLY); +} + + +/** + Begin a new transaction. + + @note Beginning a transaction implicitly commits any current + transaction and releases existing locks. + + @param thd Current thread + @param flags Transaction flags + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_begin(THD *thd, uint flags) +{ + int res= FALSE; + DBUG_ENTER("trans_begin"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + thd->locked_tables_list.unlock_locked_tables(thd); + + DBUG_ASSERT(!thd->locked_tables_mode); + + if (trans_commit_implicit(thd)) + DBUG_RETURN(TRUE); + + thd->options|= OPTION_BEGIN; + thd->server_status|= SERVER_STATUS_IN_TRANS; + + if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) + res= ha_start_consistent_snapshot(thd); + + DBUG_RETURN(test(res)); +} + + +/** + Commit the current transaction, making its changes permanent. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_commit(THD *thd) +{ + int res; + DBUG_ENTER("trans_commit"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= ha_commit_trans(thd, TRUE); + if (res) + /* + if res is non-zero, then ha_commit_trans has rolled back the + transaction, so the hooks for rollback will be called. + */ + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + else + RUN_HOOK(transaction, after_commit, (thd, FALSE)); + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->lex->start_transaction_opt= 0; + + DBUG_RETURN(test(res)); +} + + +/** + Implicitly commit the current transaction. + + @note A implicit commit does not releases existing table locks. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_commit_implicit(THD *thd) +{ + bool res= FALSE; + DBUG_ENTER("trans_commit_implicit"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN | + OPTION_TABLE_LOCK)) + { + /* Safety if one did "drop table" on locked tables */ + if (!thd->locked_tables_mode) + thd->options&= ~OPTION_TABLE_LOCK; + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= test(ha_commit_trans(thd, TRUE)); + } + + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + + DBUG_RETURN(res); +} + + +/** + Rollback the current transaction, canceling its changes. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_rollback(THD *thd) +{ + int res; + DBUG_ENTER("trans_rollback"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= ha_rollback_trans(thd, TRUE); + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->lex->start_transaction_opt= 0; + + DBUG_RETURN(test(res)); +} + + +/** + Commit the single statement transaction. + + @note Note that if the autocommit is on, then the following call + inside InnoDB will commit or rollback the whole transaction + (= the statement). The autocommit mechanism built into InnoDB + is based on counting locks, but if the user has used LOCK + TABLES then that mechanism does not know to do the commit. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_commit_stmt(THD *thd) +{ + DBUG_ENTER("trans_commit_stmt"); + int res= FALSE; + if (thd->transaction.stmt.ha_list) + res= ha_commit_trans(thd, FALSE); + + if (res) + /* + if res is non-zero, then ha_commit_trans has rolled back the + transaction, so the hooks for rollback will be called. + */ + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + else + RUN_HOOK(transaction, after_commit, (thd, FALSE)); + DBUG_RETURN(test(res)); +} + + +/** + Rollback the single statement transaction. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ +bool trans_rollback_stmt(THD *thd) +{ + DBUG_ENTER("trans_rollback_stmt"); + + if (thd->transaction.stmt.ha_list) + { + ha_rollback_trans(thd, FALSE); + if (thd->transaction_rollback_request && !thd->in_sub_stmt) + ha_rollback_trans(thd, TRUE); + } + + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + + DBUG_RETURN(FALSE); +} + +/* Find a named savepoint in the current transaction. */ +static SAVEPOINT ** +find_savepoint(THD *thd, LEX_STRING name) +{ + SAVEPOINT **sv= &thd->transaction.savepoints; + + while (*sv) + { + if (my_strnncoll(system_charset_info, (uchar *) name.str, name.length, + (uchar *) (*sv)->name, (*sv)->length) == 0) + break; + sv= &(*sv)->prev; + } + + return sv; +} + + +/** + Set a named transaction savepoint. + + @param thd Current thread + @param name Savepoint name + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_savepoint(THD *thd, LEX_STRING name) +{ + SAVEPOINT **sv, *newsv; + DBUG_ENTER("trans_savepoint"); + + if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || + thd->in_sub_stmt) || !opt_using_transactions) + DBUG_RETURN(FALSE); + + sv= find_savepoint(thd, name); + + if (*sv) /* old savepoint of the same name exists */ + { + newsv= *sv; + ha_release_savepoint(thd, *sv); + *sv= (*sv)->prev; + } + else if ((newsv= (SAVEPOINT *) alloc_root(&thd->transaction.mem_root, + savepoint_alloc_size)) == NULL) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + DBUG_RETURN(TRUE); + } + + newsv->name= strmake_root(&thd->transaction.mem_root, name.str, name.length); + newsv->length= name.length; + + /* + if we'll get an error here, don't add new savepoint to the list. + we'll lose a little bit of memory in transaction mem_root, but it'll + be free'd when transaction ends anyway + */ + if (ha_savepoint(thd, newsv)) + DBUG_RETURN(TRUE); + + newsv->prev= thd->transaction.savepoints; + thd->transaction.savepoints= newsv; + + DBUG_RETURN(FALSE); +} + + +/** + Rollback a transaction to the named savepoint. + + @note Modifications that the current transaction made to + rows after the savepoint was set are undone in the + rollback. + + @note Savepoints that were set at a later time than the + named savepoint are deleted. + + @param thd Current thread + @param name Savepoint name + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name) +{ + int res= FALSE; + SAVEPOINT *sv= *find_savepoint(thd, name); + DBUG_ENTER("trans_rollback_to_savepoint"); + + if (sv == NULL) + { + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str); + DBUG_RETURN(TRUE); + } + + if (ha_rollback_to_savepoint(thd, sv)) + res= TRUE; + else if (((thd->options & OPTION_KEEP_LOG) || + thd->transaction.all.modified_non_trans_table) && + !thd->slave_thread) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARNING_NOT_COMPLETE_ROLLBACK, + ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); + + thd->transaction.savepoints= sv; + + DBUG_RETURN(test(res)); +} + + +/** + Remove the named savepoint from the set of savepoints of + the current transaction. + + @note No commit or rollback occurs. It is an error if the + savepoint does not exist. + + @param thd Current thread + @param name Savepoint name + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_release_savepoint(THD *thd, LEX_STRING name) +{ + int res= FALSE; + SAVEPOINT *sv= *find_savepoint(thd, name); + DBUG_ENTER("trans_release_savepoint"); + + if (sv == NULL) + { + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str); + DBUG_RETURN(TRUE); + } + + if (ha_release_savepoint(thd, sv)) + res= TRUE; + + thd->transaction.savepoints= sv->prev; + + DBUG_RETURN(test(res)); +} + + +/** + Starts an XA transaction with the given xid value. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_start(THD *thd) +{ + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_xa_start"); + + if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_RESUME) + { + bool not_equal= !thd->transaction.xid_state.xid.eq(thd->lex->xid); + if (not_equal) + my_error(ER_XAER_NOTA, MYF(0)); + else + thd->transaction.xid_state.xa_state= XA_ACTIVE; + DBUG_RETURN(not_equal); + } + + /* TODO: JOIN is not supported yet. */ + if (thd->lex->xa_opt != XA_NONE) + my_error(ER_XAER_INVAL, MYF(0)); + else if (xa_state != XA_NOTR) + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + else if (thd->locked_tables_mode || thd->active_transaction()) + my_error(ER_XAER_OUTSIDE, MYF(0)); + else if (xid_cache_search(thd->lex->xid)) + my_error(ER_XAER_DUPID, MYF(0)); + else if (!trans_begin(thd)) + { + DBUG_ASSERT(thd->transaction.xid_state.xid.is_null()); + thd->transaction.xid_state.xa_state= XA_ACTIVE; + thd->transaction.xid_state.rm_error= 0; + thd->transaction.xid_state.xid.set(thd->lex->xid); + xid_cache_insert(&thd->transaction.xid_state); + DBUG_RETURN(FALSE); + } + + DBUG_RETURN(TRUE); +} + + +/** + Put a XA transaction in the IDLE state. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_end(THD *thd) +{ + DBUG_ENTER("trans_xa_end"); + + /* TODO: SUSPEND and FOR MIGRATE are not supported yet. */ + if (thd->lex->xa_opt != XA_NONE) + my_error(ER_XAER_INVAL, MYF(0)); + else if (thd->transaction.xid_state.xa_state != XA_ACTIVE) + my_error(ER_XAER_RMFAIL, MYF(0), + xa_state_names[thd->transaction.xid_state.xa_state]); + else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + my_error(ER_XAER_NOTA, MYF(0)); + else if (!xa_trans_rolled_back(&thd->transaction.xid_state)) + thd->transaction.xid_state.xa_state= XA_IDLE; + + DBUG_RETURN(thd->transaction.xid_state.xa_state != XA_IDLE); +} + + +/** + Put a XA transaction in the PREPARED state. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_prepare(THD *thd) +{ + DBUG_ENTER("trans_xa_prepare"); + + if (thd->transaction.xid_state.xa_state != XA_IDLE) + my_error(ER_XAER_RMFAIL, MYF(0), + xa_state_names[thd->transaction.xid_state.xa_state]); + else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + my_error(ER_XAER_NOTA, MYF(0)); + else if (ha_prepare(thd)) + { + xid_cache_delete(&thd->transaction.xid_state); + thd->transaction.xid_state.xa_state= XA_NOTR; + my_error(ER_XA_RBROLLBACK, MYF(0)); + } + else + thd->transaction.xid_state.xa_state= XA_PREPARED; + + DBUG_RETURN(thd->transaction.xid_state.xa_state != XA_PREPARED); +} + + +/** + Commit and terminate the a XA transaction. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_commit(THD *thd) +{ + bool res= TRUE; + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_xa_commit"); + + if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + { + XID_STATE *xs= xid_cache_search(thd->lex->xid); + res= !xs || xs->in_thd; + if (res) + my_error(ER_XAER_NOTA, MYF(0)); + else + { + res= xa_trans_rolled_back(xs); + ha_commit_or_rollback_by_xid(thd->lex->xid, !res); + xid_cache_delete(xs); + } + DBUG_RETURN(res); + } + + if (xa_trans_rolled_back(&thd->transaction.xid_state)) + { + if ((res= test(ha_rollback_trans(thd, TRUE)))) + my_error(ER_XAER_RMERR, MYF(0)); + } + else if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_ONE_PHASE) + { + int r= ha_commit_trans(thd, TRUE); + if ((res= test(r))) + my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0)); + } + else if (xa_state == XA_PREPARED && thd->lex->xa_opt == XA_NONE) + { + if (wait_if_global_read_lock(thd, 0, 0)) + { + ha_rollback_trans(thd, TRUE); + my_error(ER_XAER_RMERR, MYF(0)); + } + else + { + res= test(ha_commit_one_phase(thd, 1)); + if (res) + my_error(ER_XAER_RMERR, MYF(0)); + start_waiting_global_read_lock(thd); + } + } + else + { + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + DBUG_RETURN(TRUE); + } + + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + xid_cache_delete(&thd->transaction.xid_state); + thd->transaction.xid_state.xa_state= XA_NOTR; + + DBUG_RETURN(res); +} + + +/** + Roll back and terminate a XA transaction. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_rollback(THD *thd) +{ + bool res= TRUE; + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_xa_rollback"); + + if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + { + XID_STATE *xs= xid_cache_search(thd->lex->xid); + if (!xs || xs->in_thd) + my_error(ER_XAER_NOTA, MYF(0)); + else + { + xa_trans_rolled_back(xs); + ha_commit_or_rollback_by_xid(thd->lex->xid, 0); + xid_cache_delete(xs); + } + DBUG_RETURN(thd->stmt_da->is_error()); + } + + if (xa_state != XA_IDLE && xa_state != XA_PREPARED && xa_state != XA_ROLLBACK_ONLY) + { + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + DBUG_RETURN(TRUE); + } + + /* + Resource Manager error is meaningless at this point, as we perform + explicit rollback request by user. We must reset rm_error before + calling ha_rollback(), so thd->transaction.xid structure gets reset + by ha_rollback()/THD::transaction::cleanup(). + */ + thd->transaction.xid_state.rm_error= 0; + if ((res= test(ha_rollback_trans(thd, TRUE)))) + my_error(ER_XAER_RMERR, MYF(0)); + + thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + xid_cache_delete(&thd->transaction.xid_state); + thd->transaction.xid_state.xa_state= XA_NOTR; + + DBUG_RETURN(res); +} diff --git a/sql/transaction.h b/sql/transaction.h new file mode 100644 index 00000000000..135e6cae73f --- /dev/null +++ b/sql/transaction.h @@ -0,0 +1,46 @@ +/* Copyright (C) 2008 Sun/MySQL + + 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 Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef TRANSACTION_H +#define TRANSACTION_H + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +#include +#include + +class THD; + +bool trans_begin(THD *thd, uint flags= 0); +bool trans_commit(THD *thd); +bool trans_commit_implicit(THD *thd); +bool trans_rollback(THD *thd); + +bool trans_commit_stmt(THD *thd); +bool trans_rollback_stmt(THD *thd); + +bool trans_savepoint(THD *thd, LEX_STRING name); +bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name); +bool trans_release_savepoint(THD *thd, LEX_STRING name); + +bool trans_xa_start(THD *thd); +bool trans_xa_end(THD *thd); +bool trans_xa_prepare(THD *thd); +bool trans_xa_commit(THD *thd); +bool trans_xa_rollback(THD *thd); + +#endif /* TRANSACTION_H */ From f9f2bd193f934c8e2904688335f5103472391b1b Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 21:59:52 +0300 Subject: [PATCH 059/212] Backport of: ----------------------------------------------------------- 2497.479.10 Sergei Golubchik 2008-08-27 proc_info_hook, mysys access to thd->proc_info This patch is necessary for backport of WL#4284 "Transactional DDL locking". include/my_global.h: proc_info_hook include/my_sys.h: proc_info_hook mysys/my_static.c: proc_info_hook sql/mysqld.cc: proc_info_hook sql/sql_class.cc: proc_info_hook --- include/my_global.h | 2 +- include/my_sys.h | 3 +++ mysys/my_static.c | 13 +++++++++++++ sql/mysqld.cc | 4 ++++ sql/sql_class.cc | 11 +++++++---- 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/include/my_global.h b/include/my_global.h index 0f2aea62438..f95c8aa2441 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -1561,7 +1561,7 @@ inline void operator delete[](void*, void*) { /* Do nothing */ } #if !defined(max) #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) -#endif +#endif /* Only Linux is known to need an explicit sync of the directory to make sure a file creation/deletion/renaming in(from,to) this directory durable. diff --git a/include/my_sys.h b/include/my_sys.h index 9121f0f249e..bbbc89cb9f8 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -235,6 +235,9 @@ extern void (*fatal_error_handler_hook)(uint my_err, const char *str, extern uint my_file_limit; extern ulong my_thread_stack_size; +extern const char *(*proc_info_hook)(void *, const char *, const char *, + const char *, const unsigned int); + #ifdef HAVE_LARGE_PAGES extern my_bool my_use_large_pages; extern uint my_large_page_size; diff --git a/mysys/my_static.c b/mysys/my_static.c index 62e6d402315..62caeee89c4 100644 --- a/mysys/my_static.c +++ b/mysys/my_static.c @@ -92,6 +92,19 @@ void (*error_handler_hook)(uint error,const char *str,myf MyFlags)= void (*fatal_error_handler_hook)(uint error,const char *str,myf MyFlags)= my_message_no_curses; +static const char *proc_info_dummy(void *a __attribute__((unused)), + const char *b __attribute__((unused)), + const char *c __attribute__((unused)), + const char *d __attribute__((unused)), + const unsigned int e __attribute__((unused))) +{ + return 0; +} + +/* this is to be able to call set_thd_proc_info from the C code */ +const char *(*proc_info_hook)(void *, const char *, const char *, const char *, + const unsigned int)= proc_info_dummy; + #if defined(ENABLED_DEBUG_SYNC) /** Global pointer to be set if callback function is defined diff --git a/sql/mysqld.cc b/sql/mysqld.cc index ee341314cd6..1055d5fed8b 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3828,6 +3828,10 @@ static int init_server_components() } } + proc_info_hook= (const char *(*)(void *, const char *, const char *, + const char *, const unsigned int)) + set_thd_proc_info; + if (xid_cache_init()) { sql_print_error("Out of memory"); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index de2ef98e993..ee6612acfe2 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -247,13 +247,16 @@ int thd_tablespace_op(const THD *thd) extern "C" -const char *set_thd_proc_info(THD *thd, const char *info, - const char *calling_function, - const char *calling_file, +const char *set_thd_proc_info(THD *thd, const char *info, + const char *calling_function, + const char *calling_file, const unsigned int calling_line) { + if (!thd) + thd= current_thd; + const char *old_info= thd->proc_info; - DBUG_PRINT("proc_info", ("%s:%d %s", calling_file, calling_line, + DBUG_PRINT("proc_info", ("%s:%d %s", calling_file, calling_line, (info != NULL) ? info : "(null)")); #if defined(ENABLED_PROFILING) thd->profiling.status_change(info, calling_function, calling_file, calling_line); From cd155be564a6e42c3a08781ba0d30458e241cdf0 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 3 Dec 2009 23:08:27 +0300 Subject: [PATCH 060/212] Backport of: ------------------------------------------------------------ revno: 3035.4.1 committer: Davi Arnaut branch nick: 39897-6.0 timestamp: Thu 2009-01-15 12:17:57 -0200 message: Bug#39897: lock_multi fails in pushbuild: timeout waiting for processlist The problem is that relying on the "Table lock" thread state in its current position to detect that a thread is waiting on a lock is race prone. The "Table lock" state change happens before the thread actually tries to grab a lock on a table. The solution is to move the "Table lock" state so that its set only when a thread is actually going to wait for a lock. The state change happens after the thread fails to grab the lock (because it is owned by other thread) and proceeds to wait on a condition. This is considered part of work related to WL#4284 "Transactional DDL locking" Warning: this patch contains an incompatible change. When waiting on a lock in thr_lock.c, the server used to display "Locked" processlist state. After this patch, the state is "Table lock". The new state was actually intended to be display since year 2002, when Monty added it. But up until removal of thd->locked boolean member, this state was ignored by SHOW PROCESSLIST code. mysql-test/r/lock_multi.result: A style fix. mysql-test/r/sp-threads.result: Changed output of SHOW PROCESSLIST (new wait state). mysql-test/t/lock_multi.test: Use a more accurate state description when waiting inside thr_lock.c. mysql-test/t/lock_sync.test: Use a more accurate state description when waiting inside thr_lock.c. mysql-test/t/multi_update.test: Use a more accurate state description when waiting inside thr_lock.c. mysql-test/t/query_cache_28249.test: Use a more accurate state description when waiting inside thr_lock.c. mysql-test/t/sp_notembedded.test: Use a more accurate state description when waiting inside thr_lock.c. mysql-test/t/status.test: Use a more accurate state description when waiting inside thr_lock.c. mysys/thr_lock.c: Update thread state while waiting for a table lock. sql/lock.cc: State change was moved inside thr_lock.c. --- mysql-test/r/lock_multi.result | 2 +- mysql-test/r/sp-threads.result | 2 +- mysql-test/t/delayed.test | 2 +- mysql-test/t/insert_notembedded.test | 2 +- mysql-test/t/lock_multi.test | 28 ++++++++++++++-------------- mysql-test/t/lock_sync.test | 2 +- mysql-test/t/multi_update.test | 4 ++-- mysql-test/t/query_cache_28249.test | 6 +++--- mysql-test/t/sp_notembedded.test | 2 +- mysql-test/t/status.test | 2 +- mysys/thr_lock.c | 7 +++++++ sql/lock.cc | 1 - 12 files changed, 33 insertions(+), 27 deletions(-) diff --git a/mysql-test/r/lock_multi.result b/mysql-test/r/lock_multi.result index 3f1165fd069..930983bae27 100644 --- a/mysql-test/r/lock_multi.result +++ b/mysql-test/r/lock_multi.result @@ -203,7 +203,7 @@ drop table if exists t1,t2; create table t1 (a int); flush status; lock tables t1 read; -insert into t1 values(1);; +insert into t1 values(1); unlock tables; drop table t1; select @tlwa < @tlwb; diff --git a/mysql-test/r/sp-threads.result b/mysql-test/r/sp-threads.result index 953830ecc87..d974cfb9605 100644 --- a/mysql-test/r/sp-threads.result +++ b/mysql-test/r/sp-threads.result @@ -35,7 +35,7 @@ call bug9486(); show processlist; Id User Host db Command Time State Info # root localhost test Sleep # NULL -# root localhost test Query # Locked update t1, t2 set val= 1 where id1=id2 +# root localhost test Query # Table lock update t1, t2 set val= 1 where id1=id2 # root localhost test Query # NULL show processlist # root localhost test Sleep # NULL unlock tables; diff --git a/mysql-test/t/delayed.test b/mysql-test/t/delayed.test index ee658d70d16..86311784b6a 100644 --- a/mysql-test/t/delayed.test +++ b/mysql-test/t/delayed.test @@ -307,7 +307,7 @@ connection update; connection default; let $wait_condition= select count(*) = 1 from information_schema.processlist - where command = "Delayed insert" and state = "upgrading lock"; + where command = "Delayed insert" and state = "Table lock"; --source include/wait_condition.inc connect (select,localhost,root,,); --echo connection: select diff --git a/mysql-test/t/insert_notembedded.test b/mysql-test/t/insert_notembedded.test index 2950acff3cc..510dc56e8f7 100644 --- a/mysql-test/t/insert_notembedded.test +++ b/mysql-test/t/insert_notembedded.test @@ -174,7 +174,7 @@ connection default; # we must wait till the insert opens and locks the table let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and id = $ID; + where state = "Table lock" and id = $ID; --source include/wait_condition.inc connect (select,localhost,root,,); --echo connection: select diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test index 58f46941920..c82e351dfef 100644 --- a/mysql-test/t/lock_multi.test +++ b/mysql-test/t/lock_multi.test @@ -22,7 +22,7 @@ connection reader; # Sleep a bit till the update of connection writer is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "update low_priority t1 set n = 4"; + where state = "Table lock" and info = "update low_priority t1 set n = 4"; --source include/wait_condition.inc send select n from t1; @@ -30,7 +30,7 @@ connection locker; # Sleep a bit till the select of connection reader is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "select n from t1"; + where state = "Table lock" and info = "select n from t1"; --source include/wait_condition.inc unlock tables; connection writer; @@ -50,7 +50,7 @@ connection reader; # Sleep a bit till the update of connection writer is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "update low_priority t1 set n = 4"; + where state = "Table lock" and info = "update low_priority t1 set n = 4"; --source include/wait_condition.inc select n from t1; connection locker; @@ -95,7 +95,7 @@ insert t1 select * from t2; connection locker; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "insert t1 select * from t2"; + where state = "Table lock" and info = "insert t1 select * from t2"; --source include/wait_condition.inc drop table t2; connection reader; @@ -119,7 +119,7 @@ connection locker; # Sleep a bit till the insert of connection reader is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "insert t1 select * from t2"; + where state = "Table lock" and info = "insert t1 select * from t2"; --source include/wait_condition.inc drop table t2; connection reader; @@ -163,8 +163,8 @@ SELECT user.Select_priv FROM user, db WHERE user.user = db.user LIMIT 1; connection locker; # Sleep a bit till the select of connection reader is in work and hangs let $wait_condition= - select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE state = "Table lock" AND info = "SELECT user.Select_priv FROM user, db WHERE user.user = db.user LIMIT 1"; --source include/wait_condition.inc # Make test case independent from earlier grants. @@ -298,7 +298,7 @@ connection reader; # Wait till connection writer is blocked let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "alter table t1 auto_increment=0"; + where state = "Table lock" and info = "alter table t1 auto_increment=0"; --source include/wait_condition.inc send alter table t1 auto_increment=0; @@ -306,7 +306,7 @@ connection locker; # Wait till connection reader is blocked let $wait_condition= select count(*) = 2 from information_schema.processlist - where state = "Locked" and info = "alter table t1 auto_increment=0"; + where state = "Table lock" and info = "alter table t1 auto_increment=0"; --source include/wait_condition.inc unlock tables; connection writer; @@ -461,16 +461,16 @@ update t1 set i= 10; connection reader; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "update t1 set i= 10"; + where state = "Table lock" and info = "update t1 set i= 10"; --source include/wait_condition.inc send select * from t1; connection default; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "select * from t1"; + where state = "Table lock" and info = "select * from t1"; --source include/wait_condition.inc -let $ID= `select id from information_schema.processlist where state = "Locked" and info = "update t1 set i= 10"`; +let $ID= `select id from information_schema.processlist where state = "Table lock" and info = "update t1 set i= 10"`; --replace_result $ID ID eval kill query $ID; connection reader; @@ -613,11 +613,11 @@ lock tables t1 read; let $tlwa= `show status like 'Table_locks_waited'`; connect (waiter,localhost,root,,); connection waiter; ---send insert into t1 values(1); +send insert into t1 values(1); connection default; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "insert into t1 values(1)"; + where state = "Table lock" and info = "insert into t1 values(1)"; --source include/wait_condition.inc let $tlwb= `show status like 'Table_locks_waited'`; unlock tables; diff --git a/mysql-test/t/lock_sync.test b/mysql-test/t/lock_sync.test index 73289685114..de8a1d7e43e 100644 --- a/mysql-test/t/lock_sync.test +++ b/mysql-test/t/lock_sync.test @@ -71,7 +71,7 @@ set debug_sync= 'now WAIT_FOR parked'; connection default; --echo # Wait until this LOCK TABLES statement starts waiting for table lock. let $wait_condition= select count(*)= 1 from information_schema.processlist - where state= 'Locked' and + where state= 'Table lock' and info='lock table t1 write'; --source include/wait_condition.inc --echo # Allow SELECT ... FOR UPDATE to resume. diff --git a/mysql-test/t/multi_update.test b/mysql-test/t/multi_update.test index fc37fd6a27d..6f8cc94e6b7 100644 --- a/mysql-test/t/multi_update.test +++ b/mysql-test/t/multi_update.test @@ -496,7 +496,7 @@ connection updater; # Wait till "alter table t1 ..." of session changer is in work. # = There is one session is in state "Locked". let $wait_condition= select count(*)= 1 from information_schema.processlist - where state= 'Locked'; + where state= 'Table lock'; --source include/wait_condition.inc send update t1, v1 set t1.b=t1.a+t1.b+v1.b where t1.a=v1.a; @@ -507,7 +507,7 @@ connection locker; # are in work. # = There are two session is in state "Locked". let $wait_condition= select count(*)= 2 from information_schema.processlist - where state= 'Locked'; + where state= 'Table lock'; --source include/wait_condition.inc unlock tables; diff --git a/mysql-test/t/query_cache_28249.test b/mysql-test/t/query_cache_28249.test index 390a1ce6e3d..c95d7553988 100644 --- a/mysql-test/t/query_cache_28249.test +++ b/mysql-test/t/query_cache_28249.test @@ -58,18 +58,18 @@ connection user3; # Typical information_schema.processlist content after sufficient sleep time # ID USER COMMAND TIME STATE INFO # .... -# 2 root Query 5 Locked SELECT *, (SELECT COUNT(*) FROM t2) FROM t1 +# 2 root Query 5 Table lock SELECT *, (SELECT COUNT(*) FROM t2) FROM t1 # .... # XXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # The values marked with 'X' must be reached. --echo # Poll till the select of connection user1 is blocked by the write lock on t1. let $wait_condition= SELECT COUNT(*) = 1 FROM information_schema.processlist -WHERE state = 'Locked' +WHERE state = 'Table lock' AND info = '$select_for_qc'; --source include/wait_condition.inc eval SELECT user,command,state,info FROM information_schema.processlist -WHERE state = 'Locked' +WHERE state = 'Table lock' AND info = '$select_for_qc'; INSERT INTO t1 VALUES (4); diff --git a/mysql-test/t/sp_notembedded.test b/mysql-test/t/sp_notembedded.test index f593e184ad2..f7984952e33 100644 --- a/mysql-test/t/sp_notembedded.test +++ b/mysql-test/t/sp_notembedded.test @@ -322,7 +322,7 @@ set session low_priority_updates=on; connection rl_wait; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and + where state = "Table lock" and info = "update t1 set value='updated' where value='old'"; --source include/wait_condition.inc diff --git a/mysql-test/t/status.test b/mysql-test/t/status.test index f951218f5c8..cca54f6c762 100644 --- a/mysql-test/t/status.test +++ b/mysql-test/t/status.test @@ -58,7 +58,7 @@ let $ID= `select connection_id()`; connection con2; --echo # Switched to connection: con2 # wait for the other query to start executing -let $wait_condition= select 1 from INFORMATION_SCHEMA.PROCESSLIST where ID = $ID and STATE = "Locked"; +let $wait_condition= select 1 from INFORMATION_SCHEMA.PROCESSLIST where ID = $ID and STATE = "Table lock"; --source include/wait_condition.inc unlock tables; diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index e92f68965d8..6433c04a96f 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -396,6 +396,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, struct timespec wait_timeout; enum enum_thr_lock_result result= THR_LOCK_ABORTED; my_bool can_deadlock= test(data->owner->info->n_cursors); + const char *old_proc_info; DBUG_ENTER("wait_for_lock"); /* @@ -434,6 +435,9 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, thread_var->current_cond= cond; data->cond= cond; + old_proc_info= proc_info_hook(NULL, "Table lock", + __func__, __FILE__, __LINE__); + if (can_deadlock) set_timespec(wait_timeout, table_lock_wait_timeout); while (!thread_var->abort || in_wait_list) @@ -504,6 +508,9 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, thread_var->current_mutex= 0; thread_var->current_cond= 0; pthread_mutex_unlock(&thread_var->mutex); + + proc_info_hook(NULL, old_proc_info, __func__, __FILE__, __LINE__); + DBUG_RETURN(result); } diff --git a/sql/lock.cc b/sql/lock.cc index d19f4b46821..a46fffc6180 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -310,7 +310,6 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, break; } DBUG_PRINT("info", ("thd->proc_info %s", thd->proc_info)); - thd_proc_info(thd, "Locked"); /* Copy the lock data array. thr_multi_lock() reorders its contens. */ memcpy(sql_lock->locks + sql_lock->lock_count, sql_lock->locks, sql_lock->lock_count * sizeof(*sql_lock->locks)); From 411a81954ecab031d58b99aaac3e2ee6518b6d80 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 4 Dec 2009 01:46:14 +0300 Subject: [PATCH 061/212] ------------------------------------------------------------ revno: 2617.22.4 committer: Davi Arnaut branch nick: mysql-6.0-runtime timestamp: Mon 2009-01-26 15:19:14 -0200 message: Move checks for OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN to a separate helper function. sql/ha_ndbcluster.cc: Use helper method to check transaction mode. sql/log.cc: Use helper method to check transaction mode. sql/sql_cache.cc: Use helper method to check transaction mode. sql/sql_class.cc: Use helper method to check transaction mode. sql/sql_class.h: Add helper method to check whether session is in a multi-statement transaction. sql/sql_parse.cc: Use helper method to check transaction mode. sql/transaction.cc: Use helper method to check transaction mode. --- sql/ha_ndbcluster.cc | 25 ++++++++++--------------- sql/log.cc | 7 +++---- sql/sql_cache.cc | 13 +++++-------- sql/sql_class.cc | 3 +-- sql/sql_class.h | 15 +++++++++++++++ sql/sql_parse.cc | 2 +- sql/transaction.cc | 7 +++---- 7 files changed, 38 insertions(+), 34 deletions(-) diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index a7c4cbf5d20..c7dbe5ad36c 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -4512,7 +4512,7 @@ int ha_ndbcluster::start_statement(THD *thd, trans_register_ha(thd, FALSE, ndbcluster_hton); if (!thd_ndb->trans) { - if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + if (thd->in_multi_stmt_transaction()) trans_register_ha(thd, TRUE, ndbcluster_hton); DBUG_PRINT("trans",("Starting transaction")); thd_ndb->trans= ndb->startTransaction(); @@ -4582,7 +4582,7 @@ int ha_ndbcluster::init_handler_for_statement(THD *thd, Thd_ndb *thd_ndb) } #endif - if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + if (thd->in_multi_stmt_transaction()) { const void *key= m_table; HASH_SEARCH_STATE state; @@ -4666,7 +4666,7 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) if (ndb_cache_check_time && m_rows_changed) { DBUG_PRINT("info", ("Rows has changed and util thread is running")); - if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + if (thd->in_multi_stmt_transaction()) { DBUG_PRINT("info", ("Add share to list of tables to be invalidated")); /* NOTE push_back allocates memory using transactions mem_root! */ @@ -4685,7 +4685,7 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) DBUG_PRINT("trans", ("Last external_lock")); PRINT_OPTION_FLAGS(thd); - if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + if (!thd->in_multi_stmt_transaction()) { if (thd_ndb->trans) { @@ -4795,8 +4795,7 @@ static int ndbcluster_commit(handlerton *hton, THD *thd, bool all) PRINT_OPTION_FLAGS(thd); DBUG_PRINT("enter", ("Commit %s", (all ? "all" : "stmt"))); thd_ndb->start_stmt_count= 0; - if (trans == NULL || (!all && - thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + if (trans == NULL || (!all && thd->in_multi_stmt_transaction())) { /* An odditity in the handler interface is that commit on handlerton @@ -4866,7 +4865,7 @@ static int ndbcluster_rollback(handlerton *hton, THD *thd, bool all) DBUG_ASSERT(ndb); thd_ndb->start_stmt_count= 0; if (trans == NULL || (!all && - thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + thd->in_multi_stmt_transaction())) { /* Ignore end-of-statement until real rollback or commit is called */ DBUG_PRINT("info", ("Rollback before start or end-of-statement only")); @@ -8041,17 +8040,15 @@ ndbcluster_cache_retrieval_allowed(THD *thd, ulonglong *engine_data) { Uint64 commit_count; - bool is_autocommit= !(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); char *dbname= full_name; char *tabname= dbname+strlen(dbname)+1; #ifndef DBUG_OFF char buff[22], buff2[22]; #endif DBUG_ENTER("ndbcluster_cache_retrieval_allowed"); - DBUG_PRINT("enter", ("dbname: %s, tabname: %s, is_autocommit: %d", - dbname, tabname, is_autocommit)); + DBUG_PRINT("enter", ("dbname: %s, tabname: %s", dbname, tabname)); - if (!is_autocommit) + if (thd->in_multi_stmt_transaction()) { DBUG_PRINT("exit", ("No, don't use cache in transaction")); DBUG_RETURN(FALSE); @@ -8116,12 +8113,10 @@ ha_ndbcluster::register_query_cache_table(THD *thd, #ifndef DBUG_OFF char buff[22]; #endif - bool is_autocommit= !(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); DBUG_ENTER("ha_ndbcluster::register_query_cache_table"); - DBUG_PRINT("enter",("dbname: %s, tabname: %s, is_autocommit: %d", - m_dbname, m_tabname, is_autocommit)); + DBUG_PRINT("enter",("dbname: %s, tabname: %s", m_dbname, m_tabname)); - if (!is_autocommit) + if (thd->in_multi_stmt_transaction()) { DBUG_PRINT("exit", ("Can't register table during transaction")); DBUG_RETURN(FALSE); diff --git a/sql/log.cc b/sql/log.cc index ea06f71b569..48e509e9275 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1503,7 +1503,7 @@ binlog_end_trans(THD *thd, binlog_trx_data *trx_data, transaction cache to remove the statement. */ thd->binlog_remove_pending_rows_event(TRUE); - if (all || !(thd->options & (OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT))) + if (all || !thd->in_multi_stmt_transaction()) { if (trx_data->has_incident()) mysql_bin_log.write_incident(thd, TRUE); @@ -1571,8 +1571,7 @@ static int binlog_commit(handlerton *hton, THD *thd, bool all) Otherwise, we accumulate the statement */ - ulonglong const in_transaction= - thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN); + bool const in_transaction= thd->in_multi_stmt_transaction(); DBUG_PRINT("debug", ("all: %d, empty: %s, in_transaction: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s", all, @@ -3903,7 +3902,7 @@ THD::binlog_start_trans_and_stmt() trx_data->before_stmt_pos == MY_OFF_T_UNDEF) { this->binlog_set_stmt_begin(); - if (options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + if (in_multi_stmt_transaction()) trans_register_ha(this, TRUE, binlog_hton); trans_register_ha(this, FALSE, binlog_hton); /* diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 871efeddd43..9d2a55f0f3a 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1517,7 +1517,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", } DBUG_PRINT("qcache", ("Query have result 0x%lx", (ulong) query)); - if ((thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && + if (thd->in_multi_stmt_transaction() && (query->tables_type() & HA_CACHE_TBL_TRANSACT)) { DBUG_PRINT("qcache", @@ -1674,8 +1674,7 @@ void Query_cache::invalidate(THD *thd, TABLE_LIST *tables_used, if (is_disabled()) DBUG_VOID_RETURN; - using_transactions= using_transactions && - (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + using_transactions= using_transactions && thd->in_multi_stmt_transaction(); for (; tables_used; tables_used= tables_used->next_local) { DBUG_ASSERT(!using_transactions || tables_used->table!=0); @@ -1759,8 +1758,7 @@ void Query_cache::invalidate(THD *thd, TABLE *table, if (is_disabled()) DBUG_VOID_RETURN; - using_transactions= using_transactions && - (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + using_transactions= using_transactions && thd->in_multi_stmt_transaction(); if (using_transactions && (table->file->table_cache_type() == HA_CACHE_TBL_TRANSACT)) thd->add_changed_table(table); @@ -1778,8 +1776,7 @@ void Query_cache::invalidate(THD *thd, const char *key, uint32 key_length, if (is_disabled()) DBUG_VOID_RETURN; - using_transactions= using_transactions && - (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + using_transactions= using_transactions && thd->in_multi_stmt_transaction(); if (using_transactions) // used for innodb => has_transactions() is TRUE thd->add_changed_table(key, key_length); else @@ -3549,7 +3546,7 @@ Query_cache::is_cacheable(THD *thd, size_t query_len, const char *query, tables_type))) DBUG_RETURN(0); - if ((thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && + if (thd->in_multi_stmt_transaction() && ((*tables_type)&HA_CACHE_TBL_TRANSACT)) { DBUG_PRINT("qcache", ("not in autocommin mode")); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ee6612acfe2..4f1777c152c 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1412,8 +1412,7 @@ void THD::add_changed_table(TABLE *table) { DBUG_ENTER("THD::add_changed_table(table)"); - DBUG_ASSERT((options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && - table->file->has_transactions()); + DBUG_ASSERT(in_multi_stmt_transaction() && table->file->has_transactions()); add_changed_table(table->s->table_cache_key.str, (long) table->s->table_cache_key.length); DBUG_VOID_RETURN; diff --git a/sql/sql_class.h b/sql/sql_class.h index bef8dff5759..34ba1386a89 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2008,6 +2008,21 @@ public: { return server_status & SERVER_STATUS_IN_TRANS; } + /** + Returns TRUE if session is in a multi-statement transaction mode. + + OPTION_NOT_AUTOCOMMIT: When autocommit is off, a multi-statement + transaction is implicitly started on the first statement after a + previous transaction has been ended. + + OPTION_BEGIN: Regardless of the autocommit status, a multi-statement + transaction can be explicitly started with the statements "START + TRANSACTION", "BEGIN [WORK]", "[COMMIT | ROLLBACK] AND CHAIN", etc. + */ + inline bool in_multi_stmt_transaction() + { + return options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN); + } inline bool fill_derived_tables() { return !stmt_arena->is_stmt_prepare() && !lex->only_view_structure(); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2df66a44fa6..83e45904816 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -5306,7 +5306,7 @@ void mysql_reset_thd_for_next_command(THD *thd) OPTION_STATUS_NO_TRANS_UPDATE | OPTION_KEEP_LOG to not get warnings in ha_rollback_trans() about some tables couldn't be rolled back. */ - if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + if (!thd->in_multi_stmt_transaction()) { thd->options&= ~OPTION_KEEP_LOG; thd->transaction.all.modified_non_trans_table= FALSE; diff --git a/sql/transaction.cc b/sql/transaction.cc index f4a616ead6b..7bfaf4846cf 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -163,8 +163,7 @@ bool trans_commit_implicit(THD *thd) if (trans_check(thd)) DBUG_RETURN(TRUE); - if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN | - OPTION_TABLE_LOCK)) + if (thd->in_multi_stmt_transaction() || (thd->options & OPTION_TABLE_LOCK)) { /* Safety if one did "drop table" on locked tables */ if (!thd->locked_tables_mode) @@ -299,8 +298,8 @@ bool trans_savepoint(THD *thd, LEX_STRING name) SAVEPOINT **sv, *newsv; DBUG_ENTER("trans_savepoint"); - if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || - thd->in_sub_stmt) || !opt_using_transactions) + if (!(thd->in_multi_stmt_transaction() || thd->in_sub_stmt) || + !opt_using_transactions) DBUG_RETURN(FALSE); sv= find_savepoint(thd, name); From cae6e72a40800664af6106560e706d38c79c3bbc Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 4 Dec 2009 01:48:14 +0300 Subject: [PATCH 062/212] Backport of: ---------------------------------------------------------- revno: 2617.22.7 committer: Konstantin Osipov branch nick: mysql-6.0-runtime timestamp: Tue 2009-01-27 16:41:58 +0300 message: WL#4284 "Transactional DDL locking" review. Improve a comment. sql/mdl.cc: Improve a comment. --- sql/mdl.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index f28778a1a83..78a85f49952 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -1320,10 +1320,11 @@ void mdl_release_locks(MDL_CONTEXT *context) { DBUG_PRINT("info", ("found lock to release lock_data=%p", lock_data)); /* - We should not release locks which pending shared locks as these - are not associated with lock object and don't present in its - lists. Allows us to avoid problems in open_tables() in case of - back-off + Don't call release_lock() for a shared lock if has not been + granted. Lock state in this case is MDL_INITIALIZED. + We have pending and granted shared locks in the same context + when this function is called from the "back-off" path of + open_tables(). */ if (lock_data->state != MDL_INITIALIZED) { From c462640d05aee113cb2aadb65e29db24939c5775 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 4 Dec 2009 01:55:34 +0300 Subject: [PATCH 063/212] WL#4284, "Transactional DDL locking". Add two more files to .bzrignore. .bzrignore: Ignore symlinks to new WL#4284 source and header files. --- .bzrignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.bzrignore b/.bzrignore index bc3ca7b0c24..0a30ac2d9b7 100644 --- a/.bzrignore +++ b/.bzrignore @@ -3071,3 +3071,5 @@ libmysqld/debug_sync.cc libmysqld/rpl_handler.cc dbug/tests libmysqld/mdl.cc +client/transaction.h +libmysqld/transaction.cc From 62ed4904c7761848774cb6a9dd70fa7dceefc81f Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 4 Dec 2009 01:55:58 +0300 Subject: [PATCH 064/212] WL#4284, "Transactional DDL locking". Add two more files to .bzrignore. .bzrignore: Ignore symlinks to new WL#4284 source and header files. From f477e66ec5773d9a078f27853d468b7c0453c975 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 4 Dec 2009 02:29:40 +0300 Subject: [PATCH 065/212] Backport of: ------------------------------------------------------------ revno: 2617.23.18 committer: Davi Arnaut branch nick: 4284-6.0 timestamp: Mon 2009-03-02 18:18:26 -0300 message: Bug#989: If DROP TABLE while there's an active transaction, wrong binlog order WL#4284: Transactional DDL locking This is a prerequisite patch: These changes are intended to split lock requests from granted locks and to allow the memory and lifetime of granted locks to be managed within the MDL subsystem. Furthermore, tickets can now be shared and therefore are used to satisfy multiple lock requests, but only shared locks can be recursive. The problem is that the MDL subsystem morphs lock requests into granted locks locks but does not manage the memory and lifetime of lock requests, and hence, does not manage the memory of granted locks either. This can be problematic because it puts the burden of tracking references on the users of the subsystem and it can't be easily done in transactional contexts where the locks have to be kept around for the duration of a transaction. Another issue is that recursive locks (when the context trying to acquire a lock already holds a lock on the same object) requires that each time the lock is granted, a unique lock request/granted lock structure structure must be kept around until the lock is released. This can lead to memory leaks in transactional contexts as locks taken during the transaction should only be released at the end of the transaction. This also leads to unnecessary wake ups (broadcasts) in the MDL subsystem if the context still holds a equivalent of the lock being released. These issues are exacerbated due to the fact that WL#4284 low-level design says that the implementation should "2) Store metadata locks in transaction memory root, rather than statement memory root" but this is not possible because a memory root, as implemented in mysys, requires all objects allocated from it to be freed all at once. This patch combines review input and significant code contributions from Konstantin Osipov (kostja) and Dmitri Lenev (dlenev). mysql-test/r/mdl_sync.result: Add test case result. mysql-test/t/mdl_sync.test: Add test case for shared lock upgrade case. sql/event_db_repository.cc: Rename mdl_alloc_lock to mdl_request_alloc. sql/ha_ndbcluster_binlog.cc: Use new function names to initialize MDL lock requests. sql/lock.cc: Rename MDL functions. sql/log_event.cc: The MDL request now holds the table and database name data (MDL_KEY). sql/mdl.cc: Move the MDL key to the MDL_LOCK structure in order to make the object suitable for allocation from a fixed-size allocator. This allows the simplification of the lists in the MDL_LOCK object, which now are just two, one for granted tickets and other for waiting (upgraders) tickets. Recursive requests for a shared lock on the same object can now be granted using the same lock ticket. This schema is only used for shared locks because that the only case that matters. This is used to avoid waste of resources in case a context (connection) already holds a shared lock on a object. sql/mdl.h: Introduce a metadata lock object key which is used to uniquely identify lock objects. Separate the structure used to represent pending lock requests from the structure used to represent granted metadata locks. Rename functions used to manipulate locks requests in order to have a more consistent function naming schema. sql/sp_head.cc: Rename mdl_alloc_lock to mdl_request_alloc. sql/sql_acl.cc: Rename alloc_mdl_locks to alloc_mdl_requests. sql/sql_base.cc: Various changes to accommodate that lock requests are separated from lock tickets (granted locks). sql/sql_class.h: Last acquired lock before the savepoint was set. sql/sql_delete.cc: Various changes to accommodate that lock requests are separated from lock tickets (granted locks). sql/sql_handler.cc: Various changes to accommodate that lock requests are separated from lock tickets (granted locks). sql/sql_insert.cc: Rename alloc_mdl_locks to alloc_mdl_requests. sql/sql_parse.cc: Rename alloc_mdl_locks to alloc_mdl_requests. sql/sql_plist.h: Typedef for iterator type. sql/sql_plugin.cc: Rename alloc_mdl_locks to alloc_mdl_requests. sql/sql_servers.cc: Rename alloc_mdl_locks to alloc_mdl_requests. sql/sql_show.cc: Various changes to accommodate that lock requests are separated from lock tickets (granted locks). sql/sql_table.cc: Various changes to accommodate that lock requests are separated from lock tickets (granted locks). sql/sql_trigger.cc: Save reference to the lock ticket so it can be downgraded later. sql/sql_udf.cc: Rename alloc_mdl_locks to alloc_mdl_requests. sql/table.cc: Rename mdl_alloc_lock to mdl_request_alloc. sql/table.h: Separate MDL lock requests from lock tickets (granted locks). storage/myisammrg/ha_myisammrg.cc: Rename alloc_mdl_locks to alloc_mdl_requests. --- mysql-test/r/mdl_sync.result | 21 + mysql-test/t/mdl_sync.test | 69 ++ sql/event_db_repository.cc | 8 +- sql/ha_ndbcluster_binlog.cc | 8 +- sql/lock.cc | 20 +- sql/log_event.cc | 13 +- sql/mdl.cc | 1020 ++++++++++++++++------------- sql/mdl.h | 300 +++++---- sql/sp_head.cc | 15 +- sql/sql_acl.cc | 16 +- sql/sql_base.cc | 104 +-- sql/sql_class.h | 2 + sql/sql_delete.cc | 24 +- sql/sql_handler.cc | 25 +- sql/sql_insert.cc | 2 +- sql/sql_parse.cc | 10 +- sql/sql_plist.h | 1 + sql/sql_plugin.cc | 6 +- sql/sql_servers.cc | 8 +- sql/sql_show.cc | 34 +- sql/sql_table.cc | 124 ++-- sql/sql_trigger.cc | 9 +- sql/sql_udf.cc | 6 +- sql/table.cc | 7 +- sql/table.h | 9 +- storage/myisammrg/ha_myisammrg.cc | 7 +- 26 files changed, 1046 insertions(+), 822 deletions(-) create mode 100644 mysql-test/r/mdl_sync.result create mode 100644 mysql-test/t/mdl_sync.test diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result new file mode 100644 index 00000000000..37f18746c57 --- /dev/null +++ b/mysql-test/r/mdl_sync.result @@ -0,0 +1,21 @@ +SET DEBUG_SYNC= 'RESET'; +drop table if exists t1,t2,t3; +create table t1 (i int); +create table t2 (i int); +connection: default +lock tables t2 read; +connection: con1 +set debug_sync='mdl_upgrade_shared_lock_to_exclusive SIGNAL parked WAIT_FOR go'; +alter table t1 rename t3; +connection: default +set debug_sync= 'now WAIT_FOR parked'; +connection: con2 +set debug_sync='mdl_acquire_exclusive_locks_wait SIGNAL go'; +drop table t1,t2; +connection: con1 +connection: default +unlock tables; +connection: con2 +ERROR 42S02: Unknown table 't1' +drop table t3; +SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test new file mode 100644 index 00000000000..ed03a9a5cb0 --- /dev/null +++ b/mysql-test/t/mdl_sync.test @@ -0,0 +1,69 @@ +# +# We need the Debug Sync Facility. +# +--source include/have_debug_sync.inc + +# Clean up resources used in this test case. +--disable_warnings +SET DEBUG_SYNC= 'RESET'; +--enable_warnings + +# +# Test the case of when a exclusive lock request waits for a +# shared lock being upgraded to a exclusive lock. +# + +connect (con1,localhost,root,,test,,); +connect (con2,localhost,root,,test,,); +connect (con3,localhost,root,,test,,); + +connection default; + +--disable_warnings +drop table if exists t1,t2,t3; +--enable_warnings + +create table t1 (i int); +create table t2 (i int); + +--echo connection: default +lock tables t2 read; + +connection con1; +--echo connection: con1 +set debug_sync='mdl_upgrade_shared_lock_to_exclusive SIGNAL parked WAIT_FOR go'; +--send alter table t1 rename t3 + +connection default; +--echo connection: default +set debug_sync= 'now WAIT_FOR parked'; + +connection con2; +--echo connection: con2 +set debug_sync='mdl_acquire_exclusive_locks_wait SIGNAL go'; +--send drop table t1,t2 + +connection con1; +--echo connection: con1 +--reap + +connection default; +--echo connection: default +unlock tables; + +connection con2; +--echo connection: con2 +--error ER_BAD_TABLE_ERROR +--reap + +connection default; +drop table t3; + +disconnect con1; +disconnect con2; +disconnect con3; + +# Clean up resources used in this test case. +--disable_warnings +SET DEBUG_SYNC= 'RESET'; +--enable_warnings diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index e9ee54ff8aa..134c8059e13 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -555,7 +555,7 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, DBUG_ENTER("Event_db_repository::open_event_table"); tables.init_one_table("mysql", 5, "event", 5, "event", lock_type); - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1110,7 +1110,7 @@ Event_db_repository::check_system_tables(THD *thd) /* Check mysql.db */ tables.init_one_table("mysql", 5, "db", 2, "db", TL_READ); - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1128,7 +1128,7 @@ Event_db_repository::check_system_tables(THD *thd) } /* Check mysql.user */ tables.init_one_table("mysql", 5, "user", 4, "user", TL_READ); - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1149,7 +1149,7 @@ Event_db_repository::check_system_tables(THD *thd) } /* Check mysql.event */ tables.init_one_table("mysql", 5, "event", 5, "event", TL_READ); - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index b9ea87aec52..aa1c50eaf5b 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -140,8 +140,7 @@ static Uint64 *p_latest_trans_gci= 0; */ static TABLE *ndb_binlog_index= 0; static TABLE_LIST binlog_tables; -static MDL_LOCK_DATA binlog_mdl_lock_data; -static char binlog_mdlkey[MAX_MDLKEY_LENGTH]; +static MDL_LOCK_REQUEST binlog_mdl_lock_request; /* Helper functions @@ -2343,9 +2342,8 @@ static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index) tables->alias= tables->table_name= reptable; tables->lock_type= TL_WRITE; thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE; - mdl_init_lock(&binlog_mdl_lock_data, binlog_mdlkey, 0, tables->db, - tables->table_name); - tables->mdl_lock_data= &binlog_mdl_lock_data; + mdl_request_init(&binlog_mdl_lock_request, 0, tables->db, tables->table_name); + tables->mdl_lock_request= &binlog_mdl_lock_request; tables->required_type= FRMTYPE_TABLE; uint counter; thd->clear_error(); diff --git a/sql/lock.cc b/sql/lock.cc index a46fffc6180..40bceae4a99 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -953,24 +953,24 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, bool lock_table_names(THD *thd, TABLE_LIST *table_list) { TABLE_LIST *lock_table; - MDL_LOCK_DATA *mdl_lock_data; + MDL_LOCK_REQUEST *mdl_lock_req; for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) { - if (!(mdl_lock_data= mdl_alloc_lock(0, lock_table->db, - lock_table->table_name, - thd->mem_root))) + mdl_lock_req= mdl_request_alloc(0, lock_table->db, lock_table->table_name, + thd->mem_root); + if (!mdl_lock_req) goto end; - mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, mdl_lock_data); - lock_table->mdl_lock_data= mdl_lock_data; + mdl_request_set_type(mdl_lock_req, MDL_EXCLUSIVE); + mdl_request_add(&thd->mdl_context, mdl_lock_req); + lock_table->mdl_lock_request= mdl_lock_req; } if (mdl_acquire_exclusive_locks(&thd->mdl_context)) goto end; return 0; end: - mdl_remove_all_locks(&thd->mdl_context); + mdl_request_remove_all(&thd->mdl_context); return 1; } @@ -986,8 +986,8 @@ end: void unlock_table_names(THD *thd) { DBUG_ENTER("unlock_table_names"); - mdl_release_locks(&thd->mdl_context); - mdl_remove_all_locks(&thd->mdl_context); + mdl_ticket_release_all(&thd->mdl_context); + mdl_request_remove_all(&thd->mdl_context); DBUG_VOID_RETURN; } diff --git a/sql/log_event.cc b/sql/log_event.cc index 9c3a2ce2131..9f4369b901e 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -8059,8 +8059,8 @@ Table_map_log_event::~Table_map_log_event() int Table_map_log_event::do_apply_event(Relay_log_info const *rli) { RPL_TABLE_LIST *table_list; - char *db_mem, *tname_mem, *mdlkey; - MDL_LOCK_DATA *mdl_lock_data; + char *db_mem, *tname_mem; + MDL_LOCK_REQUEST *mdl_lock_request; size_t dummy_len; void *memory; DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)"); @@ -8075,8 +8075,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) &table_list, (uint) sizeof(RPL_TABLE_LIST), &db_mem, (uint) NAME_LEN + 1, &tname_mem, (uint) NAME_LEN + 1, - &mdl_lock_data, sizeof(MDL_LOCK_DATA), - &mdlkey, MAX_MDLKEY_LENGTH, + &mdl_lock_request, sizeof(MDL_LOCK_REQUEST), NullS))) DBUG_RETURN(HA_ERR_OUT_OF_MEM); @@ -8089,9 +8088,9 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) table_list->updating= 1; strmov(table_list->db, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len)); strmov(table_list->table_name, m_tblnam); - mdl_init_lock(mdl_lock_data, mdlkey, 0, table_list->db, - table_list->table_name); - table_list->mdl_lock_data= mdl_lock_data; + mdl_request_init(mdl_lock_request, 0, table_list->db, + table_list->table_name); + table_list->mdl_lock_request= mdl_lock_request; int error= 0; diff --git a/sql/mdl.cc b/sql/mdl.cc index 78a85f49952..18f0debdbb5 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -16,6 +16,7 @@ #include "mdl.h" +#include "debug_sync.h" #include #include @@ -29,39 +30,43 @@ struct MDL_LOCK { - I_P_List active_shared; + typedef I_P_List > + Ticket_list; + + typedef Ticket_list::Iterator Ticket_iterator; + + /** The type of lock (shared or exclusive). */ + enum + { + SHARED, + EXCLUSIVE, + } type; + /** The key of the object (data) being protected. */ + MDL_KEY key; + /** List of granted tickets for this lock. */ + Ticket_list granted; /* There can be several upgraders and active exclusive belonging to the same context. */ - I_P_List active_shared_waiting_upgrade; - I_P_List active_exclusive; - I_P_List waiting_exclusive; - /** - Number of MDL_LOCK_DATA objects associated with this MDL_LOCK instance - and therefore present in one of above lists. Note that this number - doesn't account for pending requests for shared lock since we don't - associate them with MDL_LOCK and don't keep them in any list. - */ - uint lock_data_count; + Ticket_list waiting; void *cached_object; mdl_cached_object_release_hook cached_object_release_hook; - MDL_LOCK() : cached_object(0), cached_object_release_hook(0) {} - - MDL_LOCK_DATA *get_key_owner() + MDL_LOCK(const MDL_KEY *mdl_key) + : type(SHARED), cached_object(0), cached_object_release_hook(0) { - return !active_shared.is_empty() ? - active_shared.head() : - (!active_shared_waiting_upgrade.is_empty() ? - active_shared_waiting_upgrade.head() : - (!active_exclusive.is_empty() ? - active_exclusive.head() : waiting_exclusive.head())); + key.mdl_key_init(mdl_key); + granted.empty(); + waiting.empty(); } - bool has_one_lock_data() + bool is_empty() { - return (lock_data_count == 1); + return (granted.is_empty() && waiting.is_empty()); } }; @@ -86,12 +91,13 @@ struct MDL_GLOBAL_LOCK } global_lock; -extern "C" uchar *mdl_locks_key(const uchar *record, size_t *length, - my_bool not_used __attribute__((unused))) +extern "C" uchar * +mdl_locks_key(const uchar *record, size_t *length, + my_bool not_used __attribute__((unused))) { MDL_LOCK *entry=(MDL_LOCK*) record; - *length= entry->get_key_owner()->key_length; - return (uchar*) entry->get_key_owner()->key; + *length= entry->key.length(); + return (uchar*) entry->key.ptr(); } @@ -148,7 +154,8 @@ void mdl_destroy() void mdl_context_init(MDL_CONTEXT *context, THD *thd) { - context->locks.empty(); + context->requests.empty(); + context->tickets.empty(); context->thd= thd; context->has_global_shared_lock= FALSE; } @@ -168,7 +175,8 @@ void mdl_context_init(MDL_CONTEXT *context, THD *thd) void mdl_context_destroy(MDL_CONTEXT *context) { - DBUG_ASSERT(context->locks.is_empty()); + DBUG_ASSERT(context->requests.is_empty()); + DBUG_ASSERT(context->tickets.is_empty()); DBUG_ASSERT(!context->has_global_shared_lock); } @@ -185,8 +193,10 @@ void mdl_context_destroy(MDL_CONTEXT *context) void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) { - backup->locks.empty(); - ctx->locks.swap(backup->locks); + backup->requests.empty(); + backup->tickets.empty(); + ctx->requests.swap(backup->requests); + ctx->tickets.swap(backup->tickets); } @@ -196,8 +206,10 @@ void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) { - DBUG_ASSERT(ctx->locks.is_empty()); - ctx->locks.swap(backup->locks); + DBUG_ASSERT(ctx->requests.is_empty()); + DBUG_ASSERT(ctx->tickets.is_empty()); + ctx->requests.swap(backup->requests); + ctx->tickets.swap(backup->tickets); } @@ -207,20 +219,29 @@ void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) { - MDL_LOCK_DATA *lock_data; + MDL_LOCK_TICKET *ticket; + MDL_LOCK_REQUEST *lock_req; DBUG_ASSERT(dst->thd == src->thd); - if (!src->locks.is_empty()) + if (!src->requests.is_empty()) { - I_P_List_iterator it(src->locks); - while ((lock_data= it++)) + MDL_CONTEXT::Request_iterator it(src->requests); + while ((lock_req= it++)) + dst->requests.push_front(lock_req); + src->requests.empty(); + } + + if (!src->tickets.is_empty()) + { + MDL_CONTEXT::Ticket_iterator it(src->tickets); + while ((ticket= it++)) { - DBUG_ASSERT(lock_data->ctx); - lock_data->ctx= dst; - dst->locks.push_front(lock_data); + DBUG_ASSERT(ticket->ctx); + ticket->ctx= dst; + dst->tickets.push_front(ticket); } - src->locks.empty(); + src->tickets.empty(); } } @@ -230,53 +251,36 @@ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) This is to be used for every lock request. - Note that initialization and allocation are split - into two calls. This is to allow flexible memory management - of lock requests. Normally a lock request is stored - in statement memory (e.g. is a member of struct TABLE_LIST), - but we would also like to allow allocation of lock - requests in other memory roots, for example in the grant - subsystem, to lock privilege tables. + Note that initialization and allocation are split into two + calls. This is to allow flexible memory management of lock + requests. Normally a lock request is stored in statement memory + (e.g. is a member of struct TABLE_LIST), but we would also like + to allow allocation of lock requests in other memory roots, + for example in the grant subsystem, to lock privilege tables. - The MDL subsystem does not own or manage memory of lock - requests. Instead it assumes that the life time of every lock - request encloses calls to mdl_acquire_shared_lock() and - mdl_release_locks(). + The MDL subsystem does not own or manage memory of lock requests. + Instead it assumes that the life time of every lock request (including + encompassed members db/name) encloses calls to mdl_request_add() + and mdl_request_remove() or mdl_request_remove_all(). - @param lock_data Pointer to an MDL_LOCK_DATA object to initialize - @param key_buff Pointer to the buffer for key for the lock request - (should be at least 4+ strlen(db) + 1 + strlen(name) - + 1 bytes, or, if the lengths are not known, - MAX_MDLKEY_LENGTH) + @param lock_req Pointer to an MDL_LOCK_REQUEST object to initialize @param type Id of type of object to be locked @param db Name of database to which the object belongs @param name Name of of the object - Stores the database name, object name and the type in the key - buffer. Initializes mdl_el to point to the key. - We can't simply initialize MDL_LOCK_DATA with type, db and name - by-pointer because of the underlying HASH implementation - requires the key to be a contiguous buffer. - The initialized lock request will have MDL_SHARED type. Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2 - Note that tables and views have the same lock type, since + Note that tables and views must have the same lock type, since they share the same name space in the SQL standard. */ -void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, - const char *db, const char *name) +void mdl_request_init(MDL_LOCK_REQUEST *lock_req, unsigned char type, + const char *db, const char *name) { - int4store(key, type); - lock_data->key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; - lock_data->key= key; - lock_data->type= MDL_SHARED; - lock_data->state= MDL_INITIALIZED; -#ifndef DBUG_OFF - lock_data->ctx= 0; - lock_data->lock= 0; -#endif + lock_req->key.mdl_key_init(type, db, name); + lock_req->type= MDL_SHARED; + lock_req->ticket= NULL; } @@ -298,19 +302,19 @@ void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, @retval non-0 Pointer to an object representing a lock request */ -MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name, - MEM_ROOT *root) +MDL_LOCK_REQUEST * +mdl_request_alloc(unsigned char type, const char *db, + const char *name, MEM_ROOT *root) { - MDL_LOCK_DATA *lock_data; - char *key; + MDL_LOCK_REQUEST *lock_req; - if (!multi_alloc_root(root, &lock_data, sizeof(MDL_LOCK_DATA), &key, - MAX_MDLKEY_LENGTH, NULL)) + if (!(lock_req= (MDL_LOCK_REQUEST*) alloc_root(root, + sizeof(MDL_LOCK_REQUEST)))) return NULL; - mdl_init_lock(lock_data, key, type, db, name); + mdl_request_init(lock_req, type, db, name); - return lock_data; + return lock_req; } @@ -318,113 +322,113 @@ MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name, Add a lock request to the list of lock requests of the context. The procedure to acquire metadata locks is: - - allocate and initialize lock requests (mdl_alloc_lock()) - - associate them with a context (mdl_add_lock()) - - call mdl_acquire_shared_lock()/mdl_release_lock() (maybe repeatedly). + - allocate and initialize lock requests (mdl_request_alloc()) + - associate them with a context (mdl_request_add()) + - call mdl_acquire_shared_lock()/mdl_ticket_release() (maybe repeatedly). Associates a lock request with the given context. @param context The MDL context to associate the lock with. There should be no more than one context per connection, to avoid deadlocks. - @param lock_data The lock request to be added. + @param lock_req The lock request to be added. */ -void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) +void mdl_request_add(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) { - DBUG_ENTER("mdl_add_lock"); - DBUG_ASSERT(lock_data->state == MDL_INITIALIZED); - DBUG_ASSERT(!lock_data->ctx); - lock_data->ctx= context; - context->locks.push_front(lock_data); + DBUG_ENTER("mdl_request_add"); + DBUG_ASSERT(lock_req->ticket == NULL); + context->requests.push_front(lock_req); DBUG_VOID_RETURN; } /** - Remove a lock request from the list of lock requests of the context. + Remove a lock request from the list of lock requests. Disassociates a lock request from the given context. @param context The MDL context to remove the lock from. - @param lock_data The lock request to be removed. + @param lock_req The lock request to be removed. - @pre The lock request being removed should correspond to lock which + @pre The lock request being removed should correspond to a ticket that was released or was not acquired. - @note Resets lock request for lock released back to its initial state + @note Resets lock request back to its initial state (i.e. sets type to MDL_SHARED). */ -void mdl_remove_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) +void mdl_request_remove(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) { - DBUG_ENTER("mdl_remove_lock"); - DBUG_ASSERT(lock_data->state == MDL_INITIALIZED); - DBUG_ASSERT(context == lock_data->ctx); + DBUG_ENTER("mdl_request_remove"); /* Reset lock request back to its initial state. */ - lock_data->type= MDL_SHARED; -#ifndef DBUG_OFF - lock_data->ctx= 0; -#endif - context->locks.remove(lock_data); + lock_req->type= MDL_SHARED; + lock_req->ticket= NULL; + context->requests.remove(lock_req); DBUG_VOID_RETURN; } /** - Clear all lock requests in the context (clear the context). - + Clear all lock requests in the context. Disassociates lock requests from the context. - All granted locks must be released prior to calling this - function. - - In other words, the expected procedure to release locks is: - - mdl_release_locks(); - - mdl_remove_all_locks(); - - We could possibly merge mdl_remove_all_locks() and mdl_release_locks(), - but this function comes in handy when we need to back off: in that case - we release all the locks acquired so-far but do not free them, since - we know that the respective lock requests will be used again. Also resets lock requests back to their initial state (i.e. MDL_SHARED). @param context Context to be cleared. */ -void mdl_remove_all_locks(MDL_CONTEXT *context) +void mdl_request_remove_all(MDL_CONTEXT *context) { - MDL_LOCK_DATA *lock_data; - I_P_List_iterator it(context->locks); - while ((lock_data= it++)) + MDL_LOCK_REQUEST *lock_req; + MDL_CONTEXT::Request_iterator it(context->requests); + while ((lock_req= it++)) { /* Reset lock request back to its initial state. */ - lock_data->type= MDL_SHARED; -#ifndef DBUG_OFF - lock_data->ctx= 0; -#endif + lock_req->type= MDL_SHARED; + lock_req->ticket= NULL; } - context->locks.empty(); + context->requests.empty(); } /** - Auxiliary functions needed for creation/destruction of MDL_LOCK + Auxiliary functions needed for creation/destruction of MDL_LOCK objects. + + @todo This naive implementation should be replaced with one that saves + on memory allocation by reusing released objects. +*/ + +static MDL_LOCK* alloc_lock_object(const MDL_KEY *mdl_key) +{ + return new MDL_LOCK(mdl_key); +} + + +static void free_lock_object(MDL_LOCK *lock) +{ + delete lock; +} + + +/** + Auxiliary functions needed for creation/destruction of MDL_LOCK_TICKET objects. @todo This naive implementation should be replaced with one that saves on memory allocation by reusing released objects. */ -static MDL_LOCK* get_lock_object(void) +static MDL_LOCK_TICKET* alloc_ticket_object(MDL_CONTEXT *context) { - return new MDL_LOCK(); + MDL_LOCK_TICKET *ticket= new MDL_LOCK_TICKET; + return ticket; } -static void release_lock_object(MDL_LOCK *lock) +static void free_ticket_object(MDL_LOCK_TICKET *ticket) { - delete lock; + delete ticket; } @@ -432,7 +436,8 @@ static void release_lock_object(MDL_LOCK *lock) Helper functions which simplifies writing various checks and asserts. */ -static bool is_shared(MDL_LOCK_DATA *lock_data) +template +static inline bool is_shared(T *lock_data) { return (lock_data->type < MDL_EXCLUSIVE); } @@ -498,7 +503,7 @@ static inline void mdl_exit_cond(MDL_CONTEXT *context, request, implying a form of lock on the global metadata, is compatible with the current state of the global metadata lock. - @param lock_data Request for lock on an individual object, implying a + @param lock_req Request for lock on an individual object, implying a certain kind of global metadata lock. @retval TRUE - Lock request can be satisfied @@ -528,9 +533,9 @@ static inline void mdl_exit_cond(MDL_CONTEXT *context, type of locks we don't even have any accounting for them. */ -static bool can_grant_global_lock(MDL_LOCK_DATA *lock_data) +static bool can_grant_global_lock(enum_mdl_type type, bool is_upgrade) { - switch (lock_data->type) + switch (type) { case MDL_SHARED: case MDL_SHARED_HIGH_PRIO: @@ -549,7 +554,7 @@ static bool can_grant_global_lock(MDL_LOCK_DATA *lock_data) return TRUE; break; case MDL_EXCLUSIVE: - if (lock_data->state == MDL_PENDING_UPGRADE) + if (is_upgrade) { /* We are upgrading MDL_SHARED to MDL_EXCLUSIVE. @@ -586,7 +591,7 @@ static bool can_grant_global_lock(MDL_LOCK_DATA *lock_data) Check if request for the lock can be satisfied given current state of lock. @param lock Lock. - @param lock_data Request for lock. + @param lock_req Request for lock. @retval TRUE Lock request can be satisfied @retval FALSE There is some conflicting lock. @@ -612,68 +617,96 @@ static bool can_grant_global_lock(MDL_LOCK_DATA *lock_data) being upgraded. */ -static bool can_grant_lock(MDL_LOCK *lock, MDL_LOCK_DATA *lock_data) +static bool can_grant_lock(MDL_CONTEXT *ctx, MDL_LOCK *lock, + enum_mdl_type type, bool is_upgrade) { - switch (lock_data->type) - { + bool can_grant= FALSE; + + switch (type) { case MDL_SHARED: case MDL_SHARED_UPGRADABLE: case MDL_SHARED_HIGH_PRIO: - if ((lock->active_exclusive.is_empty() && - (lock_data->type == MDL_SHARED_HIGH_PRIO || - lock->waiting_exclusive.is_empty() && - lock->active_shared_waiting_upgrade.is_empty())) || - (!lock->active_exclusive.is_empty() && - lock->active_exclusive.head()->ctx == lock_data->ctx)) + if (lock->type == MDL_LOCK::SHARED) + { + /* Pending exclusive locks have higher priority over shared locks. */ + if (lock->waiting.is_empty() || type == MDL_SHARED_HIGH_PRIO) + can_grant= TRUE; + } + else if (lock->granted.head()->ctx == ctx) { /* When exclusive lock comes from the same context we can satisfy our shared lock. This is required for CREATE TABLE ... SELECT ... and ALTER VIEW ... AS .... */ - return TRUE; + can_grant= TRUE; } - else - return FALSE; break; case MDL_EXCLUSIVE: - if (lock_data->state == MDL_PENDING_UPGRADE) + if (is_upgrade) { /* We are upgrading MDL_SHARED to MDL_EXCLUSIVE. */ - MDL_LOCK_DATA *conf_lock_data; - I_P_List_iterator it(lock->active_shared); + MDL_LOCK_TICKET *conf_lock_ticket; + MDL_LOCK::Ticket_iterator it(lock->granted); /* There should be no active exclusive locks since we own shared lock on the object. */ - DBUG_ASSERT(lock->active_exclusive.is_empty() && - lock->active_shared_waiting_upgrade.head() == lock_data); + DBUG_ASSERT(lock->type == MDL_LOCK::SHARED); - while ((conf_lock_data= it++)) + while ((conf_lock_ticket= it++)) { /* When upgrading shared lock to exclusive one we can have other shared locks for the same object in the same context, e.g. in case when several instances of TABLE are open. */ - if (conf_lock_data->ctx != lock_data->ctx) - return FALSE; + if (conf_lock_ticket->ctx != ctx) + break; } - return TRUE; + /* Grant lock if there are no conflicting shared locks. */ + if (conf_lock_ticket == NULL) + can_grant= TRUE; + break; } - else + else if (lock->type == MDL_LOCK::SHARED) { - return (lock->active_exclusive.is_empty() && - lock->active_shared_waiting_upgrade.is_empty() && - lock->active_shared.is_empty()); + can_grant= lock->granted.is_empty(); } break; default: DBUG_ASSERT(0); } - return FALSE; + return can_grant; +} + + +/** + Check whether the context already holds a compatible lock ticket + on a object. Only shared locks can be recursive. + + @param lock_req Lock request object for lock to be acquired + + @return A pointer to the lock ticket for the object or NULL otherwise. +*/ + +static MDL_LOCK_TICKET * +mdl_context_find_ticket(MDL_CONTEXT *ctx, MDL_LOCK_REQUEST *lock_req) +{ + MDL_LOCK_TICKET *ticket; + MDL_CONTEXT::Ticket_iterator it(ctx->tickets); + + DBUG_ASSERT(is_shared(lock_req)); + + while ((ticket= it++)) + { + if (lock_req->type == ticket->type && + lock_req->key.is_equal(&ticket->lock->key)) + break; + } + + return ticket; } @@ -691,7 +724,7 @@ static bool can_grant_lock(MDL_LOCK *lock, MDL_LOCK_DATA *lock_data) This function must be called after the lock is added to a context. @param context [in] Context containing request for lock - @param lock_data [in] Lock request object for lock to be acquired + @param lock_req [in] Lock request object for lock to be acquired @param retry [out] Indicates that conflicting lock exists and another attempt should be made after releasing all current locks and waiting for conflicting lock go away @@ -702,81 +735,111 @@ static bool can_grant_lock(MDL_LOCK *lock, MDL_LOCK_DATA *lock_data) In the latter case "retry" parameter is set to TRUE. */ -bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data, +bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req, bool *retry) { MDL_LOCK *lock; + MDL_KEY *key= &lock_req->key; + MDL_LOCK_TICKET *ticket; *retry= FALSE; - DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_INITIALIZED); - - DBUG_ASSERT(lock_data->ctx == context); + DBUG_ASSERT(is_shared(lock_req) && lock_req->ticket == NULL); safe_mutex_assert_not_owner(&LOCK_open); if (context->has_global_shared_lock && - lock_data->type == MDL_SHARED_UPGRADABLE) + lock_req->type == MDL_SHARED_UPGRADABLE) { my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); return TRUE; } + /* + Check whether the context already holds a shared lock on the object, + and if so, grant the request. + */ + if ((ticket= mdl_context_find_ticket(context, lock_req))) + { + DBUG_ASSERT(ticket->state == MDL_ACQUIRED); + lock_req->ticket= ticket; + return FALSE; + } + pthread_mutex_lock(&LOCK_mdl); - if (!can_grant_global_lock(lock_data)) + if (!can_grant_global_lock(lock_req->type, FALSE)) { pthread_mutex_unlock(&LOCK_mdl); *retry= TRUE; return TRUE; } - if (!(lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, - lock_data->key_length))) + if (!(ticket= alloc_ticket_object(context))) { - if (!(lock= get_lock_object())) + pthread_mutex_unlock(&LOCK_mdl); + return TRUE; + } + + if (!(lock= (MDL_LOCK*) my_hash_search(&mdl_locks, + key->ptr(), key->length()))) + { + /* Default lock type is MDL_LOCK::SHARED */ + lock= alloc_lock_object(key); + if (!lock || my_hash_insert(&mdl_locks, (uchar*)lock)) { + free_lock_object(lock); + free_ticket_object(ticket); pthread_mutex_unlock(&LOCK_mdl); return TRUE; } - /* - Before inserting MDL_LOCK object into hash we should add at least one - MDL_LOCK_DATA to its lists in order to provide key for this element. - Thus we can't merge two branches of the above if-statement. - */ - lock->active_shared.push_front(lock_data); - lock->lock_data_count= 1; - if (my_hash_insert(&mdl_locks, (uchar*)lock)) - { - release_lock_object(lock); - pthread_mutex_unlock(&LOCK_mdl); - return TRUE; - } - lock_data->state= MDL_ACQUIRED; - lock_data->lock= lock; - if (lock_data->type == MDL_SHARED_UPGRADABLE) + } + + if (can_grant_lock(context, lock, lock_req->type, FALSE)) + { + lock->granted.push_front(ticket); + context->tickets.push_front(ticket); + ticket->state= MDL_ACQUIRED; + lock_req->ticket= ticket; + ticket->lock= lock; + ticket->type= lock_req->type; + ticket->ctx= context; + if (lock_req->type == MDL_SHARED_UPGRADABLE) global_lock.active_intention_exclusive++; } else { - if (can_grant_lock(lock, lock_data)) - { - lock->active_shared.push_front(lock_data); - lock->lock_data_count++; - lock_data->state= MDL_ACQUIRED; - lock_data->lock= lock; - if (lock_data->type == MDL_SHARED_UPGRADABLE) - global_lock.active_intention_exclusive++; - } - else - *retry= TRUE; + /* We can't get here if we allocated a new lock. */ + DBUG_ASSERT(! lock->is_empty()); + *retry= TRUE; + free_ticket_object(ticket); } + pthread_mutex_unlock(&LOCK_mdl); return *retry; } -static void release_lock(MDL_LOCK_DATA *lock_data); +static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket); + + +/** + Notify a thread holding a shared metadata lock of a pending exclusive lock. + + @param thd Current thread context + @param conf_lock_ticket Conflicting metadata lock + + @retval TRUE A thread was woken up + @retval FALSE Lock is not a shared one or no thread was woken up +*/ + +static bool notify_shared_lock(THD *thd, MDL_LOCK_TICKET *conf_lock_ticket) +{ + bool woke= FALSE; + if (conf_lock_ticket->type != MDL_EXCLUSIVE) + woke= mysql_notify_thread_having_shared_lock(thd, conf_lock_ticket->ctx->thd); + return woke; +} /** @@ -796,12 +859,13 @@ static void release_lock(MDL_LOCK_DATA *lock_data); bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) { - MDL_LOCK_DATA *lock_data; MDL_LOCK *lock; bool signalled= FALSE; const char *old_msg; - I_P_List_iterator it(context->locks); + MDL_LOCK_REQUEST *lock_req; + MDL_LOCK_TICKET *ticket; st_my_thread_var *mysys_var= my_thread_var; + MDL_CONTEXT::Request_iterator it(context->requests); safe_mutex_assert_not_owner(&LOCK_open); @@ -815,46 +879,44 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) old_msg= MDL_ENTER_COND(context, mysys_var); - while ((lock_data= it++)) + while ((lock_req= it++)) { - DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE && - lock_data->state == MDL_INITIALIZED); - if (!(lock= (MDL_LOCK *) my_hash_search(&mdl_locks, (uchar*)lock_data->key, - lock_data->key_length))) + MDL_KEY *key= &lock_req->key; + DBUG_ASSERT(lock_req->type == MDL_EXCLUSIVE && + lock_req->ticket == NULL); + + /* Early allocation: ticket is used as a shortcut to the lock. */ + if (!(ticket= alloc_ticket_object(context))) + goto err; + + if (!(lock= (MDL_LOCK*) my_hash_search(&mdl_locks, + key->ptr(), key->length()))) { - if (!(lock= get_lock_object())) - goto err; - /* - Again before inserting MDL_LOCK into hash provide key for - it by adding MDL_LOCK_DATA to one of its lists. - */ - lock->waiting_exclusive.push_front(lock_data); - lock->lock_data_count= 1; - if (my_hash_insert(&mdl_locks, (uchar*)lock)) + lock= alloc_lock_object(key); + if (!lock || my_hash_insert(&mdl_locks, (uchar*)lock)) { - release_lock_object(lock); + free_ticket_object(ticket); + free_lock_object(lock); goto err; } - lock_data->lock= lock; - lock_data->state= MDL_PENDING; - } - else - { - lock->waiting_exclusive.push_front(lock_data); - lock->lock_data_count++; - lock_data->lock= lock; - lock_data->state= MDL_PENDING; } + + lock_req->ticket= ticket; + ticket->state= MDL_PENDING; + ticket->ctx= context; + ticket->lock= lock; + ticket->type= lock_req->type; + lock->waiting.push_front(ticket); } while (1) { it.rewind(); - while ((lock_data= it++)) + while ((lock_req= it++)) { - lock= lock_data->lock; + lock= lock_req->ticket->lock; - if (!can_grant_global_lock(lock_data)) + if (!can_grant_global_lock(lock_req->type, FALSE)) { /* There is an active or pending global shared lock so we have @@ -863,27 +925,25 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) signalled= TRUE; break; } - else if (!can_grant_lock(lock, lock_data)) + else if (!can_grant_lock(context, lock, lock_req->type, FALSE)) { - MDL_LOCK_DATA *conf_lock_data; - I_P_List_iterator it(lock->active_shared); + MDL_LOCK_TICKET *conf_lock_ticket; + MDL_LOCK::Ticket_iterator it(lock->granted); - signalled= !lock->active_exclusive.is_empty() || - !lock->active_shared_waiting_upgrade.is_empty(); + signalled= (lock->type == MDL_LOCK::EXCLUSIVE); - while ((conf_lock_data= it++)) - { - signalled|= - mysql_notify_thread_having_shared_lock(context->thd, - conf_lock_data->ctx->thd); - } + while ((conf_lock_ticket= it++)) + signalled|= notify_shared_lock(context->thd, conf_lock_ticket); break; } } - if (!lock_data) + if (!lock_req) break; + + /* There is a shared or exclusive lock on the object. */ + DEBUG_SYNC(context->thd, "mdl_acquire_exclusive_locks_wait"); + if (signalled) pthread_cond_wait(&COND_mdl, &LOCK_mdl); else @@ -902,13 +962,16 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) goto err; } it.rewind(); - while ((lock_data= it++)) + while ((lock_req= it++)) { global_lock.active_intention_exclusive++; - lock= lock_data->lock; - lock->waiting_exclusive.remove(lock_data); - lock->active_exclusive.push_front(lock_data); - lock_data->state= MDL_ACQUIRED; + ticket= lock_req->ticket; + lock= ticket->lock; + lock->type= MDL_LOCK::EXCLUSIVE; + lock->waiting.remove(ticket); + lock->granted.push_front(ticket); + context->tickets.push_front(ticket); + ticket->state= MDL_ACQUIRED; if (lock->cached_object) (*lock->cached_object_release_hook)(lock->cached_object); lock->cached_object= NULL; @@ -923,10 +986,20 @@ err: Ignore those lock requests which were not made MDL_PENDING. */ it.rewind(); - while ((lock_data= it++) && lock_data->state == MDL_PENDING) + while ((lock_req= it++) && lock_req->ticket) { - release_lock(lock_data); - lock_data->state= MDL_INITIALIZED; + ticket= lock_req->ticket; + DBUG_ASSERT(ticket->state == MDL_PENDING); + lock= ticket->lock; + free_ticket_object(ticket); + lock->waiting.remove(ticket); + /* Reset lock request back to its initial state. */ + lock_req->ticket= NULL; + if (lock->is_empty()) + { + my_hash_delete(&mdl_locks, (uchar *)lock); + free_lock_object(lock); + } } /* May be some pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); @@ -942,77 +1015,61 @@ err: new definition has been constructed. @param context Context to which shared lock belongs - @param lock_data Satisfied request for shared lock to be upgraded + @param ticket Ticket for shared lock to be upgraded @note In case of failure to upgrade lock (e.g. because upgrader was killed) leaves lock in its original state (locked in shared mode). + @note There can be only one upgrader for a lock or we will have deadlock. + This invariant is ensured by code outside of metadata subsystem usually + by obtaining some sort of exclusive table-level lock (e.g. TL_WRITE, + TL_WRITE_ALLOW_READ) before performing upgrade of metadata lock. + @retval FALSE Success @retval TRUE Failure (thread was killed) */ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data) + MDL_LOCK_TICKET *ticket) { - MDL_LOCK *lock; + MDL_LOCK *lock= ticket->lock; const char *old_msg; st_my_thread_var *mysys_var= my_thread_var; DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive"); + DEBUG_SYNC(context->thd, "mdl_upgrade_shared_lock_to_exclusive"); safe_mutex_assert_not_owner(&LOCK_open); - DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); - /* Allow this function to be called twice for the same lock request. */ - if (lock_data->type == MDL_EXCLUSIVE) + if (ticket->type == MDL_EXCLUSIVE) DBUG_RETURN(FALSE); - DBUG_ASSERT(lock_data->type == MDL_SHARED_UPGRADABLE); - - lock= lock_data->lock; - pthread_mutex_lock(&LOCK_mdl); old_msg= MDL_ENTER_COND(context, mysys_var); - lock_data->state= MDL_PENDING_UPGRADE; - /* Set type of lock request to the type at which we are aiming. */ - lock_data->type= MDL_EXCLUSIVE; - lock->active_shared.remove(lock_data); - /* - There can be only one upgrader for this lock or we will have deadlock. - This invariant is ensured by code outside of metadata subsystem usually - by obtaining some sort of exclusive table-level lock (e.g. TL_WRITE, - TL_WRITE_ALLOW_READ) before performing upgrade of metadata lock. - */ - DBUG_ASSERT(lock->active_shared_waiting_upgrade.is_empty()); - lock->active_shared_waiting_upgrade.push_front(lock_data); /* - Since we should have been already acquired intention exclusive global lock - this call is only enforcing asserts. + Since we should have already acquired an intention exclusive + global lock this call is only enforcing asserts. */ - DBUG_ASSERT(can_grant_global_lock(lock_data)); + DBUG_ASSERT(can_grant_global_lock(MDL_EXCLUSIVE, TRUE)); while (1) { - if (can_grant_lock(lock, lock_data)) + if (can_grant_lock(context, lock, MDL_EXCLUSIVE, TRUE)) break; bool signalled= FALSE; - MDL_LOCK_DATA *conf_lock_data; - I_P_List_iterator it(lock->active_shared); + MDL_LOCK_TICKET *conf_lock_ticket; + MDL_LOCK::Ticket_iterator it(lock->granted); - while ((conf_lock_data= it++)) + while ((conf_lock_ticket= it++)) { - if (conf_lock_data->ctx != context) - { - signalled|= - mysql_notify_thread_having_shared_lock(context->thd, - conf_lock_data->ctx->thd); - } + if (conf_lock_ticket->ctx != context) + signalled|= notify_shared_lock(context->thd, conf_lock_ticket); } if (signalled) @@ -1032,10 +1089,6 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, } if (mysys_var->abort) { - lock_data->state= MDL_ACQUIRED; - lock_data->type= MDL_SHARED_UPGRADABLE; - lock->active_shared_waiting_upgrade.remove(lock_data); - lock->active_shared.push_front(lock_data); /* Pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); MDL_EXIT_COND(context, mysys_var, old_msg); @@ -1043,9 +1096,9 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, } } - lock->active_shared_waiting_upgrade.remove(lock_data); - lock->active_exclusive.push_front(lock_data); - lock_data->state= MDL_ACQUIRED; + lock->type= MDL_LOCK::EXCLUSIVE; + /* Set the new type of lock in the ticket. */ + ticket->type= MDL_EXCLUSIVE; if (lock->cached_object) (*lock->cached_object_release_hook)(lock->cached_object); lock->cached_object= 0; @@ -1069,7 +1122,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, block and wait for the lock if the table already exists. @param context [in] The context containing the lock request - @param lock [in] The lock request + @param lock_req [in] The lock request @param conflict [out] Indicates that conflicting lock exists @retval TRUE Failure either conflicting lock exists or some error @@ -1081,13 +1134,15 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, */ bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data, + MDL_LOCK_REQUEST *lock_req, bool *conflict) { MDL_LOCK *lock; + MDL_LOCK_TICKET *ticket; + MDL_KEY *key= &lock_req->key; - DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE && - lock_data->state == MDL_INITIALIZED); + DBUG_ASSERT(lock_req->type == MDL_EXCLUSIVE && + lock_req->ticket == NULL); safe_mutex_assert_not_owner(&LOCK_open); @@ -1095,20 +1150,25 @@ bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, pthread_mutex_lock(&LOCK_mdl); - if (!(lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, - lock_data->key_length))) + if (!(lock= (MDL_LOCK*) my_hash_search(&mdl_locks, + key->ptr(), key->length()))) { - if (!(lock= get_lock_object())) - goto err; - lock->active_exclusive.push_front(lock_data); - lock->lock_data_count= 1; - if (my_hash_insert(&mdl_locks, (uchar*)lock)) + ticket= alloc_ticket_object(context); + lock= alloc_lock_object(key); + if (!ticket || !lock || my_hash_insert(&mdl_locks, (uchar*)lock)) { - release_lock_object(lock); + free_ticket_object(ticket); + free_lock_object(lock); goto err; } - lock_data->state= MDL_ACQUIRED; - lock_data->lock= lock; + lock->type= MDL_LOCK::EXCLUSIVE; + lock->granted.push_front(ticket); + context->tickets.push_front(ticket); + ticket->state= MDL_ACQUIRED; + lock_req->ticket= ticket; + ticket->ctx= context; + ticket->lock= lock; + ticket->type= lock_req->type; global_lock.active_intention_exclusive++; pthread_mutex_unlock(&LOCK_mdl); return FALSE; @@ -1183,9 +1243,9 @@ bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) bool mdl_wait_for_locks(MDL_CONTEXT *context) { - MDL_LOCK_DATA *lock_data; MDL_LOCK *lock; - I_P_List_iterator it(context->locks); + MDL_LOCK_REQUEST *lock_req; + MDL_CONTEXT::Request_iterator it(context->requests); const char *old_msg; st_my_thread_var *mysys_var= my_thread_var; @@ -1207,22 +1267,23 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) pthread_mutex_lock(&LOCK_mdl); old_msg= MDL_ENTER_COND(context, mysys_var); it.rewind(); - while ((lock_data= it++)) + while ((lock_req= it++)) { - DBUG_ASSERT(lock_data->state == MDL_INITIALIZED); - if (!can_grant_global_lock(lock_data)) + MDL_KEY *key= &lock_req->key; + DBUG_ASSERT(lock_req->ticket == NULL); + if (!can_grant_global_lock(lock_req->type, FALSE)) break; /* To avoid starvation we don't wait if we have a conflict against request for MDL_EXCLUSIVE lock. */ - if (is_shared(lock_data) && - (lock= (MDL_LOCK *)my_hash_search(&mdl_locks, (uchar*)lock_data->key, - lock_data->key_length)) && - !can_grant_lock(lock, lock_data)) + if (is_shared(lock_req) && + (lock= (MDL_LOCK*) my_hash_search(&mdl_locks, key->ptr(), + key->length())) && + !can_grant_lock(context, lock, lock_req->type, FALSE)) break; } - if (!lock_data) + if (!lock_req) { pthread_mutex_unlock(&LOCK_mdl); break; @@ -1237,58 +1298,48 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) /** Auxiliary function which allows to release particular lock - ownership of which is represented by lock request object. + ownership of which is represented by a lock ticket object. */ -static void release_lock(MDL_LOCK_DATA *lock_data) +static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) { - MDL_LOCK *lock; + MDL_LOCK *lock= ticket->lock; + DBUG_ENTER("release_ticket"); + DBUG_PRINT("enter", ("db=%s name=%s", lock->key.db_name(), + lock->key.table_name())); - DBUG_ENTER("release_lock"); - DBUG_PRINT("enter", ("db=%s name=%s", lock_data->key + 4, - lock_data->key + 4 + strlen(lock_data->key + 4) + 1)); + safe_mutex_assert_owner(&LOCK_mdl); - DBUG_ASSERT(lock_data->state == MDL_PENDING || - lock_data->state == MDL_ACQUIRED); + context->tickets.remove(ticket); - lock= lock_data->lock; - if (lock->has_one_lock_data()) + switch (ticket->type) + { + case MDL_SHARED_UPGRADABLE: + global_lock.active_intention_exclusive--; + /* Fallthrough. */ + case MDL_SHARED: + case MDL_SHARED_HIGH_PRIO: + lock->granted.remove(ticket); + break; + case MDL_EXCLUSIVE: + lock->type= MDL_LOCK::SHARED; + lock->granted.remove(ticket); + global_lock.active_intention_exclusive--; + break; + default: + DBUG_ASSERT(0); + } + + free_ticket_object(ticket); + + if (lock->is_empty()) { my_hash_delete(&mdl_locks, (uchar *)lock); DBUG_PRINT("info", ("releasing cached_object cached_object=%p", lock->cached_object)); if (lock->cached_object) (*lock->cached_object_release_hook)(lock->cached_object); - release_lock_object(lock); - if (lock_data->state == MDL_ACQUIRED && - (lock_data->type == MDL_EXCLUSIVE || - lock_data->type == MDL_SHARED_UPGRADABLE)) - global_lock.active_intention_exclusive--; - } - else - { - switch (lock_data->type) - { - case MDL_SHARED_UPGRADABLE: - global_lock.active_intention_exclusive--; - /* Fallthrough. */ - case MDL_SHARED: - case MDL_SHARED_HIGH_PRIO: - lock->active_shared.remove(lock_data); - break; - case MDL_EXCLUSIVE: - if (lock_data->state == MDL_PENDING) - lock->waiting_exclusive.remove(lock_data); - else - { - lock->active_exclusive.remove(lock_data); - global_lock.active_intention_exclusive--; - } - break; - default: - DBUG_ASSERT(0); - } - lock->lock_data_count--; + free_lock_object(lock); } DBUG_VOID_RETURN; @@ -1307,42 +1358,38 @@ static void release_lock(MDL_LOCK_DATA *lock_data) are associated. */ -void mdl_release_locks(MDL_CONTEXT *context) +void mdl_ticket_release_all(MDL_CONTEXT *context) { - MDL_LOCK_DATA *lock_data; - I_P_List_iterator it(context->locks); - DBUG_ENTER("mdl_release_locks"); + MDL_LOCK_TICKET *ticket; + MDL_CONTEXT::Ticket_iterator it(context->tickets); + DBUG_ENTER("mdl_ticket_release_all"); safe_mutex_assert_not_owner(&LOCK_open); - pthread_mutex_lock(&LOCK_mdl); - while ((lock_data= it++)) + /* Detach lock tickets from the requests for back off. */ { - DBUG_PRINT("info", ("found lock to release lock_data=%p", lock_data)); - /* - Don't call release_lock() for a shared lock if has not been - granted. Lock state in this case is MDL_INITIALIZED. - We have pending and granted shared locks in the same context - when this function is called from the "back-off" path of - open_tables(). - */ - if (lock_data->state != MDL_INITIALIZED) - { - release_lock(lock_data); - lock_data->state= MDL_INITIALIZED; -#ifndef DBUG_OFF - lock_data->lock= 0; -#endif - } - /* - We will return lock request to its initial state only in - mdl_remove_all_locks() since we need to know type of lock - request in mdl_wait_for_locks(). - */ + MDL_LOCK_REQUEST *lock_req; + MDL_CONTEXT::Request_iterator it(context->requests); + + while ((lock_req= it++)) + lock_req->ticket= NULL; + } + + if (context->tickets.is_empty()) + DBUG_VOID_RETURN; + + pthread_mutex_lock(&LOCK_mdl); + while ((ticket= it++)) + { + DBUG_PRINT("info", ("found lock to release ticket=%p", ticket)); + release_ticket(context, ticket); } /* Inefficient but will do for a while */ pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); + + context->tickets.empty(); + DBUG_VOID_RETURN; } @@ -1351,20 +1398,17 @@ void mdl_release_locks(MDL_CONTEXT *context) Release a lock. @param context Context containing lock in question - @param lock_data Lock to be released + @param ticket Lock to be released */ -void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) +void mdl_ticket_release(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) { + DBUG_ASSERT(context == ticket->ctx); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - release_lock(lock_data); -#ifndef DBUG_OFF - lock_data->lock= 0; -#endif - lock_data->state= MDL_INITIALIZED; + release_ticket(context, ticket); pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); } @@ -1375,34 +1419,43 @@ void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data) object as this lock request, remove lock requests from the context. @param context Context containing locks in question - @param lock_data One of the locks for the name/object for which all + @param ticket One of the locks for the name/object for which all locks should be released. */ -void mdl_release_and_remove_all_locks_for_name(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data) +void mdl_ticket_release_all_for_name(MDL_CONTEXT *context, + MDL_LOCK_TICKET *ticket) { MDL_LOCK *lock; - I_P_List_iterator it(context->locks); - - DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); /* - We can use MDL_LOCK_DATA::lock here to identify other locks for the same - object since even altough MDL_LOCK object might be reused for different + We can use MDL_LOCK_TICKET::lock here to identify other locks for the same + object since even though MDL_LOCK object might be reused for different lock after the first lock for this object have been released we can't have references to this other MDL_LOCK object in this context. */ - lock= lock_data->lock; + lock= ticket->lock; - while ((lock_data= it++)) + /* Remove matching lock requests from the context. */ + MDL_LOCK_REQUEST *lock_req; + MDL_CONTEXT::Request_iterator it_lock_req(context->requests); + + while ((lock_req= it_lock_req++)) { - DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); - if (lock_data->lock == lock) - { - mdl_release_lock(context, lock_data); - mdl_remove_lock(context, lock_data); - } + DBUG_ASSERT(lock_req->ticket && lock_req->ticket->state == MDL_ACQUIRED); + if (lock_req->ticket->lock == lock) + mdl_request_remove(context, lock_req); + } + + /* Remove matching lock tickets from the context. */ + MDL_LOCK_TICKET *lock_tkt; + MDL_CONTEXT::Ticket_iterator it_lock_tkt(context->tickets); + + while ((lock_tkt= it_lock_tkt++)) + { + DBUG_ASSERT(lock_tkt->state == MDL_ACQUIRED); + if (lock_tkt->lock == lock) + mdl_ticket_release(context, lock_tkt); } } @@ -1411,27 +1464,26 @@ void mdl_release_and_remove_all_locks_for_name(MDL_CONTEXT *context, Downgrade an exclusive lock to shared metadata lock. @param context A context to which exclusive lock belongs - @param lock_data Satisfied request for exclusive lock to be downgraded + @param ticket Ticket for exclusive lock to be downgraded */ void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data) + MDL_LOCK_TICKET *ticket) { MDL_LOCK *lock; + DBUG_ASSERT(context == ticket->ctx); + safe_mutex_assert_not_owner(&LOCK_open); - DBUG_ASSERT(lock_data->state == MDL_ACQUIRED); - - if (is_shared(lock_data)) + if (is_shared(ticket)) return; - lock= lock_data->lock; + lock= ticket->lock; pthread_mutex_lock(&LOCK_mdl); - lock->active_exclusive.remove(lock_data); - lock_data->type= MDL_SHARED_UPGRADABLE; - lock->active_shared.push_front(lock_data); + lock->type= MDL_LOCK::SHARED; + ticket->type= MDL_SHARED_UPGRADABLE; pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); } @@ -1469,30 +1521,29 @@ void mdl_release_global_shared_lock(MDL_CONTEXT *context) FALSE otherwise. */ -bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, +bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, unsigned char type, const char *db, const char *name) { - char key[MAX_MDLKEY_LENGTH]; - uint key_length; - MDL_LOCK_DATA *lock_data; - I_P_List_iterator it(context->locks); + MDL_KEY key; + MDL_LOCK_TICKET *ticket; + MDL_CONTEXT::Ticket_iterator it(context->tickets); - int4store(key, type); - key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; + key.mdl_key_init(type, db, name); - while ((lock_data= it++) && - (lock_data->key_length != key_length || - memcmp(lock_data->key, key, key_length) || - !(lock_data->type == MDL_EXCLUSIVE && - lock_data->state == MDL_ACQUIRED))) - continue; - return lock_data; + while ((ticket= it++)) + { + if (ticket->lock->type == MDL_LOCK::EXCLUSIVE && + ticket->lock->key.is_equal(&key)) + break; + } + + return ticket; } /** - Auxiliary function which allows to check if we some kind of lock on - the object. + Auxiliary function which allows to check if we have some kind of lock on + a object. @param context Current context @param type Id of object type @@ -1503,24 +1554,22 @@ bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, FALSE otherwise. */ -bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, - const char *name) +bool mdl_is_lock_owner(MDL_CONTEXT *context, unsigned char type, + const char *db, const char *name) { - char key[MAX_MDLKEY_LENGTH]; - uint key_length; - MDL_LOCK_DATA *lock_data; - I_P_List_iterator it(context->locks); + MDL_KEY key; + MDL_LOCK_TICKET *ticket; + MDL_CONTEXT::Ticket_iterator it(context->tickets); - int4store(key, type); - key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1; + key.mdl_key_init(type, db, name); - while ((lock_data= it++) && - (lock_data->key_length != key_length || - memcmp(lock_data->key, key, key_length) || - lock_data->state != MDL_ACQUIRED)) - continue; + while ((ticket= it++)) + { + if (ticket->lock->key.is_equal(&key)) + break; + } - return lock_data; + return ticket; } @@ -1528,21 +1577,20 @@ bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, Check if we have any pending exclusive locks which conflict with existing shared lock. - @param lock_data Shared lock against which check should be performed. + @param ticket Shared lock against which check should be performed. @return TRUE if there are any conflicting locks, FALSE otherwise. */ -bool mdl_has_pending_conflicting_lock(MDL_LOCK_DATA *lock_data) +bool mdl_has_pending_conflicting_lock(MDL_LOCK_TICKET *ticket) { bool result; - DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_ACQUIRED); + DBUG_ASSERT(is_shared(ticket)); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - result= !(lock_data->lock->waiting_exclusive.is_empty() && - lock_data->lock->active_shared_waiting_upgrade.is_empty()); + result= !ticket->lock->waiting.is_empty(); pthread_mutex_unlock(&LOCK_mdl); return result; } @@ -1551,8 +1599,8 @@ bool mdl_has_pending_conflicting_lock(MDL_LOCK_DATA *lock_data) /** Associate pointer to an opaque object with a lock. - @param lock_data Lock request for the lock with which the - object should be associated. + @param ticket Lock ticket for the lock with which the object + should be associated. @param cached_object Pointer to the object @param release_hook Cleanup function to be called when MDL subsystem decides to remove lock or associate another object. @@ -1576,27 +1624,24 @@ bool mdl_has_pending_conflicting_lock(MDL_LOCK_DATA *lock_data) lock on this name is released. */ -void mdl_set_cached_object(MDL_LOCK_DATA *lock_data, void *cached_object, +void mdl_set_cached_object(MDL_LOCK_TICKET *ticket, void *cached_object, mdl_cached_object_release_hook release_hook) { + MDL_LOCK *lock= ticket->lock; DBUG_ENTER("mdl_set_cached_object"); - DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", lock_data->key + 4, - lock_data->key + 4 + strlen(lock_data->key + 4) + 1, - cached_object)); - - DBUG_ASSERT(lock_data->state == MDL_ACQUIRED || - lock_data->state == MDL_PENDING_UPGRADE); - + DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", + lock->key.db_name(), lock->key.table_name(), + cached_object)); /* TODO: This assumption works now since we do mdl_get_cached_object() and mdl_set_cached_object() in the same critical section. Once this becomes false we will have to call release_hook here and use additional mutex protecting 'cached_object' member. */ - DBUG_ASSERT(!lock_data->lock->cached_object); + DBUG_ASSERT(!lock->cached_object); - lock_data->lock->cached_object= cached_object; - lock_data->lock->cached_object_release_hook= release_hook; + lock->cached_object= cached_object; + lock->cached_object_release_hook= release_hook; DBUG_VOID_RETURN; } @@ -1605,15 +1650,46 @@ void mdl_set_cached_object(MDL_LOCK_DATA *lock_data, void *cached_object, /** Get a pointer to an opaque object that associated with the lock. - @param lock_data Lock request for the lock with which the object is - associated. + @param ticket Lock ticket for the lock which the object is associated to. @return Pointer to an opaque object associated with the lock. */ -void* mdl_get_cached_object(MDL_LOCK_DATA *lock_data) +void* mdl_get_cached_object(MDL_LOCK_TICKET *ticket) { - DBUG_ASSERT(lock_data->state == MDL_ACQUIRED || - lock_data->state == MDL_PENDING_UPGRADE); - return lock_data->lock->cached_object; + return ticket->lock->cached_object; } + + + +/** + Releases metadata locks that were acquired after a specific savepoint. + + @note Used to release tickets acquired during a savepoint unit. + @note It's safe to iterate and unlock any locks after taken after this + savepoint because other statements that take other special locks + cause a implicit commit (ie LOCK TABLES). + + @param thd Current thread + @param sv Savepoint +*/ + +void mdl_rollback_to_savepoint(MDL_CONTEXT *ctx, + MDL_LOCK_TICKET *mdl_savepoint) +{ + MDL_LOCK_TICKET *mdl_lock_ticket; + MDL_CONTEXT::Ticket_iterator it(ctx->tickets); + DBUG_ENTER("mdl_rollback_to_savepoint"); + + while ((mdl_lock_ticket= it++)) + { + /* Stop when lock was acquired before this savepoint. */ + if (mdl_lock_ticket == mdl_savepoint) + break; + mdl_ticket_release(ctx, mdl_lock_ticket); + } + + DBUG_VOID_RETURN; +} + + diff --git a/sql/mdl.h b/sql/mdl.h index b4b84a9ab24..103ab8130ba 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -23,7 +23,8 @@ class THD; -struct MDL_LOCK_DATA; +struct MDL_LOCK_REQUEST; +struct MDL_LOCK_TICKET; struct MDL_LOCK; struct MDL_CONTEXT; @@ -43,87 +44,147 @@ enum enum_mdl_type {MDL_SHARED=0, MDL_SHARED_HIGH_PRIO, MDL_SHARED_UPGRADABLE, MDL_EXCLUSIVE}; -/** States which metadata lock request can have. */ +/** States which a metadata lock ticket can have. */ -enum enum_mdl_state {MDL_INITIALIZED=0, MDL_PENDING, - MDL_ACQUIRED, MDL_PENDING_UPGRADE}; +enum enum_mdl_state { MDL_PENDING, MDL_ACQUIRED }; + + +/** Maximal length of key for metadata locking subsystem. */ +#define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1) /** - A pending lock request or a granted metadata lock. A lock is requested - or granted based on a fully qualified name and type. E.g. for a table - the key consists of <0 (=table)>++
. - Later in this document this triple will be referred to simply as - "key" or "name". + Metadata lock object key. + + A lock is requested or granted based on a fully qualified name and type. + E.g. They key for a table consists of <0 (=table)>++
. + Elsewhere in the comments this triple will be referred to simply as "key" + or "name". */ -struct MDL_LOCK_DATA +class MDL_KEY { - char *key; - uint key_length; - enum enum_mdl_type type; - enum enum_mdl_state state; +public: + const uchar *ptr() const { return (uchar*) m_ptr; } + uint length() const { return m_length; } + const char *db_name() const { return m_ptr + 1; } + uint db_name_length() const { return m_db_name_length; } + + const char *table_name() const { return m_ptr + m_db_name_length + 2; } + uint table_name_length() const { return m_length - m_db_name_length - 3; } + + /** + Construct a metadata lock key from a triplet (type, database and name). + + @remark The key for a table is <0 (=table)>++
+ + @param type Id of type of object to be locked + @param db Name of database to which the object belongs + @param name Name of of the object + @param key Where to store the the MDL key. + */ + void mdl_key_init(char type, const char *db, const char *name) + { + m_ptr[0]= type; + m_db_name_length= (uint) (strmov(m_ptr + 1, db) - m_ptr - 1); + m_length= (uint) (strmov(m_ptr + m_db_name_length + 2, name) - m_ptr + 1); + } + void mdl_key_init(const MDL_KEY *rhs) + { + memcpy(m_ptr, rhs->m_ptr, rhs->m_length); + m_length= rhs->m_length; + m_db_name_length= rhs->m_db_name_length; + } + bool is_equal(const MDL_KEY *rhs) const + { + return (m_length == rhs->m_length && + memcmp(m_ptr, rhs->m_ptr, m_length) == 0); + } private: + char m_ptr[MAX_MDLKEY_LENGTH]; + uint m_length; + uint m_db_name_length; +}; + + + +/** + Hook class which via its methods specifies which members + of T should be used for participating in MDL lists. +*/ + +template +struct I_P_List_adapter +{ + static inline T **next_ptr(T *el) { return &(el->*next); } + + static inline T ***prev_ptr(T *el) { return &(el->*prev); } +}; + + +/** + A pending metadata lock request. + A pending lock request or a granted metadata lock share the same abstract + base but are presented individually because they have different allocation + sites and hence different lifetimes. The allocation of lock requests is + controlled from outside of the MDL subsystem, while allocation of granted + locks (tickets) is controlled within the MDL subsystem. +*/ + +struct MDL_LOCK_REQUEST +{ + /** Type of metadata lock. */ + enum enum_mdl_type type; + /** Pointers for participating in the list of lock requests for this context. */ - MDL_LOCK_DATA *next_context; - MDL_LOCK_DATA **prev_context; + MDL_LOCK_REQUEST *next_in_context; + MDL_LOCK_REQUEST **prev_in_context; + /** A lock is requested based on a fully qualified name and type. */ + MDL_KEY key; + + /** + Pointer to the lock ticket object for this lock request. + Valid only if this lock request is satisfied. + */ + MDL_LOCK_TICKET *ticket; +}; + + +/** + A granted metadata lock. + + @warning MDL_LOCK_TICKET members are private to the MDL subsystem. + + @note Multiple shared locks on a same object are represented by a + single ticket. The same does not apply for other lock types. +*/ + +struct MDL_LOCK_TICKET +{ + /** Type of metadata lock. */ + enum enum_mdl_type type; + /** State of the metadata lock ticket. */ + enum enum_mdl_state state; + + /** + Pointers for participating in the list of lock requests for this context. + */ + MDL_LOCK_TICKET *next_in_context; + MDL_LOCK_TICKET **prev_in_context; /** Pointers for participating in the list of satisfied/pending requests for the lock. */ - MDL_LOCK_DATA *next_lock; - MDL_LOCK_DATA **prev_lock; + MDL_LOCK_TICKET *next_in_lock; + MDL_LOCK_TICKET **prev_in_lock; + /** Context of the owner of the metadata lock ticket. */ + MDL_CONTEXT *ctx; - friend struct MDL_LOCK_DATA_context; - friend struct MDL_LOCK_DATA_lock; - -public: - /* - Pointer to the lock object for this lock request. Valid only if this lock - request is satisified or is present in the list of pending lock requests - for particular lock. - */ - MDL_LOCK *lock; - MDL_CONTEXT *ctx; -}; - - -/** - Helper class which specifies which members of MDL_LOCK_DATA are used for - participation in the list lock requests belonging to one context. -*/ - -struct MDL_LOCK_DATA_context -{ - static inline MDL_LOCK_DATA **next_ptr(MDL_LOCK_DATA *l) - { - return &l->next_context; - } - static inline MDL_LOCK_DATA ***prev_ptr(MDL_LOCK_DATA *l) - { - return &l->prev_context; - } -}; - - -/** - Helper class which specifies which members of MDL_LOCK_DATA are used for - participation in the list of satisfied/pending requests for the lock. -*/ - -struct MDL_LOCK_DATA_lock -{ - static inline MDL_LOCK_DATA **next_ptr(MDL_LOCK_DATA *l) - { - return &l->next_lock; - } - static inline MDL_LOCK_DATA ***prev_ptr(MDL_LOCK_DATA *l) - { - return &l->prev_lock; - } + /** Pointer to the lock object for this lock ticket. */ + MDL_LOCK *lock; }; @@ -134,7 +195,24 @@ struct MDL_LOCK_DATA_lock struct MDL_CONTEXT { - I_P_List locks; + typedef I_P_List > + Request_list; + + typedef Request_list::Iterator Request_iterator; + + typedef I_P_List > + Ticket_list; + + typedef Ticket_list::Iterator Ticket_iterator; + + Request_list requests; + Ticket_list tickets; bool has_global_shared_lock; THD *thd; }; @@ -149,96 +227,80 @@ void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); void mdl_context_merge(MDL_CONTEXT *target, MDL_CONTEXT *source); -/** Maximal length of key for metadata locking subsystem. */ -#define MAX_MDLKEY_LENGTH (4 + NAME_LEN + 1 + NAME_LEN + 1) - -void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type, - const char *db, const char *name); -MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name, - MEM_ROOT *root); -void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); -void mdl_remove_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); -void mdl_remove_all_locks(MDL_CONTEXT *context); +void mdl_request_init(MDL_LOCK_REQUEST *lock_req, unsigned char type, + const char *db, const char *name); +MDL_LOCK_REQUEST *mdl_request_alloc(unsigned char type, const char *db, + const char *name, MEM_ROOT *root); +void mdl_request_add(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req); +void mdl_request_remove(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req); +void mdl_request_remove_all(MDL_CONTEXT *context); /** Set type of lock request. Can be only applied to pending locks. */ -inline void mdl_set_lock_type(MDL_LOCK_DATA *lock_data, enum_mdl_type lock_type) +inline void mdl_request_set_type(MDL_LOCK_REQUEST *lock_req, enum_mdl_type lock_type) { - DBUG_ASSERT(lock_data->state == MDL_INITIALIZED); - lock_data->type= lock_type; + DBUG_ASSERT(lock_req->ticket == NULL); + lock_req->type= lock_type; } -bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data, +bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req, bool *retry); bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context); bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data); + MDL_LOCK_TICKET *ticket); bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data, + MDL_LOCK_REQUEST *lock_req, bool *conflict); bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context); bool mdl_wait_for_locks(MDL_CONTEXT *context); -void mdl_release_locks(MDL_CONTEXT *context); -void mdl_release_and_remove_all_locks_for_name(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data); -void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data); +void mdl_ticket_release_all(MDL_CONTEXT *context); +void mdl_ticket_release_all_for_name(MDL_CONTEXT *context, + MDL_LOCK_TICKET *ticket); +void mdl_ticket_release(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket); void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_DATA *lock_data); + MDL_LOCK_TICKET *ticket); void mdl_release_global_shared_lock(MDL_CONTEXT *context); -bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, const char *db, - const char *name); -bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db, - const char *name); +bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, unsigned char type, + const char *db, const char *name); +bool mdl_is_lock_owner(MDL_CONTEXT *context, unsigned char type, + const char *db, const char *name); -bool mdl_has_pending_conflicting_lock(MDL_LOCK_DATA *lock_data); +bool mdl_has_pending_conflicting_lock(MDL_LOCK_TICKET *ticket); inline bool mdl_has_locks(MDL_CONTEXT *context) { - return !context->locks.is_empty(); + return !context->tickets.is_empty(); } +inline MDL_LOCK_TICKET *mdl_savepoint(MDL_CONTEXT *ctx) +{ + return ctx->tickets.head(); +} + +void mdl_rollback_to_savepoint(MDL_CONTEXT *ctx, + MDL_LOCK_TICKET *mdl_savepoint); /** Get iterator for walking through all lock requests in the context. */ -inline I_P_List_iterator -mdl_get_locks(MDL_CONTEXT *ctx) +inline MDL_CONTEXT::Request_iterator +mdl_get_requests(MDL_CONTEXT *ctx) { - I_P_List_iterator result(ctx->locks); + MDL_CONTEXT::Request_iterator result(ctx->requests); return result; } -/** - Give metadata lock request object for the table get table definition - cache key corresponding to it. - - @param lock_data [in] Lock request object for the table. - @param key [out] LEX_STRING object where table definition cache key - should be put. - - @note This key will have the same life-time as this lock request object. - - @note This is yet another place where border between MDL subsystem and the - rest of the server is broken. OTOH it allows to save some CPU cycles - and memory by avoiding generating these TDC keys from table list. -*/ - -inline void mdl_get_tdc_key(MDL_LOCK_DATA *lock_data, LEX_STRING *key) -{ - key->str= lock_data->key + 4; - key->length= lock_data->key_length - 4; -} - +void mdl_get_tdc_key(MDL_LOCK_TICKET *ticket, LEX_STRING *key); typedef void (* mdl_cached_object_release_hook)(void *); -void* mdl_get_cached_object(MDL_LOCK_DATA *lock_data); -void mdl_set_cached_object(MDL_LOCK_DATA *lock_data, void *cached_object, +void *mdl_get_cached_object(MDL_LOCK_TICKET *ticket); +void mdl_set_cached_object(MDL_LOCK_TICKET *ticket, void *cached_object, mdl_cached_object_release_hook release_hook); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 9f672653266..5732d7b7079 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3981,10 +3981,10 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; - table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name, - thd->locked_tables_root ? - thd->locked_tables_root : - thd->mem_root); + table->mdl_lock_request= mdl_request_alloc(0, table->db, table->table_name, + thd->locked_tables_root ? + thd->locked_tables_root : + thd->mem_root); /* Everyting else should be zeroed */ @@ -4026,9 +4026,10 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->lock_type= locktype; table->select_lex= lex->current_select; table->cacheable_table= 1; - table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name, - thd->locked_tables_root ? - thd->locked_tables_root : thd->mem_root); + table->mdl_lock_request= mdl_request_alloc(0, table->db, table->table_name, + thd->locked_tables_root ? + thd->locked_tables_root : + thd->mem_root); lex->add_to_query_tables(table); return table; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 817a5e982ee..aa96483cf09 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -693,7 +693,7 @@ my_bool acl_reload(THD *thd) tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ; tables[0].skip_temporary= tables[1].skip_temporary= tables[2].skip_temporary= TRUE; - alloc_mdl_locks(tables, thd->mem_root); + alloc_mdl_requests(tables, thd->mem_root); if (simple_open_n_lock_tables(thd, tables)) { @@ -1598,7 +1598,7 @@ bool change_password(THD *thd, const char *host, const char *user, bzero((char*) &tables, sizeof(tables)); tables.alias= tables.table_name= (char*) "user"; tables.db= (char*) "mysql"; - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); #ifdef HAVE_REPLICATION /* @@ -3110,7 +3110,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, ? tables+2 : 0); tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_WRITE; tables[0].db=tables[1].db=tables[2].db=(char*) "mysql"; - alloc_mdl_locks(tables, thd->mem_root); + alloc_mdl_requests(tables, thd->mem_root); /* This statement will be replicated as a statement, even when using @@ -3328,7 +3328,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type=tables[1].lock_type=TL_WRITE; tables[0].db=tables[1].db=(char*) "mysql"; - alloc_mdl_locks(tables, thd->mem_root); + alloc_mdl_requests(tables, thd->mem_root); /* This statement will be replicated as a statement, even when using @@ -3467,7 +3467,7 @@ bool mysql_grant(THD *thd, const char *db, List &list, tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type=tables[1].lock_type=TL_WRITE; tables[0].db=tables[1].db=(char*) "mysql"; - alloc_mdl_locks(tables, thd->mem_root); + alloc_mdl_requests(tables, thd->mem_root); /* This statement will be replicated as a statement, even when using @@ -3801,7 +3801,7 @@ static my_bool grant_reload_procs_priv(THD *thd) table.db= (char *) "mysql"; table.lock_type= TL_READ; table.skip_temporary= 1; - alloc_mdl_locks(&table, thd->mem_root); + alloc_mdl_requests(&table, thd->mem_root); if (simple_open_n_lock_tables(thd, &table)) { @@ -3868,7 +3868,7 @@ my_bool grant_reload(THD *thd) tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type= tables[1].lock_type= TL_READ; tables[0].skip_temporary= tables[1].skip_temporary= TRUE; - alloc_mdl_locks(tables, thd->mem_root); + alloc_mdl_requests(tables, thd->mem_root); /* To avoid deadlocks we should obtain table locks before @@ -5215,7 +5215,7 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) (tables+4)->lock_type= TL_WRITE; tables->db= (tables+1)->db= (tables+2)->db= (tables+3)->db= (tables+4)->db= (char*) "mysql"; - alloc_mdl_locks(tables, thd->mem_root); + alloc_mdl_requests(tables, thd->mem_root); #ifdef HAVE_REPLICATION /* diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 0bd1acaa08e..f4f07ad08f7 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1055,7 +1055,7 @@ err_with_reopen: than picking only those tables that were flushed. */ for (TABLE *tab= thd->open_tables; tab; tab= tab->next) - mdl_downgrade_exclusive_lock(&thd->mdl_context, tab->mdl_lock_data); + mdl_downgrade_exclusive_lock(&thd->mdl_context, tab->mdl_lock_ticket); } DBUG_RETURN(result); } @@ -1478,10 +1478,10 @@ void close_thread_tables(THD *thd, if (thd->open_tables) close_open_tables(thd); - mdl_release_locks(&thd->mdl_context); + mdl_ticket_release_all(&thd->mdl_context); if (!skip_mdl) { - mdl_remove_all_locks(&thd->mdl_context); + mdl_request_remove_all(&thd->mdl_context); } DBUG_VOID_RETURN; } @@ -1500,7 +1500,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) *table_ptr=table->next; - table->mdl_lock_data= 0; + table->mdl_lock_ticket= NULL; if (table->needs_reopen() || thd->version != refresh_version || !table->db_stat) { @@ -2096,7 +2096,7 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, - table->mdl_lock_data)) + table->mdl_lock_ticket)) { mysql_lock_downgrade_write(thd, table, old_lock_type); DBUG_RETURN(TRUE); @@ -2279,11 +2279,11 @@ void table_share_release_hook(void *share) static bool open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, - MDL_LOCK_DATA *mdl_lock_data, + MDL_LOCK_REQUEST *mdl_lock_request, uint flags, enum_open_table_action *action) { - mdl_add_lock(&thd->mdl_context, mdl_lock_data); + mdl_request_add(&thd->mdl_context, mdl_lock_request); if (table_list->open_type) { @@ -2296,10 +2296,10 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, shared locks. This invariant is preserved here and is also enforced by asserts in metadata locking subsystem. */ - mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); + mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) { - mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); return 1; } } @@ -2316,16 +2316,16 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL && table_list->lock_type >= TL_WRITE_ALLOW_WRITE) - mdl_set_lock_type(mdl_lock_data, MDL_SHARED_UPGRADABLE); + mdl_request_set_type(mdl_lock_request, MDL_SHARED_UPGRADABLE); if (flags & MYSQL_LOCK_IGNORE_FLUSH) - mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO); + mdl_request_set_type(mdl_lock_request, MDL_SHARED_HIGH_PRIO); - if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry)) + if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_request, &retry)) { if (retry) *action= OT_BACK_OFF_AND_RETRY; else - mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); return 1; } } @@ -2380,7 +2380,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, char key[MAX_DBKEY_LENGTH]; uint key_length; char *alias= table_list->alias; - MDL_LOCK_DATA *mdl_lock_data; + MDL_LOCK_REQUEST *mdl_lock_request; + MDL_LOCK_TICKET *mdl_lock_ticket; int error; TABLE_SHARE *share; DBUG_ENTER("open_table"); @@ -2559,14 +2560,21 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, This is the normal use case. */ - mdl_lock_data= table_list->mdl_lock_data; + mdl_lock_request= table_list->mdl_lock_request; if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { - if (open_table_get_mdl_lock(thd, table_list, mdl_lock_data, flags, + if (open_table_get_mdl_lock(thd, table_list, mdl_lock_request, flags, action)) DBUG_RETURN(TRUE); } + /* + Grab reference to the granted MDL lock ticket. Must be done after + open_table_get_mdl_lock as the lock on the table might have been + acquired previously (MYSQL_OPEN_HAS_MDL_LOCK). + */ + mdl_lock_ticket= mdl_lock_request->ticket; + pthread_mutex_lock(&LOCK_open); /* @@ -2608,7 +2616,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, DBUG_RETURN(FALSE); } - if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock_data))) + if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock_ticket))) { if (!(share= get_table_share_with_create(thd, table_list, key, key_length, OPEN_VIEW, @@ -2679,7 +2687,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, so we need to increase reference counter; */ reference_table_share(share); - mdl_set_cached_object(mdl_lock_data, share, table_share_release_hook); + mdl_set_cached_object(mdl_lock_ticket, share, table_share_release_hook); } else { @@ -2788,9 +2796,9 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, lock on this table to shared metadata lock. */ if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE) - mdl_downgrade_exclusive_lock(&thd->mdl_context, table_list->mdl_lock_data); + mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_ticket); - table->mdl_lock_data= mdl_lock_data; + table->mdl_lock_ticket= mdl_lock_ticket; table->next=thd->open_tables; /* Link into simple list */ thd->open_tables=table; @@ -2842,8 +2850,8 @@ err_unlock2: pthread_mutex_unlock(&LOCK_open); if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { - mdl_release_lock(&thd->mdl_context, mdl_lock_data); - mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + mdl_ticket_release(&thd->mdl_context, mdl_lock_ticket); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); } DBUG_RETURN(TRUE); } @@ -2961,7 +2969,7 @@ Locked_tables_list::init_locked_tables(THD *thd) dst_table_list->init_one_table(db, db_len, table_name, table_name_len, alias, src_table_list->table->reginfo.lock_type); - dst_table_list->mdl_lock_data= src_table_list->mdl_lock_data; + dst_table_list->mdl_lock_request= src_table_list->mdl_lock_request; dst_table_list->table= table; memcpy(db, src_table_list->db, db_len + 1); memcpy(table_name, src_table_list->table_name, table_name_len + 1); @@ -3012,6 +3020,8 @@ Locked_tables_list::unlock_locked_tables(THD *thd) thd->locked_tables_mode= LTM_NONE; close_thread_tables(thd); + + mdl_ticket_release_all(&thd->mdl_context); } /* After closing tables we can free memory used for storing lock @@ -3496,20 +3506,21 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, enum_open_table_action action) { bool result= FALSE; + MDL_LOCK_REQUEST *mdl_lock_request= table->mdl_lock_request; switch (action) { case OT_BACK_OFF_AND_RETRY: result= (mdl_wait_for_locks(&thd->mdl_context) || tdc_wait_for_old_versions(thd, &thd->mdl_context)); - mdl_remove_all_locks(&thd->mdl_context); + mdl_request_remove_all(&thd->mdl_context); break; case OT_DISCOVER: - mdl_set_lock_type(table->mdl_lock_data, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, table->mdl_lock_data); + mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); + mdl_request_add(&thd->mdl_context, mdl_lock_request); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) { - mdl_remove_lock(&thd->mdl_context, table->mdl_lock_data); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); return TRUE; } pthread_mutex_lock(&LOCK_open); @@ -3519,15 +3530,15 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, thd->warning_info->clear_warning_info(thd->query_id); thd->clear_error(); // Clear error message - mdl_release_lock(&thd->mdl_context, table->mdl_lock_data); - mdl_remove_lock(&thd->mdl_context, table->mdl_lock_data); + mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); break; case OT_REPAIR: - mdl_set_lock_type(table->mdl_lock_data, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, table->mdl_lock_data); + mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); + mdl_request_add(&thd->mdl_context, mdl_lock_request); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) { - mdl_remove_lock(&thd->mdl_context, table->mdl_lock_data); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); return TRUE; } pthread_mutex_lock(&LOCK_open); @@ -3535,8 +3546,8 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, pthread_mutex_unlock(&LOCK_open); result= auto_repair_table(thd, table); - mdl_release_lock(&thd->mdl_context, table->mdl_lock_data); - mdl_remove_lock(&thd->mdl_context, table->mdl_lock_data); + mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); break; default: DBUG_ASSERT(0); @@ -7730,10 +7741,9 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context) { - MDL_LOCK_DATA *lock_data; TABLE_SHARE *share; const char *old_msg; - LEX_STRING key; + MDL_LOCK_REQUEST *lock_req; while (!thd->killed) { @@ -7746,18 +7756,16 @@ static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context) mysql_ha_flush(thd); pthread_mutex_lock(&LOCK_open); - I_P_List_iterator it= mdl_get_locks(context); - while ((lock_data= it++)) + MDL_CONTEXT::Request_iterator it= mdl_get_requests(context); + while ((lock_req= it++)) { - mdl_get_tdc_key(lock_data, &key); - if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache, (uchar*) key.str, - key.length)) && + if ((share= get_cached_table_share(lock_req->key.db_name(), + lock_req->key.table_name())) && share->version != refresh_version && !share->used_tables.is_empty()) break; } - if (!lock_data) + if (!lock_req) { pthread_mutex_unlock(&LOCK_open); break; @@ -7971,7 +7979,7 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, DBUG_ENTER("open_system_tables_for_read"); - alloc_mdl_locks(table_list, thd->mem_root); + alloc_mdl_requests(table_list, thd->mem_root); /* Besides using new Open_tables_state for opening system tables, @@ -8046,7 +8054,7 @@ open_system_table_for_update(THD *thd, TABLE_LIST *one_table) { DBUG_ENTER("open_system_table_for_update"); - alloc_mdl_locks(one_table, thd->mem_root); + alloc_mdl_requests(one_table, thd->mem_root); TABLE *table= open_ltable(thd, one_table, one_table->lock_type, 0); if (table) @@ -8084,7 +8092,7 @@ open_performance_schema_table(THD *thd, TABLE_LIST *one_table, thd->reset_n_backup_open_tables_state(backup); - alloc_mdl_locks(one_table, thd->mem_root); + alloc_mdl_requests(one_table, thd->mem_root); if ((table= open_ltable(thd, one_table, one_table->lock_type, flags))) { DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_PERFORMANCE); @@ -8161,8 +8169,8 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup) pthread_mutex_unlock(&LOCK_open); - mdl_release_locks(&thd->mdl_context); - mdl_remove_all_locks(&thd->mdl_context); + mdl_ticket_release_all(&thd->mdl_context); + mdl_request_remove_all(&thd->mdl_context); thd->restore_backup_open_tables_state(backup); } diff --git a/sql/sql_class.h b/sql/sql_class.h index 34ba1386a89..80817fa4c12 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -779,6 +779,8 @@ struct st_savepoint { char *name; uint length; Ha_trx_info *ha_list; + /** Last acquired lock before this savepoint was set. */ + MDL_LOCK_TICKET *mdl_savepoint; }; enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED, XA_ROLLBACK_ONLY}; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 13c331af95c..c96594a8032 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1100,7 +1100,7 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) TABLE *table; bool error; uint path_length; - MDL_LOCK_DATA *mdl_lock_data= 0; + MDL_LOCK_REQUEST *mdl_lock_request= NULL; DBUG_ENTER("mysql_truncate"); bzero((char*) &create_info,sizeof(create_info)); @@ -1175,13 +1175,13 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) tries to get table enging and therefore accesses table in some way without holding any kind of meta-data lock. */ - mdl_lock_data= mdl_alloc_lock(0, table_list->db, table_list->table_name, - thd->mem_root); - mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, mdl_lock_data); + mdl_lock_request= mdl_request_alloc(0, table_list->db, + table_list->table_name, thd->mem_root); + mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); + mdl_request_add(&thd->mdl_context, mdl_lock_request); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) { - mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); DBUG_RETURN(TRUE); } pthread_mutex_lock(&LOCK_open); @@ -1212,18 +1212,18 @@ end: write_bin_log(thd, TRUE, thd->query(), thd->query_length()); my_ok(thd); // This should return record count } - if (mdl_lock_data) + if (mdl_lock_request) { - mdl_release_lock(&thd->mdl_context, mdl_lock_data); - mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); } } else if (error) { - if (mdl_lock_data) + if (mdl_lock_request) { - mdl_release_lock(&thd->mdl_context, mdl_lock_data); - mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); } } DBUG_RETURN(error); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 49d6cbaa447..83c5c60dc01 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -125,7 +125,7 @@ static void mysql_ha_hash_free(TABLE_LIST *tables) static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) { TABLE **table_ptr; - MDL_LOCK_DATA *mdl_lock_data; + MDL_LOCK_TICKET *mdl_lock_ticket; /* Though we could take the table pointer from hash_tables->table, @@ -141,7 +141,7 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) if (*table_ptr) { (*table_ptr)->file->ha_index_or_rnd_end(); - mdl_lock_data= (*table_ptr)->mdl_lock_data; + mdl_lock_ticket= (*table_ptr)->mdl_lock_ticket; pthread_mutex_lock(&LOCK_open); if (close_thread_table(thd, table_ptr)) { @@ -149,8 +149,8 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) broadcast_refresh(); } pthread_mutex_unlock(&LOCK_open); - mdl_release_lock(&thd->handler_mdl_context, mdl_lock_data); - mdl_remove_lock(&thd->handler_mdl_context, mdl_lock_data); + mdl_ticket_release(&thd->handler_mdl_context, mdl_lock_ticket); + mdl_request_remove(&thd->handler_mdl_context, tables->mdl_lock_request); } else if (tables->table) { @@ -190,12 +190,12 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) { TABLE_LIST *hash_tables = NULL; - MDL_LOCK_DATA *mdl_lock_data; - char *db, *name, *alias, *mdlkey; + char *db, *name, *alias; uint dblen, namelen, aliaslen, counter; int error; TABLE *backup_open_tables; MDL_CONTEXT backup_mdl_context; + MDL_LOCK_REQUEST *mdl_lock_request; DBUG_ENTER("mysql_ha_open"); DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", tables->db, tables->table_name, tables->alias, @@ -246,8 +246,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) &db, (uint) dblen, &name, (uint) namelen, &alias, (uint) aliaslen, - &mdl_lock_data, sizeof(MDL_LOCK_DATA), - &mdlkey, MAX_MDLKEY_LENGTH, + &mdl_lock_request, sizeof(MDL_LOCK_REQUEST), NullS))) { DBUG_PRINT("exit",("ERROR")); @@ -261,8 +260,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) memcpy(hash_tables->db, tables->db, dblen); memcpy(hash_tables->table_name, tables->table_name, namelen); memcpy(hash_tables->alias, tables->alias, aliaslen); - mdl_init_lock(mdl_lock_data, mdlkey, 0, db, name); - hash_tables->mdl_lock_data= mdl_lock_data; + mdl_request_init(mdl_lock_request, 0, db, name); + hash_tables->mdl_lock_request= mdl_lock_request; /* add to hash */ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) @@ -801,11 +800,11 @@ void mysql_ha_flush(THD *thd) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); /* - TABLE::mdl_lock_data is 0 for temporary tables so we need extra check. + TABLE::mdl_lock_ticket is 0 for temporary tables so we need extra check. */ if (hash_tables->table && - (hash_tables->table->mdl_lock_data && - mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock_data) || + (hash_tables->table->mdl_lock_ticket && + mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock_ticket) || hash_tables->table->needs_reopen())) mysql_ha_close_table(thd, hash_tables); } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 16e9c1ded38..8553db8cf2b 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2399,7 +2399,7 @@ pthread_handler_t handle_delayed_insert(void *arg) thd->lex->set_stmt_unsafe(); thd->set_current_stmt_binlog_row_based_if_mixed(); - alloc_mdl_locks(&di->table_list, thd->mem_root); + alloc_mdl_requests(&di->table_list, thd->mem_root); if (di->open_and_lock_table()) goto err; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 83e45904816..4166267c5a9 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1115,7 +1115,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, select_lex.table_list.link_in_list((uchar*) &table_list, (uchar**) &table_list.next_local); thd->lex->add_to_query_tables(&table_list); - alloc_mdl_locks(&table_list, thd->mem_root); + alloc_mdl_requests(&table_list, thd->mem_root); /* switch on VIEW optimisation: do not fill temporary tables */ thd->lex->sql_command= SQLCOM_SHOW_FIELDS; @@ -3324,7 +3324,7 @@ end_with_restore_list: !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) goto error; - alloc_mdl_locks(all_tables, thd->locked_tables_list.locked_tables_root()); + alloc_mdl_requests(all_tables, thd->locked_tables_list.locked_tables_root()); thd->options|= OPTION_TABLE_LOCK; thd->in_lock_tables=1; @@ -5977,9 +5977,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); - ptr->mdl_lock_data= mdl_alloc_lock(0 , ptr->db, ptr->table_name, - thd->locked_tables_root ? - thd->locked_tables_root : thd->mem_root); + ptr->mdl_lock_request= + mdl_request_alloc(0, ptr->db, ptr->table_name, thd->locked_tables_root ? + thd->locked_tables_root : thd->mem_root); DBUG_RETURN(ptr); } diff --git a/sql/sql_plist.h b/sql/sql_plist.h index b05a6318f0f..91fa9ef52bb 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -90,6 +90,7 @@ public: #ifndef _lint friend class I_P_List_iterator; #endif + typedef I_P_List_iterator Iterator; }; diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index e9f4152f861..77b5552d977 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1366,7 +1366,7 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) tables.alias= tables.table_name= (char*)"plugin"; tables.lock_type= TL_READ; tables.db= new_thd->db; - alloc_mdl_locks(&tables, tmp_root); + alloc_mdl_requests(&tables, tmp_root); #ifdef EMBEDDED_LIBRARY /* @@ -1660,7 +1660,7 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, const LEX_STRING *dl if (check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE)) DBUG_RETURN(TRUE); - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); /* need to open before acquiring LOCK_plugin or it will deadlock */ if (! (table = open_ltable(thd, &tables, TL_WRITE, 0))) @@ -1735,7 +1735,7 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name) bzero(&tables, sizeof(tables)); tables.db= (char *)"mysql"; tables.table_name= tables.alias= (char *)"plugin"; - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); /* need to open before acquiring LOCK_plugin or it will deadlock */ if (! (table= open_ltable(thd, &tables, TL_WRITE, 0))) diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index 6d25b0c2b59..40cdfeed946 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -234,7 +234,7 @@ bool servers_reload(THD *thd) tables[0].alias= tables[0].table_name= (char*) "servers"; tables[0].db= (char*) "mysql"; tables[0].lock_type= TL_READ; - alloc_mdl_locks(tables, thd->mem_root); + alloc_mdl_requests(tables, thd->mem_root); if (simple_open_n_lock_tables(thd, tables)) { @@ -365,7 +365,7 @@ insert_server(THD *thd, FOREIGN_SERVER *server) bzero((char*) &tables, sizeof(tables)); tables.db= (char*) "mysql"; tables.alias= tables.table_name= (char*) "servers"; - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); /* need to open before acquiring THR_LOCK_plugin or it will deadlock */ if (! (table= open_ltable(thd, &tables, TL_WRITE, 0))) @@ -584,7 +584,7 @@ int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options) bzero((char*) &tables, sizeof(tables)); tables.db= (char*) "mysql"; tables.alias= tables.table_name= (char*) "servers"; - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); rw_wrlock(&THR_LOCK_servers); @@ -709,7 +709,7 @@ int update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered) bzero((char*) &tables, sizeof(tables)); tables.db= (char*)"mysql"; tables.alias= tables.table_name= (char*)"servers"; - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); if (!(table= open_ltable(thd, &tables, TL_WRITE, 0))) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index d9da11b79fd..440efbf7c6d 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3050,12 +3050,8 @@ uint get_table_open_method(TABLE_LIST *tables, Acquire high priority share metadata lock on a table. @param thd Thread context. - @param mdl_lock_data Pointer to memory to be used for MDL_LOCK_DATA + @param mdl_lock_req Pointer to memory to be used for MDL_LOCK_REQUEST object for a lock request. - @param mdlkey Pointer to the buffer for key for the lock request - (should be at least strlen(db) + strlen(name) + 2 - bytes, or, if the lengths are not known, - MAX_MDLKEY_LENGTH) @param table Table list element for the table @note This is an auxiliary function to be used in cases when we want to @@ -3069,23 +3065,23 @@ uint get_table_open_method(TABLE_LIST *tables, */ static bool -acquire_high_prio_shared_mdl_lock(THD *thd, MDL_LOCK_DATA *mdl_lock_data, - char *mdlkey, TABLE_LIST *table) +acquire_high_prio_shared_mdl_lock(THD *thd, MDL_LOCK_REQUEST *mdl_lock_req, + TABLE_LIST *table) { bool retry; - mdl_init_lock(mdl_lock_data, mdlkey, 0, table->db, table->table_name); - table->mdl_lock_data= mdl_lock_data; - mdl_add_lock(&thd->mdl_context, mdl_lock_data); - mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO); + mdl_request_init(mdl_lock_req, 0, table->db, table->table_name); + table->mdl_lock_request= mdl_lock_req; + mdl_request_add(&thd->mdl_context, mdl_lock_req); + mdl_request_set_type(mdl_lock_req, MDL_SHARED_HIGH_PRIO); while (1) { - if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry)) + if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_req, &retry)) { if (!retry || mdl_wait_for_locks(&thd->mdl_context)) { - mdl_remove_all_locks(&thd->mdl_context); + mdl_request_remove_all(&thd->mdl_context); return TRUE; } continue; @@ -3127,8 +3123,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, char key[MAX_DBKEY_LENGTH]; uint key_length; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; - MDL_LOCK_DATA mdl_lock_data; - char mdlkey[MAX_MDLKEY_LENGTH]; + MDL_LOCK_REQUEST mdl_lock_request; bzero((char*) &table_list, sizeof(TABLE_LIST)); bzero((char*) &tbl, sizeof(TABLE)); @@ -3158,8 +3153,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, simply obtaining internal lock of data-dictionary (ATM it is LOCK_open) instead of obtaning full-blown metadata lock. */ - if (acquire_high_prio_shared_mdl_lock(thd, &mdl_lock_data, mdlkey, - &table_list)) + if (acquire_high_prio_shared_mdl_lock(thd, &mdl_lock_request, &table_list)) { /* Some error occured (most probably we have been killed while @@ -3220,8 +3214,8 @@ err_unlock: pthread_mutex_unlock(&LOCK_open); err: - mdl_release_lock(&thd->mdl_context, &mdl_lock_data); - mdl_remove_lock(&thd->mdl_context, &mdl_lock_data); + mdl_ticket_release(&thd->mdl_context, mdl_lock_request.ticket); + mdl_request_remove(&thd->mdl_context, &mdl_lock_request); thd->clear_error(); return res; } @@ -7317,7 +7311,7 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name) uint num_tables; /* NOTE: unused, only to pass to open_tables(). */ - alloc_mdl_locks(lst, thd->mem_root); + alloc_mdl_requests(lst, thd->mem_root); if (open_tables(thd, &lst, &num_tables, 0)) { diff --git a/sql/sql_table.cc b/sql/sql_table.cc index d6a592c4799..54f06935d3b 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1910,7 +1910,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, Since we don't acquire metadata lock if we have found temporary table, we should do something to avoid releasing it at the end. */ - table->mdl_lock_data= 0; + table->mdl_lock_request= NULL; } else { @@ -1923,7 +1923,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->table_name); if (!table->table) DBUG_RETURN(1); - table->mdl_lock_data= table->table->mdl_lock_data; + table->mdl_lock_request->ticket= table->table->mdl_lock_ticket; } } } @@ -2202,15 +2202,15 @@ err: } for (table= tables; table; table= table->next_local) { - if (table->mdl_lock_data) + if (table->mdl_lock_request) { /* Under LOCK TABLES we may have several instances of table open and locked and therefore have to remove several metadata lock requests associated with them. */ - mdl_release_and_remove_all_locks_for_name(&thd->mdl_context, - table->mdl_lock_data); + mdl_ticket_release_all_for_name(&thd->mdl_context, + table->mdl_lock_request->ticket); } } } @@ -4108,29 +4108,28 @@ warn: static bool lock_table_name_if_not_cached(THD *thd, const char *db, const char *table_name, - MDL_LOCK_DATA **lock_data) + MDL_LOCK_REQUEST **lock_req) { bool conflict; - if (!(*lock_data= mdl_alloc_lock(0, db, table_name, thd->mem_root))) + if (!(*lock_req= mdl_request_alloc(0, db, table_name, thd->mem_root))) return TRUE; - mdl_set_lock_type(*lock_data, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, *lock_data); - if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock_data, - &conflict)) + mdl_request_set_type(*lock_req, MDL_EXCLUSIVE); + mdl_request_add(&thd->mdl_context, *lock_req); + if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock_req, &conflict)) { /* To simplify our life under LOCK TABLES we remove unsatisfied lock request from the context. */ - mdl_remove_lock(&thd->mdl_context, *lock_data); + mdl_request_remove(&thd->mdl_context, *lock_req); if (!conflict) { /* Probably OOM. */ return TRUE; } else - *lock_data= 0; + *lock_req= NULL; } return FALSE; } @@ -4146,7 +4145,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, bool internal_tmp_table, uint select_field_count) { - MDL_LOCK_DATA *target_lock_data= 0; + MDL_LOCK_REQUEST *target_lock_req= NULL; bool result; DBUG_ENTER("mysql_create_table"); @@ -4169,12 +4168,12 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock_data)) + if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock_req)) { result= TRUE; goto unlock; } - if (!target_lock_data) + if (!target_lock_req) { if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) { @@ -4200,10 +4199,10 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, select_field_count); unlock: - if (target_lock_data) + if (target_lock_req) { - mdl_release_lock(&thd->mdl_context, target_lock_data); - mdl_remove_lock(&thd->mdl_context, target_lock_data); + mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); + mdl_request_remove(&thd->mdl_context, target_lock_req); } pthread_mutex_lock(&LOCK_lock_db); if (!--creating_table && creating_database) @@ -4368,7 +4367,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, char from[FN_REFLEN],tmp[FN_REFLEN+32]; const char **ext; MY_STAT stat_info; - MDL_LOCK_DATA *mdl_lock_data; + MDL_LOCK_REQUEST *mdl_lock_request= NULL; enum enum_open_table_action ot_action_unused; DBUG_ENTER("prepare_for_repair"); uint reopen_for_repair_flags= (MYSQL_LOCK_IGNORE_FLUSH | @@ -4387,13 +4386,13 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, uint key_length; key_length= create_table_def_key(thd, key, table_list, 0); - mdl_lock_data= mdl_alloc_lock(0, table_list->db, table_list->table_name, - thd->mem_root); - mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); - mdl_add_lock(&thd->mdl_context, mdl_lock_data); + mdl_lock_request= mdl_request_alloc(0, table_list->db, + table_list->table_name, thd->mem_root); + mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); + mdl_request_add(&thd->mdl_context, mdl_lock_request); if (mdl_acquire_exclusive_locks(&thd->mdl_context)) { - mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); DBUG_RETURN(0); } @@ -4413,11 +4412,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, } pthread_mutex_unlock(&LOCK_open); table= &tmp_table; - table_list->mdl_lock_data= mdl_lock_data; - } - else - { - mdl_lock_data= table->mdl_lock_data; + table_list->mdl_lock_request= mdl_lock_request; } /* A MERGE table must not come here. */ @@ -4528,10 +4523,10 @@ end: pthread_mutex_unlock(&LOCK_open); } /* In case of a temporary table there will be no metadata lock. */ - if (error && mdl_lock_data) + if (error && mdl_lock_request) { - mdl_release_lock(&thd->mdl_context, mdl_lock_data); - mdl_remove_lock(&thd->mdl_context, mdl_lock_data); + mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); + mdl_request_remove(&thd->mdl_context, mdl_lock_request); } DBUG_RETURN(error); } @@ -5234,7 +5229,7 @@ bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO *create_info) { - MDL_LOCK_DATA *target_lock_data= 0; + MDL_LOCK_REQUEST *target_lock_req= NULL; char src_path[FN_REFLEN], dst_path[FN_REFLEN + 1]; uint dst_path_length; char *db= table->db; @@ -5291,9 +5286,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, } else { - if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock_data)) + if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock_req)) goto err; - if (!target_lock_data) + if (!target_lock_req) goto table_exists; dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1, db, table_name, reg_ext, 0); @@ -5303,7 +5298,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, Make the metadata lock available to open_table() called to reopen the table down the road. */ - table->mdl_lock_data= target_lock_data; + table->mdl_lock_request= target_lock_req; } DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000);); @@ -5474,10 +5469,10 @@ binlog: res= FALSE; err: - if (target_lock_data) + if (target_lock_req) { - mdl_release_lock(&thd->mdl_context, target_lock_data); - mdl_remove_lock(&thd->mdl_context, target_lock_data); + mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); + mdl_request_remove(&thd->mdl_context, target_lock_req); } DBUG_RETURN(res); } @@ -6416,7 +6411,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, uint order_num, ORDER *order, bool ignore) { TABLE *table, *new_table= 0; - MDL_LOCK_DATA *mdl_lock_data, *target_lock_data= 0; + MDL_LOCK_TICKET *mdl_lock_ticket; + MDL_LOCK_REQUEST *target_lock_req= NULL; int error= 0; char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1]; char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias; @@ -6587,7 +6583,7 @@ view_err: MYSQL_OPEN_TAKE_UPGRADABLE_MDL))) DBUG_RETURN(TRUE); table->use_all_columns(); - mdl_lock_data= table->mdl_lock_data; + mdl_lock_ticket= table->mdl_lock_ticket; /* Prohibit changing of the UNION list of a non-temporary MERGE table @@ -6640,9 +6636,9 @@ view_err: else { if (lock_table_name_if_not_cached(thd, new_db, new_name, - &target_lock_data)) + &target_lock_req)) DBUG_RETURN(TRUE); - if (!target_lock_data) + if (!target_lock_req) { my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); DBUG_RETURN(TRUE); @@ -6835,13 +6831,12 @@ view_err: */ if (new_name != table_name || new_db != db) { - mdl_release_lock(&thd->mdl_context, target_lock_data); - mdl_remove_lock(&thd->mdl_context, target_lock_data); - mdl_release_and_remove_all_locks_for_name(&thd->mdl_context, - mdl_lock_data); + mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); + mdl_request_remove(&thd->mdl_context, target_lock_req); + mdl_ticket_release_all_for_name(&thd->mdl_context, mdl_lock_ticket); } else - mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_data); + mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_ticket); } DBUG_RETURN(error); } @@ -7074,7 +7069,7 @@ view_err: #ifdef WITH_PARTITION_STORAGE_ENGINE if (fast_alter_partition) { - DBUG_ASSERT(!target_lock_data); + DBUG_ASSERT(!target_lock_req); DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, create_info, table_list, db, table_name, @@ -7441,16 +7436,16 @@ view_err: table_list->table_name_length= strlen(new_name); table_list->db= new_db; table_list->db_length= strlen(new_db); - table_list->mdl_lock_data= target_lock_data; + table_list->mdl_lock_request= target_lock_req; } else { /* - Under LOCK TABLES, we have a different mdl_lock_data + Under LOCK TABLES, we have a different mdl_lock_ticket points to a different instance than the one set initially to request the lock. */ - table_list->mdl_lock_data= mdl_lock_data; + table_list->mdl_lock_request->ticket= mdl_lock_ticket; } if (open_table(thd, table_list, thd->mem_root, &ot_action_unused, MYSQL_OPEN_REOPEN)) @@ -7516,13 +7511,12 @@ view_err: { if ((new_name != table_name || new_db != db)) { - mdl_release_lock(&thd->mdl_context, target_lock_data); - mdl_remove_lock(&thd->mdl_context, target_lock_data); - mdl_release_and_remove_all_locks_for_name(&thd->mdl_context, - mdl_lock_data); + mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); + mdl_request_remove(&thd->mdl_context, target_lock_req); + mdl_ticket_release_all_for_name(&thd->mdl_context, mdl_lock_ticket); } else - mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_data); + mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_ticket); } end_temporary: @@ -7577,10 +7571,10 @@ err: alter_info->datetime_field->field_name); thd->abort_on_warning= save_abort_on_warning; } - if (target_lock_data) + if (target_lock_req) { - mdl_release_lock(&thd->mdl_context, target_lock_data); - mdl_remove_lock(&thd->mdl_context, target_lock_data); + mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); + mdl_request_remove(&thd->mdl_context, target_lock_req); } DBUG_RETURN(TRUE); @@ -7592,12 +7586,12 @@ err_with_mdl: tables and release the exclusive metadata lock. */ thd->locked_tables_list.unlink_all_closed_tables(); - if (target_lock_data) + if (target_lock_req) { - mdl_release_lock(&thd->mdl_context, target_lock_data); - mdl_remove_lock(&thd->mdl_context, target_lock_data); + mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); + mdl_request_remove(&thd->mdl_context, target_lock_req); } - mdl_release_and_remove_all_locks_for_name(&thd->mdl_context, mdl_lock_data); + mdl_ticket_release_all_for_name(&thd->mdl_context, mdl_lock_ticket); DBUG_RETURN(TRUE); } /* mysql_alter_table */ diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 9a42dd189e7..894204fec58 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -329,6 +329,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) String stmt_query; bool need_start_waiting= FALSE; bool lock_upgrade_done= FALSE; + MDL_LOCK_TICKET *mdl_lock_ticket= NULL; DBUG_ENTER("mysql_create_or_drop_trigger"); @@ -451,8 +452,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) if (!(tables->table= find_write_locked_table(thd->open_tables, tables->db, tables->table_name))) goto end; - /* Later on we will need it to downgrade the lock */ - tables->mdl_lock_data= tables->table->mdl_lock_data; } else { @@ -465,6 +464,9 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) } table= tables->table; + /* Later on we will need it to downgrade the lock */ + mdl_lock_ticket= table->mdl_lock_ticket; + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto end; @@ -511,8 +513,7 @@ end: TABLE instance created by open_n_lock_single_table() and metadata lock. */ if (thd->locked_tables_mode && tables && lock_upgrade_done) - mdl_downgrade_exclusive_lock(&thd->mdl_context, - tables->mdl_lock_data); + mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_ticket); if (need_start_waiting) start_waiting_global_read_lock(thd); diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc index 25a0db2fbb8..7e557c3ce68 100644 --- a/sql/sql_udf.cc +++ b/sql/sql_udf.cc @@ -142,7 +142,7 @@ void udf_init() tables.alias= tables.table_name= (char*) "func"; tables.lock_type = TL_READ; tables.db= db; - alloc_mdl_locks(&tables, new_thd->mem_root); + alloc_mdl_requests(&tables, new_thd->mem_root); if (simple_open_n_lock_tables(new_thd, &tables)) { @@ -486,7 +486,7 @@ int mysql_create_function(THD *thd,udf_func *udf) bzero((char*) &tables,sizeof(tables)); tables.db= (char*) "mysql"; tables.table_name= tables.alias= (char*) "func"; - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); /* Allow creation of functions even if we can't open func table */ if (!(table = open_ltable(thd, &tables, TL_WRITE, 0))) goto err; @@ -565,7 +565,7 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) bzero((char*) &tables,sizeof(tables)); tables.db=(char*) "mysql"; tables.table_name= tables.alias= (char*) "func"; - alloc_mdl_locks(&tables, thd->mem_root); + alloc_mdl_requests(&tables, thd->mem_root); if (!(table = open_ltable(thd, &tables, TL_WRITE, 0))) goto err; table->use_all_columns(); diff --git a/sql/table.cc b/sql/table.cc index dd39d05733e..b1988faf722 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4811,12 +4811,11 @@ size_t max_row_length(TABLE *table, const uchar *data) objects for all elements of table list. */ -void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root) +void alloc_mdl_requests(TABLE_LIST *table_list, MEM_ROOT *root) { for ( ; table_list ; table_list= table_list->next_global) - table_list->mdl_lock_data= mdl_alloc_lock(0, table_list->db, - table_list->table_name, - root); + table_list->mdl_lock_request= + mdl_request_alloc(0, table_list->db, table_list->table_name, root); } diff --git a/sql/table.h b/sql/table.h index 918fb8dfff0..3b125217338 100644 --- a/sql/table.h +++ b/sql/table.h @@ -30,7 +30,8 @@ class st_select_lex; class partition_info; class COND_EQUAL; class Security_context; -struct MDL_LOCK_DATA; +struct MDL_LOCK_REQUEST; +struct MDL_LOCK_TICKET; /*************************************************************************/ @@ -813,7 +814,7 @@ public: partition_info *part_info; /* Partition related information */ bool no_partitions_used; /* If true, all partitions have been pruned away */ #endif - MDL_LOCK_DATA *mdl_lock_data; + MDL_LOCK_TICKET *mdl_lock_ticket; bool fill_item_list(List *item_list) const; void reset_item_list(List *item_list) const; @@ -1416,7 +1417,7 @@ struct TABLE_LIST uint table_open_method; enum enum_schema_table_state schema_table_state; - MDL_LOCK_DATA *mdl_lock_data; + MDL_LOCK_REQUEST *mdl_lock_request; void calc_md5(char *buffer); void set_underlying_merge(); @@ -1785,6 +1786,6 @@ static inline void dbug_tmp_restore_column_maps(MY_BITMAP *read_set, size_t max_row_length(TABLE *table, const uchar *data); -void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root); +void alloc_mdl_requests(TABLE_LIST *table_list, MEM_ROOT *root); #endif /* TABLE_INCLUDED */ diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index 3d1b2b13c1e..b6992256d5a 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -434,16 +434,15 @@ int ha_myisammrg::add_children_list(void) /* Copy select_lex. Used in unique_table() at least. */ child_l->select_lex= parent_l->select_lex; - child_l->mdl_lock_data= NULL; /* Safety, if alloc_mdl_locks fails. */ + child_l->mdl_lock_request= NULL; /* Safety, if alloc_mdl_requests fails. */ /* Break when this was the last child. */ if (&child_l->next_global == this->children_last_l) break; } - alloc_mdl_locks(children_l, - thd->locked_tables_root ? thd->locked_tables_root : - thd->mem_root); + alloc_mdl_requests(children_l, thd->locked_tables_root ? + thd->locked_tables_root : thd->mem_root); /* Insert children into the table list. */ if (parent_l->next_global) From 97abce0e7c3c7948b5b83d661638a20b175ec8bd Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 4 Dec 2009 02:34:19 +0300 Subject: [PATCH 066/212] Backport of: ---------------------------------------------------------- revno: 2617.23.19 committer: Konstantin Osipov branch nick: mysql-6.0-runtime timestamp: Tue 2009-03-03 01:20:44 +0300 message: Metadata locking: realign comments. No semantical changes, only enforce a bit of the coding style. This is a review fix for WL#4284 "Transactional DDL locking". sql/mdl.cc: Realign doxygen comments. sql/mdl.h: Realign doxygen comments. --- sql/mdl.cc | 609 ++++++++++++++++++++++++++--------------------------- sql/mdl.h | 62 +++--- 2 files changed, 335 insertions(+), 336 deletions(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index 18f0debdbb5..e3f285f3a55 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -22,10 +22,10 @@ /** - The lock context. Created internally for an acquired lock. - For a given name, there exists only one MDL_LOCK instance, - and it exists only when the lock has been granted. - Can be seen as an MDL subsystem's version of TABLE_SHARE. + The lock context. Created internally for an acquired lock. + For a given name, there exists only one MDL_LOCK instance, + and it exists only when the lock has been granted. + Can be seen as an MDL subsystem's version of TABLE_SHARE. */ struct MDL_LOCK @@ -48,7 +48,7 @@ struct MDL_LOCK MDL_KEY key; /** List of granted tickets for this lock. */ Ticket_list granted; - /* + /** There can be several upgraders and active exclusive belonging to the same context. */ @@ -76,11 +76,11 @@ pthread_cond_t COND_mdl; HASH mdl_locks; /** - Structure implementing global metadata lock. The only types - of locks which are supported at the moment are shared and - intention exclusive locks. Note that the latter type of global - lock acquired automatically when one tries to acquire exclusive - or shared upgradable lock on particular object. + Structure implementing global metadata lock. The only types + of locks which are supported at the moment are shared and + intention exclusive locks. Note that the latter type of global + lock acquired automatically when one tries to acquire exclusive + or shared upgradable lock on particular object. */ struct MDL_GLOBAL_LOCK @@ -102,21 +102,21 @@ mdl_locks_key(const uchar *record, size_t *length, /** - Initialize the metadata locking subsystem. + Initialize the metadata locking subsystem. - This function is called at server startup. + This function is called at server startup. - In particular, initializes the new global mutex and - the associated condition variable: LOCK_mdl and COND_mdl. - These locking primitives are implementation details of the MDL - subsystem and are private to it. + In particular, initializes the new global mutex and + the associated condition variable: LOCK_mdl and COND_mdl. + These locking primitives are implementation details of the MDL + subsystem and are private to it. - Note, that even though the new implementation adds acquisition - of a new global mutex to the execution flow of almost every SQL - statement, the design capitalizes on that to later save on - look ups in the table definition cache. This leads to reduced - contention overall and on LOCK_open in particular. - Please see the description of mdl_acquire_shared_lock() for details. + Note, that even though the new implementation adds acquisition + of a new global mutex to the execution flow of almost every SQL + statement, the design capitalizes on that to later save on + look ups in the table definition cache. This leads to reduced + contention overall and on LOCK_open in particular. + Please see the description of mdl_acquire_shared_lock() for details. */ void mdl_init() @@ -131,10 +131,10 @@ void mdl_init() /** - Release resources of metadata locking subsystem. + Release resources of metadata locking subsystem. - Destroys the global mutex and the condition variable. - Called at server shutdown. + Destroys the global mutex and the condition variable. + Called at server shutdown. */ void mdl_destroy() @@ -147,9 +147,9 @@ void mdl_destroy() /** - Initialize a metadata locking context. + Initialize a metadata locking context. - This is to be called when a new server connection is created. + This is to be called when a new server connection is created. */ void mdl_context_init(MDL_CONTEXT *context, THD *thd) @@ -162,15 +162,15 @@ void mdl_context_init(MDL_CONTEXT *context, THD *thd) /** - Destroy metadata locking context. + Destroy metadata locking context. - Assumes and asserts that there are no active or pending locks - associated with this context at the time of the destruction. + Assumes and asserts that there are no active or pending locks + associated with this context at the time of the destruction. - Currently does nothing. Asserts that there are no pending - or satisfied lock requests. The pending locks must be released - prior to destruction. This is a new way to express the assertion - that all tables are closed before a connection is destroyed. + Currently does nothing. Asserts that there are no pending + or satisfied lock requests. The pending locks must be released + prior to destruction. This is a new way to express the assertion + that all tables are closed before a connection is destroyed. */ void mdl_context_destroy(MDL_CONTEXT *context) @@ -182,13 +182,13 @@ void mdl_context_destroy(MDL_CONTEXT *context) /** - Backup and reset state of meta-data locking context. + Backup and reset state of meta-data locking context. - mdl_context_backup_and_reset(), mdl_context_restore() and - mdl_context_merge() are used by HANDLER implementation which - needs to open table for new HANDLER independently of already - open HANDLERs and add this table/metadata lock to the set of - tables open/metadata locks for HANDLERs afterwards. + mdl_context_backup_and_reset(), mdl_context_restore() and + mdl_context_merge() are used by HANDLER implementation which + needs to open table for new HANDLER independently of already + open HANDLERs and add this table/metadata lock to the set of + tables open/metadata locks for HANDLERs afterwards. */ void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) @@ -201,7 +201,7 @@ void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) /** - Restore state of meta-data locking context from backup. + Restore state of meta-data locking context from backup. */ void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) @@ -214,7 +214,7 @@ void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) /** - Merge meta-data locks from one context into another. + Merge meta-data locks from one context into another. */ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) @@ -247,32 +247,32 @@ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) /** - Initialize a lock request. + Initialize a lock request. - This is to be used for every lock request. + This is to be used for every lock request. - Note that initialization and allocation are split into two - calls. This is to allow flexible memory management of lock - requests. Normally a lock request is stored in statement memory - (e.g. is a member of struct TABLE_LIST), but we would also like - to allow allocation of lock requests in other memory roots, - for example in the grant subsystem, to lock privilege tables. + Note that initialization and allocation are split into two + calls. This is to allow flexible memory management of lock + requests. Normally a lock request is stored in statement memory + (e.g. is a member of struct TABLE_LIST), but we would also like + to allow allocation of lock requests in other memory roots, + for example in the grant subsystem, to lock privilege tables. - The MDL subsystem does not own or manage memory of lock requests. - Instead it assumes that the life time of every lock request (including - encompassed members db/name) encloses calls to mdl_request_add() - and mdl_request_remove() or mdl_request_remove_all(). + The MDL subsystem does not own or manage memory of lock requests. + Instead it assumes that the life time of every lock request (including + encompassed members db/name) encloses calls to mdl_request_add() + and mdl_request_remove() or mdl_request_remove_all(). - @param lock_req Pointer to an MDL_LOCK_REQUEST object to initialize - @param type Id of type of object to be locked - @param db Name of database to which the object belongs - @param name Name of of the object + @param lock_req Pointer to an MDL_LOCK_REQUEST object to initialize + @param type Id of type of object to be locked + @param db Name of database to which the object belongs + @param name Name of of the object - The initialized lock request will have MDL_SHARED type. + The initialized lock request will have MDL_SHARED type. - Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2 - Note that tables and views must have the same lock type, since - they share the same name space in the SQL standard. + Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2 + Note that tables and views must have the same lock type, since + they share the same name space in the SQL standard. */ void mdl_request_init(MDL_LOCK_REQUEST *lock_req, unsigned char type, @@ -285,21 +285,21 @@ void mdl_request_init(MDL_LOCK_REQUEST *lock_req, unsigned char type, /** - Allocate and initialize one lock request. + Allocate and initialize one lock request. - Same as mdl_init_lock(), but allocates the lock and the key buffer - on a memory root. Necessary to lock ad-hoc tables, e.g. - mysql.* tables of grant and data dictionary subsystems. + Same as mdl_init_lock(), but allocates the lock and the key buffer + on a memory root. Necessary to lock ad-hoc tables, e.g. + mysql.* tables of grant and data dictionary subsystems. - @param type Id of type of object to be locked - @param db Name of database to which object belongs - @param name Name of of object - @param root MEM_ROOT on which object should be allocated + @param type Id of type of object to be locked + @param db Name of database to which object belongs + @param name Name of of object + @param root MEM_ROOT on which object should be allocated - @note The allocated lock request will have MDL_SHARED type. + @note The allocated lock request will have MDL_SHARED type. - @retval 0 Error - @retval non-0 Pointer to an object representing a lock request + @retval 0 Error if out of memory + @retval non-0 Pointer to an object representing a lock request */ MDL_LOCK_REQUEST * @@ -319,19 +319,19 @@ mdl_request_alloc(unsigned char type, const char *db, /** - Add a lock request to the list of lock requests of the context. + Add a lock request to the list of lock requests of the context. - The procedure to acquire metadata locks is: - - allocate and initialize lock requests (mdl_request_alloc()) - - associate them with a context (mdl_request_add()) - - call mdl_acquire_shared_lock()/mdl_ticket_release() (maybe repeatedly). + The procedure to acquire metadata locks is: + - allocate and initialize lock requests (mdl_request_alloc()) + - associate them with a context (mdl_request_add()) + - call mdl_acquire_shared_lock()/mdl_ticket_release() (maybe repeatedly). - Associates a lock request with the given context. + Associates a lock request with the given context. - @param context The MDL context to associate the lock with. - There should be no more than one context per - connection, to avoid deadlocks. - @param lock_req The lock request to be added. + @param context The MDL context to associate the lock with. + There should be no more than one context per + connection, to avoid deadlocks. + @param lock_req The lock request to be added. */ void mdl_request_add(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) @@ -344,18 +344,18 @@ void mdl_request_add(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) /** - Remove a lock request from the list of lock requests. + Remove a lock request from the list of lock requests. - Disassociates a lock request from the given context. + Disassociates a lock request from the given context. - @param context The MDL context to remove the lock from. - @param lock_req The lock request to be removed. + @param context The MDL context to remove the lock from. + @param lock_req The lock request to be removed. - @pre The lock request being removed should correspond to a ticket that - was released or was not acquired. + @pre The lock request being removed should correspond to a ticket that + was released or was not acquired. - @note Resets lock request back to its initial state - (i.e. sets type to MDL_SHARED). + @note Resets lock request back to its initial state + (i.e. sets type to MDL_SHARED). */ void mdl_request_remove(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) @@ -370,12 +370,12 @@ void mdl_request_remove(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) /** - Clear all lock requests in the context. - Disassociates lock requests from the context. + Clear all lock requests in the context. + Disassociates lock requests from the context. - Also resets lock requests back to their initial state (i.e. MDL_SHARED). + Also resets lock requests back to their initial state (i.e. MDL_SHARED). - @param context Context to be cleared. + @param context Context to be cleared. */ void mdl_request_remove_all(MDL_CONTEXT *context) @@ -393,10 +393,10 @@ void mdl_request_remove_all(MDL_CONTEXT *context) /** - Auxiliary functions needed for creation/destruction of MDL_LOCK objects. + Auxiliary functions needed for creation/destruction of MDL_LOCK objects. - @todo This naive implementation should be replaced with one that saves - on memory allocation by reusing released objects. + @todo This naive implementation should be replaced with one that saves + on memory allocation by reusing released objects. */ static MDL_LOCK* alloc_lock_object(const MDL_KEY *mdl_key) @@ -412,11 +412,11 @@ static void free_lock_object(MDL_LOCK *lock) /** - Auxiliary functions needed for creation/destruction of MDL_LOCK_TICKET - objects. + Auxiliary functions needed for creation/destruction of MDL_LOCK_TICKET + objects. - @todo This naive implementation should be replaced with one that saves - on memory allocation by reusing released objects. + @todo This naive implementation should be replaced with one that saves + on memory allocation by reusing released objects. */ static MDL_LOCK_TICKET* alloc_ticket_object(MDL_CONTEXT *context) @@ -433,7 +433,7 @@ static void free_ticket_object(MDL_LOCK_TICKET *ticket) /** - Helper functions which simplifies writing various checks and asserts. + Helper functions which simplifies writing various checks and asserts. */ template @@ -444,16 +444,16 @@ static inline bool is_shared(T *lock_data) /** - Helper functions and macros to be used for killable waiting in metadata - locking subsystem. + Helper functions and macros to be used for killable waiting in metadata + locking subsystem. - @sa THD::enter_cond()/exit_cond()/killed. + @sa THD::enter_cond()/exit_cond()/killed. - @note We can't use THD::enter_cond()/exit_cond()/killed directly here - since this will make metadata subsystem dependant on THD class - and thus prevent us from writing unit tests for it. And usage of - wrapper functions to access THD::killed/enter_cond()/exit_cond() - will probably introduce too much overhead. + @note We can't use THD::enter_cond()/exit_cond()/killed directly here + since this will make metadata subsystem dependant on THD class + and thus prevent us from writing unit tests for it. And usage of + wrapper functions to access THD::killed/enter_cond()/exit_cond() + will probably introduce too much overhead. */ #define MDL_ENTER_COND(A, B) mdl_enter_cond(A, B, __func__, __FILE__, __LINE__) @@ -496,41 +496,41 @@ static inline void mdl_exit_cond(MDL_CONTEXT *context, /** - Check if request for the lock on particular object can be satisfied given - current state of the global metadata lock. + Check if request for the lock on particular object can be satisfied given + current state of the global metadata lock. - @note In other words, we're trying to check that the individual lock - request, implying a form of lock on the global metadata, is - compatible with the current state of the global metadata lock. + @note In other words, we're trying to check that the individual lock + request, implying a form of lock on the global metadata, is + compatible with the current state of the global metadata lock. - @param lock_req Request for lock on an individual object, implying a - certain kind of global metadata lock. + @param lock_req Request for lock on an individual object, implying a + certain kind of global metadata lock. - @retval TRUE - Lock request can be satisfied - @retval FALSE - There is some conflicting lock + @retval TRUE - Lock request can be satisfied + @retval FALSE - There is some conflicting lock - Here is a compatibility matrix defined by this function: + Here is a compatibility matrix defined by this function: - | | Satisfied or pending requests - | | for global metadata lock - ----------------+-------------+-------------------------------------------- - Type of request | Correspond. | - for indiv. lock | global lock | Active-S Pending-S Active-IS(**) Active-IX - ----------------+-------------+-------------------------------------------- - S, high-prio S | IS | + + + + - upgradable S | IX | - - + + - X | IX | - - + + - S upgraded to X | IX (*) | 0 + + + + | | Satisfied or pending requests + | | for global metadata lock + ----------------+-------------+-------------------------------------------- + Type of request | Correspond. | + for indiv. lock | global lock | Active-S Pending-S Active-IS(**) Active-IX + ----------------+-------------+-------------------------------------------- + S, high-prio S | IS | + + + + + upgradable S | IX | - - + + + X | IX | - - + + + S upgraded to X | IX (*) | 0 + + + - Here: "+" -- means that request can be satisfied - "-" -- means that request can't be satisfied and should wait - "0" -- means impossible situation which will trigger assert + Here: "+" -- means that request can be satisfied + "-" -- means that request can't be satisfied and should wait + "0" -- means impossible situation which will trigger assert - (*) Since for upgradable shared locks we always take intention exclusive - global lock at the same time when obtaining the shared lock, there - is no need to obtain such lock during the upgrade itself. - (**) Since intention shared global locks are compatible with all other - type of locks we don't even have any accounting for them. + (*) Since for upgradable shared locks we always take intention exclusive + global lock at the same time when obtaining the shared lock, there + is no need to obtain such lock during the upgrade itself. + (**) Since intention shared global locks are compatible with all other + type of locks we don't even have any accounting for them. */ static bool can_grant_global_lock(enum_mdl_type type, bool is_upgrade) @@ -588,33 +588,33 @@ static bool can_grant_global_lock(enum_mdl_type type, bool is_upgrade) /** - Check if request for the lock can be satisfied given current state of lock. + Check if request for the lock can be satisfied given current state of lock. - @param lock Lock. - @param lock_req Request for lock. + @param lock Lock. + @param lock_req Request for lock. - @retval TRUE Lock request can be satisfied - @retval FALSE There is some conflicting lock. + @retval TRUE Lock request can be satisfied + @retval FALSE There is some conflicting lock. - This function defines the following compatibility matrix for metadata locks: + This function defines the following compatibility matrix for metadata locks: - | Satisfied or pending requests which we have in MDL_LOCK - ----------------+--------------------------------------------------------- - Current request | Active-S Pending-X Active-X Act-S-pend-upgrade-to-X - ----------------+--------------------------------------------------------- - S, upgradable S | + - - (*) - - High-prio S | + + - + - X | - + - - - S upgraded to X | - (**) + 0 0 + | Satisfied or pending requests which we have in MDL_LOCK + ----------------+--------------------------------------------------------- + Current request | Active-S Pending-X Active-X Act-S-pend-upgrade-to-X + ----------------+--------------------------------------------------------- + S, upgradable S | + - - (*) - + High-prio S | + + - + + X | - + - - + S upgraded to X | - (**) + 0 0 - Here: "+" -- means that request can be satisfied - "-" -- means that request can't be satisfied and should wait - "0" -- means impossible situation which will trigger assert + Here: "+" -- means that request can be satisfied + "-" -- means that request can't be satisfied and should wait + "0" -- means impossible situation which will trigger assert - (*) Unless active exclusive lock belongs to the same context as shared - lock being requested. - (**) Unless all active shared locks belong to the same context as one - being upgraded. + (*) Unless active exclusive lock belongs to the same context as shared + lock being requested. + (**) Unless all active shared locks belong to the same context as one + being upgraded. */ static bool can_grant_lock(MDL_CONTEXT *ctx, MDL_LOCK *lock, @@ -683,12 +683,12 @@ static bool can_grant_lock(MDL_CONTEXT *ctx, MDL_LOCK *lock, /** - Check whether the context already holds a compatible lock ticket - on a object. Only shared locks can be recursive. + Check whether the context already holds a compatible lock ticket + on a object. Only shared locks can be recursive. - @param lock_req Lock request object for lock to be acquired + @param lock_req Lock request object for lock to be acquired - @return A pointer to the lock ticket for the object or NULL otherwise. + @return A pointer to the lock ticket for the object or NULL otherwise. */ static MDL_LOCK_TICKET * @@ -711,28 +711,28 @@ mdl_context_find_ticket(MDL_CONTEXT *ctx, MDL_LOCK_REQUEST *lock_req) /** - Try to acquire one shared lock. + Try to acquire one shared lock. - Unlike exclusive locks, shared locks are acquired one by - one. This is interface is chosen to simplify introduction of - the new locking API to the system. mdl_acquire_shared_lock() - is currently used from open_table(), and there we have only one - table to work with. + Unlike exclusive locks, shared locks are acquired one by + one. This is interface is chosen to simplify introduction of + the new locking API to the system. mdl_acquire_shared_lock() + is currently used from open_table(), and there we have only one + table to work with. - In future we may consider allocating multiple shared locks at once. + In future we may consider allocating multiple shared locks at once. - This function must be called after the lock is added to a context. + This function must be called after the lock is added to a context. - @param context [in] Context containing request for lock - @param lock_req [in] Lock request object for lock to be acquired - @param retry [out] Indicates that conflicting lock exists and another - attempt should be made after releasing all current - locks and waiting for conflicting lock go away - (using mdl_wait_for_locks()). + @param context [in] Context containing request for lock + @param lock_req [in] Lock request object for lock to be acquired + @param retry [out] Indicates that conflicting lock exists and another + attempt should be made after releasing all current + locks and waiting for conflicting lock go away + (using mdl_wait_for_locks()). - @retval FALSE Success. - @retval TRUE Failure. Either error occured or conflicting lock exists. - In the latter case "retry" parameter is set to TRUE. + @retval FALSE Success. + @retval TRUE Failure. Either error occured or conflicting lock exists. + In the latter case "retry" parameter is set to TRUE. */ bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req, @@ -824,13 +824,13 @@ static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket); /** - Notify a thread holding a shared metadata lock of a pending exclusive lock. + Notify a thread holding a shared metadata lock of a pending exclusive lock. - @param thd Current thread context - @param conf_lock_ticket Conflicting metadata lock + @param thd Current thread context + @param conf_lock_ticket Conflicting metadata lock - @retval TRUE A thread was woken up - @retval FALSE Lock is not a shared one or no thread was woken up + @retval TRUE A thread was woken up + @retval FALSE Lock is not a shared one or no thread was woken up */ static bool notify_shared_lock(THD *thd, MDL_LOCK_TICKET *conf_lock_ticket) @@ -843,18 +843,18 @@ static bool notify_shared_lock(THD *thd, MDL_LOCK_TICKET *conf_lock_ticket) /** - Acquire exclusive locks. The context must contain the list of - locks to be acquired. There must be no granted locks in the - context. + Acquire exclusive locks. The context must contain the list of + locks to be acquired. There must be no granted locks in the + context. - This is a replacement of lock_table_names(). It is used in - RENAME, DROP and other DDL SQL statements. + This is a replacement of lock_table_names(). It is used in + RENAME, DROP and other DDL SQL statements. - @param context A context containing requests for exclusive locks - The context may not have other lock requests. + @param context A context containing requests for exclusive locks + The context may not have other lock requests. - @retval FALSE Success - @retval TRUE Failure + @retval FALSE Success + @retval TRUE Failure */ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) @@ -1009,25 +1009,25 @@ err: /** - Upgrade a shared metadata lock to exclusive. + Upgrade a shared metadata lock to exclusive. - Used in ALTER TABLE, when a copy of the table with the - new definition has been constructed. + Used in ALTER TABLE, when a copy of the table with the + new definition has been constructed. - @param context Context to which shared lock belongs - @param ticket Ticket for shared lock to be upgraded + @param context Context to which shared lock belongs + @param ticket Ticket for shared lock to be upgraded - @note In case of failure to upgrade lock (e.g. because upgrader - was killed) leaves lock in its original state (locked in - shared mode). + @note In case of failure to upgrade lock (e.g. because upgrader + was killed) leaves lock in its original state (locked in + shared mode). - @note There can be only one upgrader for a lock or we will have deadlock. - This invariant is ensured by code outside of metadata subsystem usually - by obtaining some sort of exclusive table-level lock (e.g. TL_WRITE, - TL_WRITE_ALLOW_READ) before performing upgrade of metadata lock. + @note There can be only one upgrader for a lock or we will have deadlock. + This invariant is ensured by code outside of metadata subsystem usually + by obtaining some sort of exclusive table-level lock (e.g. TL_WRITE, + TL_WRITE_ALLOW_READ) before performing upgrade of metadata lock. - @retval FALSE Success - @retval TRUE Failure (thread was killed) + @retval FALSE Success + @retval TRUE Failure (thread was killed) */ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, @@ -1110,27 +1110,27 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, /** - Try to acquire an exclusive lock on the object if there are - no conflicting locks. + Try to acquire an exclusive lock on the object if there are + no conflicting locks. - Similar to the previous function, but returns - immediately without any side effect if encounters a lock - conflict. Otherwise takes the lock. + Similar to the previous function, but returns + immediately without any side effect if encounters a lock + conflict. Otherwise takes the lock. - This function is used in CREATE TABLE ... LIKE to acquire a lock - on the table to be created. In this statement we don't want to - block and wait for the lock if the table already exists. + This function is used in CREATE TABLE ... LIKE to acquire a lock + on the table to be created. In this statement we don't want to + block and wait for the lock if the table already exists. - @param context [in] The context containing the lock request - @param lock_req [in] The lock request - @param conflict [out] Indicates that conflicting lock exists + @param context [in] The context containing the lock request + @param lock_req [in] The lock request + @param conflict [out] Indicates that conflicting lock exists - @retval TRUE Failure either conflicting lock exists or some error - occured (probably OOM). - @retval FALSE Success, lock was acquired. + @retval TRUE Failure either conflicting lock exists or some error + occured (probably OOM). + @retval FALSE Success, lock was acquired. - FIXME: Compared to lock_table_name_if_not_cached() - it gives sligthly more false negatives. + FIXME: Compared to lock_table_name_if_not_cached() + it gives sligthly more false negatives. */ bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, @@ -1184,15 +1184,15 @@ err: /** - Acquire global shared metadata lock. + Acquire global shared metadata lock. - Holding this lock will block all requests for exclusive locks - and shared locks which can be potentially upgraded to exclusive. + Holding this lock will block all requests for exclusive locks + and shared locks which can be potentially upgraded to exclusive. - @param context Current metadata locking context. + @param context Current metadata locking context. - @retval FALSE Success -- the lock was granted. - @retval TRUE Failure -- our thread was killed. + @retval FALSE Success -- the lock was granted. + @retval TRUE Failure -- our thread was killed. */ bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) @@ -1227,18 +1227,18 @@ bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) /** - Wait until there will be no locks that conflict with lock requests - in the context. + Wait until there will be no locks that conflict with lock requests + in the context. - This is a part of the locking protocol and must be used by the - acquirer of shared locks after a back-off. + This is a part of the locking protocol and must be used by the + acquirer of shared locks after a back-off. - Does not acquire the locks! + Does not acquire the locks! - @param context Context with which lock requests are associated. + @param context Context with which lock requests are associated. - @retval FALSE Success. One can try to obtain metadata locks. - @retval TRUE Failure (thread was killed) + @retval FALSE Success. One can try to obtain metadata locks. + @retval TRUE Failure (thread was killed) */ bool mdl_wait_for_locks(MDL_CONTEXT *context) @@ -1297,8 +1297,8 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) /** - Auxiliary function which allows to release particular lock - ownership of which is represented by a lock ticket object. + Auxiliary function which allows to release particular lock + ownership of which is represented by a lock ticket object. */ static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) @@ -1347,15 +1347,15 @@ static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) /** - Release all locks associated with the context, but leave them - in the context as lock requests. + Release all locks associated with the context, but leave them + in the context as lock requests. - This function is used to back off in case of a lock conflict. - It is also used to release shared locks in the end of an SQL - statement. + This function is used to back off in case of a lock conflict. + It is also used to release shared locks in the end of an SQL + statement. - @param context The context with which the locks to be released - are associated. + @param context The context with which the locks to be released + are associated. */ void mdl_ticket_release_all(MDL_CONTEXT *context) @@ -1395,11 +1395,10 @@ void mdl_ticket_release_all(MDL_CONTEXT *context) /** - Release a lock. - - @param context Context containing lock in question - @param ticket Lock to be released + Release a lock. + @param context Context containing lock in question + @param ticket Lock to be released */ void mdl_ticket_release(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) @@ -1415,12 +1414,12 @@ void mdl_ticket_release(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) /** - Release all locks in the context which correspond to the same name/ - object as this lock request, remove lock requests from the context. + Release all locks in the context which correspond to the same name/ + object as this lock request, remove lock requests from the context. - @param context Context containing locks in question - @param ticket One of the locks for the name/object for which all - locks should be released. + @param context Context containing locks in question + @param ticket One of the locks for the name/object for which all + locks should be released. */ void mdl_ticket_release_all_for_name(MDL_CONTEXT *context, @@ -1461,10 +1460,10 @@ void mdl_ticket_release_all_for_name(MDL_CONTEXT *context, /** - Downgrade an exclusive lock to shared metadata lock. + Downgrade an exclusive lock to shared metadata lock. - @param context A context to which exclusive lock belongs - @param ticket Ticket for exclusive lock to be downgraded + @param context A context to which exclusive lock belongs + @param ticket Ticket for exclusive lock to be downgraded */ void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, @@ -1490,9 +1489,9 @@ void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, /** - Release global shared metadata lock. + Release global shared metadata lock. - @param context Current context + @param context Current context */ void mdl_release_global_shared_lock(MDL_CONTEXT *context) @@ -1509,16 +1508,16 @@ void mdl_release_global_shared_lock(MDL_CONTEXT *context) /** - Auxiliary function which allows to check if we have exclusive lock - on the object. + Auxiliary function which allows to check if we have exclusive lock + on the object. - @param context Current context - @param type Id of object type - @param db Name of the database - @param name Name of the object + @param context Current context + @param type Id of object type + @param db Name of the database + @param name Name of the object - @return TRUE if current context contains exclusive lock for the object, - FALSE otherwise. + @return TRUE if current context contains exclusive lock for the object, + FALSE otherwise. */ bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, unsigned char type, @@ -1542,16 +1541,16 @@ bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, unsigned char type, /** - Auxiliary function which allows to check if we have some kind of lock on - a object. + Auxiliary function which allows to check if we have some kind of lock on + a object. - @param context Current context - @param type Id of object type - @param db Name of the database - @param name Name of the object + @param context Current context + @param type Id of object type + @param db Name of the database + @param name Name of the object - @return TRUE if current context contains satisfied lock for the object, - FALSE otherwise. + @return TRUE if current context contains satisfied lock for the object, + FALSE otherwise. */ bool mdl_is_lock_owner(MDL_CONTEXT *context, unsigned char type, @@ -1574,12 +1573,12 @@ bool mdl_is_lock_owner(MDL_CONTEXT *context, unsigned char type, /** - Check if we have any pending exclusive locks which conflict with - existing shared lock. + Check if we have any pending exclusive locks which conflict with + existing shared lock. - @param ticket Shared lock against which check should be performed. + @param ticket Shared lock against which check should be performed. - @return TRUE if there are any conflicting locks, FALSE otherwise. + @return TRUE if there are any conflicting locks, FALSE otherwise. */ bool mdl_has_pending_conflicting_lock(MDL_LOCK_TICKET *ticket) @@ -1597,31 +1596,31 @@ bool mdl_has_pending_conflicting_lock(MDL_LOCK_TICKET *ticket) /** - Associate pointer to an opaque object with a lock. + Associate pointer to an opaque object with a lock. - @param ticket Lock ticket for the lock with which the object - should be associated. - @param cached_object Pointer to the object - @param release_hook Cleanup function to be called when MDL subsystem - decides to remove lock or associate another object. + @param ticket Lock ticket for the lock with which the object + should be associated. + @param cached_object Pointer to the object + @param release_hook Cleanup function to be called when MDL subsystem + decides to remove lock or associate another object. - This is used to cache a pointer to TABLE_SHARE in the lock - structure. Such caching can save one acquisition of LOCK_open - and one table definition cache lookup for every table. + This is used to cache a pointer to TABLE_SHARE in the lock + structure. Such caching can save one acquisition of LOCK_open + and one table definition cache lookup for every table. - Since the pointer may be stored only inside an acquired lock, - the caching is only effective when there is more than one lock - granted on a given table. + Since the pointer may be stored only inside an acquired lock, + the caching is only effective when there is more than one lock + granted on a given table. - This function has the following usage pattern: - - try to acquire an MDL lock - - when done, call for mdl_get_cached_object(). If it returns NULL, our - thread has the only lock on this table. - - look up TABLE_SHARE in the table definition cache - - call mdl_set_cache_object() to assign the share to the opaque pointer. + This function has the following usage pattern: + - try to acquire an MDL lock + - when done, call for mdl_get_cached_object(). If it returns NULL, our + thread has the only lock on this table. + - look up TABLE_SHARE in the table definition cache + - call mdl_set_cache_object() to assign the share to the opaque pointer. - The release hook is invoked when the last shared metadata - lock on this name is released. + The release hook is invoked when the last shared metadata + lock on this name is released. */ void mdl_set_cached_object(MDL_LOCK_TICKET *ticket, void *cached_object, @@ -1648,11 +1647,11 @@ void mdl_set_cached_object(MDL_LOCK_TICKET *ticket, void *cached_object, /** - Get a pointer to an opaque object that associated with the lock. + Get a pointer to an opaque object that associated with the lock. - @param ticket Lock ticket for the lock which the object is associated to. + @param ticket Lock ticket for the lock which the object is associated to. - @return Pointer to an opaque object associated with the lock. + @return Pointer to an opaque object associated with the lock. */ void* mdl_get_cached_object(MDL_LOCK_TICKET *ticket) diff --git a/sql/mdl.h b/sql/mdl.h index 103ab8130ba..b5d05b06144 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -29,15 +29,15 @@ struct MDL_LOCK; struct MDL_CONTEXT; /** - Type of metadata lock request. + Type of metadata lock request. - - High-priority shared locks differ from ordinary shared locks by - that they ignore pending requests for exclusive locks. - - Upgradable shared locks can be later upgraded to exclusive - (because of that their acquisition involves implicit - acquisition of global intention-exclusive lock). + - High-priority shared locks differ from ordinary shared locks by + that they ignore pending requests for exclusive locks. + - Upgradable shared locks can be later upgraded to exclusive + (because of that their acquisition involves implicit + acquisition of global intention-exclusive lock). - @see Comments for can_grant_lock() and can_grant_global_lock() for details. + @see Comments for can_grant_lock() and can_grant_global_lock() for details. */ enum enum_mdl_type {MDL_SHARED=0, MDL_SHARED_HIGH_PRIO, @@ -54,12 +54,12 @@ enum enum_mdl_state { MDL_PENDING, MDL_ACQUIRED }; /** - Metadata lock object key. + Metadata lock object key. - A lock is requested or granted based on a fully qualified name and type. - E.g. They key for a table consists of <0 (=table)>++
. - Elsewhere in the comments this triple will be referred to simply as "key" - or "name". + A lock is requested or granted based on a fully qualified name and type. + E.g. They key for a table consists of <0 (=table)>++
. + Elsewhere in the comments this triple will be referred to simply as "key" + or "name". */ class MDL_KEY @@ -110,8 +110,8 @@ private: /** - Hook class which via its methods specifies which members - of T should be used for participating in MDL lists. + Hook class which via its methods specifies which members + of T should be used for participating in MDL lists. */ template @@ -124,12 +124,12 @@ struct I_P_List_adapter /** - A pending metadata lock request. - A pending lock request or a granted metadata lock share the same abstract - base but are presented individually because they have different allocation - sites and hence different lifetimes. The allocation of lock requests is - controlled from outside of the MDL subsystem, while allocation of granted - locks (tickets) is controlled within the MDL subsystem. + A pending metadata lock request. + A pending lock request or a granted metadata lock share the same abstract + base but are presented individually because they have different allocation + sites and hence different lifetimes. The allocation of lock requests is + controlled from outside of the MDL subsystem, while allocation of granted + locks (tickets) is controlled within the MDL subsystem. */ struct MDL_LOCK_REQUEST @@ -138,7 +138,7 @@ struct MDL_LOCK_REQUEST enum enum_mdl_type type; /** - Pointers for participating in the list of lock requests for this context. + Pointers for participating in the list of lock requests for this context. */ MDL_LOCK_REQUEST *next_in_context; MDL_LOCK_REQUEST **prev_in_context; @@ -154,12 +154,12 @@ struct MDL_LOCK_REQUEST /** - A granted metadata lock. + A granted metadata lock. - @warning MDL_LOCK_TICKET members are private to the MDL subsystem. + @warning MDL_LOCK_TICKET members are private to the MDL subsystem. - @note Multiple shared locks on a same object are represented by a - single ticket. The same does not apply for other lock types. + @note Multiple shared locks on a same object are represented by a + single ticket. The same does not apply for other lock types. */ struct MDL_LOCK_TICKET @@ -170,13 +170,13 @@ struct MDL_LOCK_TICKET enum enum_mdl_state state; /** - Pointers for participating in the list of lock requests for this context. + Pointers for participating in the list of lock requests for this context. */ MDL_LOCK_TICKET *next_in_context; MDL_LOCK_TICKET **prev_in_context; /** - Pointers for participating in the list of satisfied/pending requests - for the lock. + Pointers for participating in the list of satisfied/pending requests + for the lock. */ MDL_LOCK_TICKET *next_in_lock; MDL_LOCK_TICKET **prev_in_lock; @@ -189,8 +189,8 @@ struct MDL_LOCK_TICKET /** - Context of the owner of metadata locks. I.e. each server - connection has such a context. + Context of the owner of metadata locks. I.e. each server + connection has such a context. */ struct MDL_CONTEXT @@ -286,7 +286,7 @@ void mdl_rollback_to_savepoint(MDL_CONTEXT *ctx, MDL_LOCK_TICKET *mdl_savepoint); /** - Get iterator for walking through all lock requests in the context. + Get iterator for walking through all lock requests in the context. */ inline MDL_CONTEXT::Request_iterator From a9013f8fbae943dc4d2b5bbd0f8005f0edb3cace Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 4 Dec 2009 02:52:05 +0300 Subject: [PATCH 067/212] Backport of: ---------------------------------------------------------- revno: 2617.23.20 committer: Konstantin Osipov branch nick: mysql-6.0-runtime timestamp: Wed 2009-03-04 16:31:31 +0300 message: WL#4284 "Transactional DDL locking" Review comments: "Objectify" the MDL API. MDL_request and MDL_context still need manual construction and destruction, since they are used in environment that is averse to constructors/destructors. sql/mdl.cc: Improve comments. Add asserts to backup()/restore_from_backup()/merge() methods. Fix an order bug in the error path of mdl_acquire_exclusive_locks(): we used to first free a ticket object, and only then exclude it from the list of tickets. --- sql/ha_ndbcluster_binlog.cc | 6 +- sql/lock.cc | 28 +- sql/log_event.cc | 9 +- sql/mdl.cc | 916 +++++++++++++++--------------- sql/mdl.h | 274 +++++---- sql/rpl_rli.cc | 2 +- sql/sp_head.cc | 16 +- sql/sql_base.cc | 128 ++--- sql/sql_class.cc | 8 +- sql/sql_class.h | 10 +- sql/sql_delete.cc | 26 +- sql/sql_handler.cc | 30 +- sql/sql_parse.cc | 6 +- sql/sql_plist.h | 1 + sql/sql_show.cc | 27 +- sql/sql_table.cc | 117 ++-- sql/sql_trigger.cc | 8 +- sql/table.cc | 4 +- sql/table.h | 8 +- storage/myisammrg/ha_myisammrg.cc | 2 +- 20 files changed, 829 insertions(+), 797 deletions(-) diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index aa1c50eaf5b..08abb88e768 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -140,7 +140,7 @@ static Uint64 *p_latest_trans_gci= 0; */ static TABLE *ndb_binlog_index= 0; static TABLE_LIST binlog_tables; -static MDL_LOCK_REQUEST binlog_mdl_lock_request; +static MDL_request binlog_mdl_request; /* Helper functions @@ -2342,8 +2342,8 @@ static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index) tables->alias= tables->table_name= reptable; tables->lock_type= TL_WRITE; thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE; - mdl_request_init(&binlog_mdl_lock_request, 0, tables->db, tables->table_name); - tables->mdl_lock_request= &binlog_mdl_lock_request; + binlog_mdl_request.init(0, tables->db, tables->table_name); + tables->mdl_request= &binlog_mdl_request; tables->required_type= FRMTYPE_TABLE; uint counter; thd->clear_error(); diff --git a/sql/lock.cc b/sql/lock.cc index 40bceae4a99..170007d8f66 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -944,7 +944,7 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, @note This function assumes that no metadata locks were acquired before calling it. Also it cannot be called while holding LOCK_open mutex. Both these invariants are enforced by asserts - in mdl_acquire_exclusive_locks() functions. + in MDL_context::acquire_exclusive_locks(). @retval FALSE Success. @retval TRUE Failure (OOM or thread was killed). @@ -953,24 +953,24 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, bool lock_table_names(THD *thd, TABLE_LIST *table_list) { TABLE_LIST *lock_table; - MDL_LOCK_REQUEST *mdl_lock_req; + MDL_request *mdl_request; for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) { - mdl_lock_req= mdl_request_alloc(0, lock_table->db, lock_table->table_name, - thd->mem_root); - if (!mdl_lock_req) + mdl_request= MDL_request::create(0, lock_table->db, lock_table->table_name, + thd->mem_root); + if (!mdl_request) goto end; - mdl_request_set_type(mdl_lock_req, MDL_EXCLUSIVE); - mdl_request_add(&thd->mdl_context, mdl_lock_req); - lock_table->mdl_lock_request= mdl_lock_req; + mdl_request->set_type(MDL_EXCLUSIVE); + thd->mdl_context.add_request(mdl_request); + lock_table->mdl_request= mdl_request; } - if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + if (thd->mdl_context.acquire_exclusive_locks()) goto end; return 0; end: - mdl_request_remove_all(&thd->mdl_context); + thd->mdl_context.remove_all_requests(); return 1; } @@ -986,8 +986,8 @@ end: void unlock_table_names(THD *thd) { DBUG_ENTER("unlock_table_names"); - mdl_ticket_release_all(&thd->mdl_context); - mdl_request_remove_all(&thd->mdl_context); + thd->mdl_context.release_all_locks(); + thd->mdl_context.remove_all_requests(); DBUG_VOID_RETURN; } @@ -1147,7 +1147,7 @@ bool lock_global_read_lock(THD *thd) redundancy between metadata locks, global read lock and DDL blocker (see WL#4399 and WL#4400). */ - if (mdl_acquire_global_shared_lock(&thd->mdl_context)) + if (thd->mdl_context.acquire_global_shared_lock()) { /* Our thread was killed -- return back to initial state. */ pthread_mutex_lock(&LOCK_global_read_lock); @@ -1181,7 +1181,7 @@ void unlock_global_read_lock(THD *thd) ("global_read_lock: %u global_read_lock_blocks_commit: %u", global_read_lock, global_read_lock_blocks_commit)); - mdl_release_global_shared_lock(&thd->mdl_context); + thd->mdl_context.release_global_shared_lock(); pthread_mutex_lock(&LOCK_global_read_lock); tmp= --global_read_lock; diff --git a/sql/log_event.cc b/sql/log_event.cc index 9f4369b901e..e41bb690e8a 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -8060,7 +8060,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) { RPL_TABLE_LIST *table_list; char *db_mem, *tname_mem; - MDL_LOCK_REQUEST *mdl_lock_request; + MDL_request *mdl_request; size_t dummy_len; void *memory; DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)"); @@ -8075,7 +8075,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) &table_list, (uint) sizeof(RPL_TABLE_LIST), &db_mem, (uint) NAME_LEN + 1, &tname_mem, (uint) NAME_LEN + 1, - &mdl_lock_request, sizeof(MDL_LOCK_REQUEST), + &mdl_request, sizeof(MDL_request), NullS))) DBUG_RETURN(HA_ERR_OUT_OF_MEM); @@ -8088,9 +8088,8 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) table_list->updating= 1; strmov(table_list->db, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len)); strmov(table_list->table_name, m_tblnam); - mdl_request_init(mdl_lock_request, 0, table_list->db, - table_list->table_name); - table_list->mdl_lock_request= mdl_lock_request; + mdl_request->init(0, table_list->db, table_list->table_name); + table_list->mdl_request= mdl_request; int error= 0; diff --git a/sql/mdl.cc b/sql/mdl.cc index e3f285f3a55..101d90d7e42 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -14,26 +14,27 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - #include "mdl.h" #include "debug_sync.h" #include #include +static bool mdl_initialized= 0; /** The lock context. Created internally for an acquired lock. - For a given name, there exists only one MDL_LOCK instance, + For a given name, there exists only one MDL_lock instance, and it exists only when the lock has been granted. Can be seen as an MDL subsystem's version of TABLE_SHARE. */ -struct MDL_LOCK +class MDL_lock { - typedef I_P_List > +public: + typedef I_P_List > Ticket_list; typedef Ticket_list::Iterator Ticket_iterator; @@ -45,60 +46,82 @@ struct MDL_LOCK EXCLUSIVE, } type; /** The key of the object (data) being protected. */ - MDL_KEY key; + MDL_key key; /** List of granted tickets for this lock. */ Ticket_list granted; /** There can be several upgraders and active exclusive - belonging to the same context. + locks belonging to the same context. E.g. + in case of RENAME t1 to t2, t2 to t3, we attempt to + exclusively lock t2 twice. */ Ticket_list waiting; void *cached_object; mdl_cached_object_release_hook cached_object_release_hook; - MDL_LOCK(const MDL_KEY *mdl_key) - : type(SHARED), cached_object(0), cached_object_release_hook(0) - { - key.mdl_key_init(mdl_key); - granted.empty(); - waiting.empty(); - } - - bool is_empty() + bool is_empty() const { return (granted.is_empty() && waiting.is_empty()); } + + bool can_grant_lock(const MDL_context *requestor_ctx, + enum_mdl_type type, bool is_upgrade); + + inline static MDL_lock *create(const MDL_key *key); + inline static void destroy(MDL_lock *lock); +private: + MDL_lock(const MDL_key *key_arg) + : type(SHARED), + key(key_arg), + cached_object(NULL), + cached_object_release_hook(NULL) + { + } }; -pthread_mutex_t LOCK_mdl; -pthread_cond_t COND_mdl; -HASH mdl_locks; +static pthread_mutex_t LOCK_mdl; +static pthread_cond_t COND_mdl; +static HASH mdl_locks; /** - Structure implementing global metadata lock. The only types - of locks which are supported at the moment are shared and - intention exclusive locks. Note that the latter type of global - lock acquired automatically when one tries to acquire exclusive - or shared upgradable lock on particular object. + An implementation of the global metadata lock. The only + locking modes which are supported at the moment are SHARED and + INTENTION EXCLUSIVE. Note, that SHARED global metadata lock + is acquired automatically when one tries to acquire an EXCLUSIVE + or UPGRADABLE SHARED metadata lock on an individual object. */ -struct MDL_GLOBAL_LOCK +class MDL_global_lock { +public: uint waiting_shared; uint active_shared; uint active_intention_exclusive; -} global_lock; + + bool is_empty() const + { + return (waiting_shared == 0 && active_shared == 0 && + active_intention_exclusive == 0); + } + bool is_lock_type_compatible(enum_mdl_type type, bool is_upgrade) const; +}; -extern "C" uchar * +static MDL_global_lock global_lock; + + +extern "C" +{ +static uchar * mdl_locks_key(const uchar *record, size_t *length, my_bool not_used __attribute__((unused))) { - MDL_LOCK *entry=(MDL_LOCK*) record; - *length= entry->key.length(); - return (uchar*) entry->key.ptr(); + MDL_lock *lock=(MDL_lock*) record; + *length= lock->key.length(); + return (uchar*) lock->key.ptr(); } +} /* extern "C" */ /** @@ -116,17 +139,20 @@ mdl_locks_key(const uchar *record, size_t *length, statement, the design capitalizes on that to later save on look ups in the table definition cache. This leads to reduced contention overall and on LOCK_open in particular. - Please see the description of mdl_acquire_shared_lock() for details. + Please see the description of MDL_context::acquire_shared_lock() + for details. */ void mdl_init() { + DBUG_ASSERT(! mdl_initialized); + mdl_initialized= TRUE; pthread_mutex_init(&LOCK_mdl, NULL); pthread_cond_init(&COND_mdl, NULL); my_hash_init(&mdl_locks, &my_charset_bin, 16 /* FIXME */, 0, 0, mdl_locks_key, 0, 0); - global_lock.waiting_shared= global_lock.active_shared= 0; - global_lock.active_intention_exclusive= 0; + /* The global lock is zero-initialized by the loader. */ + DBUG_ASSERT(global_lock.is_empty()); } @@ -139,10 +165,15 @@ void mdl_init() void mdl_destroy() { - DBUG_ASSERT(!mdl_locks.records); - pthread_mutex_destroy(&LOCK_mdl); - pthread_cond_destroy(&COND_mdl); - my_hash_free(&mdl_locks); + if (mdl_initialized) + { + mdl_initialized= FALSE; + DBUG_ASSERT(!mdl_locks.records); + DBUG_ASSERT(global_lock.is_empty()); + pthread_mutex_destroy(&LOCK_mdl); + pthread_cond_destroy(&COND_mdl); + my_hash_free(&mdl_locks); + } } @@ -152,12 +183,19 @@ void mdl_destroy() This is to be called when a new server connection is created. */ -void mdl_context_init(MDL_CONTEXT *context, THD *thd) +void MDL_context::init(THD *thd_arg) { - context->requests.empty(); - context->tickets.empty(); - context->thd= thd; - context->has_global_shared_lock= FALSE; + m_has_global_shared_lock= FALSE; + m_thd= thd_arg; + /* + FIXME: In reset_n_backup_open_tables_state, + we abuse "init" as a reset, i.e. call it on an already + constructed non-empty object. This is why we can't + rely here on the default constructors of I_P_List + to empty the list. + */ + m_requests.empty(); + m_tickets.empty(); } @@ -173,11 +211,11 @@ void mdl_context_init(MDL_CONTEXT *context, THD *thd) that all tables are closed before a connection is destroyed. */ -void mdl_context_destroy(MDL_CONTEXT *context) +void MDL_context::destroy() { - DBUG_ASSERT(context->requests.is_empty()); - DBUG_ASSERT(context->tickets.is_empty()); - DBUG_ASSERT(!context->has_global_shared_lock); + DBUG_ASSERT(m_requests.is_empty()); + DBUG_ASSERT(m_tickets.is_empty()); + DBUG_ASSERT(! m_has_global_shared_lock); } @@ -191,12 +229,22 @@ void mdl_context_destroy(MDL_CONTEXT *context) tables open/metadata locks for HANDLERs afterwards. */ -void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) +void MDL_context::backup_and_reset(MDL_context *backup) { - backup->requests.empty(); - backup->tickets.empty(); - ctx->requests.swap(backup->requests); - ctx->tickets.swap(backup->tickets); + DBUG_ASSERT(backup->m_requests.is_empty()); + DBUG_ASSERT(backup->m_tickets.is_empty()); + + m_requests.swap(backup->m_requests); + m_tickets.swap(backup->m_tickets); + + backup->m_has_global_shared_lock= m_has_global_shared_lock; + /* + When the main context is swapped out, one can not take + the global shared lock, and one can not rely on it: + the functionality in this mode is reduced, since it exists as + a temporary hack to support ad-hoc opening of system tables. + */ + m_has_global_shared_lock= FALSE; } @@ -204,12 +252,15 @@ void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) Restore state of meta-data locking context from backup. */ -void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) +void MDL_context::restore_from_backup(MDL_context *backup) { - DBUG_ASSERT(ctx->requests.is_empty()); - DBUG_ASSERT(ctx->tickets.is_empty()); - ctx->requests.swap(backup->requests); - ctx->tickets.swap(backup->tickets); + DBUG_ASSERT(m_requests.is_empty()); + DBUG_ASSERT(m_tickets.is_empty()); + DBUG_ASSERT(m_has_global_shared_lock == FALSE); + + m_requests.swap(backup->m_requests); + m_tickets.swap(backup->m_tickets); + m_has_global_shared_lock= backup->m_has_global_shared_lock; } @@ -217,32 +268,37 @@ void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup) Merge meta-data locks from one context into another. */ -void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) +void MDL_context::merge(MDL_context *src) { - MDL_LOCK_TICKET *ticket; - MDL_LOCK_REQUEST *lock_req; + MDL_ticket *ticket; + MDL_request *mdl_request; - DBUG_ASSERT(dst->thd == src->thd); + DBUG_ASSERT(m_thd == src->m_thd); - if (!src->requests.is_empty()) + if (!src->m_requests.is_empty()) { - MDL_CONTEXT::Request_iterator it(src->requests); - while ((lock_req= it++)) - dst->requests.push_front(lock_req); - src->requests.empty(); + Request_iterator it(src->m_requests); + while ((mdl_request= it++)) + m_requests.push_front(mdl_request); + src->m_requests.empty(); } - if (!src->tickets.is_empty()) + if (!src->m_tickets.is_empty()) { - MDL_CONTEXT::Ticket_iterator it(src->tickets); + Ticket_iterator it(src->m_tickets); while ((ticket= it++)) { - DBUG_ASSERT(ticket->ctx); - ticket->ctx= dst; - dst->tickets.push_front(ticket); + DBUG_ASSERT(ticket->m_ctx); + ticket->m_ctx= this; + m_tickets.push_front(ticket); } - src->tickets.empty(); + src->m_tickets.empty(); } + /* + MDL_context::merge() is a hack used in one place only: to open + an SQL handler. We never acquire the global shared lock there. + */ + DBUG_ASSERT(! src->m_has_global_shared_lock); } @@ -260,10 +316,9 @@ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) The MDL subsystem does not own or manage memory of lock requests. Instead it assumes that the life time of every lock request (including - encompassed members db/name) encloses calls to mdl_request_add() - and mdl_request_remove() or mdl_request_remove_all(). + encompassed members db/name) encloses calls to MDL_context::add_request() + and MDL_context::remove_request() or MDL_context::remove_all_requests(). - @param lock_req Pointer to an MDL_LOCK_REQUEST object to initialize @param type Id of type of object to be locked @param db Name of database to which the object belongs @param name Name of of the object @@ -275,12 +330,13 @@ void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src) they share the same name space in the SQL standard. */ -void mdl_request_init(MDL_LOCK_REQUEST *lock_req, unsigned char type, - const char *db, const char *name) +void MDL_request::init(unsigned char type_arg, + const char *db_arg, + const char *name_arg) { - lock_req->key.mdl_key_init(type, db, name); - lock_req->type= MDL_SHARED; - lock_req->ticket= NULL; + key.mdl_key_init(type_arg, db_arg, name_arg); + type= MDL_SHARED; + ticket= NULL; } @@ -302,19 +358,18 @@ void mdl_request_init(MDL_LOCK_REQUEST *lock_req, unsigned char type, @retval non-0 Pointer to an object representing a lock request */ -MDL_LOCK_REQUEST * -mdl_request_alloc(unsigned char type, const char *db, - const char *name, MEM_ROOT *root) +MDL_request * +MDL_request::create(unsigned char type, const char *db, + const char *name, MEM_ROOT *root) { - MDL_LOCK_REQUEST *lock_req; + MDL_request *mdl_request; - if (!(lock_req= (MDL_LOCK_REQUEST*) alloc_root(root, - sizeof(MDL_LOCK_REQUEST)))) + if (!(mdl_request= (MDL_request*) alloc_root(root, sizeof(MDL_request)))) return NULL; - mdl_request_init(lock_req, type, db, name); + mdl_request->init(type, db, name); - return lock_req; + return mdl_request; } @@ -322,23 +377,24 @@ mdl_request_alloc(unsigned char type, const char *db, Add a lock request to the list of lock requests of the context. The procedure to acquire metadata locks is: - - allocate and initialize lock requests (mdl_request_alloc()) - - associate them with a context (mdl_request_add()) - - call mdl_acquire_shared_lock()/mdl_ticket_release() (maybe repeatedly). + - allocate and initialize lock requests + (MDL_request::create()) + - associate them with a context (MDL_context::add_request()) + - call MDL_context::acquire_shared_lock() and + MDL_context::release_lock() (maybe repeatedly). Associates a lock request with the given context. + There should be no more than one context per connection, to + avoid deadlocks. - @param context The MDL context to associate the lock with. - There should be no more than one context per - connection, to avoid deadlocks. - @param lock_req The lock request to be added. + @param mdl_request The lock request to be added. */ -void mdl_request_add(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) +void MDL_context::add_request(MDL_request *mdl_request) { - DBUG_ENTER("mdl_request_add"); - DBUG_ASSERT(lock_req->ticket == NULL); - context->requests.push_front(lock_req); + DBUG_ENTER("MDL_context::add_request"); + DBUG_ASSERT(mdl_request->ticket == NULL); + m_requests.push_front(mdl_request); DBUG_VOID_RETURN; } @@ -348,8 +404,7 @@ void mdl_request_add(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) Disassociates a lock request from the given context. - @param context The MDL context to remove the lock from. - @param lock_req The lock request to be removed. + @param mdl_request The lock request to be removed. @pre The lock request being removed should correspond to a ticket that was released or was not acquired. @@ -358,13 +413,13 @@ void mdl_request_add(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) (i.e. sets type to MDL_SHARED). */ -void mdl_request_remove(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) +void MDL_context::remove_request(MDL_request *mdl_request) { - DBUG_ENTER("mdl_request_remove"); + DBUG_ENTER("MDL_context::remove_request"); /* Reset lock request back to its initial state. */ - lock_req->type= MDL_SHARED; - lock_req->ticket= NULL; - context->requests.remove(lock_req); + mdl_request->type= MDL_SHARED; + mdl_request->ticket= NULL; + m_requests.remove(mdl_request); DBUG_VOID_RETURN; } @@ -374,75 +429,61 @@ void mdl_request_remove(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req) Disassociates lock requests from the context. Also resets lock requests back to their initial state (i.e. MDL_SHARED). - - @param context Context to be cleared. */ -void mdl_request_remove_all(MDL_CONTEXT *context) +void MDL_context::remove_all_requests() { - MDL_LOCK_REQUEST *lock_req; - MDL_CONTEXT::Request_iterator it(context->requests); - while ((lock_req= it++)) + MDL_request *mdl_request; + Request_iterator it(m_requests); + while ((mdl_request= it++)) { /* Reset lock request back to its initial state. */ - lock_req->type= MDL_SHARED; - lock_req->ticket= NULL; + mdl_request->type= MDL_SHARED; + mdl_request->ticket= NULL; } - context->requests.empty(); + m_requests.empty(); } /** - Auxiliary functions needed for creation/destruction of MDL_LOCK objects. + Auxiliary functions needed for creation/destruction of MDL_lock objects. @todo This naive implementation should be replaced with one that saves on memory allocation by reusing released objects. */ -static MDL_LOCK* alloc_lock_object(const MDL_KEY *mdl_key) +inline MDL_lock *MDL_lock::create(const MDL_key *mdl_key) { - return new MDL_LOCK(mdl_key); + return new MDL_lock(mdl_key); } -static void free_lock_object(MDL_LOCK *lock) +void MDL_lock::destroy(MDL_lock *lock) { delete lock; } /** - Auxiliary functions needed for creation/destruction of MDL_LOCK_TICKET + Auxiliary functions needed for creation/destruction of MDL_ticket objects. @todo This naive implementation should be replaced with one that saves on memory allocation by reusing released objects. */ -static MDL_LOCK_TICKET* alloc_ticket_object(MDL_CONTEXT *context) +MDL_ticket *MDL_ticket::create(MDL_context *ctx_arg, enum_mdl_type type_arg) { - MDL_LOCK_TICKET *ticket= new MDL_LOCK_TICKET; - return ticket; + return new MDL_ticket(ctx_arg, type_arg); } -static void free_ticket_object(MDL_LOCK_TICKET *ticket) +void MDL_ticket::destroy(MDL_ticket *ticket) { delete ticket; } -/** - Helper functions which simplifies writing various checks and asserts. -*/ - -template -static inline bool is_shared(T *lock_data) -{ - return (lock_data->type < MDL_EXCLUSIVE); -} - - /** Helper functions and macros to be used for killable waiting in metadata locking subsystem. @@ -458,7 +499,7 @@ static inline bool is_shared(T *lock_data) #define MDL_ENTER_COND(A, B) mdl_enter_cond(A, B, __func__, __FILE__, __LINE__) -static inline const char* mdl_enter_cond(MDL_CONTEXT *context, +static inline const char *mdl_enter_cond(THD *thd, st_my_thread_var *mysys_var, const char *calling_func, const char *calling_file, @@ -469,13 +510,15 @@ static inline const char* mdl_enter_cond(MDL_CONTEXT *context, mysys_var->current_mutex= &LOCK_mdl; mysys_var->current_cond= &COND_mdl; - return set_thd_proc_info(context->thd, "Waiting for table", + DEBUG_SYNC(thd, "mdl_enter_cond"); + + return set_thd_proc_info(thd, "Waiting for table", calling_func, calling_file, calling_line); } #define MDL_EXIT_COND(A, B, C) mdl_exit_cond(A, B, C, __func__, __FILE__, __LINE__) -static inline void mdl_exit_cond(MDL_CONTEXT *context, +static inline void mdl_exit_cond(THD *thd, st_my_thread_var *mysys_var, const char* old_msg, const char *calling_func, @@ -490,7 +533,9 @@ static inline void mdl_exit_cond(MDL_CONTEXT *context, mysys_var->current_cond= 0; pthread_mutex_unlock(&mysys_var->mutex); - (void) set_thd_proc_info(context->thd, old_msg, calling_func, + DEBUG_SYNC(thd, "mdl_exit_cond"); + + (void) set_thd_proc_info(thd, old_msg, calling_func, calling_file, calling_line); } @@ -503,8 +548,8 @@ static inline void mdl_exit_cond(MDL_CONTEXT *context, request, implying a form of lock on the global metadata, is compatible with the current state of the global metadata lock. - @param lock_req Request for lock on an individual object, implying a - certain kind of global metadata lock. + @param mdl_request Request for lock on an individual object, implying a + certain kind of global metadata lock. @retval TRUE - Lock request can be satisfied @retval FALSE - There is some conflicting lock @@ -533,7 +578,9 @@ static inline void mdl_exit_cond(MDL_CONTEXT *context, type of locks we don't even have any accounting for them. */ -static bool can_grant_global_lock(enum_mdl_type type, bool is_upgrade) +bool +MDL_global_lock::is_lock_type_compatible(enum_mdl_type type, + bool is_upgrade) const { switch (type) { @@ -542,7 +589,7 @@ static bool can_grant_global_lock(enum_mdl_type type, bool is_upgrade) return TRUE; break; case MDL_SHARED_UPGRADABLE: - if (global_lock.active_shared || global_lock.waiting_shared) + if (active_shared || waiting_shared) { /* We are going to obtain intention exclusive global lock and @@ -562,13 +609,12 @@ static bool can_grant_global_lock(enum_mdl_type type, bool is_upgrade) There should be no conflicting global locks since for each upgradable shared lock we obtain intention exclusive global lock first. */ - DBUG_ASSERT(global_lock.active_shared == 0 && - global_lock.active_intention_exclusive); + DBUG_ASSERT(active_shared == 0 && active_intention_exclusive); return TRUE; } else { - if (global_lock.active_shared || global_lock.waiting_shared) + if (active_shared || waiting_shared) { /* We are going to obtain intention exclusive global lock and @@ -590,15 +636,15 @@ static bool can_grant_global_lock(enum_mdl_type type, bool is_upgrade) /** Check if request for the lock can be satisfied given current state of lock. - @param lock Lock. - @param lock_req Request for lock. + @param lock Lock. + @param mdl_request Request for lock. @retval TRUE Lock request can be satisfied @retval FALSE There is some conflicting lock. This function defines the following compatibility matrix for metadata locks: - | Satisfied or pending requests which we have in MDL_LOCK + | Satisfied or pending requests which we have in MDL_lock ----------------+--------------------------------------------------------- Current request | Active-S Pending-X Active-X Act-S-pend-upgrade-to-X ----------------+--------------------------------------------------------- @@ -617,22 +663,23 @@ static bool can_grant_global_lock(enum_mdl_type type, bool is_upgrade) being upgraded. */ -static bool can_grant_lock(MDL_CONTEXT *ctx, MDL_LOCK *lock, - enum_mdl_type type, bool is_upgrade) +bool +MDL_lock::can_grant_lock(const MDL_context *requestor_ctx, enum_mdl_type type_arg, + bool is_upgrade) { bool can_grant= FALSE; - switch (type) { + switch (type_arg) { case MDL_SHARED: case MDL_SHARED_UPGRADABLE: case MDL_SHARED_HIGH_PRIO: - if (lock->type == MDL_LOCK::SHARED) + if (type == MDL_lock::SHARED) { /* Pending exclusive locks have higher priority over shared locks. */ - if (lock->waiting.is_empty() || type == MDL_SHARED_HIGH_PRIO) + if (waiting.is_empty() || type_arg == MDL_SHARED_HIGH_PRIO) can_grant= TRUE; } - else if (lock->granted.head()->ctx == ctx) + else if (granted.head()->get_ctx() == requestor_ctx) { /* When exclusive lock comes from the same context we can satisfy our @@ -646,33 +693,33 @@ static bool can_grant_lock(MDL_CONTEXT *ctx, MDL_LOCK *lock, if (is_upgrade) { /* We are upgrading MDL_SHARED to MDL_EXCLUSIVE. */ - MDL_LOCK_TICKET *conf_lock_ticket; - MDL_LOCK::Ticket_iterator it(lock->granted); + MDL_ticket *conflicting_ticket; + MDL_lock::Ticket_iterator it(granted); /* There should be no active exclusive locks since we own shared lock on the object. */ - DBUG_ASSERT(lock->type == MDL_LOCK::SHARED); + DBUG_ASSERT(type == MDL_lock::SHARED); - while ((conf_lock_ticket= it++)) + while ((conflicting_ticket= it++)) { /* When upgrading shared lock to exclusive one we can have other shared locks for the same object in the same context, e.g. in case when several instances of TABLE are open. */ - if (conf_lock_ticket->ctx != ctx) + if (conflicting_ticket->get_ctx() != requestor_ctx) break; } /* Grant lock if there are no conflicting shared locks. */ - if (conf_lock_ticket == NULL) + if (conflicting_ticket == NULL) can_grant= TRUE; break; } - else if (lock->type == MDL_LOCK::SHARED) + else if (type == MDL_lock::SHARED) { - can_grant= lock->granted.is_empty(); + can_grant= granted.is_empty(); } break; default: @@ -686,23 +733,23 @@ static bool can_grant_lock(MDL_CONTEXT *ctx, MDL_LOCK *lock, Check whether the context already holds a compatible lock ticket on a object. Only shared locks can be recursive. - @param lock_req Lock request object for lock to be acquired + @param mdl_request Lock request object for lock to be acquired @return A pointer to the lock ticket for the object or NULL otherwise. */ -static MDL_LOCK_TICKET * -mdl_context_find_ticket(MDL_CONTEXT *ctx, MDL_LOCK_REQUEST *lock_req) +MDL_ticket * +MDL_context::find_ticket(MDL_request *mdl_request) { - MDL_LOCK_TICKET *ticket; - MDL_CONTEXT::Ticket_iterator it(ctx->tickets); + MDL_ticket *ticket; + Ticket_iterator it(m_tickets); - DBUG_ASSERT(is_shared(lock_req)); + DBUG_ASSERT(mdl_request->is_shared()); while ((ticket= it++)) { - if (lock_req->type == ticket->type && - lock_req->key.is_equal(&ticket->lock->key)) + if (mdl_request->type == ticket->m_type && + mdl_request->key.is_equal(&ticket->m_lock->key)) break; } @@ -715,7 +762,7 @@ mdl_context_find_ticket(MDL_CONTEXT *ctx, MDL_LOCK_REQUEST *lock_req) Unlike exclusive locks, shared locks are acquired one by one. This is interface is chosen to simplify introduction of - the new locking API to the system. mdl_acquire_shared_lock() + the new locking API to the system. MDL_context::acquire_shared_lock() is currently used from open_table(), and there we have only one table to work with. @@ -723,32 +770,31 @@ mdl_context_find_ticket(MDL_CONTEXT *ctx, MDL_LOCK_REQUEST *lock_req) This function must be called after the lock is added to a context. - @param context [in] Context containing request for lock - @param lock_req [in] Lock request object for lock to be acquired + @param mdl_request [in] Lock request object for lock to be acquired @param retry [out] Indicates that conflicting lock exists and another attempt should be made after releasing all current locks and waiting for conflicting lock go away - (using mdl_wait_for_locks()). + (using MDL_context::wait_for_locks()). @retval FALSE Success. - @retval TRUE Failure. Either error occured or conflicting lock exists. + @retval TRUE Failure. Either error occurred or conflicting lock exists. In the latter case "retry" parameter is set to TRUE. */ -bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req, - bool *retry) +bool +MDL_context::acquire_shared_lock(MDL_request *mdl_request, bool *retry) { - MDL_LOCK *lock; - MDL_KEY *key= &lock_req->key; - MDL_LOCK_TICKET *ticket; + MDL_lock *lock; + MDL_key *key= &mdl_request->key; + MDL_ticket *ticket; *retry= FALSE; - DBUG_ASSERT(is_shared(lock_req) && lock_req->ticket == NULL); + DBUG_ASSERT(mdl_request->is_shared() && mdl_request->ticket == NULL); safe_mutex_assert_not_owner(&LOCK_open); - if (context->has_global_shared_lock && - lock_req->type == MDL_SHARED_UPGRADABLE) + if (m_has_global_shared_lock && + mdl_request->type == MDL_SHARED_UPGRADABLE) { my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); return TRUE; @@ -758,52 +804,50 @@ bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req, Check whether the context already holds a shared lock on the object, and if so, grant the request. */ - if ((ticket= mdl_context_find_ticket(context, lock_req))) + if ((ticket= find_ticket(mdl_request))) { - DBUG_ASSERT(ticket->state == MDL_ACQUIRED); - lock_req->ticket= ticket; + DBUG_ASSERT(ticket->m_state == MDL_ACQUIRED); + mdl_request->ticket= ticket; return FALSE; } pthread_mutex_lock(&LOCK_mdl); - if (!can_grant_global_lock(lock_req->type, FALSE)) + if (!global_lock.is_lock_type_compatible(mdl_request->type, FALSE)) { pthread_mutex_unlock(&LOCK_mdl); *retry= TRUE; return TRUE; } - if (!(ticket= alloc_ticket_object(context))) + if (!(ticket= MDL_ticket::create(this, mdl_request->type))) { pthread_mutex_unlock(&LOCK_mdl); return TRUE; } - if (!(lock= (MDL_LOCK*) my_hash_search(&mdl_locks, + if (!(lock= (MDL_lock*) my_hash_search(&mdl_locks, key->ptr(), key->length()))) { - /* Default lock type is MDL_LOCK::SHARED */ - lock= alloc_lock_object(key); + /* Default lock type is MDL_lock::SHARED */ + lock= MDL_lock::create(key); if (!lock || my_hash_insert(&mdl_locks, (uchar*)lock)) { - free_lock_object(lock); - free_ticket_object(ticket); + MDL_lock::destroy(lock); + MDL_ticket::destroy(ticket); pthread_mutex_unlock(&LOCK_mdl); return TRUE; } } - if (can_grant_lock(context, lock, lock_req->type, FALSE)) + if (lock->can_grant_lock(this, mdl_request->type, FALSE)) { + mdl_request->ticket= ticket; lock->granted.push_front(ticket); - context->tickets.push_front(ticket); - ticket->state= MDL_ACQUIRED; - lock_req->ticket= ticket; - ticket->lock= lock; - ticket->type= lock_req->type; - ticket->ctx= context; - if (lock_req->type == MDL_SHARED_UPGRADABLE) + m_tickets.push_front(ticket); + ticket->m_state= MDL_ACQUIRED; + ticket->m_lock= lock; + if (mdl_request->type == MDL_SHARED_UPGRADABLE) global_lock.active_intention_exclusive++; } else @@ -811,7 +855,7 @@ bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req, /* We can't get here if we allocated a new lock. */ DBUG_ASSERT(! lock->is_empty()); *retry= TRUE; - free_ticket_object(ticket); + MDL_ticket::destroy(ticket); } pthread_mutex_unlock(&LOCK_mdl); @@ -820,24 +864,25 @@ bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req, } -static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket); - - /** - Notify a thread holding a shared metadata lock of a pending exclusive lock. + Notify a thread holding a shared metadata lock which + conflicts with a pending exclusive lock. @param thd Current thread context - @param conf_lock_ticket Conflicting metadata lock + @param conflicting_ticket Conflicting metadata lock @retval TRUE A thread was woken up @retval FALSE Lock is not a shared one or no thread was woken up */ -static bool notify_shared_lock(THD *thd, MDL_LOCK_TICKET *conf_lock_ticket) +static bool notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) { bool woke= FALSE; - if (conf_lock_ticket->type != MDL_EXCLUSIVE) - woke= mysql_notify_thread_having_shared_lock(thd, conf_lock_ticket->ctx->thd); + if (conflicting_ticket->is_shared()) + { + THD *conflicting_thd= conflicting_ticket->get_ctx()->get_thd(); + woke= mysql_notify_thread_having_shared_lock(thd, conflicting_thd); + } return woke; } @@ -850,26 +895,26 @@ static bool notify_shared_lock(THD *thd, MDL_LOCK_TICKET *conf_lock_ticket) This is a replacement of lock_table_names(). It is used in RENAME, DROP and other DDL SQL statements. - @param context A context containing requests for exclusive locks - The context may not have other lock requests. + @note The MDL context may not have non-exclusive lock requests + or acquired locks. @retval FALSE Success @retval TRUE Failure */ -bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) +bool MDL_context::acquire_exclusive_locks() { - MDL_LOCK *lock; + MDL_lock *lock; bool signalled= FALSE; const char *old_msg; - MDL_LOCK_REQUEST *lock_req; - MDL_LOCK_TICKET *ticket; + MDL_request *mdl_request; + MDL_ticket *ticket; st_my_thread_var *mysys_var= my_thread_var; - MDL_CONTEXT::Request_iterator it(context->requests); + Request_iterator it(m_requests); safe_mutex_assert_not_owner(&LOCK_open); - if (context->has_global_shared_lock) + if (m_has_global_shared_lock) { my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); return TRUE; @@ -877,79 +922,76 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) pthread_mutex_lock(&LOCK_mdl); - old_msg= MDL_ENTER_COND(context, mysys_var); + old_msg= MDL_ENTER_COND(m_thd, mysys_var); - while ((lock_req= it++)) + while ((mdl_request= it++)) { - MDL_KEY *key= &lock_req->key; - DBUG_ASSERT(lock_req->type == MDL_EXCLUSIVE && - lock_req->ticket == NULL); + MDL_key *key= &mdl_request->key; + DBUG_ASSERT(mdl_request->type == MDL_EXCLUSIVE && + mdl_request->ticket == NULL); /* Early allocation: ticket is used as a shortcut to the lock. */ - if (!(ticket= alloc_ticket_object(context))) + if (!(ticket= MDL_ticket::create(this, mdl_request->type))) goto err; - if (!(lock= (MDL_LOCK*) my_hash_search(&mdl_locks, + if (!(lock= (MDL_lock*) my_hash_search(&mdl_locks, key->ptr(), key->length()))) { - lock= alloc_lock_object(key); + lock= MDL_lock::create(key); if (!lock || my_hash_insert(&mdl_locks, (uchar*)lock)) { - free_ticket_object(ticket); - free_lock_object(lock); + MDL_ticket::destroy(ticket); + MDL_lock::destroy(lock); goto err; } } - lock_req->ticket= ticket; - ticket->state= MDL_PENDING; - ticket->ctx= context; - ticket->lock= lock; - ticket->type= lock_req->type; + mdl_request->ticket= ticket; lock->waiting.push_front(ticket); + ticket->m_lock= lock; } while (1) { it.rewind(); - while ((lock_req= it++)) + while ((mdl_request= it++)) { - lock= lock_req->ticket->lock; + lock= mdl_request->ticket->m_lock; - if (!can_grant_global_lock(lock_req->type, FALSE)) + if (!global_lock.is_lock_type_compatible(mdl_request->type, FALSE)) { /* - There is an active or pending global shared lock so we have - to wait until it goes away. + Someone owns or wants to acquire the global shared lock so + we have to wait until he goes away. */ signalled= TRUE; break; } - else if (!can_grant_lock(context, lock, lock_req->type, FALSE)) + else if (!lock->can_grant_lock(this, mdl_request->type, FALSE)) { - MDL_LOCK_TICKET *conf_lock_ticket; - MDL_LOCK::Ticket_iterator it(lock->granted); + MDL_ticket *conflicting_ticket; + MDL_lock::Ticket_iterator it(lock->granted); - signalled= (lock->type == MDL_LOCK::EXCLUSIVE); + signalled= (lock->type == MDL_lock::EXCLUSIVE); - while ((conf_lock_ticket= it++)) - signalled|= notify_shared_lock(context->thd, conf_lock_ticket); + while ((conflicting_ticket= it++)) + signalled|= notify_shared_lock(m_thd, conflicting_ticket); break; } } - if (!lock_req) + if (!mdl_request) break; /* There is a shared or exclusive lock on the object. */ - DEBUG_SYNC(context->thd, "mdl_acquire_exclusive_locks_wait"); + DEBUG_SYNC(m_thd, "mdl_acquire_exclusive_locks_wait"); if (signalled) pthread_cond_wait(&COND_mdl, &LOCK_mdl); else { /* - Another thread obtained shared MDL-lock on some table but + Another thread obtained a shared MDL lock on some table but has not yet opened it and/or tried to obtain data lock on it. In this case we need to wait until this happens and try to abort this thread once again. @@ -962,22 +1004,22 @@ bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context) goto err; } it.rewind(); - while ((lock_req= it++)) + while ((mdl_request= it++)) { global_lock.active_intention_exclusive++; - ticket= lock_req->ticket; - lock= ticket->lock; - lock->type= MDL_LOCK::EXCLUSIVE; + ticket= mdl_request->ticket; + lock= ticket->m_lock; + lock->type= MDL_lock::EXCLUSIVE; lock->waiting.remove(ticket); lock->granted.push_front(ticket); - context->tickets.push_front(ticket); - ticket->state= MDL_ACQUIRED; + m_tickets.push_front(ticket); + ticket->m_state= MDL_ACQUIRED; if (lock->cached_object) (*lock->cached_object_release_hook)(lock->cached_object); lock->cached_object= NULL; } /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ - MDL_EXIT_COND(context, mysys_var, old_msg); + MDL_EXIT_COND(m_thd, mysys_var, old_msg); return FALSE; err: @@ -986,24 +1028,24 @@ err: Ignore those lock requests which were not made MDL_PENDING. */ it.rewind(); - while ((lock_req= it++) && lock_req->ticket) + while ((mdl_request= it++) && mdl_request->ticket) { - ticket= lock_req->ticket; - DBUG_ASSERT(ticket->state == MDL_PENDING); - lock= ticket->lock; - free_ticket_object(ticket); + ticket= mdl_request->ticket; + DBUG_ASSERT(ticket->m_state == MDL_PENDING); + lock= ticket->m_lock; lock->waiting.remove(ticket); + MDL_ticket::destroy(ticket); /* Reset lock request back to its initial state. */ - lock_req->ticket= NULL; + mdl_request->ticket= NULL; if (lock->is_empty()) { my_hash_delete(&mdl_locks, (uchar *)lock); - free_lock_object(lock); + MDL_lock::destroy(lock); } } /* May be some pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); - MDL_EXIT_COND(context, mysys_var, old_msg); + MDL_EXIT_COND(m_thd, mysys_var, old_msg); return TRUE; } @@ -1014,9 +1056,6 @@ err: Used in ALTER TABLE, when a copy of the table with the new definition has been constructed. - @param context Context to which shared lock belongs - @param ticket Ticket for shared lock to be upgraded - @note In case of failure to upgrade lock (e.g. because upgrader was killed) leaves lock in its original state (locked in shared mode). @@ -1030,46 +1069,46 @@ err: @retval TRUE Failure (thread was killed) */ -bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, - MDL_LOCK_TICKET *ticket) +bool +MDL_ticket::upgrade_shared_lock_to_exclusive() { - MDL_LOCK *lock= ticket->lock; const char *old_msg; st_my_thread_var *mysys_var= my_thread_var; + THD *thd= m_ctx->get_thd(); - DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive"); - DEBUG_SYNC(context->thd, "mdl_upgrade_shared_lock_to_exclusive"); + DBUG_ENTER("MDL_ticket::upgrade_shared_lock_to_exclusive"); + DEBUG_SYNC(thd, "mdl_upgrade_shared_lock_to_exclusive"); safe_mutex_assert_not_owner(&LOCK_open); /* Allow this function to be called twice for the same lock request. */ - if (ticket->type == MDL_EXCLUSIVE) + if (m_type == MDL_EXCLUSIVE) DBUG_RETURN(FALSE); pthread_mutex_lock(&LOCK_mdl); - old_msg= MDL_ENTER_COND(context, mysys_var); + old_msg= MDL_ENTER_COND(thd, mysys_var); /* Since we should have already acquired an intention exclusive global lock this call is only enforcing asserts. */ - DBUG_ASSERT(can_grant_global_lock(MDL_EXCLUSIVE, TRUE)); + DBUG_ASSERT(global_lock.is_lock_type_compatible(MDL_EXCLUSIVE, TRUE)); while (1) { - if (can_grant_lock(context, lock, MDL_EXCLUSIVE, TRUE)) + if (m_lock->can_grant_lock(m_ctx, MDL_EXCLUSIVE, TRUE)) break; bool signalled= FALSE; - MDL_LOCK_TICKET *conf_lock_ticket; - MDL_LOCK::Ticket_iterator it(lock->granted); + MDL_ticket *conflicting_ticket; + MDL_lock::Ticket_iterator it(m_lock->granted); - while ((conf_lock_ticket= it++)) + while ((conflicting_ticket= it++)) { - if (conf_lock_ticket->ctx != context) - signalled|= notify_shared_lock(context->thd, conf_lock_ticket); + if (conflicting_ticket->m_ctx != m_ctx) + signalled|= notify_shared_lock(thd, conflicting_ticket); } if (signalled) @@ -1077,7 +1116,7 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, else { /* - Another thread obtained shared MDL-lock on some table but + Another thread obtained a shared MDL lock on some table but has not yet opened it and/or tried to obtain data lock on it. In this case we need to wait until this happens and try to abort this thread once again. @@ -1091,20 +1130,20 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, { /* Pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); - MDL_EXIT_COND(context, mysys_var, old_msg); + MDL_EXIT_COND(thd, mysys_var, old_msg); DBUG_RETURN(TRUE); } } - lock->type= MDL_LOCK::EXCLUSIVE; + m_lock->type= MDL_lock::EXCLUSIVE; /* Set the new type of lock in the ticket. */ - ticket->type= MDL_EXCLUSIVE; - if (lock->cached_object) - (*lock->cached_object_release_hook)(lock->cached_object); - lock->cached_object= 0; + m_type= MDL_EXCLUSIVE; + if (m_lock->cached_object) + (*m_lock->cached_object_release_hook)(m_lock->cached_object); + m_lock->cached_object= 0; /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ - MDL_EXIT_COND(context, mysys_var, old_msg); + MDL_EXIT_COND(thd, mysys_var, old_msg); DBUG_RETURN(FALSE); } @@ -1121,28 +1160,27 @@ bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, on the table to be created. In this statement we don't want to block and wait for the lock if the table already exists. - @param context [in] The context containing the lock request - @param lock_req [in] The lock request - @param conflict [out] Indicates that conflicting lock exists + @param mdl_request [in] The lock request + @param conflict [out] Indicates that conflicting lock exists @retval TRUE Failure either conflicting lock exists or some error occured (probably OOM). @retval FALSE Success, lock was acquired. FIXME: Compared to lock_table_name_if_not_cached() - it gives sligthly more false negatives. + it gives slightly more false negatives. */ -bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_REQUEST *lock_req, - bool *conflict) +bool +MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request, + bool *conflict) { - MDL_LOCK *lock; - MDL_LOCK_TICKET *ticket; - MDL_KEY *key= &lock_req->key; + MDL_lock *lock; + MDL_ticket *ticket; + MDL_key *key= &mdl_request->key; - DBUG_ASSERT(lock_req->type == MDL_EXCLUSIVE && - lock_req->ticket == NULL); + DBUG_ASSERT(mdl_request->type == MDL_EXCLUSIVE && + mdl_request->ticket == NULL); safe_mutex_assert_not_owner(&LOCK_open); @@ -1150,25 +1188,23 @@ bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, pthread_mutex_lock(&LOCK_mdl); - if (!(lock= (MDL_LOCK*) my_hash_search(&mdl_locks, + if (!(lock= (MDL_lock*) my_hash_search(&mdl_locks, key->ptr(), key->length()))) { - ticket= alloc_ticket_object(context); - lock= alloc_lock_object(key); + ticket= MDL_ticket::create(this, mdl_request->type); + lock= MDL_lock::create(key); if (!ticket || !lock || my_hash_insert(&mdl_locks, (uchar*)lock)) { - free_ticket_object(ticket); - free_lock_object(lock); + MDL_ticket::destroy(ticket); + MDL_lock::destroy(lock); goto err; } - lock->type= MDL_LOCK::EXCLUSIVE; + mdl_request->ticket= ticket; + lock->type= MDL_lock::EXCLUSIVE; lock->granted.push_front(ticket); - context->tickets.push_front(ticket); - ticket->state= MDL_ACQUIRED; - lock_req->ticket= ticket; - ticket->ctx= context; - ticket->lock= lock; - ticket->type= lock_req->type; + m_tickets.push_front(ticket); + ticket->m_state= MDL_ACQUIRED; + ticket->m_lock= lock; global_lock.active_intention_exclusive++; pthread_mutex_unlock(&LOCK_mdl); return FALSE; @@ -1184,29 +1220,27 @@ err: /** - Acquire global shared metadata lock. + Acquire the global shared metadata lock. Holding this lock will block all requests for exclusive locks and shared locks which can be potentially upgraded to exclusive. - @param context Current metadata locking context. - @retval FALSE Success -- the lock was granted. @retval TRUE Failure -- our thread was killed. */ -bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) +bool MDL_context::acquire_global_shared_lock() { st_my_thread_var *mysys_var= my_thread_var; const char *old_msg; safe_mutex_assert_not_owner(&LOCK_open); - DBUG_ASSERT(!context->has_global_shared_lock); + DBUG_ASSERT(!m_has_global_shared_lock); pthread_mutex_lock(&LOCK_mdl); global_lock.waiting_shared++; - old_msg= MDL_ENTER_COND(context, mysys_var); + old_msg= MDL_ENTER_COND(m_thd, mysys_var); while (!mysys_var->abort && global_lock.active_intention_exclusive) pthread_cond_wait(&COND_mdl, &LOCK_mdl); @@ -1215,13 +1249,13 @@ bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) if (mysys_var->abort) { /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ - MDL_EXIT_COND(context, mysys_var, old_msg); + MDL_EXIT_COND(m_thd, mysys_var, old_msg); return TRUE; } global_lock.active_shared++; - context->has_global_shared_lock= TRUE; + m_has_global_shared_lock= TRUE; /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ - MDL_EXIT_COND(context, mysys_var, old_msg); + MDL_EXIT_COND(m_thd, mysys_var, old_msg); return FALSE; } @@ -1235,17 +1269,16 @@ bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context) Does not acquire the locks! - @param context Context with which lock requests are associated. - @retval FALSE Success. One can try to obtain metadata locks. @retval TRUE Failure (thread was killed) */ -bool mdl_wait_for_locks(MDL_CONTEXT *context) +bool +MDL_context::wait_for_locks() { - MDL_LOCK *lock; - MDL_LOCK_REQUEST *lock_req; - MDL_CONTEXT::Request_iterator it(context->requests); + MDL_lock *lock; + MDL_request *mdl_request; + Request_iterator it(m_requests); const char *old_msg; st_my_thread_var *mysys_var= my_thread_var; @@ -1263,34 +1296,34 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) TODO: investigate situations in which we need to broadcast on COND_mdl because of above scenario. */ - mysql_ha_flush(context->thd); + mysql_ha_flush(m_thd); pthread_mutex_lock(&LOCK_mdl); - old_msg= MDL_ENTER_COND(context, mysys_var); + old_msg= MDL_ENTER_COND(m_thd, mysys_var); it.rewind(); - while ((lock_req= it++)) + while ((mdl_request= it++)) { - MDL_KEY *key= &lock_req->key; - DBUG_ASSERT(lock_req->ticket == NULL); - if (!can_grant_global_lock(lock_req->type, FALSE)) + MDL_key *key= &mdl_request->key; + DBUG_ASSERT(mdl_request->ticket == NULL); + if (!global_lock.is_lock_type_compatible(mdl_request->type, FALSE)) break; /* To avoid starvation we don't wait if we have a conflict against request for MDL_EXCLUSIVE lock. */ - if (is_shared(lock_req) && - (lock= (MDL_LOCK*) my_hash_search(&mdl_locks, key->ptr(), + if (mdl_request->is_shared() && + (lock= (MDL_lock*) my_hash_search(&mdl_locks, key->ptr(), key->length())) && - !can_grant_lock(context, lock, lock_req->type, FALSE)) + !lock->can_grant_lock(this, mdl_request->type, FALSE)) break; } - if (!lock_req) + if (!mdl_request) { pthread_mutex_unlock(&LOCK_mdl); break; } pthread_cond_wait(&COND_mdl, &LOCK_mdl); /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ - MDL_EXIT_COND(context, mysys_var, old_msg); + MDL_EXIT_COND(m_thd, mysys_var, old_msg); } return mysys_var->abort; } @@ -1301,18 +1334,18 @@ bool mdl_wait_for_locks(MDL_CONTEXT *context) ownership of which is represented by a lock ticket object. */ -static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) +void MDL_context::release_ticket(MDL_ticket *ticket) { - MDL_LOCK *lock= ticket->lock; + MDL_lock *lock= ticket->m_lock; DBUG_ENTER("release_ticket"); DBUG_PRINT("enter", ("db=%s name=%s", lock->key.db_name(), lock->key.table_name())); safe_mutex_assert_owner(&LOCK_mdl); - context->tickets.remove(ticket); + m_tickets.remove(ticket); - switch (ticket->type) + switch (ticket->m_type) { case MDL_SHARED_UPGRADABLE: global_lock.active_intention_exclusive--; @@ -1322,7 +1355,7 @@ static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) lock->granted.remove(ticket); break; case MDL_EXCLUSIVE: - lock->type= MDL_LOCK::SHARED; + lock->type= MDL_lock::SHARED; lock->granted.remove(ticket); global_lock.active_intention_exclusive--; break; @@ -1330,7 +1363,7 @@ static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) DBUG_ASSERT(0); } - free_ticket_object(ticket); + MDL_ticket::destroy(ticket); if (lock->is_empty()) { @@ -1339,7 +1372,7 @@ static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) lock->cached_object)); if (lock->cached_object) (*lock->cached_object_release_hook)(lock->cached_object); - free_lock_object(lock); + MDL_lock::destroy(lock); } DBUG_VOID_RETURN; @@ -1353,42 +1386,39 @@ static void release_ticket(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) This function is used to back off in case of a lock conflict. It is also used to release shared locks in the end of an SQL statement. - - @param context The context with which the locks to be released - are associated. */ -void mdl_ticket_release_all(MDL_CONTEXT *context) +void MDL_context::release_all_locks() { - MDL_LOCK_TICKET *ticket; - MDL_CONTEXT::Ticket_iterator it(context->tickets); - DBUG_ENTER("mdl_ticket_release_all"); + MDL_ticket *ticket; + Ticket_iterator it(m_tickets); + DBUG_ENTER("MDL_context::release_all_locks"); safe_mutex_assert_not_owner(&LOCK_open); /* Detach lock tickets from the requests for back off. */ { - MDL_LOCK_REQUEST *lock_req; - MDL_CONTEXT::Request_iterator it(context->requests); + MDL_request *mdl_request; + Request_iterator it(m_requests); - while ((lock_req= it++)) - lock_req->ticket= NULL; + while ((mdl_request= it++)) + mdl_request->ticket= NULL; } - if (context->tickets.is_empty()) + if (m_tickets.is_empty()) DBUG_VOID_RETURN; pthread_mutex_lock(&LOCK_mdl); while ((ticket= it++)) { DBUG_PRINT("info", ("found lock to release ticket=%p", ticket)); - release_ticket(context, ticket); + release_ticket(ticket); } /* Inefficient but will do for a while */ pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); - context->tickets.empty(); + m_tickets.empty(); DBUG_VOID_RETURN; } @@ -1397,17 +1427,16 @@ void mdl_ticket_release_all(MDL_CONTEXT *context) /** Release a lock. - @param context Context containing lock in question @param ticket Lock to be released */ -void mdl_ticket_release(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) +void MDL_context::release_lock(MDL_ticket *ticket) { - DBUG_ASSERT(context == ticket->ctx); + DBUG_ASSERT(this == ticket->m_ctx); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - release_ticket(context, ticket); + release_ticket(ticket); pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); } @@ -1417,91 +1446,76 @@ void mdl_ticket_release(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket) Release all locks in the context which correspond to the same name/ object as this lock request, remove lock requests from the context. - @param context Context containing locks in question @param ticket One of the locks for the name/object for which all locks should be released. */ -void mdl_ticket_release_all_for_name(MDL_CONTEXT *context, - MDL_LOCK_TICKET *ticket) +void MDL_context::release_all_locks_for_name(MDL_ticket *name) { - MDL_LOCK *lock; - - /* - We can use MDL_LOCK_TICKET::lock here to identify other locks for the same - object since even though MDL_LOCK object might be reused for different - lock after the first lock for this object have been released we can't - have references to this other MDL_LOCK object in this context. - */ - lock= ticket->lock; + /* Use MDL_ticket::lock to identify other locks for the same object. */ + MDL_lock *lock= name->m_lock; /* Remove matching lock requests from the context. */ - MDL_LOCK_REQUEST *lock_req; - MDL_CONTEXT::Request_iterator it_lock_req(context->requests); + MDL_request *mdl_request; + Request_iterator it_mdl_request(m_requests); - while ((lock_req= it_lock_req++)) + while ((mdl_request= it_mdl_request++)) { - DBUG_ASSERT(lock_req->ticket && lock_req->ticket->state == MDL_ACQUIRED); - if (lock_req->ticket->lock == lock) - mdl_request_remove(context, lock_req); + DBUG_ASSERT(mdl_request->ticket && + mdl_request->ticket->m_state == MDL_ACQUIRED); + + if (mdl_request->ticket->m_lock == lock) + remove_request(mdl_request); } /* Remove matching lock tickets from the context. */ - MDL_LOCK_TICKET *lock_tkt; - MDL_CONTEXT::Ticket_iterator it_lock_tkt(context->tickets); + MDL_ticket *ticket; + Ticket_iterator it_ticket(m_tickets); - while ((lock_tkt= it_lock_tkt++)) + while ((ticket= it_ticket++)) { - DBUG_ASSERT(lock_tkt->state == MDL_ACQUIRED); - if (lock_tkt->lock == lock) - mdl_ticket_release(context, lock_tkt); + DBUG_ASSERT(ticket->m_state == MDL_ACQUIRED); + /* + We rarely have more than one ticket in this loop, + let's not bother saving on pthread_cond_broadcast(). + */ + if (ticket->m_lock == lock) + release_lock(ticket); } } /** Downgrade an exclusive lock to shared metadata lock. - - @param context A context to which exclusive lock belongs - @param ticket Ticket for exclusive lock to be downgraded */ -void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_TICKET *ticket) +void MDL_ticket::downgrade_exclusive_lock() { - MDL_LOCK *lock; - - DBUG_ASSERT(context == ticket->ctx); - safe_mutex_assert_not_owner(&LOCK_open); - if (is_shared(ticket)) + if (is_shared()) return; - lock= ticket->lock; - pthread_mutex_lock(&LOCK_mdl); - lock->type= MDL_LOCK::SHARED; - ticket->type= MDL_SHARED_UPGRADABLE; + m_lock->type= MDL_lock::SHARED; + m_type= MDL_SHARED_UPGRADABLE; pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); } /** - Release global shared metadata lock. - - @param context Current context + Release the global shared metadata lock. */ -void mdl_release_global_shared_lock(MDL_CONTEXT *context) +void MDL_context::release_global_shared_lock() { safe_mutex_assert_not_owner(&LOCK_open); - DBUG_ASSERT(context->has_global_shared_lock); + DBUG_ASSERT(m_has_global_shared_lock); pthread_mutex_lock(&LOCK_mdl); global_lock.active_shared--; - context->has_global_shared_lock= FALSE; + m_has_global_shared_lock= FALSE; pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); } @@ -1511,7 +1525,6 @@ void mdl_release_global_shared_lock(MDL_CONTEXT *context) Auxiliary function which allows to check if we have exclusive lock on the object. - @param context Current context @param type Id of object type @param db Name of the database @param name Name of the object @@ -1520,19 +1533,18 @@ void mdl_release_global_shared_lock(MDL_CONTEXT *context) FALSE otherwise. */ -bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, unsigned char type, - const char *db, const char *name) +bool +MDL_context::is_exclusive_lock_owner(unsigned char type, + const char *db, const char *name) { - MDL_KEY key; - MDL_LOCK_TICKET *ticket; - MDL_CONTEXT::Ticket_iterator it(context->tickets); - - key.mdl_key_init(type, db, name); + MDL_key key(type, db, name); + MDL_ticket *ticket; + MDL_context::Ticket_iterator it(m_tickets); while ((ticket= it++)) { - if (ticket->lock->type == MDL_LOCK::EXCLUSIVE && - ticket->lock->key.is_equal(&key)) + if (ticket->m_lock->type == MDL_lock::EXCLUSIVE && + ticket->m_lock->key.is_equal(&key)) break; } @@ -1544,7 +1556,6 @@ bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, unsigned char type, Auxiliary function which allows to check if we have some kind of lock on a object. - @param context Current context @param type Id of object type @param db Name of the database @param name Name of the object @@ -1553,18 +1564,17 @@ bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, unsigned char type, FALSE otherwise. */ -bool mdl_is_lock_owner(MDL_CONTEXT *context, unsigned char type, - const char *db, const char *name) +bool +MDL_context::is_lock_owner(unsigned char type, + const char *db, const char *name) { - MDL_KEY key; - MDL_LOCK_TICKET *ticket; - MDL_CONTEXT::Ticket_iterator it(context->tickets); - - key.mdl_key_init(type, db, name); + MDL_key key(type, db, name); + MDL_ticket *ticket; + MDL_context::Ticket_iterator it(m_tickets); while ((ticket= it++)) { - if (ticket->lock->key.is_equal(&key)) + if (ticket->m_lock->key.is_equal(&key)) break; } @@ -1576,20 +1586,22 @@ bool mdl_is_lock_owner(MDL_CONTEXT *context, unsigned char type, Check if we have any pending exclusive locks which conflict with existing shared lock. + @pre The ticket must match an acquired lock. + @param ticket Shared lock against which check should be performed. @return TRUE if there are any conflicting locks, FALSE otherwise. */ -bool mdl_has_pending_conflicting_lock(MDL_LOCK_TICKET *ticket) +bool MDL_ticket::has_pending_conflicting_lock() const { bool result; - DBUG_ASSERT(is_shared(ticket)); + DBUG_ASSERT(is_shared()); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - result= !ticket->lock->waiting.is_empty(); + result= !m_lock->waiting.is_empty(); pthread_mutex_unlock(&LOCK_mdl); return result; } @@ -1598,8 +1610,6 @@ bool mdl_has_pending_conflicting_lock(MDL_LOCK_TICKET *ticket) /** Associate pointer to an opaque object with a lock. - @param ticket Lock ticket for the lock with which the object - should be associated. @param cached_object Pointer to the object @param release_hook Cleanup function to be called when MDL subsystem decides to remove lock or associate another object. @@ -1623,24 +1633,24 @@ bool mdl_has_pending_conflicting_lock(MDL_LOCK_TICKET *ticket) lock on this name is released. */ -void mdl_set_cached_object(MDL_LOCK_TICKET *ticket, void *cached_object, - mdl_cached_object_release_hook release_hook) +void +MDL_ticket::set_cached_object(void *cached_object, + mdl_cached_object_release_hook release_hook) { - MDL_LOCK *lock= ticket->lock; DBUG_ENTER("mdl_set_cached_object"); DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", - lock->key.db_name(), lock->key.table_name(), + m_lock->key.db_name(), m_lock->key.table_name(), cached_object)); /* - TODO: This assumption works now since we do mdl_get_cached_object() - and mdl_set_cached_object() in the same critical section. Once + TODO: This assumption works now since we do get_cached_object() + and set_cached_object() in the same critical section. Once this becomes false we will have to call release_hook here and use additional mutex protecting 'cached_object' member. */ - DBUG_ASSERT(!lock->cached_object); + DBUG_ASSERT(!m_lock->cached_object); - lock->cached_object= cached_object; - lock->cached_object_release_hook= release_hook; + m_lock->cached_object= cached_object; + m_lock->cached_object_release_hook= release_hook; DBUG_VOID_RETURN; } @@ -1654,13 +1664,12 @@ void mdl_set_cached_object(MDL_LOCK_TICKET *ticket, void *cached_object, @return Pointer to an opaque object associated with the lock. */ -void* mdl_get_cached_object(MDL_LOCK_TICKET *ticket) +void *MDL_ticket::get_cached_object() { - return ticket->lock->cached_object; + return m_lock->cached_object; } - /** Releases metadata locks that were acquired after a specific savepoint. @@ -1669,23 +1678,22 @@ void* mdl_get_cached_object(MDL_LOCK_TICKET *ticket) savepoint because other statements that take other special locks cause a implicit commit (ie LOCK TABLES). - @param thd Current thread - @param sv Savepoint + @param mdl_savepont The last acquired MDL lock when the + savepoint was set. */ -void mdl_rollback_to_savepoint(MDL_CONTEXT *ctx, - MDL_LOCK_TICKET *mdl_savepoint) +void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint) { - MDL_LOCK_TICKET *mdl_lock_ticket; - MDL_CONTEXT::Ticket_iterator it(ctx->tickets); - DBUG_ENTER("mdl_rollback_to_savepoint"); + MDL_ticket *ticket; + Ticket_iterator it(m_tickets); + DBUG_ENTER("MDL_context::rollback_to_savepoint"); - while ((mdl_lock_ticket= it++)) + while ((ticket= it++)) { /* Stop when lock was acquired before this savepoint. */ - if (mdl_lock_ticket == mdl_savepoint) + if (ticket == mdl_savepoint) break; - mdl_ticket_release(ctx, mdl_lock_ticket); + release_lock(ticket); } DBUG_VOID_RETURN; diff --git a/sql/mdl.h b/sql/mdl.h index b5d05b06144..cfa3e671706 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -23,10 +23,9 @@ class THD; -struct MDL_LOCK_REQUEST; -struct MDL_LOCK_TICKET; -struct MDL_LOCK; -struct MDL_CONTEXT; +class MDL_context; +class MDL_lock; +class MDL_ticket; /** Type of metadata lock request. @@ -62,7 +61,7 @@ enum enum_mdl_state { MDL_PENDING, MDL_ACQUIRED }; or "name". */ -class MDL_KEY +class MDL_key { public: const uchar *ptr() const { return (uchar*) m_ptr; } @@ -90,21 +89,34 @@ public: m_db_name_length= (uint) (strmov(m_ptr + 1, db) - m_ptr - 1); m_length= (uint) (strmov(m_ptr + m_db_name_length + 2, name) - m_ptr + 1); } - void mdl_key_init(const MDL_KEY *rhs) + void mdl_key_init(const MDL_key *rhs) { memcpy(m_ptr, rhs->m_ptr, rhs->m_length); m_length= rhs->m_length; m_db_name_length= rhs->m_db_name_length; } - bool is_equal(const MDL_KEY *rhs) const + bool is_equal(const MDL_key *rhs) const { return (m_length == rhs->m_length && memcmp(m_ptr, rhs->m_ptr, m_length) == 0); } + MDL_key(const MDL_key *rhs) + { + mdl_key_init(rhs); + } + MDL_key(char type_arg, const char *db_arg, const char *name_arg) + { + mdl_key_init(type_arg, db_arg, name_arg); + } + MDL_key() {} /* To use when part of MDL_request. */ + private: char m_ptr[MAX_MDLKEY_LENGTH]; uint m_length; uint m_db_name_length; +private: + MDL_key(const MDL_key &); /* not implemented */ + MDL_key &operator=(const MDL_key &); /* not implemented */ }; @@ -125,14 +137,18 @@ struct I_P_List_adapter /** A pending metadata lock request. - A pending lock request or a granted metadata lock share the same abstract - base but are presented individually because they have different allocation + + A lock request and a granted metadata lock are represented by + different classes because they have different allocation sites and hence different lifetimes. The allocation of lock requests is controlled from outside of the MDL subsystem, while allocation of granted locks (tickets) is controlled within the MDL subsystem. + + MDL_request is a C structure, you don't need to call a constructor + or destructor for it. */ -struct MDL_LOCK_REQUEST +struct MDL_request { /** Type of metadata lock. */ enum enum_mdl_type type; @@ -140,51 +156,94 @@ struct MDL_LOCK_REQUEST /** Pointers for participating in the list of lock requests for this context. */ - MDL_LOCK_REQUEST *next_in_context; - MDL_LOCK_REQUEST **prev_in_context; + MDL_request *next_in_context; + MDL_request **prev_in_context; /** A lock is requested based on a fully qualified name and type. */ - MDL_KEY key; + MDL_key key; + + void init(unsigned char type_arg, const char *db_arg, const char *name_arg); + /** Set type of lock request. Can be only applied to pending locks. */ + inline void set_type(enum_mdl_type type_arg) + { + DBUG_ASSERT(ticket == NULL); + type= type_arg; + } + bool is_shared() const { return type < MDL_EXCLUSIVE; } /** Pointer to the lock ticket object for this lock request. Valid only if this lock request is satisfied. */ - MDL_LOCK_TICKET *ticket; + MDL_ticket *ticket; + + static MDL_request *create(unsigned char type, const char *db, + const char *name, MEM_ROOT *root); + }; +typedef void (*mdl_cached_object_release_hook)(void *); + /** A granted metadata lock. - @warning MDL_LOCK_TICKET members are private to the MDL subsystem. + @warning MDL_ticket members are private to the MDL subsystem. @note Multiple shared locks on a same object are represented by a single ticket. The same does not apply for other lock types. */ -struct MDL_LOCK_TICKET +class MDL_ticket { - /** Type of metadata lock. */ - enum enum_mdl_type type; - /** State of the metadata lock ticket. */ - enum enum_mdl_state state; - +public: /** Pointers for participating in the list of lock requests for this context. */ - MDL_LOCK_TICKET *next_in_context; - MDL_LOCK_TICKET **prev_in_context; + MDL_ticket *next_in_context; + MDL_ticket **prev_in_context; /** Pointers for participating in the list of satisfied/pending requests for the lock. */ - MDL_LOCK_TICKET *next_in_lock; - MDL_LOCK_TICKET **prev_in_lock; + MDL_ticket *next_in_lock; + MDL_ticket **prev_in_lock; +public: + bool has_pending_conflicting_lock() const; + + void *get_cached_object(); + void set_cached_object(void *cached_object, + mdl_cached_object_release_hook release_hook); + const MDL_context *get_ctx() const { return m_ctx; } + bool is_shared() const { return m_type < MDL_EXCLUSIVE; } + bool upgrade_shared_lock_to_exclusive(); + void downgrade_exclusive_lock(); +private: + friend class MDL_context; + + MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg) + : m_type(type_arg), + m_state(MDL_PENDING), + m_ctx(ctx_arg), + m_lock(NULL) + {} + + + static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg); + static void destroy(MDL_ticket *ticket); +private: + /** Type of metadata lock. */ + enum enum_mdl_type m_type; + /** State of the metadata lock ticket. */ + enum enum_mdl_state m_state; + /** Context of the owner of the metadata lock ticket. */ - MDL_CONTEXT *ctx; + MDL_context *m_ctx; /** Pointer to the lock object for this lock ticket. */ - MDL_LOCK *lock; + MDL_lock *m_lock; +private: + MDL_ticket(const MDL_ticket &); /* not implemented */ + MDL_ticket &operator=(const MDL_ticket &); /* not implemented */ }; @@ -193,116 +252,87 @@ struct MDL_LOCK_TICKET connection has such a context. */ -struct MDL_CONTEXT +class MDL_context { - typedef I_P_List > +public: + typedef I_P_List > Request_list; typedef Request_list::Iterator Request_iterator; - typedef I_P_List > + typedef I_P_List > Ticket_list; typedef Ticket_list::Iterator Ticket_iterator; - Request_list requests; - Ticket_list tickets; - bool has_global_shared_lock; - THD *thd; + void init(THD *thd); + void destroy(); + + void backup_and_reset(MDL_context *backup); + void restore_from_backup(MDL_context *backup); + void merge(MDL_context *source); + + void add_request(MDL_request *mdl_request); + void remove_request(MDL_request *mdl_request); + void remove_all_requests(); + + bool acquire_shared_lock(MDL_request *mdl_request, bool *retry); + bool acquire_exclusive_locks(); + bool try_acquire_exclusive_lock(MDL_request *mdl_request, bool *conflict); + bool acquire_global_shared_lock(); + + bool wait_for_locks(); + + void release_all_locks(); + void release_all_locks_for_name(MDL_ticket *ticket); + void release_lock(MDL_ticket *ticket); + void release_global_shared_lock(); + + bool is_exclusive_lock_owner(unsigned char type, + const char *db, + const char *name); + bool is_lock_owner(unsigned char type, const char *db, const char *name); + + inline bool has_locks() const + { + return !m_tickets.is_empty(); + } + + inline MDL_ticket *mdl_savepoint() + { + return m_tickets.head(); + } + + void rollback_to_savepoint(MDL_ticket *mdl_savepoint); + + /** + Get iterator for walking through all lock requests in the context. + */ + inline Request_iterator get_requests() + { + return Request_iterator(m_requests); + } + inline THD *get_thd() const { return m_thd; } +private: + Request_list m_requests; + Ticket_list m_tickets; + bool m_has_global_shared_lock; + THD *m_thd; +private: + void release_ticket(MDL_ticket *ticket); + MDL_ticket *find_ticket(MDL_request *mdl_req); }; void mdl_init(); void mdl_destroy(); -void mdl_context_init(MDL_CONTEXT *context, THD *thd); -void mdl_context_destroy(MDL_CONTEXT *context); -void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); -void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup); -void mdl_context_merge(MDL_CONTEXT *target, MDL_CONTEXT *source); - -void mdl_request_init(MDL_LOCK_REQUEST *lock_req, unsigned char type, - const char *db, const char *name); -MDL_LOCK_REQUEST *mdl_request_alloc(unsigned char type, const char *db, - const char *name, MEM_ROOT *root); -void mdl_request_add(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req); -void mdl_request_remove(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req); -void mdl_request_remove_all(MDL_CONTEXT *context); - -/** - Set type of lock request. Can be only applied to pending locks. -*/ - -inline void mdl_request_set_type(MDL_LOCK_REQUEST *lock_req, enum_mdl_type lock_type) -{ - DBUG_ASSERT(lock_req->ticket == NULL); - lock_req->type= lock_type; -} - -bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_REQUEST *lock_req, - bool *retry); -bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context); -bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, - MDL_LOCK_TICKET *ticket); -bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_REQUEST *lock_req, - bool *conflict); -bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context); - -bool mdl_wait_for_locks(MDL_CONTEXT *context); - -void mdl_ticket_release_all(MDL_CONTEXT *context); -void mdl_ticket_release_all_for_name(MDL_CONTEXT *context, - MDL_LOCK_TICKET *ticket); -void mdl_ticket_release(MDL_CONTEXT *context, MDL_LOCK_TICKET *ticket); -void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context, - MDL_LOCK_TICKET *ticket); -void mdl_release_global_shared_lock(MDL_CONTEXT *context); - -bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, unsigned char type, - const char *db, const char *name); -bool mdl_is_lock_owner(MDL_CONTEXT *context, unsigned char type, - const char *db, const char *name); - -bool mdl_has_pending_conflicting_lock(MDL_LOCK_TICKET *ticket); - -inline bool mdl_has_locks(MDL_CONTEXT *context) -{ - return !context->tickets.is_empty(); -} - -inline MDL_LOCK_TICKET *mdl_savepoint(MDL_CONTEXT *ctx) -{ - return ctx->tickets.head(); -} - -void mdl_rollback_to_savepoint(MDL_CONTEXT *ctx, - MDL_LOCK_TICKET *mdl_savepoint); - -/** - Get iterator for walking through all lock requests in the context. -*/ - -inline MDL_CONTEXT::Request_iterator -mdl_get_requests(MDL_CONTEXT *ctx) -{ - MDL_CONTEXT::Request_iterator result(ctx->requests); - return result; -} - - -void mdl_get_tdc_key(MDL_LOCK_TICKET *ticket, LEX_STRING *key); -typedef void (* mdl_cached_object_release_hook)(void *); -void *mdl_get_cached_object(MDL_LOCK_TICKET *ticket); -void mdl_set_cached_object(MDL_LOCK_TICKET *ticket, void *cached_object, - mdl_cached_object_release_hook release_hook); - /* Functions in the server's kernel used by metadata locking subsystem. diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 0a703475aa7..695b160fc01 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -1205,7 +1205,7 @@ void Relay_log_info::clear_tables_to_lock() meta-data locks are stored. So we want to be sure that we don't have any references to this memory left. */ - DBUG_ASSERT(!mdl_has_locks(&(current_thd->mdl_context))); + DBUG_ASSERT(!current_thd->mdl_context.has_locks()); while (tables_to_lock) { diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 5732d7b7079..92019299f6c 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3981,10 +3981,10 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; - table->mdl_lock_request= mdl_request_alloc(0, table->db, table->table_name, - thd->locked_tables_root ? - thd->locked_tables_root : - thd->mem_root); + table->mdl_request= MDL_request::create(0, table->db, table->table_name, + thd->locked_tables_root ? + thd->locked_tables_root : + thd->mem_root); /* Everyting else should be zeroed */ @@ -4026,10 +4026,10 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->lock_type= locktype; table->select_lex= lex->current_select; table->cacheable_table= 1; - table->mdl_lock_request= mdl_request_alloc(0, table->db, table->table_name, - thd->locked_tables_root ? - thd->locked_tables_root : - thd->mem_root); + table->mdl_request= MDL_request::create(0, table->db, table->table_name, + thd->locked_tables_root ? + thd->locked_tables_root : + thd->mem_root); lex->add_to_query_tables(table); return table; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index f4f07ad08f7..0441458510f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -124,7 +124,7 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root); -static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context); +static bool tdc_wait_for_old_versions(THD *thd, MDL_context *context); static bool has_write_table_with_auto_increment(TABLE_LIST *tables); @@ -449,8 +449,8 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, To be able perform any operation on table we should own some kind of metadata lock on it. */ - DBUG_ASSERT(mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db, - table_list->table_name)); + DBUG_ASSERT(thd->mdl_context.is_lock_owner(0, table_list->db, + table_list->table_name)); /* Read table definition from cache */ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, @@ -1050,12 +1050,12 @@ err_with_reopen: */ thd->locked_tables_list.reopen_tables(thd); /* - Since mdl_downgrade_exclusive_lock() won't do anything with shared - metadata lock it is much simplier to go through all open tables rather + Since downgrade_exclusive_lock() won't do anything with shared + metadata lock it is much simpler to go through all open tables rather than picking only those tables that were flushed. */ for (TABLE *tab= thd->open_tables; tab; tab= tab->next) - mdl_downgrade_exclusive_lock(&thd->mdl_context, tab->mdl_lock_ticket); + tab->mdl_ticket->downgrade_exclusive_lock(); } DBUG_RETURN(result); } @@ -1336,7 +1336,7 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, */ void close_thread_tables(THD *thd, - bool skip_mdl) + bool is_back_off) { TABLE *table; DBUG_ENTER("close_thread_tables"); @@ -1478,10 +1478,10 @@ void close_thread_tables(THD *thd, if (thd->open_tables) close_open_tables(thd); - mdl_ticket_release_all(&thd->mdl_context); - if (!skip_mdl) + thd->mdl_context.release_all_locks(); + if (!is_back_off) { - mdl_request_remove_all(&thd->mdl_context); + thd->mdl_context.remove_all_requests(); } DBUG_VOID_RETURN; } @@ -1500,7 +1500,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) *table_ptr=table->next; - table->mdl_lock_ticket= NULL; + table->mdl_ticket= NULL; if (table->needs_reopen() || thd->version != refresh_version || !table->db_stat) { @@ -2095,8 +2095,7 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, old_lock_type= table->reginfo.lock_type; mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ - if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, - table->mdl_lock_ticket)) + if (table->mdl_ticket->upgrade_shared_lock_to_exclusive()) { mysql_lock_downgrade_write(thd, table, old_lock_type); DBUG_RETURN(TRUE); @@ -2279,11 +2278,11 @@ void table_share_release_hook(void *share) static bool open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, - MDL_LOCK_REQUEST *mdl_lock_request, + MDL_request *mdl_request, uint flags, enum_open_table_action *action) { - mdl_request_add(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.add_request(mdl_request); if (table_list->open_type) { @@ -2296,10 +2295,10 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, shared locks. This invariant is preserved here and is also enforced by asserts in metadata locking subsystem. */ - mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); - if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + mdl_request->set_type(MDL_EXCLUSIVE); + if (thd->mdl_context.acquire_exclusive_locks()) { - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.remove_request(mdl_request); return 1; } } @@ -2316,16 +2315,16 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL && table_list->lock_type >= TL_WRITE_ALLOW_WRITE) - mdl_request_set_type(mdl_lock_request, MDL_SHARED_UPGRADABLE); + mdl_request->set_type(MDL_SHARED_UPGRADABLE); if (flags & MYSQL_LOCK_IGNORE_FLUSH) - mdl_request_set_type(mdl_lock_request, MDL_SHARED_HIGH_PRIO); + mdl_request->set_type(MDL_SHARED_HIGH_PRIO); - if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_request, &retry)) + if (thd->mdl_context.acquire_shared_lock(mdl_request, &retry)) { if (retry) *action= OT_BACK_OFF_AND_RETRY; else - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.remove_request(mdl_request); return 1; } } @@ -2380,8 +2379,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, char key[MAX_DBKEY_LENGTH]; uint key_length; char *alias= table_list->alias; - MDL_LOCK_REQUEST *mdl_lock_request; - MDL_LOCK_TICKET *mdl_lock_ticket; + MDL_request *mdl_request; + MDL_ticket *mdl_ticket; int error; TABLE_SHARE *share; DBUG_ENTER("open_table"); @@ -2517,8 +2516,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, TABLES breaks metadata locking protocol (potentially can lead to deadlocks) it should be disallowed. */ - if (mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db, - table_list->table_name)) + if (thd->mdl_context.is_lock_owner(0, table_list->db, + table_list->table_name)) { char path[FN_REFLEN + 1]; enum legacy_db_type not_used; @@ -2560,10 +2559,10 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, This is the normal use case. */ - mdl_lock_request= table_list->mdl_lock_request; + mdl_request= table_list->mdl_request; if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { - if (open_table_get_mdl_lock(thd, table_list, mdl_lock_request, flags, + if (open_table_get_mdl_lock(thd, table_list, mdl_request, flags, action)) DBUG_RETURN(TRUE); } @@ -2573,7 +2572,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, open_table_get_mdl_lock as the lock on the table might have been acquired previously (MYSQL_OPEN_HAS_MDL_LOCK). */ - mdl_lock_ticket= mdl_lock_request->ticket; + mdl_ticket= mdl_request->ticket; pthread_mutex_lock(&LOCK_open); @@ -2616,7 +2615,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, DBUG_RETURN(FALSE); } - if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock_ticket))) + if (!(share= (TABLE_SHARE *) mdl_ticket->get_cached_object())) { if (!(share= get_table_share_with_create(thd, table_list, key, key_length, OPEN_VIEW, @@ -2687,7 +2686,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, so we need to increase reference counter; */ reference_table_share(share); - mdl_set_cached_object(mdl_lock_ticket, share, table_share_release_hook); + mdl_ticket->set_cached_object(share, table_share_release_hook); } else { @@ -2796,9 +2795,9 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, lock on this table to shared metadata lock. */ if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE) - mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_ticket); + mdl_ticket->downgrade_exclusive_lock(); - table->mdl_lock_ticket= mdl_lock_ticket; + table->mdl_ticket= mdl_ticket; table->next=thd->open_tables; /* Link into simple list */ thd->open_tables=table; @@ -2850,8 +2849,8 @@ err_unlock2: pthread_mutex_unlock(&LOCK_open); if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { - mdl_ticket_release(&thd->mdl_context, mdl_lock_ticket); - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.release_lock(mdl_ticket); + thd->mdl_context.remove_request(mdl_request); } DBUG_RETURN(TRUE); } @@ -2969,7 +2968,7 @@ Locked_tables_list::init_locked_tables(THD *thd) dst_table_list->init_one_table(db, db_len, table_name, table_name_len, alias, src_table_list->table->reginfo.lock_type); - dst_table_list->mdl_lock_request= src_table_list->mdl_lock_request; + dst_table_list->mdl_request= src_table_list->mdl_request; dst_table_list->table= table; memcpy(db, src_table_list->db, db_len + 1); memcpy(table_name, src_table_list->table_name, table_name_len + 1); @@ -3020,8 +3019,6 @@ Locked_tables_list::unlock_locked_tables(THD *thd) thd->locked_tables_mode= LTM_NONE; close_thread_tables(thd); - - mdl_ticket_release_all(&thd->mdl_context); } /* After closing tables we can free memory used for storing lock @@ -3506,21 +3503,21 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, enum_open_table_action action) { bool result= FALSE; - MDL_LOCK_REQUEST *mdl_lock_request= table->mdl_lock_request; + MDL_request *mdl_request= table->mdl_request; switch (action) { case OT_BACK_OFF_AND_RETRY: - result= (mdl_wait_for_locks(&thd->mdl_context) || + result= (thd->mdl_context.wait_for_locks() || tdc_wait_for_old_versions(thd, &thd->mdl_context)); - mdl_request_remove_all(&thd->mdl_context); + thd->mdl_context.remove_all_requests(); break; case OT_DISCOVER: - mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); - mdl_request_add(&thd->mdl_context, mdl_lock_request); - if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + mdl_request->set_type(MDL_EXCLUSIVE); + thd->mdl_context.add_request(mdl_request); + if (thd->mdl_context.acquire_exclusive_locks()) { - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.remove_request(mdl_request); return TRUE; } pthread_mutex_lock(&LOCK_open); @@ -3530,15 +3527,15 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, thd->warning_info->clear_warning_info(thd->query_id); thd->clear_error(); // Clear error message - mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.release_lock(mdl_request->ticket); + thd->mdl_context.remove_request(mdl_request); break; case OT_REPAIR: - mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); - mdl_request_add(&thd->mdl_context, mdl_lock_request); - if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + mdl_request->set_type(MDL_EXCLUSIVE); + thd->mdl_context.add_request(mdl_request); + if (thd->mdl_context.acquire_exclusive_locks()) { - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.remove_request(mdl_request); return TRUE; } pthread_mutex_lock(&LOCK_open); @@ -3546,8 +3543,8 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, pthread_mutex_unlock(&LOCK_open); result= auto_repair_table(thd, table); - mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.release_lock(mdl_request->ticket); + thd->mdl_context.remove_request(mdl_request); break; default: DBUG_ASSERT(0); @@ -4652,7 +4649,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, */ -void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl) +void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool is_back_off) { /* If table list consists only from tables from prelocking set, table list @@ -4664,7 +4661,7 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl) sp_remove_not_own_routines(thd->lex); for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global) tmp->table= 0; - close_thread_tables(thd, skip_mdl); + close_thread_tables(thd, is_back_off); } @@ -7691,8 +7688,7 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, safe_mutex_assert_owner(&LOCK_open); DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || - mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, - db, table_name)); + thd->mdl_context.is_exclusive_lock_owner(0, db, table_name)); key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; @@ -7739,11 +7735,11 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, @param context Metadata locking context with locks. */ -static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context) +static bool tdc_wait_for_old_versions(THD *thd, MDL_context *mdl_context) { TABLE_SHARE *share; const char *old_msg; - MDL_LOCK_REQUEST *lock_req; + MDL_request *mdl_request; while (!thd->killed) { @@ -7756,16 +7752,16 @@ static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context) mysql_ha_flush(thd); pthread_mutex_lock(&LOCK_open); - MDL_CONTEXT::Request_iterator it= mdl_get_requests(context); - while ((lock_req= it++)) + MDL_context::Request_iterator it= mdl_context->get_requests(); + while ((mdl_request= it++)) { - if ((share= get_cached_table_share(lock_req->key.db_name(), - lock_req->key.table_name())) && + if ((share= get_cached_table_share(mdl_request->key.db_name(), + mdl_request->key.table_name())) && share->version != refresh_version && !share->used_tables.is_empty()) break; } - if (!lock_req) + if (!mdl_request) { pthread_mutex_unlock(&LOCK_open); break; @@ -8169,8 +8165,8 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup) pthread_mutex_unlock(&LOCK_open); - mdl_ticket_release_all(&thd->mdl_context); - mdl_request_remove_all(&thd->mdl_context); + thd->mdl_context.release_all_locks(); + thd->mdl_context.remove_all_requests(); thd->restore_backup_open_tables_state(backup); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 4f1777c152c..654d7ad718d 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1050,8 +1050,8 @@ THD::~THD() if (!cleanup_done) cleanup(); - mdl_context_destroy(&mdl_context); - mdl_context_destroy(&handler_mdl_context); + mdl_context.destroy(); + handler_mdl_context.destroy(); ha_close_connection(this); plugin_thdvar_cleanup(this); @@ -3033,8 +3033,8 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup) lock == 0 && locked_tables_mode == LTM_NONE && m_reprepare_observer == NULL); - mdl_context_destroy(&mdl_context); - mdl_context_destroy(&handler_mdl_context); + mdl_context.destroy(); + handler_mdl_context.destroy(); set_open_tables_state(backup); DBUG_VOID_RETURN; diff --git a/sql/sql_class.h b/sql/sql_class.h index 80817fa4c12..92b9f9f4611 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -780,7 +780,7 @@ struct st_savepoint { uint length; Ha_trx_info *ha_list; /** Last acquired lock before this savepoint was set. */ - MDL_LOCK_TICKET *mdl_savepoint; + MDL_ticket *mdl_savepoint; }; enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED, XA_ROLLBACK_ONLY}; @@ -983,8 +983,8 @@ public: */ uint state_flags; - MDL_CONTEXT mdl_context; - MDL_CONTEXT handler_mdl_context; + MDL_context mdl_context; + MDL_context handler_mdl_context; /** This constructor initializes Open_tables_state instance which can only @@ -1015,8 +1015,8 @@ public: locked_tables_mode= LTM_NONE; state_flags= 0U; m_reprepare_observer= NULL; - mdl_context_init(&mdl_context, thd); - mdl_context_init(&handler_mdl_context, thd); + mdl_context.init(thd); + handler_mdl_context.init(thd); } }; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index c96594a8032..fc92dbee0a1 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1100,7 +1100,7 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) TABLE *table; bool error; uint path_length; - MDL_LOCK_REQUEST *mdl_lock_request= NULL; + MDL_request *mdl_request= NULL; DBUG_ENTER("mysql_truncate"); bzero((char*) &create_info,sizeof(create_info)); @@ -1175,13 +1175,13 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) tries to get table enging and therefore accesses table in some way without holding any kind of meta-data lock. */ - mdl_lock_request= mdl_request_alloc(0, table_list->db, - table_list->table_name, thd->mem_root); - mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); - mdl_request_add(&thd->mdl_context, mdl_lock_request); - if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + mdl_request= MDL_request::create(0, table_list->db, + table_list->table_name, thd->mem_root); + mdl_request->set_type(MDL_EXCLUSIVE); + thd->mdl_context.add_request(mdl_request); + if (thd->mdl_context.acquire_exclusive_locks()) { - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.remove_request(mdl_request); DBUG_RETURN(TRUE); } pthread_mutex_lock(&LOCK_open); @@ -1212,18 +1212,18 @@ end: write_bin_log(thd, TRUE, thd->query(), thd->query_length()); my_ok(thd); // This should return record count } - if (mdl_lock_request) + if (mdl_request) { - mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.release_lock(mdl_request->ticket); + thd->mdl_context.remove_request(mdl_request); } } else if (error) { - if (mdl_lock_request) + if (mdl_request) { - mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.release_lock(mdl_request->ticket); + thd->mdl_context.remove_request(mdl_request); } } DBUG_RETURN(error); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 83c5c60dc01..4b07a00c779 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -125,7 +125,7 @@ static void mysql_ha_hash_free(TABLE_LIST *tables) static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) { TABLE **table_ptr; - MDL_LOCK_TICKET *mdl_lock_ticket; + MDL_ticket *mdl_ticket; /* Though we could take the table pointer from hash_tables->table, @@ -141,7 +141,7 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) if (*table_ptr) { (*table_ptr)->file->ha_index_or_rnd_end(); - mdl_lock_ticket= (*table_ptr)->mdl_lock_ticket; + mdl_ticket= (*table_ptr)->mdl_ticket; pthread_mutex_lock(&LOCK_open); if (close_thread_table(thd, table_ptr)) { @@ -149,8 +149,8 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) broadcast_refresh(); } pthread_mutex_unlock(&LOCK_open); - mdl_ticket_release(&thd->handler_mdl_context, mdl_lock_ticket); - mdl_request_remove(&thd->handler_mdl_context, tables->mdl_lock_request); + thd->handler_mdl_context.release_lock(mdl_ticket); + thd->handler_mdl_context.remove_request(tables->mdl_request); } else if (tables->table) { @@ -194,8 +194,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) uint dblen, namelen, aliaslen, counter; int error; TABLE *backup_open_tables; - MDL_CONTEXT backup_mdl_context; - MDL_LOCK_REQUEST *mdl_lock_request; + MDL_context backup_mdl_context; + MDL_request *mdl_request; DBUG_ENTER("mysql_ha_open"); DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", tables->db, tables->table_name, tables->alias, @@ -246,7 +246,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) &db, (uint) dblen, &name, (uint) namelen, &alias, (uint) aliaslen, - &mdl_lock_request, sizeof(MDL_LOCK_REQUEST), + &mdl_request, sizeof(MDL_request), NullS))) { DBUG_PRINT("exit",("ERROR")); @@ -260,8 +260,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) memcpy(hash_tables->db, tables->db, dblen); memcpy(hash_tables->table_name, tables->table_name, namelen); memcpy(hash_tables->alias, tables->alias, aliaslen); - mdl_request_init(mdl_lock_request, 0, db, name); - hash_tables->mdl_lock_request= mdl_lock_request; + mdl_request->init(0, db, name); + hash_tables->mdl_request= mdl_request; /* add to hash */ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) @@ -289,7 +289,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) */ backup_open_tables= thd->open_tables; thd->open_tables= NULL; - mdl_context_backup_and_reset(&thd->mdl_context, &backup_mdl_context); + thd->mdl_context.backup_and_reset(&backup_mdl_context); /* open_tables() will set 'hash_tables->table' if successful. @@ -328,10 +328,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) thd->handler_tables= thd->open_tables; } } - mdl_context_merge(&thd->handler_mdl_context, &thd->mdl_context); + thd->handler_mdl_context.merge(&thd->mdl_context); thd->open_tables= backup_open_tables; - mdl_context_restore(&thd->mdl_context, &backup_mdl_context); + thd->mdl_context.restore_from_backup(&backup_mdl_context); if (error) goto err; @@ -800,11 +800,11 @@ void mysql_ha_flush(THD *thd) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); /* - TABLE::mdl_lock_ticket is 0 for temporary tables so we need extra check. + TABLE::mdl_ticket is 0 for temporary tables so we need extra check. */ if (hash_tables->table && - (hash_tables->table->mdl_lock_ticket && - mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock_ticket) || + (hash_tables->table->mdl_ticket && + hash_tables->table->mdl_ticket->has_pending_conflicting_lock() || hash_tables->table->needs_reopen())) mysql_ha_close_table(thd, hash_tables); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 4166267c5a9..579fbcea4ea 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -5977,9 +5977,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); - ptr->mdl_lock_request= - mdl_request_alloc(0, ptr->db, ptr->table_name, thd->locked_tables_root ? - thd->locked_tables_root : thd->mem_root); + ptr->mdl_request= + MDL_request::create(0, ptr->db, ptr->table_name, thd->locked_tables_root ? + thd->locked_tables_root : thd->mem_root); DBUG_RETURN(ptr); } diff --git a/sql/sql_plist.h b/sql/sql_plist.h index 91fa9ef52bb..b0a0bb016d0 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -79,6 +79,7 @@ public: **B::prev_ptr(a)= next; } inline T* head() { return first; } + inline const T *head() const { return first; } void swap(I_P_List &rhs) { swap_variables(T *, first, rhs.first); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 440efbf7c6d..9727498cb2e 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3050,7 +3050,7 @@ uint get_table_open_method(TABLE_LIST *tables, Acquire high priority share metadata lock on a table. @param thd Thread context. - @param mdl_lock_req Pointer to memory to be used for MDL_LOCK_REQUEST + @param mdl_request Pointer to memory to be used for MDL_request object for a lock request. @param table Table list element for the table @@ -3065,23 +3065,23 @@ uint get_table_open_method(TABLE_LIST *tables, */ static bool -acquire_high_prio_shared_mdl_lock(THD *thd, MDL_LOCK_REQUEST *mdl_lock_req, +acquire_high_prio_shared_mdl_lock(THD *thd, MDL_request *mdl_request, TABLE_LIST *table) { bool retry; - mdl_request_init(mdl_lock_req, 0, table->db, table->table_name); - table->mdl_lock_request= mdl_lock_req; - mdl_request_add(&thd->mdl_context, mdl_lock_req); - mdl_request_set_type(mdl_lock_req, MDL_SHARED_HIGH_PRIO); + mdl_request->init(0, table->db, table->table_name); + table->mdl_request= mdl_request; + thd->mdl_context.add_request(mdl_request); + mdl_request->set_type(MDL_SHARED_HIGH_PRIO); while (1) { - if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_req, &retry)) + if (thd->mdl_context.acquire_shared_lock(mdl_request, &retry)) { - if (!retry || mdl_wait_for_locks(&thd->mdl_context)) + if (!retry || thd->mdl_context.wait_for_locks()) { - mdl_request_remove_all(&thd->mdl_context); + thd->mdl_context.remove_all_requests(); return TRUE; } continue; @@ -3123,7 +3123,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, char key[MAX_DBKEY_LENGTH]; uint key_length; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; - MDL_LOCK_REQUEST mdl_lock_request; + MDL_request mdl_request; bzero((char*) &table_list, sizeof(TABLE_LIST)); bzero((char*) &tbl, sizeof(TABLE)); @@ -3153,7 +3153,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, simply obtaining internal lock of data-dictionary (ATM it is LOCK_open) instead of obtaning full-blown metadata lock. */ - if (acquire_high_prio_shared_mdl_lock(thd, &mdl_lock_request, &table_list)) + if (acquire_high_prio_shared_mdl_lock(thd, &mdl_request, &table_list)) { /* Some error occured (most probably we have been killed while @@ -3213,9 +3213,8 @@ err_share: err_unlock: pthread_mutex_unlock(&LOCK_open); -err: - mdl_ticket_release(&thd->mdl_context, mdl_lock_request.ticket); - mdl_request_remove(&thd->mdl_context, &mdl_lock_request); + thd->mdl_context.release_lock(mdl_request.ticket); + thd->mdl_context.remove_request(&mdl_request); thd->clear_error(); return res; } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 54f06935d3b..42ae0a89458 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1910,7 +1910,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, Since we don't acquire metadata lock if we have found temporary table, we should do something to avoid releasing it at the end. */ - table->mdl_lock_request= NULL; + table->mdl_request= NULL; } else { @@ -1923,7 +1923,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->table_name); if (!table->table) DBUG_RETURN(1); - table->mdl_lock_request->ticket= table->table->mdl_lock_ticket; + table->mdl_request->ticket= table->table->mdl_ticket; } } } @@ -2202,15 +2202,14 @@ err: } for (table= tables; table; table= table->next_local) { - if (table->mdl_lock_request) + if (table->mdl_request) { /* Under LOCK TABLES we may have several instances of table open and locked and therefore have to remove several metadata lock requests associated with them. */ - mdl_ticket_release_all_for_name(&thd->mdl_context, - table->mdl_lock_request->ticket); + thd->mdl_context.release_all_locks_for_name(table->mdl_request->ticket); } } } @@ -4108,28 +4107,28 @@ warn: static bool lock_table_name_if_not_cached(THD *thd, const char *db, const char *table_name, - MDL_LOCK_REQUEST **lock_req) + MDL_request **mdl_request) { bool conflict; - if (!(*lock_req= mdl_request_alloc(0, db, table_name, thd->mem_root))) + if (!(*mdl_request= MDL_request::create(0, db, table_name, thd->mem_root))) return TRUE; - mdl_request_set_type(*lock_req, MDL_EXCLUSIVE); - mdl_request_add(&thd->mdl_context, *lock_req); - if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock_req, &conflict)) + (*mdl_request)->set_type(MDL_EXCLUSIVE); + thd->mdl_context.add_request(*mdl_request); + if (thd->mdl_context.try_acquire_exclusive_lock(*mdl_request, &conflict)) { /* To simplify our life under LOCK TABLES we remove unsatisfied lock request from the context. */ - mdl_request_remove(&thd->mdl_context, *lock_req); + thd->mdl_context.remove_request(*mdl_request); if (!conflict) { /* Probably OOM. */ return TRUE; } else - *lock_req= NULL; + *mdl_request= NULL; } return FALSE; } @@ -4145,7 +4144,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, bool internal_tmp_table, uint select_field_count) { - MDL_LOCK_REQUEST *target_lock_req= NULL; + MDL_request *target_mdl_request= NULL; bool result; DBUG_ENTER("mysql_create_table"); @@ -4168,12 +4167,12 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock_req)) + if (lock_table_name_if_not_cached(thd, db, table_name, &target_mdl_request)) { result= TRUE; goto unlock; } - if (!target_lock_req) + if (!target_mdl_request) { if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) { @@ -4199,10 +4198,10 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, select_field_count); unlock: - if (target_lock_req) + if (target_mdl_request) { - mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); - mdl_request_remove(&thd->mdl_context, target_lock_req); + thd->mdl_context.release_lock(target_mdl_request->ticket); + thd->mdl_context.remove_request(target_mdl_request); } pthread_mutex_lock(&LOCK_lock_db); if (!--creating_table && creating_database) @@ -4367,7 +4366,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, char from[FN_REFLEN],tmp[FN_REFLEN+32]; const char **ext; MY_STAT stat_info; - MDL_LOCK_REQUEST *mdl_lock_request= NULL; + MDL_request *mdl_request= NULL; enum enum_open_table_action ot_action_unused; DBUG_ENTER("prepare_for_repair"); uint reopen_for_repair_flags= (MYSQL_LOCK_IGNORE_FLUSH | @@ -4386,13 +4385,13 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, uint key_length; key_length= create_table_def_key(thd, key, table_list, 0); - mdl_lock_request= mdl_request_alloc(0, table_list->db, - table_list->table_name, thd->mem_root); - mdl_request_set_type(mdl_lock_request, MDL_EXCLUSIVE); - mdl_request_add(&thd->mdl_context, mdl_lock_request); - if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + mdl_request= MDL_request::create(0, table_list->db, + table_list->table_name, thd->mem_root); + mdl_request->set_type(MDL_EXCLUSIVE); + thd->mdl_context.add_request(mdl_request); + if (thd->mdl_context.acquire_exclusive_locks()) { - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.remove_request(mdl_request); DBUG_RETURN(0); } @@ -4412,7 +4411,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, } pthread_mutex_unlock(&LOCK_open); table= &tmp_table; - table_list->mdl_lock_request= mdl_lock_request; + table_list->mdl_request= mdl_request; } /* A MERGE table must not come here. */ @@ -4523,10 +4522,10 @@ end: pthread_mutex_unlock(&LOCK_open); } /* In case of a temporary table there will be no metadata lock. */ - if (error && mdl_lock_request) + if (error && mdl_request) { - mdl_ticket_release(&thd->mdl_context, mdl_lock_request->ticket); - mdl_request_remove(&thd->mdl_context, mdl_lock_request); + thd->mdl_context.release_lock(mdl_request->ticket); + thd->mdl_context.remove_request(mdl_request); } DBUG_RETURN(error); } @@ -5229,7 +5228,7 @@ bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO *create_info) { - MDL_LOCK_REQUEST *target_lock_req= NULL; + MDL_request *target_mdl_request= NULL; char src_path[FN_REFLEN], dst_path[FN_REFLEN + 1]; uint dst_path_length; char *db= table->db; @@ -5286,9 +5285,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, } else { - if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock_req)) + if (lock_table_name_if_not_cached(thd, db, table_name, &target_mdl_request)) goto err; - if (!target_lock_req) + if (!target_mdl_request) goto table_exists; dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1, db, table_name, reg_ext, 0); @@ -5298,7 +5297,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, Make the metadata lock available to open_table() called to reopen the table down the road. */ - table->mdl_lock_request= target_lock_req; + table->mdl_request= target_mdl_request; } DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000);); @@ -5469,10 +5468,10 @@ binlog: res= FALSE; err: - if (target_lock_req) + if (target_mdl_request) { - mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); - mdl_request_remove(&thd->mdl_context, target_lock_req); + thd->mdl_context.release_lock(target_mdl_request->ticket); + thd->mdl_context.remove_request(target_mdl_request); } DBUG_RETURN(res); } @@ -6411,8 +6410,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, uint order_num, ORDER *order, bool ignore) { TABLE *table, *new_table= 0; - MDL_LOCK_TICKET *mdl_lock_ticket; - MDL_LOCK_REQUEST *target_lock_req= NULL; + MDL_ticket *mdl_ticket; + MDL_request *target_mdl_request= NULL; int error= 0; char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1]; char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias; @@ -6583,7 +6582,7 @@ view_err: MYSQL_OPEN_TAKE_UPGRADABLE_MDL))) DBUG_RETURN(TRUE); table->use_all_columns(); - mdl_lock_ticket= table->mdl_lock_ticket; + mdl_ticket= table->mdl_ticket; /* Prohibit changing of the UNION list of a non-temporary MERGE table @@ -6636,9 +6635,9 @@ view_err: else { if (lock_table_name_if_not_cached(thd, new_db, new_name, - &target_lock_req)) + &target_mdl_request)) DBUG_RETURN(TRUE); - if (!target_lock_req) + if (!target_mdl_request) { my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); DBUG_RETURN(TRUE); @@ -6831,12 +6830,12 @@ view_err: */ if (new_name != table_name || new_db != db) { - mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); - mdl_request_remove(&thd->mdl_context, target_lock_req); - mdl_ticket_release_all_for_name(&thd->mdl_context, mdl_lock_ticket); + thd->mdl_context.release_lock(target_mdl_request->ticket); + thd->mdl_context.remove_request(target_mdl_request); + thd->mdl_context.release_all_locks_for_name(mdl_ticket); } else - mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_ticket); + mdl_ticket->downgrade_exclusive_lock(); } DBUG_RETURN(error); } @@ -7069,7 +7068,7 @@ view_err: #ifdef WITH_PARTITION_STORAGE_ENGINE if (fast_alter_partition) { - DBUG_ASSERT(!target_lock_req); + DBUG_ASSERT(!target_mdl_request); DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, create_info, table_list, db, table_name, @@ -7436,7 +7435,7 @@ view_err: table_list->table_name_length= strlen(new_name); table_list->db= new_db; table_list->db_length= strlen(new_db); - table_list->mdl_lock_request= target_lock_req; + table_list->mdl_request= target_mdl_request; } else { @@ -7445,7 +7444,7 @@ view_err: points to a different instance than the one set initially to request the lock. */ - table_list->mdl_lock_request->ticket= mdl_lock_ticket; + table_list->mdl_request->ticket= mdl_ticket; } if (open_table(thd, table_list, thd->mem_root, &ot_action_unused, MYSQL_OPEN_REOPEN)) @@ -7511,12 +7510,12 @@ view_err: { if ((new_name != table_name || new_db != db)) { - mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); - mdl_request_remove(&thd->mdl_context, target_lock_req); - mdl_ticket_release_all_for_name(&thd->mdl_context, mdl_lock_ticket); + thd->mdl_context.release_lock(target_mdl_request->ticket); + thd->mdl_context.remove_request(target_mdl_request); + thd->mdl_context.release_all_locks_for_name(mdl_ticket); } else - mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_ticket); + mdl_ticket->downgrade_exclusive_lock(); } end_temporary: @@ -7571,10 +7570,10 @@ err: alter_info->datetime_field->field_name); thd->abort_on_warning= save_abort_on_warning; } - if (target_lock_req) + if (target_mdl_request) { - mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); - mdl_request_remove(&thd->mdl_context, target_lock_req); + thd->mdl_context.release_lock(target_mdl_request->ticket); + thd->mdl_context.remove_request(target_mdl_request); } DBUG_RETURN(TRUE); @@ -7586,12 +7585,12 @@ err_with_mdl: tables and release the exclusive metadata lock. */ thd->locked_tables_list.unlink_all_closed_tables(); - if (target_lock_req) + if (target_mdl_request) { - mdl_ticket_release(&thd->mdl_context, target_lock_req->ticket); - mdl_request_remove(&thd->mdl_context, target_lock_req); + thd->mdl_context.release_lock(target_mdl_request->ticket); + thd->mdl_context.remove_request(target_mdl_request); } - mdl_ticket_release_all_for_name(&thd->mdl_context, mdl_lock_ticket); + thd->mdl_context.release_all_locks_for_name(mdl_ticket); DBUG_RETURN(TRUE); } /* mysql_alter_table */ diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 894204fec58..93b89b869d6 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -329,7 +329,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) String stmt_query; bool need_start_waiting= FALSE; bool lock_upgrade_done= FALSE; - MDL_LOCK_TICKET *mdl_lock_ticket= NULL; + MDL_ticket *mdl_ticket= NULL; DBUG_ENTER("mysql_create_or_drop_trigger"); @@ -465,7 +465,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) table= tables->table; /* Later on we will need it to downgrade the lock */ - mdl_lock_ticket= table->mdl_lock_ticket; + mdl_ticket= table->mdl_ticket; if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto end; @@ -513,7 +513,7 @@ end: TABLE instance created by open_n_lock_single_table() and metadata lock. */ if (thd->locked_tables_mode && tables && lock_upgrade_done) - mdl_downgrade_exclusive_lock(&thd->mdl_context, mdl_lock_ticket); + mdl_ticket->downgrade_exclusive_lock(); if (need_start_waiting) start_waiting_global_read_lock(thd); @@ -1882,7 +1882,7 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, In the future, only an exclusive metadata lock will be enough. */ #ifndef DBUG_OFF - if (mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, db, old_table)) + if (thd->mdl_context.is_exclusive_lock_owner(0, db, old_table)) safe_mutex_assert_owner(&LOCK_open); #endif diff --git a/sql/table.cc b/sql/table.cc index b1988faf722..8d28514e912 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4814,8 +4814,8 @@ size_t max_row_length(TABLE *table, const uchar *data) void alloc_mdl_requests(TABLE_LIST *table_list, MEM_ROOT *root) { for ( ; table_list ; table_list= table_list->next_global) - table_list->mdl_lock_request= - mdl_request_alloc(0, table_list->db, table_list->table_name, root); + table_list->mdl_request= + MDL_request::create(0, table_list->db, table_list->table_name, root); } diff --git a/sql/table.h b/sql/table.h index 3b125217338..02492ea4e81 100644 --- a/sql/table.h +++ b/sql/table.h @@ -30,8 +30,8 @@ class st_select_lex; class partition_info; class COND_EQUAL; class Security_context; -struct MDL_LOCK_REQUEST; -struct MDL_LOCK_TICKET; +struct MDL_request; +struct MDL_ticket; /*************************************************************************/ @@ -814,7 +814,7 @@ public: partition_info *part_info; /* Partition related information */ bool no_partitions_used; /* If true, all partitions have been pruned away */ #endif - MDL_LOCK_TICKET *mdl_lock_ticket; + MDL_ticket *mdl_ticket; bool fill_item_list(List *item_list) const; void reset_item_list(List *item_list) const; @@ -1417,7 +1417,7 @@ struct TABLE_LIST uint table_open_method; enum enum_schema_table_state schema_table_state; - MDL_LOCK_REQUEST *mdl_lock_request; + MDL_request *mdl_request; void calc_md5(char *buffer); void set_underlying_merge(); diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index b6992256d5a..21a7e2c43d3 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -434,7 +434,7 @@ int ha_myisammrg::add_children_list(void) /* Copy select_lex. Used in unique_table() at least. */ child_l->select_lex= parent_l->select_lex; - child_l->mdl_lock_request= NULL; /* Safety, if alloc_mdl_requests fails. */ + child_l->mdl_request= NULL; /* Safety, if alloc_mdl_requests fails. */ /* Break when this was the last child. */ if (&child_l->next_global == this->children_last_l) From f5f708d4e122e8d6dbfffea7390978645c40dc11 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 4 Dec 2009 02:55:26 +0300 Subject: [PATCH 068/212] Backport of (WL#4284): ------------------------------------------------------------ revno: 2617.23.23 committer: Davi Arnaut branch nick: mysql-6.0-runtime timestamp: Thu 2009-03-05 18:39:58 -0300 message: Fix for broken build: SHARED is defined by Solaris headers. sql/mdl.cc: Add MDL_LOCK prefix to the lock types of MDL_LOCK. --- sql/mdl.cc | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index 101d90d7e42..1d591bb2244 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -42,8 +42,8 @@ public: /** The type of lock (shared or exclusive). */ enum { - SHARED, - EXCLUSIVE, + MDL_LOCK_SHARED, + MDL_LOCK_EXCLUSIVE, } type; /** The key of the object (data) being protected. */ MDL_key key; @@ -71,7 +71,7 @@ public: inline static void destroy(MDL_lock *lock); private: MDL_lock(const MDL_key *key_arg) - : type(SHARED), + : type(MDL_LOCK_SHARED), key(key_arg), cached_object(NULL), cached_object_release_hook(NULL) @@ -673,7 +673,7 @@ MDL_lock::can_grant_lock(const MDL_context *requestor_ctx, enum_mdl_type type_ar case MDL_SHARED: case MDL_SHARED_UPGRADABLE: case MDL_SHARED_HIGH_PRIO: - if (type == MDL_lock::SHARED) + if (type == MDL_lock::MDL_LOCK_SHARED) { /* Pending exclusive locks have higher priority over shared locks. */ if (waiting.is_empty() || type_arg == MDL_SHARED_HIGH_PRIO) @@ -700,7 +700,7 @@ MDL_lock::can_grant_lock(const MDL_context *requestor_ctx, enum_mdl_type type_ar There should be no active exclusive locks since we own shared lock on the object. */ - DBUG_ASSERT(type == MDL_lock::SHARED); + DBUG_ASSERT(type == MDL_lock::MDL_LOCK_SHARED); while ((conflicting_ticket= it++)) { @@ -717,7 +717,7 @@ MDL_lock::can_grant_lock(const MDL_context *requestor_ctx, enum_mdl_type type_ar can_grant= TRUE; break; } - else if (type == MDL_lock::SHARED) + else if (type == MDL_lock::MDL_LOCK_SHARED) { can_grant= granted.is_empty(); } @@ -829,7 +829,7 @@ MDL_context::acquire_shared_lock(MDL_request *mdl_request, bool *retry) if (!(lock= (MDL_lock*) my_hash_search(&mdl_locks, key->ptr(), key->length()))) { - /* Default lock type is MDL_lock::SHARED */ + /* Default lock type is MDL_lock::MDL_LOCK_SHARED */ lock= MDL_lock::create(key); if (!lock || my_hash_insert(&mdl_locks, (uchar*)lock)) { @@ -972,7 +972,7 @@ bool MDL_context::acquire_exclusive_locks() MDL_ticket *conflicting_ticket; MDL_lock::Ticket_iterator it(lock->granted); - signalled= (lock->type == MDL_lock::EXCLUSIVE); + signalled= (lock->type == MDL_lock::MDL_LOCK_EXCLUSIVE); while ((conflicting_ticket= it++)) signalled|= notify_shared_lock(m_thd, conflicting_ticket); @@ -1009,7 +1009,7 @@ bool MDL_context::acquire_exclusive_locks() global_lock.active_intention_exclusive++; ticket= mdl_request->ticket; lock= ticket->m_lock; - lock->type= MDL_lock::EXCLUSIVE; + lock->type= MDL_lock::MDL_LOCK_EXCLUSIVE; lock->waiting.remove(ticket); lock->granted.push_front(ticket); m_tickets.push_front(ticket); @@ -1135,7 +1135,7 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() } } - m_lock->type= MDL_lock::EXCLUSIVE; + m_lock->type= MDL_lock::MDL_LOCK_EXCLUSIVE; /* Set the new type of lock in the ticket. */ m_type= MDL_EXCLUSIVE; if (m_lock->cached_object) @@ -1200,7 +1200,7 @@ MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request, goto err; } mdl_request->ticket= ticket; - lock->type= MDL_lock::EXCLUSIVE; + lock->type= MDL_lock::MDL_LOCK_EXCLUSIVE; lock->granted.push_front(ticket); m_tickets.push_front(ticket); ticket->m_state= MDL_ACQUIRED; @@ -1355,7 +1355,7 @@ void MDL_context::release_ticket(MDL_ticket *ticket) lock->granted.remove(ticket); break; case MDL_EXCLUSIVE: - lock->type= MDL_lock::SHARED; + lock->type= MDL_lock::MDL_LOCK_SHARED; lock->granted.remove(ticket); global_lock.active_intention_exclusive--; break; @@ -1497,7 +1497,7 @@ void MDL_ticket::downgrade_exclusive_lock() return; pthread_mutex_lock(&LOCK_mdl); - m_lock->type= MDL_lock::SHARED; + m_lock->type= MDL_lock::MDL_LOCK_SHARED; m_type= MDL_SHARED_UPGRADABLE; pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); @@ -1543,7 +1543,7 @@ MDL_context::is_exclusive_lock_owner(unsigned char type, while ((ticket= it++)) { - if (ticket->m_lock->type == MDL_lock::EXCLUSIVE && + if (ticket->m_lock->type == MDL_lock::MDL_LOCK_EXCLUSIVE && ticket->m_lock->key.is_equal(&key)) break; } From 191bb81241584e2057e239f477e4a2fb30518ced Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 4 Dec 2009 02:57:01 +0300 Subject: [PATCH 069/212] Backport of: ---------------------------------------------------------- revno: 2617.23.22 committer: Konstantin Osipov branch nick: mysql-6.0-runtime timestamp: Wed 2009-03-04 23:29:16 +0300 message: WL#4284, "Transactional DDL locking": fix a Windows compilation warning. --- sql/mdl.h | 3 ++- sql/table.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sql/mdl.h b/sql/mdl.h index cfa3e671706..e3e41652bf4 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -148,8 +148,9 @@ struct I_P_List_adapter or destructor for it. */ -struct MDL_request +class MDL_request { +public: /** Type of metadata lock. */ enum enum_mdl_type type; diff --git a/sql/table.h b/sql/table.h index 02492ea4e81..b6ea372b41e 100644 --- a/sql/table.h +++ b/sql/table.h @@ -30,8 +30,8 @@ class st_select_lex; class partition_info; class COND_EQUAL; class Security_context; -struct MDL_request; -struct MDL_ticket; +class MDL_request; +class MDL_ticket; /*************************************************************************/ From a14bbee5ab0d60358b06463e881256c2d505d37a Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Sat, 5 Dec 2009 02:02:48 +0300 Subject: [PATCH 070/212] Backport of revno ## 2617.31.1, 2617.31.3, 2617.31.4, 2617.31.5, 2617.31.12, 2617.31.15, 2617.31.15, 2617.31.16, 2617.43.1 - initial changeset that introduced the fix for Bug#989 and follow up fixes for all test suite failures introduced in the initial changeset. ------------------------------------------------------------ revno: 2617.31.1 committer: Davi Arnaut branch nick: 4284-6.0 timestamp: Fri 2009-03-06 19:17:00 -0300 message: Bug#989: If DROP TABLE while there's an active transaction, wrong binlog order WL#4284: Transactional DDL locking Currently the MySQL server does not keep metadata locks on schema objects for the duration of a transaction, thus failing to guarantee the integrity of the schema objects being used during the transaction and to protect then from concurrent DDL operations. This also poses a problem for replication as a DDL operation might be replicated even thought there are active transactions using the object being modified. The solution is to defer the release of metadata locks until a active transaction is either committed or rolled back. This prevents other statements from modifying the table for the entire duration of the transaction. This provides commitment ordering for guaranteeing serializability across multiple transactions. - Incompatible change: If MySQL's metadata locking system encounters a lock conflict, the usual schema is to use the try and back-off technique to avoid deadlocks -- this schema consists in releasing all locks and trying to acquire them all in one go. But in a transactional context this algorithm can't be utilized as its not possible to release locks acquired during the course of the transaction without breaking the transaction commitments. To avoid deadlocks in this case, the ER_LOCK_DEADLOCK will be returned if a lock conflict is encountered during a transaction. Let's consider an example: A transaction has two statements that modify table t1, then table t2, and then commits. The first statement of the transaction will acquire a shared metadata lock on table t1, and it will be kept utill COMMIT to ensure serializability. At the moment when the second statement attempts to acquire a shared metadata lock on t2, a concurrent ALTER or DROP statement might have locked t2 exclusively. The prescription of the current locking protocol is that the acquirer of the shared lock backs off -- gives up all his current locks and retries. This implies that the entire multi-statement transaction has to be rolled back. - Incompatible change: FLUSH commands such as FLUSH PRIVILEGES and FLUSH TABLES WITH READ LOCK won't cause locked tables to be implicitly unlocked anymore. mysql-test/extra/binlog_tests/drop_table.test: Add test case for Bug#989. mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test: Fix test case to reflect the fact that transactions now hold metadata locks for the duration of a transaction. mysql-test/include/mix1.inc: Fix test case to reflect the fact that transactions now hold metadata locks for the duration of a transaction. mysql-test/include/mix2.inc: Fix test case to reflect the fact that transactions now hold metadata locks for the duration of a transaction. mysql-test/r/flush_block_commit.result: Update test case result (WL#4284). mysql-test/r/flush_block_commit_notembedded.result: Update test case result (WL#4284). mysql-test/r/innodb.result: Update test case result (WL#4284). mysql-test/r/innodb_mysql.result: Update test case result (WL#4284). mysql-test/r/lock.result: Add test case result for an effect of WL#4284/Bug#989 (all locks should be released when a connection terminates). mysql-test/r/mix2_myisam.result: Update test case result (effects of WL#4284/Bug#989). mysql-test/r/not_embedded_server.result: Update test case result (effects of WL#4284/Bug#989). Add a test case for interaction of WL#4284 and FLUSH PRIVILEGES. mysql-test/r/partition_innodb_semi_consistent.result: Update test case result (effects of WL#4284/Bug#989). mysql-test/r/partition_sync.result: Temporarily disable the test case for Bug#43867, which will be fixed by a subsequent backport. mysql-test/r/ps.result: Add a test case for effect of PREPARE on transactional locks: we take a savepoint at beginning of PREAPRE and release it at the end. Thus PREPARE does not accumulate metadata locks (Bug#989/WL#4284). mysql-test/r/read_only_innodb.result: Update test case result (effects of WL#4284/Bug#989). mysql-test/suite/binlog/r/binlog_row_drop_tbl.result: Add a test case result (WL#4284/Bug#989). mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result: Update test case result (effects of WL#4284/Bug#989). mysql-test/suite/binlog/r/binlog_stm_drop_tbl.result: Add a test case result (WL#4284/Bug#989). mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result: Update test case result (effects of WL#4284/Bug#989). mysql-test/suite/binlog/r/binlog_unsafe.result: A side effect of Bug#989 -- slightly different table map ids. mysql-test/suite/binlog/t/binlog_row_drop_tbl.test: Add a test case for WL#4284/Bug#989. mysql-test/suite/binlog/t/binlog_stm_drop_tbl.test: Add a test case for WL#4284/Bug#989. mysql-test/suite/binlog/t/binlog_stm_row.test: Update to the new state name. This is actually a follow up to another patch for WL#4284, that changes Locked thread state to Table lock. mysql-test/suite/ndb/r/ndb_index_ordered.result: Remove result for disabled part of the test case. mysql-test/suite/ndb/t/disabled.def: Temporarily disable a test case (Bug#45621). mysql-test/suite/ndb/t/ndb_index_ordered.test: Disable a part of a test case (needs update to reflect semantics of Bug#989). mysql-test/suite/rpl/t/disabled.def: Disable tests made meaningless by transactional metadata locking. mysql-test/suite/sys_vars/r/autocommit_func.result: Add a commit (Bug#989). mysql-test/suite/sys_vars/t/autocommit_func.test: Add a commit (Bug#989). mysql-test/t/flush_block_commit.test: Fix test case to reflect the fact that transactions now hold metadata locks for the duration of a transaction. mysql-test/t/flush_block_commit_notembedded.test: Fix test case to reflect the fact that transactions now hold metadata locks for the duration of a transaction. Add a test case for transaction-scope locks and the global read lock (Bug#989/WL#4284). mysql-test/t/innodb.test: Fix test case to reflect the fact that transactions now hold metadata locks for the duration of a transaction (effects of Bug#989/WL#4284). mysql-test/t/lock.test: Add a test case for Bug#989/WL#4284. mysql-test/t/not_embedded_server.test: Add a test case for Bug#989/WL#4284. mysql-test/t/partition_innodb_semi_consistent.test: Replace TRUNCATE with DELETE, to not issue an implicit commit of a transaction, and not depend on metadata locks. mysql-test/t/partition_sync.test: Temporarily disable the test case for Bug#43867, which needs a fix to be backported from 6.0. mysql-test/t/ps.test: Add a test case for semantics of PREPARE and transaction-scope locks: metadata locks on tables used in PREPARE are enclosed into a temporary savepoint, taken at the beginning of PREPARE, and released at the end. Thus PREPARE does not effect what locks a transaction owns. mysql-test/t/read_only_innodb.test: Fix test case to reflect the fact that transactions now hold metadata locks for the duration of a transaction (Bug#989/WL#4284). Wait for the read_only statement to actually flush tables before sending other concurrent statements that depend on its state. mysql-test/t/xa.test: Fix test case to reflect the fact that transactions now hold metadata locks for the duration of a transaction (Bug#989/WL#4284). sql/ha_ndbcluster_binlog.cc: Backport bits of changes of ha_ndbcluster_binlog.cc from 6.0, to fix the failing binlog test suite with WL#4284. WL#4284 implementation does not work with 5.1 implementation of ndbcluster binlog index. sql/log_event.cc: Release metadata locks after issuing a commit. sql/mdl.cc: Style changes (WL#4284). sql/mysql_priv.h: Rename parameter to match the name used in the definition (WL#4284). sql/rpl_injector.cc: Release metadata locks on commit (WL#4284). sql/rpl_rli.cc: Remove assert made meaningless, metadata locks are released at the end of the transaction. sql/set_var.cc: Close tables and release locks if autocommit mode is set. sql/slave.cc: Release metadata locks after a rollback. sql/sql_acl.cc: Don't implicitly unlock locked tables. Issue a implicit commit at the end and unlock tables. sql/sql_base.cc: Defer the release of metadata locks when closing tables if not required to. Issue a deadlock error if the locking protocol requires that a transaction re-acquire its locks. Release metadata locks when closing tables for reopen. sql/sql_class.cc: Release metadata locks if the thread is killed. sql/sql_parse.cc: Release metadata locks after implicitly committing a active transaction, or after explicit commits or rollbacks. sql/sql_plugin.cc: Allocate MDL request on the stack as the use of the table is contained within the function. It will be removed from the context once close_thread_tables is called at the end of the function. sql/sql_prepare.cc: The problem is that the prepare phase of the CREATE TABLE statement takes a exclusive metadata lock lock and this can cause a self-deadlock the thread already holds a shared lock on the table being that should be created. The solution is to make the prepare phase take a shared metadata lock when preparing a CREATE TABLE statement. The execution of the statement will still acquire a exclusive lock, but won't cause any problem as it issues a implicit commit. After some discussions with stakeholders it has been decided that metadata locks acquired during a PREPARE statement must be released once the statement is prepared even if it is prepared within a multi statement transaction. sql/sql_servers.cc: Don't implicitly unlock locked tables. Issue a implicit commit at the end and unlock tables. sql/sql_table.cc: Close table and release metadata locks after a admin operation. sql/table.h: The problem is that the prepare phase of the CREATE TABLE statement takes a exclusive metadata lock lock and this can cause a self-deadlock the thread already holds a shared lock on the table being that should be created. The solution is to make the prepare phase take a shared metadata lock when preparing a CREATE TABLE statement. The execution of the statement will still acquire a exclusive lock, but won't cause any problem as it issues a implicit commit. sql/transaction.cc: Release metadata locks after the implicitly committed due to a new transaction being started. Also, release metadata locks acquired after a savepoint if the transaction is rolled back to the save point. The problem is that in some cases transaction-long metadata locks could be released before the transaction was committed. This could happen when a active transaction was ended by a "START TRANSACTION" or "BEGIN" statement, in which case the metadata locks would be released before the actual commit of the active transaction. The solution is to defer the release of metadata locks to after the transaction has been implicitly committed. No test case is provided as the effort to provide one is too disproportional to the size of the fix. --- mysql-test/extra/binlog_tests/drop_table.test | 34 ++++++++ .../mix_innodb_myisam_binlog.test | 4 + mysql-test/include/mix1.inc | 1 + mysql-test/include/mix2.inc | 2 + mysql-test/r/flush_block_commit.result | 18 ++-- .../r/flush_block_commit_notembedded.result | 15 +++- mysql-test/r/innodb.result | 3 +- mysql-test/r/innodb_mysql.result | 1 + mysql-test/r/lock.result | 22 ++++- mysql-test/r/mix2_myisam.result | 2 + mysql-test/r/not_embedded_server.result | 16 +++- .../r/partition_innodb_semi_consistent.result | 2 +- mysql-test/r/partition_sync.result | 27 +----- mysql-test/r/ps.result | 28 +++++- mysql-test/r/read_only_innodb.result | 6 +- .../suite/binlog/r/binlog_row_drop_tbl.result | 16 ++++ .../r/binlog_row_mix_innodb_myisam.result | 1 + .../suite/binlog/r/binlog_stm_drop_tbl.result | 13 +++ .../r/binlog_stm_mix_innodb_myisam.result | 1 + .../suite/binlog/r/binlog_unsafe.result | 20 ++--- .../suite/binlog/t/binlog_row_drop_tbl.test | 5 ++ .../suite/binlog/t/binlog_stm_drop_tbl.test | 5 ++ mysql-test/suite/binlog/t/binlog_stm_row.test | 2 +- .../suite/ndb/r/ndb_index_ordered.result | 15 ---- mysql-test/suite/ndb/t/disabled.def | 1 + mysql-test/suite/ndb/t/ndb_index_ordered.test | 36 +++++--- mysql-test/suite/rpl/t/disabled.def | 2 + .../suite/sys_vars/r/autocommit_func.result | 2 + .../suite/sys_vars/t/autocommit_func.test | 4 + mysql-test/t/flush_block_commit.test | 36 +++++--- .../t/flush_block_commit_notembedded.test | 27 +++++- mysql-test/t/innodb.test | 10 ++- mysql-test/t/lock.test | 37 +++++++- mysql-test/t/not_embedded_server.test | 23 +++-- .../t/partition_innodb_semi_consistent.test | 2 +- mysql-test/t/partition_sync.test | 65 +++++++------- mysql-test/t/ps.test | 41 ++++++++- mysql-test/t/read_only_innodb.test | 14 ++- mysql-test/t/xa.test | 3 +- sql/ha_ndbcluster_binlog.cc | 37 ++------ sql/log_event.cc | 9 +- sql/mdl.cc | 6 +- sql/mysql_priv.h | 2 +- sql/rpl_injector.cc | 8 +- sql/rpl_rli.cc | 15 +--- sql/set_var.cc | 12 ++- sql/slave.cc | 3 + sql/sql_acl.cc | 10 ++- sql/sql_base.cc | 55 +++++++++--- sql/sql_class.cc | 10 +++ sql/sql_parse.cc | 85 ++++++++++++++----- sql/sql_plugin.cc | 4 +- sql/sql_prepare.cc | 25 +++++- sql/sql_servers.cc | 7 +- sql/sql_table.cc | 19 ++++- sql/sql_view.cc | 3 +- sql/table.h | 38 ++++++--- sql/transaction.cc | 17 ++++ tests/mysql_client_test.c | 6 +- 59 files changed, 671 insertions(+), 262 deletions(-) create mode 100644 mysql-test/extra/binlog_tests/drop_table.test create mode 100644 mysql-test/suite/binlog/r/binlog_row_drop_tbl.result create mode 100644 mysql-test/suite/binlog/r/binlog_stm_drop_tbl.result create mode 100644 mysql-test/suite/binlog/t/binlog_row_drop_tbl.test create mode 100644 mysql-test/suite/binlog/t/binlog_stm_drop_tbl.test diff --git a/mysql-test/extra/binlog_tests/drop_table.test b/mysql-test/extra/binlog_tests/drop_table.test new file mode 100644 index 00000000000..c55cbb67560 --- /dev/null +++ b/mysql-test/extra/binlog_tests/drop_table.test @@ -0,0 +1,34 @@ +# +# Bug#989: If DROP TABLE while there's an active transaction, wrong binlog order +# + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +connect (con1,localhost,root,,); +connect (con2,localhost,root,,); + +connection con1; +RESET MASTER; +CREATE TABLE t1 (a INT); +SET AUTOCOMMIT=OFF; +BEGIN; +INSERT INTO t1 VALUES(1); + +connection con2; +--send DROP TABLE t1; + +connection con1; +COMMIT; + +connection con2; +--reap + +connection default; + +--disconnect con1 +--disconnect con2 + +let $VERSION=`select version()`; +source include/show_binlog_events.inc; diff --git a/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test b/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test index da0b77fbc23..7c928b565dd 100644 --- a/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test +++ b/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test @@ -204,6 +204,10 @@ select (@after:=unix_timestamp())*0; # always give repeatable output # the bug, the reap would return immediately after the insert into t2. select (@after-@before) >= 2; +connection con3; +commit; + +connection con2; drop table t1,t2; commit; diff --git a/mysql-test/include/mix1.inc b/mysql-test/include/mix1.inc index 194d9e41108..3eaaf37cd83 100644 --- a/mysql-test/include/mix1.inc +++ b/mysql-test/include/mix1.inc @@ -1392,6 +1392,7 @@ SELECT * FROM t1; connection con2; --reap SELECT * FROM t1; +COMMIT; --echo # Switch to connection con1 connection con1; diff --git a/mysql-test/include/mix2.inc b/mysql-test/include/mix2.inc index b4c4a9b8836..001d4cf44d4 100644 --- a/mysql-test/include/mix2.inc +++ b/mysql-test/include/mix2.inc @@ -1994,6 +1994,7 @@ commit; connection b; set autocommit = 0; update t1 set b = 5 where a = 2; +commit; connection a; delimiter |; create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | @@ -2056,6 +2057,7 @@ update t2 set b = b + 5 where a = 1; update t3 set b = b + 5 where a = 1; update t4 set b = b + 5 where a = 1; insert into t5(a) values(20); +commit; connection b; set autocommit = 0; insert into t1(a) values(7); diff --git a/mysql-test/r/flush_block_commit.result b/mysql-test/r/flush_block_commit.result index d2197beaaab..da09d07b813 100644 --- a/mysql-test/r/flush_block_commit.result +++ b/mysql-test/r/flush_block_commit.result @@ -1,3 +1,4 @@ +# Save the initial number of concurrent sessions # Establish connection con1 (user=root) # Establish connection con2 (user=root) # Establish connection con3 (user=root) @@ -8,13 +9,15 @@ BEGIN; INSERT INTO t1 VALUES(1); # Switch to connection con2 FLUSH TABLES WITH READ LOCK; -SELECT * FROM t1; -a # Switch to connection con1 +# Sending: COMMIT; # Switch to connection con2 +# Wait until COMMIT gets blocked. +# Verify that 'con1' was blocked and data did not move. SELECT * FROM t1; a +1 UNLOCK TABLES; # Switch to connection con1 # Switch to connection con1 @@ -32,6 +35,7 @@ COMMIT; # Switch to connection con2 a 1 +COMMIT; # Switch to connection con3 UNLOCK TABLES; # Switch to connection con2 @@ -40,8 +44,6 @@ COMMIT; BEGIN; INSERT INTO t1 VALUES(10); FLUSH TABLES WITH READ LOCK; -COMMIT; -UNLOCK TABLES; # Switch to connection con2 FLUSH TABLES WITH READ LOCK; UNLOCK TABLES; @@ -53,5 +55,11 @@ a SHOW CREATE DATABASE test; Database Create Database test CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET latin1 */ -DROP TABLE t1; +COMMIT; +# Cleanup # Switch to connection default and close connections con1, con2, con3 +# We commit open transactions when we disconnect: only then we can +# drop the table. +DROP TABLE t1; +# End of 4.1 tests +# Wait till all disconnects are completed diff --git a/mysql-test/r/flush_block_commit_notembedded.result b/mysql-test/r/flush_block_commit_notembedded.result index 4348dbd67e5..6d8af3f5864 100644 --- a/mysql-test/r/flush_block_commit_notembedded.result +++ b/mysql-test/r/flush_block_commit_notembedded.result @@ -1,17 +1,20 @@ +# Save the initial number of concurrent sessions # Establish connection con1 (user=root) # Establish connection con2 (user=root) # Switch to connection con1 CREATE TABLE t1 (a INT) ENGINE=innodb; RESET MASTER; SET AUTOCOMMIT=0; -INSERT t1 VALUES (1); +SELECT 1; +1 +1 # Switch to connection con2 FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB master-bin.000001 107 # Switch to connection con1 -COMMIT; +INSERT INTO t1 VALUES (1); # Switch to connection con2 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB @@ -20,4 +23,12 @@ UNLOCK TABLES; # Switch to connection con1 DROP TABLE t1; SET AUTOCOMMIT=1; +create table t1 (a int) engine=innodb; +flush tables with read lock; +begin; +insert into t1 values (1);; +unlock tables; +commit; +drop table t1; # Switch to connection default and close connections con1 and con2 +# Wait till all disconnects are completed diff --git a/mysql-test/r/innodb.result b/mysql-test/r/innodb.result index 17b679d99e4..268bc839483 100644 --- a/mysql-test/r/innodb.result +++ b/mysql-test/r/innodb.result @@ -2836,10 +2836,10 @@ t2 CREATE TABLE `t2` ( DROP TABLE t2,t1; create table t1(a int not null, b int, c int, d int, primary key(a)) engine=innodb; insert into t1(a) values (1),(2),(3); +create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | commit; set autocommit = 0; update t1 set b = 5 where a = 2; -create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | set autocommit = 0; insert into t1(a) values (10),(20),(30),(40),(50),(60),(70),(80),(90),(100), (11),(21),(31),(41),(51),(61),(71),(81),(91),(101), @@ -2887,6 +2887,7 @@ insert into t2(a) values(8); delete from t2 where a = 3; update t4 set b = b + 1 where a = 3; commit; +commit; drop trigger t1t; drop trigger t2t; drop trigger t3t; diff --git a/mysql-test/r/innodb_mysql.result b/mysql-test/r/innodb_mysql.result index d6aa6a02468..0fe704c13f6 100644 --- a/mysql-test/r/innodb_mysql.result +++ b/mysql-test/r/innodb_mysql.result @@ -1618,6 +1618,7 @@ a b SELECT * FROM t1; a b 1 init+con1+con2 +COMMIT; # Switch to connection con1 # 3. test for updated key column: TRUNCATE t1; diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index 092c376b34a..c60ec4bc1bd 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -1,4 +1,4 @@ -drop table if exists t1,t2; +drop table if exists t1,t2,t3; CREATE TABLE t1 ( `id` int(11) NOT NULL default '0', `id2` int(11) NOT NULL default '0', `id3` int(11) NOT NULL default '0', `dummy1` char(30) default NULL, PRIMARY KEY (`id`,`id2`), KEY `index_id3` (`id3`)) ENGINE=MyISAM; insert into t1 (id,id2) values (1,1),(1,2),(1,3); LOCK TABLE t1 WRITE; @@ -262,5 +262,25 @@ unlock tables; drop table t1; drop view v1; # +# WL#4284: Transactional DDL locking +# +drop table if exists t1; +create table t1 (a int); +set autocommit= 0; +insert into t1 values (1); +lock table t1 write; +# Disconnect +# Ensure that metadata locks will be released if there is an open +# transaction (autocommit=off) in conjunction with lock tables. +drop table t1; +# Same problem but now for BEGIN +drop table if exists t1; +create table t1 (a int); +begin; +insert into t1 values (1); +# Disconnect +# Ensure that metadata locks held by the transaction are released. +drop table t1; +# # End of 6.0 tests. # diff --git a/mysql-test/r/mix2_myisam.result b/mysql-test/r/mix2_myisam.result index cabc4de8d21..99596c7774d 100644 --- a/mysql-test/r/mix2_myisam.result +++ b/mysql-test/r/mix2_myisam.result @@ -2063,6 +2063,7 @@ insert into t1(a) values (1),(2),(3); commit; set autocommit = 0; update t1 set b = 5 where a = 2; +commit; create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | set autocommit = 0; insert into t1(a) values (10),(20),(30),(40),(50),(60),(70),(80),(90),(100), @@ -2105,6 +2106,7 @@ update t2 set b = b + 5 where a = 1; update t3 set b = b + 5 where a = 1; update t4 set b = b + 5 where a = 1; insert into t5(a) values(20); +commit; set autocommit = 0; insert into t1(a) values(7); insert into t2(a) values(8); diff --git a/mysql-test/r/not_embedded_server.result b/mysql-test/r/not_embedded_server.result index 60c92bd0196..fac38624695 100644 --- a/mysql-test/r/not_embedded_server.result +++ b/mysql-test/r/not_embedded_server.result @@ -1,6 +1,16 @@ -select 1; -1 -1 +call mtr.add_suppression("Can't open and lock privilege tables: Table 'host' was not locked with LOCK TABLES"); SHOW VARIABLES like 'slave_skip_errors'; Variable_name Value slave_skip_errors OFF +# +# WL#4284: Transactional DDL locking +# +# FLUSH PRIVILEGES should not implicitly unlock locked tables. +# +drop table if exists t1; +create table t1 (c1 int); +lock tables t1 read; +flush privileges; +ERROR HY000: Table 'host' was not locked with LOCK TABLES +unlock tables; +drop table t1; diff --git a/mysql-test/r/partition_innodb_semi_consistent.result b/mysql-test/r/partition_innodb_semi_consistent.result index 471da4c1c2e..48a1bb3d258 100644 --- a/mysql-test/r/partition_innodb_semi_consistent.result +++ b/mysql-test/r/partition_innodb_semi_consistent.result @@ -102,7 +102,7 @@ a b # Switch to connection con1 # 3. test for updated key column: TRUNCATE t1; -TRUNCATE t2; +DELETE FROM t2; INSERT INTO t1 VALUES (1,'init'); BEGIN; UPDATE t1 SET a = 2, b = CONCAT(b, '+con1') WHERE a = 1; diff --git a/mysql-test/r/partition_sync.result b/mysql-test/r/partition_sync.result index 31cf0569464..41ca19426fe 100644 --- a/mysql-test/r/partition_sync.result +++ b/mysql-test/r/partition_sync.result @@ -1,25 +1,2 @@ -# -# Bug #43867 ALTER TABLE on a partitioned table -# causes unnecessary deadlocks -# -CREATE TABLE t1 (a int) PARTITION BY RANGE (a) -(PARTITION p0 VALUES LESS THAN (1), -PARTITION p1 VALUES LESS THAN (2)); -INSERT INTO t1 VALUES (0),(1); -# Connection 2 -BEGIN; -SELECT * FROM t1; -a -0 -1 -# Connection 1 -ALTER TABLE t1 DROP PARTITION p3; -ERROR HY000: Error in list of partitions to DROP -# Connection 2 -# This failed with deadlock and should not do so. -SELECT * FROM t1; -a -0 -1 -# Connection 1 -DROP TABLE t1; +# Disabled until Bug#46654 False deadlock on concurrent DML/DDL +# with partitions, inconsistent behavior is backported diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 6c7e83134d7..2526049c539 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -3086,5 +3086,29 @@ DROP PROCEDURE p1; DROP PROCEDURE p2; # End of WL#4435. - -End of 6.0 tests. +# +# WL#4284: Transactional DDL locking +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT); +BEGIN; +SELECT * FROM t1; +a +# Test that preparing a CREATE TABLE does not take a exclusive metdata lock. +PREPARE stmt1 FROM "CREATE TABLE t1 AS SELECT 1"; +EXECUTE stmt1; +ERROR 42S01: Table 't1' already exists +DEALLOCATE PREPARE stmt1; +DROP TABLE t1; +# +# WL#4284: Transactional DDL locking +# +# Test that metadata locks taken during prepare are released. +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT); +BEGIN; +PREPARE stmt1 FROM "SELECT * FROM t1"; +DROP TABLE t1; +# +# End of 6.0 tests. diff --git a/mysql-test/r/read_only_innodb.result b/mysql-test/r/read_only_innodb.result index 690de085bf9..4cba98900a1 100644 --- a/mysql-test/r/read_only_innodb.result +++ b/mysql-test/r/read_only_innodb.result @@ -7,12 +7,10 @@ insert into table_11733 values(11733); set global read_only=1; select @@global.read_only; @@global.read_only -1 +0 select * from table_11733 ; -a -11733 +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction COMMIT; -ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement set global read_only=0; drop table table_11733 ; drop user test@localhost; diff --git a/mysql-test/suite/binlog/r/binlog_row_drop_tbl.result b/mysql-test/suite/binlog/r/binlog_row_drop_tbl.result new file mode 100644 index 00000000000..8b32e9e5a45 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_row_drop_tbl.result @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS t1; +RESET MASTER; +CREATE TABLE t1 (a INT); +SET AUTOCOMMIT=OFF; +BEGIN; +INSERT INTO t1 VALUES(1); +DROP TABLE t1;; +COMMIT; +show binlog events from ; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT) +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Query # # use `test`; DROP TABLE t1 diff --git a/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result b/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result index 4ccc3b5e797..c1254643a18 100644 --- a/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result +++ b/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result @@ -238,6 +238,7 @@ select (@after:=unix_timestamp())*0; select (@after-@before) >= 2; (@after-@before) >= 2 1 +commit; drop table t1,t2; commit; begin; diff --git a/mysql-test/suite/binlog/r/binlog_stm_drop_tbl.result b/mysql-test/suite/binlog/r/binlog_stm_drop_tbl.result new file mode 100644 index 00000000000..f4596b808cf --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_stm_drop_tbl.result @@ -0,0 +1,13 @@ +DROP TABLE IF EXISTS t1; +RESET MASTER; +CREATE TABLE t1 (a INT); +SET AUTOCOMMIT=OFF; +BEGIN; +INSERT INTO t1 VALUES(1); +DROP TABLE t1;; +COMMIT; +show binlog events from ; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT) +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES(1) +master-bin.000001 # Query # # use `test`; DROP TABLE t1 diff --git a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result index ea081183cd1..f05e184976c 100644 --- a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result +++ b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result @@ -207,6 +207,7 @@ select (@after:=unix_timestamp())*0; select (@after-@before) >= 2; (@after-@before) >= 2 1 +commit; drop table t1,t2; commit; begin; diff --git a/mysql-test/suite/binlog/r/binlog_unsafe.result b/mysql-test/suite/binlog/r/binlog_unsafe.result index 58738a0d97c..3885acd079b 100644 --- a/mysql-test/suite/binlog/r/binlog_unsafe.result +++ b/mysql-test/suite/binlog/r/binlog_unsafe.result @@ -320,10 +320,10 @@ INSERT INTO t2 SET a = func_modify_t1(); SHOW BINLOG EVENTS FROM 12283; Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 12283 Query 1 12351 BEGIN -master-bin.000001 12351 Table_map 1 12393 table_id: 44 (test.t2) -master-bin.000001 12393 Table_map 1 12435 table_id: 45 (test.t1) -master-bin.000001 12435 Write_rows 1 12473 table_id: 45 -master-bin.000001 12473 Write_rows 1 12511 table_id: 44 flags: STMT_END_F +master-bin.000001 12351 Table_map 1 12393 table_id: 41 (test.t2) +master-bin.000001 12393 Table_map 1 12435 table_id: 42 (test.t1) +master-bin.000001 12435 Write_rows 1 12473 table_id: 42 +master-bin.000001 12473 Write_rows 1 12511 table_id: 41 flags: STMT_END_F master-bin.000001 12511 Query 1 12580 COMMIT DROP TABLE t1,t2; DROP FUNCTION func_modify_t1; @@ -347,12 +347,12 @@ INSERT INTO t1 SET a = 2; SHOW BINLOG EVENTS FROM 13426; Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 13426 Query 1 13494 BEGIN -master-bin.000001 13494 Table_map 1 13535 table_id: 47 (test.t1) -master-bin.000001 13535 Table_map 1 13577 table_id: 48 (test.t3) -master-bin.000001 13577 Table_map 1 13619 table_id: 49 (test.t2) -master-bin.000001 13619 Write_rows 1 13657 table_id: 49 -master-bin.000001 13657 Write_rows 1 13695 table_id: 48 -master-bin.000001 13695 Write_rows 1 13729 table_id: 47 flags: STMT_END_F +master-bin.000001 13494 Table_map 1 13535 table_id: 44 (test.t1) +master-bin.000001 13535 Table_map 1 13577 table_id: 45 (test.t3) +master-bin.000001 13577 Table_map 1 13619 table_id: 46 (test.t2) +master-bin.000001 13619 Write_rows 1 13657 table_id: 46 +master-bin.000001 13657 Write_rows 1 13695 table_id: 45 +master-bin.000001 13695 Write_rows 1 13729 table_id: 44 flags: STMT_END_F master-bin.000001 13729 Query 1 13798 COMMIT DROP TABLE t1,t2,t3; "End of tests" diff --git a/mysql-test/suite/binlog/t/binlog_row_drop_tbl.test b/mysql-test/suite/binlog/t/binlog_row_drop_tbl.test new file mode 100644 index 00000000000..06854900612 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_row_drop_tbl.test @@ -0,0 +1,5 @@ +# This is a wrapper for drop_table.test so that the same test case can be used +# For both statement and row based bin logs + +-- source include/have_binlog_format_row.inc +-- source extra/binlog_tests/drop_table.test diff --git a/mysql-test/suite/binlog/t/binlog_stm_drop_tbl.test b/mysql-test/suite/binlog/t/binlog_stm_drop_tbl.test new file mode 100644 index 00000000000..f2b07bb69f6 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_stm_drop_tbl.test @@ -0,0 +1,5 @@ +# This is a wrapper for drop_table.test so that the same test case can be used +# For both statement and row based bin logs + +-- source include/have_binlog_format_mixed_or_statement.inc +-- source extra/binlog_tests/drop_table.test diff --git a/mysql-test/suite/binlog/t/binlog_stm_row.test b/mysql-test/suite/binlog/t/binlog_stm_row.test index e923faae940..c501df324e8 100644 --- a/mysql-test/suite/binlog/t/binlog_stm_row.test +++ b/mysql-test/suite/binlog/t/binlog_stm_row.test @@ -57,7 +57,7 @@ let $wait_condition= --echo # con1 let $wait_condition= SELECT COUNT(*) = 1 FROM information_schema.processlist WHERE - state = "Locked" and info = "INSERT INTO t2 VALUES (3)"; + state = "Table lock" and info = "INSERT INTO t2 VALUES (3)"; --source include/wait_condition.inc SELECT RELEASE_LOCK('Bug#34306'); --connection con2 diff --git a/mysql-test/suite/ndb/r/ndb_index_ordered.result b/mysql-test/suite/ndb/r/ndb_index_ordered.result index a29b5343d7c..c99db354314 100644 --- a/mysql-test/suite/ndb/r/ndb_index_ordered.result +++ b/mysql-test/suite/ndb/r/ndb_index_ordered.result @@ -637,21 +637,6 @@ select count(*)- 4 from t1 use index (v) where v > 0000965.00042; count(*)- 4 0 drop table t1; -create table t1(a int primary key, b int not null, index(b)); -insert into t1 values (1,1), (2,2); -set autocommit=0; -begin; -select count(*) from t1; -count(*) -2 -ALTER TABLE t1 ADD COLUMN c int; -select a from t1 where b = 2; -a -2 -show tables; -Tables_in_test -t1 -drop table t1; create table t1 (a int, c varchar(10), primary key using hash (a), index(c)) engine=ndb; insert into t1 (a, c) values (1,'aaa'),(3,'bbb'); diff --git a/mysql-test/suite/ndb/t/disabled.def b/mysql-test/suite/ndb/t/disabled.def index 0fc9a5d3ad6..b2aa3e515be 100644 --- a/mysql-test/suite/ndb/t/disabled.def +++ b/mysql-test/suite/ndb/t/disabled.def @@ -13,3 +13,4 @@ ndb_partition_error2 : Bug#40989 ndb_partition_error2 needs maintenance # the below testcase have been reworked to avoid the bug, test contains comment, keep bug open +ndb_alter_table3 : Bug#45621 2009-06-10 alik A few test files are disabled due to WL#4284 diff --git a/mysql-test/suite/ndb/t/ndb_index_ordered.test b/mysql-test/suite/ndb/t/ndb_index_ordered.test index 782f17ca5b2..c8dfc1de59f 100644 --- a/mysql-test/suite/ndb/t/ndb_index_ordered.test +++ b/mysql-test/suite/ndb/t/ndb_index_ordered.test @@ -333,21 +333,29 @@ select count(*)- 4 from t1 use index (v) where v > 0000965.00042; drop table t1; +# +# Disabled due to WL#4284 +# +# Needs to be reworked. It's not possible anymore to do a non-fast alter table +# on a table that is being used by a pending transaction (transaction holds a +# metadata lock on the table). +# # bug#7798 -create table t1(a int primary key, b int not null, index(b)); -insert into t1 values (1,1), (2,2); -connect (con1,localhost,root,,test); -connect (con2,localhost,root,,test); -connection con1; -set autocommit=0; -begin; -select count(*) from t1; -connection con2; -ALTER TABLE t1 ADD COLUMN c int; -connection con1; -select a from t1 where b = 2; -show tables; -drop table t1; +# create table t1(a int primary key, b int not null, c int, index(b)); +# insert into t1 values (1,1,1), (2,2,2); +# connect (con1,localhost,root,,test); +# connect (con2,localhost,root,,test); +# connection con1; +# set autocommit=0; +# begin; +# select count(*) from t1; +# connection con2; +# ALTER TABLE t1 ADD COLUMN c int +# connection con1; +# select a from t1 where b = 2; +# show tables; +# drop table t1; +# # mysqld 5.0.13 crash, no bug# create table t1 (a int, c varchar(10), diff --git a/mysql-test/suite/rpl/t/disabled.def b/mysql-test/suite/rpl/t/disabled.def index 485ba229257..6bd4e7e833a 100644 --- a/mysql-test/suite/rpl/t/disabled.def +++ b/mysql-test/suite/rpl/t/disabled.def @@ -12,3 +12,5 @@ rpl_cross_version : BUG#43913 2009-10-22 luis rpl_cross_version fails with symptom in described in bug report rpl_spec_variables : BUG#47661 2009-10-27 jasonh rpl_spec_variables fails on PB2 hpux +rpl_failed_optimize : WL#4284: Can't optimize table used by a pending transaction (there is metadata lock on the table). +rpl_read_only : WL#4284: Setting Read only won't succeed until all metadata locks are released. diff --git a/mysql-test/suite/sys_vars/r/autocommit_func.result b/mysql-test/suite/sys_vars/r/autocommit_func.result index 47c2c921022..cb59c9a7b32 100644 --- a/mysql-test/suite/sys_vars/r/autocommit_func.result +++ b/mysql-test/suite/sys_vars/r/autocommit_func.result @@ -104,6 +104,8 @@ id name 2 Record_2 4 Record_4 5 Record_5 +## Commit changes +COMMIT; ## Dropping table t1 ## DROP table t1; ## Disconnecting both connections ## diff --git a/mysql-test/suite/sys_vars/t/autocommit_func.test b/mysql-test/suite/sys_vars/t/autocommit_func.test index 07e15ce40da..716189bb7be 100644 --- a/mysql-test/suite/sys_vars/t/autocommit_func.test +++ b/mysql-test/suite/sys_vars/t/autocommit_func.test @@ -153,6 +153,10 @@ SELECT * from t1; CONNECTION test_con2; SELECT * from t1; +--echo ## Commit changes +CONNECTION test_con1; +COMMIT; + --echo ## Dropping table t1 ## DROP table t1; diff --git a/mysql-test/t/flush_block_commit.test b/mysql-test/t/flush_block_commit.test index 74892def63f..98bca8cdad7 100644 --- a/mysql-test/t/flush_block_commit.test +++ b/mysql-test/t/flush_block_commit.test @@ -6,7 +6,7 @@ # And it requires InnoDB --source include/have_innodb.inc -# Save the initial number of concurrent sessions +--echo # Save the initial number of concurrent sessions --source include/count_sessions.inc --echo # Establish connection con1 (user=root) @@ -29,19 +29,26 @@ BEGIN; INSERT INTO t1 VALUES(1); --echo # Switch to connection con2 connection con2; -FLUSH TABLES WITH READ LOCK; -SELECT * FROM t1; +--send FLUSH TABLES WITH READ LOCK --echo # Switch to connection con1 connection con1; -send COMMIT; # blocked by con2 -sleep 1; +--echo # Sending: +COMMIT; --echo # Switch to connection con2 connection con2; -SELECT * FROM t1; # verify con1 was blocked and data did not move +--reap +--echo # Wait until COMMIT gets blocked. +#let $wait_condition= +# select count(*) = 1 from information_schema.processlist +# where state = "Waiting for release of readlock" and info = "COMMIT"; +#--source include/wait_condition.inc +--echo # Verify that 'con1' was blocked and data did not move. +SELECT * FROM t1; UNLOCK TABLES; --echo # Switch to connection con1 connection con1; -reap; +#--echo # Reaping COMMIT +#--reap # No deadlock ? @@ -63,6 +70,7 @@ COMMIT; # should not be blocked by con3 --echo # Switch to connection con2 connection con2; reap; +COMMIT; --echo # Switch to connection con3 connection con3; reap; @@ -79,8 +87,6 @@ connection con1; BEGIN; INSERT INTO t1 VALUES(10); FLUSH TABLES WITH READ LOCK; -COMMIT; -UNLOCK TABLES; --echo # Switch to connection con2 connection con2; FLUSH TABLES WITH READ LOCK; # bug caused hang here @@ -91,19 +97,21 @@ UNLOCK TABLES; BEGIN; SELECT * FROM t1; SHOW CREATE DATABASE test; - -DROP TABLE t1; +COMMIT; -# Cleanup +--echo # Cleanup --echo # Switch to connection default and close connections con1, con2, con3 connection default; disconnect con1; disconnect con2; disconnect con3; -# End of 4.1 tests +--echo # We commit open transactions when we disconnect: only then we can +--echo # drop the table. +DROP TABLE t1; +--echo # End of 4.1 tests -# Wait till all disconnects are completed +--echo # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/flush_block_commit_notembedded.test b/mysql-test/t/flush_block_commit_notembedded.test index aea38250218..d7ffbd475b4 100644 --- a/mysql-test/t/flush_block_commit_notembedded.test +++ b/mysql-test/t/flush_block_commit_notembedded.test @@ -9,7 +9,7 @@ --source include/have_log_bin.inc --source include/have_innodb.inc -# Save the initial number of concurrent sessions +--echo # Save the initial number of concurrent sessions --source include/count_sessions.inc @@ -24,14 +24,14 @@ connection con1; CREATE TABLE t1 (a INT) ENGINE=innodb; RESET MASTER; SET AUTOCOMMIT=0; -INSERT t1 VALUES (1); +SELECT 1; --echo # Switch to connection con2 connection con2; FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS; --echo # Switch to connection con1 connection con1; -send COMMIT; +send INSERT INTO t1 VALUES (1); --echo # Switch to connection con2 connection con2; sleep 1; @@ -43,11 +43,30 @@ reap; DROP TABLE t1; SET AUTOCOMMIT=1; +# GLR blocks new transactions +create table t1 (a int) engine=innodb; +connection con1; +flush tables with read lock; +connection con2; +begin; +--send insert into t1 values (1); +connection con1; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for release of readlock" and + info = "insert into t1 values (1)"; +--source include/wait_condition.inc +unlock tables; +connection con2; +--reap +commit; +drop table t1; + --echo # Switch to connection default and close connections con1 and con2 connection default; disconnect con1; disconnect con2; -# Wait till all disconnects are completed +--echo # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/innodb.test b/mysql-test/t/innodb.test index 2879a4c0b9f..6e7d9b5e780 100644 --- a/mysql-test/t/innodb.test +++ b/mysql-test/t/innodb.test @@ -1849,16 +1849,15 @@ connect (b,localhost,root,,); connection a; create table t1(a int not null, b int, c int, d int, primary key(a)) engine=innodb; insert into t1(a) values (1),(2),(3); +delimiter |; +create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | +delimiter ;| commit; connection b; set autocommit = 0; update t1 set b = 5 where a = 2; connection a; -delimiter |; -create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | -delimiter ;| set autocommit = 0; -connection a; insert into t1(a) values (10),(20),(30),(40),(50),(60),(70),(80),(90),(100), (11),(21),(31),(41),(51),(61),(71),(81),(91),(101), (12),(22),(32),(42),(52),(62),(72),(82),(92),(102), @@ -1922,6 +1921,9 @@ insert into t2(a) values(8); delete from t2 where a = 3; update t4 set b = b + 1 where a = 3; commit; +connection a; +commit; +connection b; drop trigger t1t; drop trigger t2t; drop trigger t3t; diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index 51900be4df8..c2aeac05cd1 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -3,7 +3,7 @@ # --disable_warnings -drop table if exists t1,t2; +drop table if exists t1,t2,t3; --enable_warnings CREATE TABLE t1 ( `id` int(11) NOT NULL default '0', `id2` int(11) NOT NULL default '0', `id3` int(11) NOT NULL default '0', `dummy1` char(30) default NULL, PRIMARY KEY (`id`,`id2`), KEY `index_id3` (`id3`)) ENGINE=MyISAM; insert into t1 (id,id2) values (1,1),(1,2),(1,3); @@ -310,6 +310,41 @@ unlock tables; drop table t1; drop view v1; +--echo # +--echo # WL#4284: Transactional DDL locking +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +connect(con1,localhost,root,,); +set autocommit= 0; +insert into t1 values (1); +lock table t1 write; +--echo # Disconnect +--echo # Ensure that metadata locks will be released if there is an open +--echo # transaction (autocommit=off) in conjunction with lock tables. +disconnect con1; +connection default; +drop table t1; + +--echo # Same problem but now for BEGIN + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +connect(con1,localhost,root,,); +begin; +insert into t1 values (1); +--echo # Disconnect +--echo # Ensure that metadata locks held by the transaction are released. +disconnect con1; +connection default; +drop table t1; + + --echo # --echo # End of 6.0 tests. --echo # diff --git a/mysql-test/t/not_embedded_server.test b/mysql-test/t/not_embedded_server.test index fa2b659ec57..917d5871682 100644 --- a/mysql-test/t/not_embedded_server.test +++ b/mysql-test/t/not_embedded_server.test @@ -4,12 +4,6 @@ -- source include/not_embedded.inc -# -# Produce output -# - -select 1; - # The following fails sporadically because 'check-testcase' runs # queries before this test and there is no way to guarantee that any # previous process finishes. The purpose of the test is not clearly @@ -36,6 +30,8 @@ select 1; #execute stmt1; #deallocate prepare stmt1; +call mtr.add_suppression("Can't open and lock privilege tables: Table 'host' was not locked with LOCK TABLES"); + # # Bug#43835: SHOW VARIABLES does not include 0 for slave_skip_errors # @@ -43,3 +39,18 @@ select 1; SHOW VARIABLES like 'slave_skip_errors'; # End of 5.1 tests + +--echo # +--echo # WL#4284: Transactional DDL locking +--echo # +--echo # FLUSH PRIVILEGES should not implicitly unlock locked tables. +--echo # +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (c1 int); +lock tables t1 read; +--error ER_TABLE_NOT_LOCKED +flush privileges; +unlock tables; +drop table t1; diff --git a/mysql-test/t/partition_innodb_semi_consistent.test b/mysql-test/t/partition_innodb_semi_consistent.test index 6a6a7cf958e..2bf879603a4 100644 --- a/mysql-test/t/partition_innodb_semi_consistent.test +++ b/mysql-test/t/partition_innodb_semi_consistent.test @@ -157,7 +157,7 @@ connection con1; --echo # 3. test for updated key column: TRUNCATE t1; -TRUNCATE t2; +DELETE FROM t2; INSERT INTO t1 VALUES (1,'init'); diff --git a/mysql-test/t/partition_sync.test b/mysql-test/t/partition_sync.test index a732b35b8b9..5d2b25e87f3 100644 --- a/mysql-test/t/partition_sync.test +++ b/mysql-test/t/partition_sync.test @@ -2,38 +2,41 @@ # Save the initial number of concurrent sessions. --source include/count_sessions.inc ---echo # ---echo # Bug #43867 ALTER TABLE on a partitioned table ---echo # causes unnecessary deadlocks ---echo # +--echo # Disabled until Bug#46654 False deadlock on concurrent DML/DDL +--echo # with partitions, inconsistent behavior is backported -CREATE TABLE t1 (a int) PARTITION BY RANGE (a) -(PARTITION p0 VALUES LESS THAN (1), - PARTITION p1 VALUES LESS THAN (2)); - -INSERT INTO t1 VALUES (0),(1); - -connect(con1,localhost,root); - ---echo # Connection 2 -connection con1; -BEGIN; -SELECT * FROM t1; - ---echo # Connection 1 -connection default; ---error ER_DROP_PARTITION_NON_EXISTENT -ALTER TABLE t1 DROP PARTITION p3; - ---echo # Connection 2 -connection con1; ---echo # This failed with deadlock and should not do so. -SELECT * FROM t1; - ---echo # Connection 1 -connection default; -disconnect con1; -DROP TABLE t1; +#--echo # +#--echo # Bug #43867 ALTER TABLE on a partitioned table +#--echo # causes unnecessary deadlocks +#--echo # +# +#CREATE TABLE t1 (a int) PARTITION BY RANGE (a) +#(PARTITION p0 VALUES LESS THAN (1), +# PARTITION p1 VALUES LESS THAN (2)); +# +#INSERT INTO t1 VALUES (0),(1); +# +#connect(con1,localhost,root); +# +#--echo # Connection 2 +#connection con1; +#BEGIN; +#SELECT * FROM t1; +# +#--echo # Connection 1 +#connection default; +#--error ER_DROP_PARTITION_NON_EXISTENT +#ALTER TABLE t1 DROP PARTITION p3; +# +#--echo # Connection 2 +#connection con1; +#--echo # This failed with deadlock and should not do so. +#SELECT * FROM t1; +# +#--echo # Connection 1 +#connection default; +#disconnect con1; +#DROP TABLE t1; # Check that all connections opened by test cases in this file are really diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index 844be582290..6cebbe6c7a1 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -3211,7 +3211,44 @@ DROP PROCEDURE p2; ########################################################################### ---echo ---echo End of 6.0 tests. + +--echo # +--echo # WL#4284: Transactional DDL locking +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings +CREATE TABLE t1 (a INT); +BEGIN; +SELECT * FROM t1; +--echo # Test that preparing a CREATE TABLE does not take a exclusive metdata lock. +PREPARE stmt1 FROM "CREATE TABLE t1 AS SELECT 1"; +--error ER_TABLE_EXISTS_ERROR +EXECUTE stmt1; +DEALLOCATE PREPARE stmt1; +DROP TABLE t1; + +--echo # +--echo # WL#4284: Transactional DDL locking +--echo # +--echo # Test that metadata locks taken during prepare are released. +--echo # + +connect(con1,localhost,root,,); +connection default; +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings +CREATE TABLE t1 (a INT); +connection con1; +BEGIN; +PREPARE stmt1 FROM "SELECT * FROM t1"; +connection default; +DROP TABLE t1; +disconnect con1; + +--echo # +--echo # End of 6.0 tests. ########################################################################### diff --git a/mysql-test/t/read_only_innodb.test b/mysql-test/t/read_only_innodb.test index f8c25fdee1d..98e704e25c7 100644 --- a/mysql-test/t/read_only_innodb.test +++ b/mysql-test/t/read_only_innodb.test @@ -16,6 +16,8 @@ DROP TABLE IF EXISTS table_11733 ; grant CREATE, SELECT, DROP on *.* to test@localhost; connect (con1,localhost,test,,test); +connect (con2,localhost,root,,); + connection default; set global read_only=0; @@ -28,15 +30,22 @@ BEGIN; insert into table_11733 values(11733); connection default; -set global read_only=1; +send set global read_only=1; + +connection con2; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Flushing tables" and info = "set global read_only=1"; +--source include/wait_condition.inc connection con1; select @@global.read_only; +--error ER_LOCK_DEADLOCK select * from table_11733 ; --- error ER_OPTION_PREVENTS_STATEMENT COMMIT; connection default; +reap; set global read_only=0; drop table table_11733 ; drop user test@localhost; @@ -81,5 +90,6 @@ DROP TABLE t1; DROP USER test@localhost; disconnect con1; +disconnect con2; --echo echo End of 5.1 tests diff --git a/mysql-test/t/xa.test b/mysql-test/t/xa.test index f84d822170f..0f705ae20c6 100644 --- a/mysql-test/t/xa.test +++ b/mysql-test/t/xa.test @@ -76,9 +76,10 @@ xa rollback 'testa','testb'; xa start 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'; select * from t1; -drop table t1; disconnect con1; +connection default; +drop table t1; # # Bug#28323: Server crashed in xid cache operations diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index 08abb88e768..7ce318394d4 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -2347,7 +2347,7 @@ static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index) tables->required_type= FRMTYPE_TABLE; uint counter; thd->clear_error(); - if (open_tables(thd, &tables, &counter, MYSQL_LOCK_IGNORE_FLUSH)) + if (simple_open_n_lock_tables(thd, tables)) { if (thd->killed) sql_print_error("NDB Binlog: Opening ndb_binlog_index: killed"); @@ -2381,28 +2381,11 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row) ulong saved_options= thd->options; thd->options&= ~(OPTION_BIN_LOG); - for ( ; ; ) /* loop for need_reopen */ + if (!ndb_binlog_index && open_ndb_binlog_index(thd, &ndb_binlog_index)) { - if (!ndb_binlog_index && open_ndb_binlog_index(thd, &ndb_binlog_index)) - { - error= -1; - goto add_ndb_binlog_index_err; - } - - if (lock_tables(thd, &binlog_tables, 1, 0, &need_reopen)) - { - if (need_reopen) - { - TABLE_LIST *p_binlog_tables= &binlog_tables; - close_tables_for_reopen(thd, &p_binlog_tables, FALSE); - ndb_binlog_index= 0; - continue; - } - sql_print_error("NDB Binlog: Unable to lock table ndb_binlog_index"); - error= -1; - goto add_ndb_binlog_index_err; - } - break; + sql_print_error("NDB Binlog: Unable to lock table ndb_binlog_index"); + error= -1; + goto add_ndb_binlog_index_err; } /* @@ -2428,13 +2411,6 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row) } - if (! thd->locked_tables_mode) /* Is always TRUE */ - { - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; - } - thd->options= saved_options; - return 0; add_ndb_binlog_index_err: close_thread_tables(thd); ndb_binlog_index= 0; @@ -3905,9 +3881,6 @@ restart: { static char db[]= ""; thd->db= db; - if (ndb_binlog_running) - open_ndb_binlog_index(thd, &ndb_binlog_index); - thd->db= db; } do_ndbcluster_binlog_close_connection= BCCC_running; for ( ; !((ndbcluster_binlog_terminating || diff --git a/sql/log_event.cc b/sql/log_event.cc index e41bb690e8a..aa8dd6e9bff 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -5314,10 +5314,17 @@ void Xid_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) int Xid_log_event::do_apply_event(Relay_log_info const *rli) { + bool res; /* For a slave Xid_log_event is COMMIT */ general_log_print(thd, COM_QUERY, "COMMIT /* implicit, from Xid_log_event */"); - return trans_commit(thd); + if (!(res= trans_commit(thd))) + { + close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); + } + return res; } Log_event::enum_skip_reason diff --git a/sql/mdl.cc b/sql/mdl.cc index 1d591bb2244..eb8fcdb323e 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -491,7 +491,7 @@ void MDL_ticket::destroy(MDL_ticket *ticket) @sa THD::enter_cond()/exit_cond()/killed. @note We can't use THD::enter_cond()/exit_cond()/killed directly here - since this will make metadata subsystem dependant on THD class + since this will make metadata subsystem dependent on THD class and thus prevent us from writing unit tests for it. And usage of wrapper functions to access THD::killed/enter_cond()/exit_cond() will probably introduce too much overhead. @@ -881,6 +881,7 @@ static bool notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) if (conflicting_ticket->is_shared()) { THD *conflicting_thd= conflicting_ticket->get_ctx()->get_thd(); + DBUG_ASSERT(thd != conflicting_thd); /* Self-deadlock */ woke= mysql_notify_thread_having_shared_lock(thd, conflicting_thd); } return woke; @@ -1089,7 +1090,6 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() old_msg= MDL_ENTER_COND(thd, mysys_var); - /* Since we should have already acquired an intention exclusive global lock this call is only enforcing asserts. @@ -1164,7 +1164,7 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() @param conflict [out] Indicates that conflicting lock exists @retval TRUE Failure either conflicting lock exists or some error - occured (probably OOM). + occurred (probably OOM). @retval FALSE Success, lock was acquired. FIXME: Compared to lock_table_name_if_not_cached() diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 3f6ed2b1cb0..159613c58a0 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -827,7 +827,7 @@ extern my_decimal decimal_zero; void free_items(Item *item); void cleanup_items(Item *item); class THD; -void close_thread_tables(THD *thd, bool skip_mdl= 0); +void close_thread_tables(THD *thd, bool is_back_off= 0); #ifndef NO_EMBEDDED_ACCESS_CHECKS bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables); diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index 738341cc034..9d82307d2e7 100644 --- a/sql/rpl_injector.cc +++ b/sql/rpl_injector.cc @@ -83,10 +83,16 @@ int injector::transaction::commit() explicitly. */ trans_commit_stmt(m_thd); - trans_commit(m_thd); + if (!trans_commit(m_thd)) + { + close_thread_tables(m_thd); + if (!m_thd->locked_tables_mode) + m_thd->mdl_context.release_all_locks(); + } DBUG_RETURN(0); } + int injector::transaction::use_table(server_id_type sid, table tbl) { DBUG_ENTER("injector::transaction::use_table"); diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 695b160fc01..b4554bb4b6c 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -1189,6 +1189,8 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) } m_table_map.clear_tables(); slave_close_thread_tables(thd); + if (error && !thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); clear_flag(IN_STMT); /* Cleanup for the flags that have been set at do_apply_event. @@ -1200,13 +1202,6 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) void Relay_log_info::clear_tables_to_lock() { - /* - Deallocating elements of table list below will also free memory where - meta-data locks are stored. So we want to be sure that we don't have - any references to this memory left. - */ - DBUG_ASSERT(!current_thd->mdl_context.has_locks()); - while (tables_to_lock) { uchar* to_free= reinterpret_cast(tables_to_lock); @@ -1225,12 +1220,6 @@ void Relay_log_info::clear_tables_to_lock() void Relay_log_info::slave_close_thread_tables(THD *thd) { - /* - Since we use same memory chunks for allocation of metadata lock - objects for tables as we use for allocating corresponding elements - of 'tables_to_lock' list, we have to release metadata locks by - closing tables before calling clear_tables_to_lock(). - */ close_thread_tables(thd); clear_tables_to_lock(); } diff --git a/sql/set_var.cc b/sql/set_var.cc index 266cdd9ad6d..dd009541274 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -3190,9 +3190,15 @@ static bool set_option_autocommit(THD *thd, set_var *var) need to commit any outstanding transactions. */ if (var->save_result.ulong_value != 0 && - (thd->options & OPTION_NOT_AUTOCOMMIT) && - trans_commit(thd)) - return 1; + (thd->options & OPTION_NOT_AUTOCOMMIT)) + { + if (trans_commit(thd)) + return TRUE; + + close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); + } if (var->save_result.ulong_value != 0) thd->options&= ~((sys_var_thd_bit*) var->var)->bit_flag; diff --git a/sql/slave.cc b/sql/slave.cc index ed722305b29..0316ae2591d 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -2432,6 +2432,9 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) { exec_res= 0; trans_rollback(thd); + close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); /* chance for concurrent connection to get more locks */ safe_sleep(thd, min(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE), (CHECK_KILLED_FUNC)sql_slave_killed, (void*)rli); diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index aa96483cf09..f4a182b321f 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -30,6 +30,7 @@ #include #include "sp_head.h" #include "sp.h" +#include "transaction.h" time_t mysql_db_table_last_check= 0L; @@ -676,9 +677,6 @@ my_bool acl_reload(THD *thd) my_bool return_val= 1; DBUG_ENTER("acl_reload"); - /* Can't have locked tables here. */ - thd->locked_tables_list.unlock_locked_tables(thd); - /* To avoid deadlocks we should obtain table locks before obtaining acl_cache->lock mutex. @@ -732,7 +730,10 @@ my_bool acl_reload(THD *thd) if (old_initialized) pthread_mutex_unlock(&acl_cache->lock); end: + trans_commit_implicit(thd); close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); DBUG_RETURN(return_val); } @@ -3900,7 +3901,10 @@ my_bool grant_reload(THD *thd) free_root(&old_mem,MYF(0)); } rw_unlock(&LOCK_grant); + trans_commit_implicit(thd); close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); /* It is OK failing to load procs_priv table because we may be diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 0441458510f..42fc3ba0566 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1478,11 +1478,22 @@ void close_thread_tables(THD *thd, if (thd->open_tables) close_open_tables(thd); - thd->mdl_context.release_all_locks(); if (!is_back_off) { thd->mdl_context.remove_all_requests(); } + + /* + Defer the release of metadata locks until the current transaction + is either committed or rolled back. This prevents other statements + from modifying the table for the entire duration of this transaction. + This provides commitment ordering for guaranteeing serializability + across multiple transactions. + */ + if (!thd->in_multi_stmt_transaction() || + (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) + thd->mdl_context.release_all_locks(); + DBUG_VOID_RETURN; } @@ -2284,7 +2295,7 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, { thd->mdl_context.add_request(mdl_request); - if (table_list->open_type) + if (table_list->lock_strategy) { /* In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table @@ -2358,10 +2369,16 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, IMPLEMENTATION Uses a cache of open tables to find a table not in use. - If table list element for the table to be opened has "open_type" set - to OPEN_OR_CREATE and table does not exist, this function will take - exclusive metadata lock on the table, also it will do this if - "open_type" is TAKE_EXCLUSIVE_MDL. + If TABLE_LIST::open_strategy is set to OPEN_IF_EXISTS, the table is opened + only if it exists. If the open strategy is OPEN_STUB, the underlying table + is never opened. In both cases, metadata locks are always taken according + to the lock strategy. + + This function will take a exclusive metadata lock on the table if + TABLE_LIST::lock_strategy is EXCLUSIVE_DOWNGRADABLE_MDL or EXCLUSIVE_MDL. + If the lock strategy is EXCLUSIVE_DOWNGRADABLE_MDL and opening the table + is successful, the exclusive metadata lock is downgraded to a shared + lock. RETURN TRUE Open failed. "action" parameter may contain type of action @@ -2595,7 +2612,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, DBUG_RETURN(TRUE); } - if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE) + if (table_list->open_strategy == TABLE_LIST::OPEN_IF_EXISTS) { bool exists; @@ -2609,7 +2626,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } /* Table exists. Let us try to open it. */ } - else if (table_list->open_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL) + else if (table_list->open_strategy == TABLE_LIST::OPEN_STUB) { pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(FALSE); @@ -2794,7 +2811,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table exists now we should downgrade our exclusive metadata lock on this table to shared metadata lock. */ - if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE) + if (table_list->lock_strategy == TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL) mdl_ticket->downgrade_exclusive_lock(); table->mdl_ticket= mdl_ticket; @@ -3623,7 +3640,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) /* Also used for indicating that prelocking is need */ TABLE_LIST **query_tables_last_own; bool safe_to_ignore_table; - + bool has_locks= thd->mdl_context.has_locks(); DBUG_ENTER("open_tables"); /* temporary mem_root for new .frm parsing. @@ -3763,6 +3780,18 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) { if (action) { + /* + We have met a exclusive metadata lock or a old version of table and + we are inside a transaction that already hold locks. We can't follow + the locking protocol in this scenario as it might lead to deadlocks. + */ + if (thd->in_multi_stmt_transaction() && has_locks) + { + my_error(ER_LOCK_DEADLOCK, MYF(0)); + result= -1; + goto err; + } + /* We have met exclusive metadata lock or old version of table. Now we have to close all tables which are not up to date/release metadata @@ -3841,7 +3870,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) Special types of open can succeed but still don't set TABLE_LIST::table to anything. */ - if (tables->open_type && !tables->table) + if (tables->open_strategy && !tables->table) continue; /* @@ -4122,7 +4151,7 @@ retry: if (!error) { /* - We can't have a view or some special "open_type" in this function + We can't have a view or some special "open_strategy" in this function so there should be a TABLE instance. */ DBUG_ASSERT(table_list->table); @@ -4662,6 +4691,8 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool is_back_off) for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global) tmp->table= 0; close_thread_tables(thd, is_back_off); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 654d7ad718d..7252f078f81 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -995,8 +995,18 @@ void THD::cleanup(void) trans_rollback(this); xid_cache_delete(&transaction.xid_state); } + locked_tables_list.unlock_locked_tables(this); + /* + If the thread was in the middle of an ongoing transaction (rolled + back a few lines above) or under LOCK TABLES (unlocked the tables + and left the mode a few lines above), there will be outstanding + metadata locks. Release them. + */ + DBUG_ASSERT(open_tables == NULL); + mdl_context.release_all_locks(); + #if defined(ENABLED_DEBUG_SYNC) /* End the Debug Sync Facility. See debug_sync.cc. */ debug_sync_end_thread(this); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 579fbcea4ea..ddc163072a7 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -122,11 +122,11 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) @param mask Bitmask used for the SQL command match. */ -static bool opt_implicit_commit(THD *thd, uint mask) +static bool stmt_causes_implicit_commit(THD *thd, uint mask) { LEX *lex= thd->lex; - bool res= FALSE, skip= FALSE; - DBUG_ENTER("opt_implicit_commit"); + bool skip= FALSE; + DBUG_ENTER("stmt_causes_implicit_commit"); if (!(sql_command_flags[lex->sql_command] & mask)) DBUG_RETURN(FALSE); @@ -147,15 +147,7 @@ static bool opt_implicit_commit(THD *thd, uint mask) break; } - if (!skip) - { - /* Commit or rollback the statement transaction. */ - thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); - /* Commit the normal transaction if one is active. */ - res= trans_commit_implicit(thd); - } - - DBUG_RETURN(res); + DBUG_RETURN(!skip); } @@ -1168,6 +1160,9 @@ bool dispatch_command(enum enum_server_command command, THD *thd, ulong options= (ulong) (uchar) packet[0]; if (trans_commit_implicit(thd)) break; + close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); if (check_global_access(thd,RELOAD_ACL)) break; general_log_print(thd, command, NullS); @@ -1196,6 +1191,9 @@ bool dispatch_command(enum enum_server_command command, THD *thd, break; if (trans_commit_implicit(thd)) break; + close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); my_ok(thd); break; } @@ -1942,8 +1940,18 @@ mysql_execute_command(THD *thd) not run in it's own transaction it may simply never appear on the slave in case the outside transaction rolls back. */ - if (opt_implicit_commit(thd, CF_IMPLICT_COMMIT_BEGIN)) - goto error; + if (stmt_causes_implicit_commit(thd, CF_IMPLICT_COMMIT_BEGIN)) + { + /* Commit or rollback the statement transaction. */ + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); + /* Commit the normal transaction if one is active. */ + if (trans_commit_implicit(thd)) + goto error; + /* Close tables and release metadata locks. */ + close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); + } switch (lex->sql_command) { @@ -2363,7 +2371,9 @@ case SQLCOM_PREPARE: if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) { lex->link_first_table_back(create_table, link_to_local); - create_table->open_type= TABLE_LIST::OPEN_OR_CREATE; + /* Set strategies: reset default or 'prepared' values. */ + create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + create_table->lock_strategy= TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL; } if (!(res= open_and_lock_tables(thd, lex->query_tables))) @@ -3306,6 +3316,7 @@ end_with_restore_list: if (thd->options & OPTION_TABLE_LOCK) { trans_commit_implicit(thd); + thd->mdl_context.release_all_locks(); thd->options&= ~(OPTION_TABLE_LOCK); } if (thd->global_read_lock) @@ -3317,6 +3328,8 @@ end_with_restore_list: /* we must end the trasaction first, regardless of anything */ if (trans_commit_implicit(thd)) goto error; + /* release transactional metadata locks. */ + thd->mdl_context.release_all_locks(); if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; @@ -3346,6 +3359,13 @@ end_with_restore_list: */ trans_rollback_stmt(thd); trans_commit_implicit(thd); + /* + Close tables and release metadata locks otherwise a later call to + close_thread_tables might not release the locks if autocommit is off. + */ + close_thread_tables(thd); + DBUG_ASSERT(!thd->locked_tables_mode); + thd->mdl_context.release_all_locks(); thd->options&= ~(OPTION_TABLE_LOCK); } else @@ -3836,6 +3856,8 @@ end_with_restore_list: thd->locked_tables_mode == LTM_LOCK_TABLES); if (trans_commit(thd)) goto error; + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); /* Begin transaction with the same isolation level. */ if (lex->tx_chain && trans_begin(thd)) goto error; @@ -3849,6 +3871,8 @@ end_with_restore_list: thd->locked_tables_mode == LTM_LOCK_TABLES); if (trans_rollback(thd)) goto error; + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); /* Begin transaction with the same isolation level. */ if (lex->tx_chain && trans_begin(thd)) goto error; @@ -4196,6 +4220,12 @@ create_sp_error: if (trans_commit_implicit(thd)) goto error; + + close_thread_tables(thd); + + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); + #ifndef NO_EMBEDDED_ACCESS_CHECKS if (sp_automatic_privileges && !opt_noacl && sp_revoke_privileges(thd, db, name, @@ -4375,11 +4405,15 @@ create_sp_error: case SQLCOM_XA_COMMIT: if (trans_xa_commit(thd)) goto error; + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); my_ok(thd); break; case SQLCOM_XA_ROLLBACK: if (trans_xa_rollback(thd)) goto error; + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); my_ok(thd); break; case SQLCOM_XA_RECOVER: @@ -4524,10 +4558,20 @@ finish: start_waiting_global_read_lock(thd); } - /* If commit fails, we should be able to reset the OK status. */ - thd->stmt_da->can_overwrite_status= TRUE; - opt_implicit_commit(thd, CF_IMPLICIT_COMMIT_END); - thd->stmt_da->can_overwrite_status= FALSE; + if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END)) + { + /* If commit fails, we should be able to reset the OK status. */ + thd->stmt_da->can_overwrite_status= TRUE; + /* Commit or rollback the statement transaction. */ + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); + /* Commit the normal transaction if one is active. */ + trans_commit_implicit(thd); + /* Close tables and release metadata locks. */ + close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); + thd->stmt_da->can_overwrite_status= FALSE; + } DBUG_RETURN(res || thd->is_error()); } @@ -6508,6 +6552,9 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, query_cache.flush(); // RESET QUERY CACHE } #endif /*HAVE_QUERY_CACHE*/ + + DBUG_ASSERT(thd->locked_tables_mode || !thd->mdl_context.has_locks()); + /* Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too (see sql_yacc.yy) diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 77b5552d977..b7efe13e26e 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1349,6 +1349,7 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) #ifdef EMBEDDED_LIBRARY bool table_exists; #endif /* EMBEDDED_LIBRARY */ + MDL_request mdl_request; DBUG_ENTER("plugin_load"); if (!(new_thd= new THD)) @@ -1366,7 +1367,8 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) tables.alias= tables.table_name= (char*)"plugin"; tables.lock_type= TL_READ; tables.db= new_thd->db; - alloc_mdl_requests(&tables, tmp_root); + tables.mdl_request= &mdl_request; + mdl_request.init(0, tables.db, tables.table_name); #ifdef EMBEDDED_LIBRARY /* diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 5efa0cea7a9..9c7949eaf1d 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1673,7 +1673,13 @@ static bool mysql_test_create_table(Prepared_statement *stmt) if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) { lex->link_first_table_back(create_table, link_to_local); - create_table->open_type= TABLE_LIST::OPEN_OR_CREATE; + /* + The open and lock strategies will be set again once the + statement is executed. These values are only meaningful + for the prepare phase. + */ + create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + create_table->lock_strategy= TABLE_LIST::SHARED_MDL; } if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0)) @@ -3130,6 +3136,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) bool error; Statement stmt_backup; Query_arena *old_stmt_arena; + MDL_ticket *mdl_savepoint= NULL; DBUG_ENTER("Prepared_statement::prepare"); /* If this is an SQLCOM_PREPARE, we also increase Com_prepare_sql. @@ -3188,6 +3195,13 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) */ DBUG_ASSERT(thd->change_list.is_empty()); + /* + Marker used to release metadata locks acquired while the prepared + statement is being checked. + */ + if (thd->in_multi_stmt_transaction()) + mdl_savepoint= thd->mdl_context.mdl_savepoint(); + /* The only case where we should have items in the thd->free_list is after stmt->set_params_from_vars(), which may in some cases create @@ -3211,6 +3225,15 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) lex_end(lex); cleanup_stmt(); + /* + If not inside a multi-statement transaction, the metadata locks have + already been released and the rollback_to_savepoint is a nop. + Otherwise, release acquired locks -- a NULL mdl_savepoint means that + all locks are going to be released or that the transaction didn't + own any locks. + */ + if (!thd->locked_tables_mode) + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); thd->restore_backup_statement(this, &stmt_backup); thd->stmt_arena= old_stmt_arena; diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index 40cdfeed946..c46a01cd534 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -39,6 +39,7 @@ #include #include "sp_head.h" #include "sp.h" +#include "transaction.h" /* We only use 1 mutex to guard the data structures - THR_LOCK_servers. @@ -224,9 +225,6 @@ bool servers_reload(THD *thd) bool return_val= TRUE; DBUG_ENTER("servers_reload"); - /* Can't have locked tables here */ - thd->locked_tables_list.unlock_locked_tables(thd); - DBUG_PRINT("info", ("locking servers_cache")); rw_wrlock(&THR_LOCK_servers); @@ -252,7 +250,10 @@ bool servers_reload(THD *thd) } end: + trans_commit_implicit(thd); close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); DBUG_PRINT("info", ("unlocking servers_cache")); rw_unlock(&THR_LOCK_servers); DBUG_RETURN(return_val); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 42ae0a89458..8fdaa2cd93a 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1974,7 +1974,8 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, } /* Probably a non-temporary table. */ - non_temp_tables_count++; + if (!drop_temporary) + non_temp_tables_count++; /* If row-based replication is used and the table is not a @@ -4738,6 +4739,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, trans_commit_stmt(thd); trans_commit(thd); close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); lex->reset_query_tables_list(FALSE); table->table=0; // For query cache if (protocol->write()) @@ -4786,7 +4789,10 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, { DBUG_PRINT("admin", ("recreating table")); trans_rollback_stmt(thd); + trans_rollback(thd); close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); tmp_disable_binlog(thd); // binlogging is done by caller if wanted result_code= mysql_recreate_table(thd, table); reenable_binlog(thd); @@ -4899,14 +4905,17 @@ send_result_message: reopen the table and do ha_innobase::analyze() on it. We have to end the row, so analyze could return more rows. */ + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); protocol->store(STRING_WITH_LEN("note"), system_charset_info); protocol->store(STRING_WITH_LEN( "Table does not support optimize, doing recreate + analyze instead"), system_charset_info); if (protocol->write()) goto err; - trans_commit_stmt(thd); - close_thread_tables(thd); DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); TABLE_LIST *save_next_local= table->next_local, *save_next_global= table->next_global; @@ -4923,7 +4932,10 @@ send_result_message: if (thd->stmt_da->is_ok()) thd->stmt_da->reset_diagnostics_area(); trans_commit_stmt(thd); + trans_commit(thd); close_thread_tables(thd); + if (!thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); if (!result_code) // recreation went ok { if ((table->table= open_ltable(thd, table, lock_type, 0)) && @@ -5017,6 +5029,7 @@ send_result_message: query_cache_invalidate3(thd, table->table, 0); } } + /* Error path, a admin command failed. */ trans_commit_stmt(thd); trans_commit_implicit(thd); close_thread_tables(thd); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index b370fc79b17..b36ff6b6743 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -395,7 +395,8 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, goto err; lex->link_first_table_back(view, link_to_local); - view->open_type= TABLE_LIST::TAKE_EXCLUSIVE_MDL; + view->open_strategy= TABLE_LIST::OPEN_STUB; + view->lock_strategy= TABLE_LIST::EXCLUSIVE_MDL; if (open_and_lock_tables(thd, lex->query_tables)) { diff --git a/sql/table.h b/sql/table.h index b6ea372b41e..1762ae8785d 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1350,24 +1350,36 @@ struct TABLE_LIST bool prelocking_placeholder; /** Indicates that if TABLE_LIST object corresponds to the table/view - which requires special handling/meta-data locking. + which requires special handling. */ enum { - /* Normal open, shared metadata lock should be taken. */ - NORMAL_OPEN= 0, + /* Normal open. */ + OPEN_NORMAL= 0, + /* Associate a table share only if the the table exists. */ + OPEN_IF_EXISTS, + /* Don't associate a table share. */ + OPEN_STUB + } open_strategy; + /** + Indicates the locking strategy for the object being opened: + whether the associated metadata lock is shared or exclusive. + */ + enum + { + /* Take a shared metadata lock before the object is opened. */ + SHARED_MDL= 0, /* - It's target table of CREATE TABLE ... SELECT so we should - either open table if it exists (and take shared metadata lock) - or take exclusive metadata lock if it doesn't exist. + Take a exclusive metadata lock before the object is opened. + If opening is successful, downgrade to a shared lock. */ - OPEN_OR_CREATE, - /* - It's target view of CREATE/ALTER VIEW. We should take exclusive - metadata lock for this table list element. - */ - TAKE_EXCLUSIVE_MDL - } open_type; + EXCLUSIVE_DOWNGRADABLE_MDL, + /* Take a exclusive metadata lock before the object is opened. */ + EXCLUSIVE_MDL + } lock_strategy; + /* For transactional locking. */ + int lock_timeout; /* NOWAIT or WAIT [X] */ + bool lock_transactional; /* If transactional lock requested. */ bool internal_tmp_table; /** TRUE if an alias for this table was specified in the SQL. */ bool is_alias; diff --git a/sql/transaction.cc b/sql/transaction.cc index 7bfaf4846cf..d1c7244ba83 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -99,6 +99,12 @@ bool trans_begin(THD *thd, uint flags) if (trans_commit_implicit(thd)) DBUG_RETURN(TRUE); + /* + Release transactional metadata locks only after the + transaction has been committed. + */ + thd->mdl_context.release_all_locks(); + thd->options|= OPTION_BEGIN; thd->server_status|= SERVER_STATUS_IN_TRANS; @@ -331,6 +337,13 @@ bool trans_savepoint(THD *thd, LEX_STRING name) newsv->prev= thd->transaction.savepoints; thd->transaction.savepoints= newsv; + /* + Remember the last acquired lock before the savepoint was set. + This is used as a marker to only release locks acquired after + the setting of this savepoint. + */ + newsv->mdl_savepoint = thd->mdl_context.mdl_savepoint(); + DBUG_RETURN(FALSE); } @@ -375,6 +388,10 @@ bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name) thd->transaction.savepoints= sv; + /* Release metadata locks that were acquired during this savepoint unit. */ + if (!res && !thd->locked_tables_mode) + thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint); + DBUG_RETURN(test(res)); } diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index c9edd210c32..56e1d609754 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -18479,8 +18479,8 @@ static void test_wl4284_1() mysql_free_result(result); - /* set AUTOCOMMIT to OFF */ - rc= mysql_autocommit(mysql, FALSE); + /* set AUTOCOMMIT to ON */ + rc= mysql_autocommit(mysql, TRUE); myquery(rc); rc= mysql_query(mysql, "DROP TABLE trans"); @@ -18637,6 +18637,8 @@ static void test_bug40365(void) DIE_UNLESS(tm[i].day == 0); } mysql_stmt_close(stmt); + rc= mysql_commit(mysql); + myquery(rc); DBUG_VOID_RETURN; } From 356597c0fafa6472cd6e97ac550d49b8e07b97ec Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 8 Dec 2009 10:39:49 +0300 Subject: [PATCH 071/212] Backport of: ------------------------------------------------------------ revno: 2617.31.7 committer: Davi Arnaut branch nick: mysql-6.0-runtime timestamp: Wed 2009-03-25 19:22:00 -0300 message: WL#4284: Transactional DDL locking Post-merge fixes for test cases. mysql-test/include/mix1.inc: Ignore deadlock errors due to the table being altered. mysql-test/r/innodb_mysql.result: Update test case result (WL$4284). mysql-test/suite/parts/r/partition_special_innodb.result: The INSERT and SELECT are not necessary to reproduce the problem as the assertion happens when the table is being altered. Furthermore, the INSERT and SELECT will yield a deadlock error as after the alter the table version is set to zero, which means that any metadata locks on the table must be relinquished, but this won't happen voluntarily in a multi-statement transaction (metadata locks are released on commit or rollback). Reported as Bug#43867. mysql-test/suite/parts/t/partition_special_innodb.test: The INSERT and SELECT are not necessary to reproduce the problem as the assertion happens when the table is being altered. Furthermore, the INSERT and SELECT will yield a deadlock error as after the alter the table version is set to zero, which means that any metadata locks on the table must be relinquished, but this won't happen voluntarily in a multi-statement transaction (metadata locks are released on commit or rollback). Reported as Bug#43867. --- mysql-test/include/mix1.inc | 2 ++ mysql-test/r/innodb_mysql.result | 2 ++ mysql-test/suite/parts/r/partition_special_innodb.result | 5 ----- mysql-test/suite/parts/t/partition_special_innodb.test | 3 --- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/mysql-test/include/mix1.inc b/mysql-test/include/mix1.inc index 3eaaf37cd83..66648aaf1bf 100644 --- a/mysql-test/include/mix1.inc +++ b/mysql-test/include/mix1.inc @@ -899,6 +899,8 @@ CREATE PROCEDURE p1 () BEGIN DECLARE i INT DEFAULT 50; DECLARE cnt INT; + # Continue even in the presence of ER_LOCK_DEADLOCK. + DECLARE CONTINUE HANDLER FOR 1213 BEGIN END; START TRANSACTION; ALTER TABLE t1 ENGINE=InnoDB; COMMIT; diff --git a/mysql-test/r/innodb_mysql.result b/mysql-test/r/innodb_mysql.result index 0fe704c13f6..54ca46a098b 100644 --- a/mysql-test/r/innodb_mysql.result +++ b/mysql-test/r/innodb_mysql.result @@ -1105,6 +1105,8 @@ CREATE PROCEDURE p1 () BEGIN DECLARE i INT DEFAULT 50; DECLARE cnt INT; +# Continue even in the presence of ER_LOCK_DEADLOCK. +DECLARE CONTINUE HANDLER FOR 1213 BEGIN END; START TRANSACTION; ALTER TABLE t1 ENGINE=InnoDB; COMMIT; diff --git a/mysql-test/suite/parts/r/partition_special_innodb.result b/mysql-test/suite/parts/r/partition_special_innodb.result index 26c1ac9356c..3c64c5c3ca4 100644 --- a/mysql-test/suite/parts/r/partition_special_innodb.result +++ b/mysql-test/suite/parts/r/partition_special_innodb.result @@ -214,9 +214,4 @@ INSERT INTO t1 VALUES (NULL, 'first row t2'); SET autocommit=OFF; ALTER TABLE t1 AUTO_INCREMENT = 10; ERROR HY000: Lock wait timeout exceeded; try restarting transaction -INSERT INTO t1 VALUES (NULL, 'second row t2'); -SELECT a,b FROM t1 ORDER BY a; -a b -1 first row t2 -2 second row t2 DROP TABLE t1; diff --git a/mysql-test/suite/parts/t/partition_special_innodb.test b/mysql-test/suite/parts/t/partition_special_innodb.test index eac19f6d588..7583f953b32 100644 --- a/mysql-test/suite/parts/t/partition_special_innodb.test +++ b/mysql-test/suite/parts/t/partition_special_innodb.test @@ -71,9 +71,6 @@ SET autocommit=OFF; --error ER_LOCK_WAIT_TIMEOUT ALTER TABLE t1 AUTO_INCREMENT = 10; ---connection con1 -INSERT INTO t1 VALUES (NULL, 'second row t2'); -SELECT a,b FROM t1 ORDER BY a; --disconnect con2 --disconnect con1 --connection default From a26d3f9963b2c5becd615a87ba4a249405e0bbf1 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 8 Dec 2009 10:53:40 +0300 Subject: [PATCH 072/212] Backport of: ------------------------------------------------------------ revno: 2617.43.3 committer: Davi Arnaut branch nick: 40188-6.0 timestamp: Thu 2009-05-07 13:15:54 +0200 message: Sort results as the file list of the database directory is not sorted (MY_DONT_SORT). (This is a follow-up fix for WL#4284). mysql-test/r/drop_debug.result: Update test case result. mysql-test/t/drop_debug.test: Sort warnings. --- mysql-test/r/drop_debug.result | 4 ++++ mysql-test/t/drop_debug.test | 3 +++ 2 files changed, 7 insertions(+) diff --git a/mysql-test/r/drop_debug.result b/mysql-test/r/drop_debug.result index 75346b88bc6..f2c89034451 100644 --- a/mysql-test/r/drop_debug.result +++ b/mysql-test/r/drop_debug.result @@ -7,12 +7,16 @@ DROP DATABASE IF EXISTS mysql_test; CREATE DATABASE mysql_test; CREATE TABLE mysql_test.t1(a INT); +CREATE TABLE mysql_test.t2(b INT); +CREATE TABLE mysql_test.t3(c INT); SET SESSION DEBUG = "+d,bug43138"; DROP DATABASE mysql_test; Warnings: Error 1051 Unknown table 't1' +Error 1051 Unknown table 't2' +Error 1051 Unknown table 't3' SET SESSION DEBUG = "-d,bug43138"; diff --git a/mysql-test/t/drop_debug.test b/mysql-test/t/drop_debug.test index 97ee5847d0a..63c85d9246b 100644 --- a/mysql-test/t/drop_debug.test +++ b/mysql-test/t/drop_debug.test @@ -17,11 +17,14 @@ DROP DATABASE IF EXISTS mysql_test; --echo CREATE DATABASE mysql_test; CREATE TABLE mysql_test.t1(a INT); +CREATE TABLE mysql_test.t2(b INT); +CREATE TABLE mysql_test.t3(c INT); --echo SET SESSION DEBUG = "+d,bug43138"; --echo +--sorted_result DROP DATABASE mysql_test; --echo From 226d3e4f1e18ddd3e065811a7ec1a0daaaf3c2e8 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 8 Dec 2009 11:26:49 +0300 Subject: [PATCH 073/212] Backport of: ------------------------------------------------------------ revno: 2617.65.6 committer: Dmitry Lenev branch nick: mysql-azalea-bg39674 timestamp: Sat 2009-07-25 00:28:43 +0400 message: Fix for bug #39674 "On shutdown mdl_destroy() called before plugin_shutdown()". Attempt to shutdown PBXT engine plugin led to assertion failure caused by using already destroyed mutex in metadata locking subsystem. This problem stemmed from the fact that we MDL subsystem and table definition cache were deinitialized before plugin shutdown while PBXT plugin during its shutdown process accessed tables and therefore expected them to be in working shape. This patch solves this problem by moving deinitialization of these two subsystems after plugins are shut down. No test case is provided since such test case would require using PBXT or other plugin which accesses tables during its shutdown process. sql/mysql_priv.h: Introduced table_def_start_shutdown() function which informs table definition cache that shutdown process has been started so it has to keep number of TABLE and TABLE_SHARE objects minimal in order to reduce number of references to pluggable engines. sql/mysqld.cc: Destroy table definition cache and meta-data locking subsystem after shutting down plugins. This allows plugins to work with tables during their shutdown. Since table definition cache hold references to storage engine plugins we have to remove unused tables from it before shutting down plugins and keep number of these references minimal during the process (by immediately removing tables opened during this process from the table definition cache). sql/sql_base.cc: Introduced table_def_start_shutdown() function which informs table definition cache that shutdown process has been started so it has to keep number of TABLE and TABLE_SHARE objects minimal in order to reduce number of references to pluggable engines. This allows to smoothly shutdown such plugins without completely prohibiting access to tables/table definition cache while shutting down other plugins. --- sql/mysql_priv.h | 1 + sql/mysqld.cc | 5 +++-- sql/sql_base.cc | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 159613c58a0..74c5af92229 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1047,6 +1047,7 @@ bool compare_record(TABLE *table); bool append_file_to_dir(THD *thd, const char **filename_ptr, const char *table_name); bool table_def_init(void); +void table_def_start_shutdown(void); void table_def_free(void); void assign_new_table_id(TABLE_SHARE *share); uint cached_open_tables(void); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 1055d5fed8b..ed1572853bc 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1294,9 +1294,7 @@ void clean_up(bool print_message) grant_free(); #endif query_cache_destroy(); - table_def_free(); hostname_cache_free(); - mdl_destroy(); item_user_lock_free(); lex_free(); /* Free some memory */ item_create_cleanup(); @@ -1308,12 +1306,15 @@ void clean_up(bool print_message) udf_free(); #endif } + table_def_start_shutdown(); plugin_shutdown(); ha_end(); if (tc_log) tc_log->close(); delegates_destroy(); xid_cache_free(); + table_def_free(); + mdl_destroy(); delete_elements(&key_caches, (void (*)(const char*, uchar*)) free_key_cache); multi_keycache_free(); free_status_vars(); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 42fc3ba0566..4f8c6be8e7b 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -114,6 +114,7 @@ TABLE *unused_tables; HASH table_def_cache; static TABLE_SHARE *oldest_unused_share, end_of_unused_share; static bool table_def_inited= 0; +static bool table_def_shutdown_in_progress= 0; static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, TABLE_SHARE *table_share); @@ -279,13 +280,36 @@ bool table_def_init(void) } +/** + Notify table definition cache that process of shutting down server + has started so it has to keep number of TABLE and TABLE_SHARE objects + minimal in order to reduce number of references to pluggable engines. +*/ + +void table_def_start_shutdown(void) +{ + if (table_def_inited) + { + pthread_mutex_lock(&LOCK_open); + /* Free all cached but unused TABLEs and TABLE_SHAREs first. */ + close_cached_tables(NULL, NULL, TRUE, FALSE); + /* + Ensure that TABLE and TABLE_SHARE objects which are created for + tables that are open during process of plugins' shutdown are + immediately released. This keeps number of references to engine + plugins minimal and allows shutdown to proceed smoothly. + */ + table_def_shutdown_in_progress= TRUE; + pthread_mutex_unlock(&LOCK_open); + } +} + + void table_def_free(void) { DBUG_ENTER("table_def_free"); if (table_def_inited) { - /* Free all open TABLEs first. */ - close_cached_tables(NULL, NULL, FALSE, FALSE); table_def_inited= 0; /* Free table definitions. */ my_hash_free(&table_def_cache); @@ -646,7 +670,8 @@ void release_table_share(TABLE_SHARE *share) DBUG_ASSERT(share->ref_count); if (!--share->ref_count) { - if (share->version != refresh_version) + if (share->version != refresh_version || + table_def_shutdown_in_progress) to_be_deleted=1; else { @@ -1513,7 +1538,8 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) table->mdl_ticket= NULL; if (table->needs_reopen() || - thd->version != refresh_version || !table->db_stat) + thd->version != refresh_version || !table->db_stat || + table_def_shutdown_in_progress) { free_cache_entry(table); found_old_table=1; From 478e09609c0922c7838c2ae1e25f4a7b2aaa5970 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 8 Dec 2009 11:38:45 +0300 Subject: [PATCH 074/212] Backport of: ---------------------------------------------------------- revno: 2617.69.2 committer: Konstantin Osipov branch nick: 5.4-azalea-bugfixing timestamp: Mon 2009-08-03 19:26:04 +0400 message: A fix and a test case for Bug#45035 "Altering table under LOCK TABLES results in "Error 1213 Deadlock found...". If a user had a table locked with LOCK TABLES for READ and for WRITE in the same connection, ALTER TABLE could fail. Root cause analysis: If a connection issues LOCK TABLE t1 write, t1 a read, t1 b read; the new LOCK TABLES code in 6.0 (part of WL 3726) will create the following list of TABLE_LIST objects (thd->locked_tables_list->m_locked_tables): {"t1" "b" tl_read_no_insert}, {"t1" "a" tl_read_no_insert}, {"t1" "t1" tl_write } Later on, when we try to ALTER table t1, mysql_alter_table() closes all TABLE instances and releases its thr_lock locks, keeping only an exclusive metadata lock on t1. But when ALTER is finished, Locked_table_list::reopen_tables() tries to restore the original list of open and locked tables. Before this patch, it used to do so one by one: Open t1 b, get TL_READ_NO_INSERT lock, Open t1 a, get TL_READ_NO_INSERT lock Open t1, try to get TL_WRITE lock, deadlock. The cause of the deadlock is that thr_lock.c doesn't resolve the situation when the read list only consists of locks taken by the same thread, followed by this very thread trying to take a WRITE lock. Indeed, since thr_lock_multi always gets a sorted list of locks, WRITE locks always precede READ locks in the list to lock. Don't try to fix thr_lock.c deficiency, keep this code simple. Instead, try to take all thr_lock locks at once in ::reopen_tables(). mysql-test/r/lock.result: Update results: test case for Bug#45035. mysql-test/t/lock.test: Add a test case for Bug#45035. sql/sql_base.cc: Take all thr_lock locks at once in Locked_tables_list::reopen_tables(). sql/sql_class.h: Add a helper array to store tables for mysql_lock_tables() in reopen_tables(). sql/sql_table.cc: Update unlink_all_closed_tables() to the new signature. --- mysql-test/r/lock.result | 37 ++++++++++++++ mysql-test/t/lock.test | 40 +++++++++++++++ sql/sql_base.cc | 103 +++++++++++++++++++++++++++++++-------- sql/sql_class.h | 19 ++++++-- sql/sql_table.cc | 4 +- 5 files changed, 178 insertions(+), 25 deletions(-) diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index c60ec4bc1bd..46ce618b99b 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -282,5 +282,42 @@ insert into t1 values (1); # Ensure that metadata locks held by the transaction are released. drop table t1; # +# Bug#45035 " Altering table under LOCK TABLES results in +# "Error 1213 Deadlock found..." +# +# When reopening tables under LOCK TABLES after ALTER TABLE, +# 6.0 used to be taking thr_lock locks one by one, and +# that would lead to a lock conflict. +# Check that taking all locks at once works. +# +drop table if exists t1; +create table t1 (i int); +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; +create temporary table t1 (i int); +# +# This is just for test coverage purposes, +# when this is allowed, remove the --error. +# +lock tables t1 write, t1 as a read, t1 as b read; +ERROR HY000: Can't reopen table: 't1' +alter table t1 add column j int; +unlock tables; +drop table t1; +# +# Separate case for partitioned tables is important +# because each partition has an own thr_lock object. +# +create table t1 (i int) partition by list (i) +(partition p0 values in (1), +partition p1 values in (2,3), +partition p2 values in (4,5)); +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; +# # End of 6.0 tests. # diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index c2aeac05cd1..eaba2693904 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -345,6 +345,46 @@ connection default; drop table t1; +--echo # +--echo # Bug#45035 " Altering table under LOCK TABLES results in +--echo # "Error 1213 Deadlock found..." +--echo # +--echo # When reopening tables under LOCK TABLES after ALTER TABLE, +--echo # 6.0 used to be taking thr_lock locks one by one, and +--echo # that would lead to a lock conflict. +--echo # Check that taking all locks at once works. +--echo # +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (i int); +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; +create temporary table t1 (i int); +--echo # +--echo # This is just for test coverage purposes, +--echo # when this is allowed, remove the --error. +--echo # +--error ER_CANT_REOPEN_TABLE +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; +--echo # +--echo # Separate case for partitioned tables is important +--echo # because each partition has an own thr_lock object. +--echo # +create table t1 (i int) partition by list (i) + (partition p0 values in (1), + partition p1 values in (2,3), + partition p2 values in (4,5)); +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; + --echo # --echo # End of 6.0 tests. --echo # diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 4f8c6be8e7b..02871c118ca 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2980,8 +2980,11 @@ Locked_tables_list::init_locked_tables(THD *thd) { DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE); DBUG_ASSERT(m_locked_tables == NULL); + DBUG_ASSERT(m_reopen_array == NULL); + DBUG_ASSERT(m_locked_tables_count == 0); - for (TABLE *table= thd->open_tables; table; table= table->next) + for (TABLE *table= thd->open_tables; table; + table= table->next, m_locked_tables_count++) { TABLE_LIST *src_table_list= table->pos_in_table_list; char *db, *table_name, *alias; @@ -3021,7 +3024,24 @@ Locked_tables_list::init_locked_tables(THD *thd) m_locked_tables_last= &dst_table_list->next_global; table->pos_in_locked_tables= dst_table_list; } + if (m_locked_tables_count) + { + /** + Allocate an auxiliary array to pass to mysql_lock_tables() + in reopen_tables(). reopen_tables() is a critical + path and we don't want to complicate it with extra allocations. + */ + m_reopen_array= (TABLE**)alloc_root(&m_locked_tables_root, + sizeof(TABLE*) * + (m_locked_tables_count+1)); + if (m_reopen_array == NULL) + { + unlock_locked_tables(0); + return TRUE; + } + } thd->locked_tables_mode= LTM_LOCK_TABLES; + return FALSE; } @@ -3070,6 +3090,8 @@ Locked_tables_list::unlock_locked_tables(THD *thd) free_root(&m_locked_tables_root, MYF(0)); m_locked_tables= NULL; m_locked_tables_last= &m_locked_tables; + m_reopen_array= NULL; + m_locked_tables_count= 0; } @@ -3141,8 +3163,39 @@ void Locked_tables_list::unlink_from_list(THD *thd, @note This function is a no-op if we're not under LOCK TABLES. */ -void Locked_tables_list::unlink_all_closed_tables() +void Locked_tables_list:: +unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) { + /* If we managed to take a lock, unlock tables and free the lock. */ + if (lock) + mysql_unlock_tables(thd, lock); + /* + If a failure happened in reopen_tables(), we may have succeeded + reopening some tables, but not all. + This works when the connection was killed in mysql_lock_tables(). + */ + if (reopen_count) + { + pthread_mutex_lock(&LOCK_open); + while (reopen_count--) + { + /* + When closing the table, we must remove it + from thd->open_tables list. + We rely on the fact that open_table() that was used + in reopen_tables() always links the opened table + to the beginning of the open_tables list. + */ + DBUG_ASSERT(thd->open_tables == m_reopen_array[reopen_count]); + + thd->open_tables->pos_in_locked_tables->table= NULL; + + close_thread_table(thd, &thd->open_tables); + } + broadcast_refresh(); + pthread_mutex_unlock(&LOCK_open); + } + /* Exclude all closed tables from the LOCK TABLES list. */ for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list= table_list->next_global) { @@ -3176,12 +3229,13 @@ Locked_tables_list::reopen_tables(THD *thd) { enum enum_open_table_action ot_action_unused; bool lt_refresh_unused; + size_t reopen_count= 0; + MYSQL_LOCK *lock; + MYSQL_LOCK *merged_lock; for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list= table_list->next_global) { - MYSQL_LOCK *lock; - if (table_list->table) /* The table was not closed */ continue; @@ -3189,33 +3243,42 @@ Locked_tables_list::reopen_tables(THD *thd) if (open_table(thd, table_list, thd->mem_root, &ot_action_unused, MYSQL_OPEN_REOPEN)) { - unlink_all_closed_tables(); + unlink_all_closed_tables(thd, 0, reopen_count); return TRUE; } table_list->table->pos_in_locked_tables= table_list; /* See also the comment on lock type in init_locked_tables(). */ table_list->table->reginfo.lock_type= table_list->lock_type; + + DBUG_ASSERT(reopen_count < m_locked_tables_count); + m_reopen_array[reopen_count++]= table_list->table; + } + if (reopen_count) + { thd->in_lock_tables= 1; - lock= mysql_lock_tables(thd, &table_list->table, 1, + /* + We re-lock all tables with mysql_lock_tables() at once rather + than locking one table at a time because of the case + reported in Bug#45035: when the same table is present + in the list many times, thr_lock.c fails to grant READ lock + on a table that is already locked by WRITE lock, even if + WRITE lock is taken by the same thread. If READ and WRITE + lock are passed to thr_lock.c in the same list, everything + works fine. Patching legacy code of thr_lock.c is risking to + break something else. + */ + lock= mysql_lock_tables(thd, m_reopen_array, reopen_count, MYSQL_OPEN_REOPEN, <_refresh_unused); thd->in_lock_tables= 0; - if (lock) - lock= mysql_lock_merge(thd->lock, lock); - if (lock == NULL) + if (lock == NULL || (merged_lock= + mysql_lock_merge(thd->lock, lock)) == NULL) { - /* - No one's seen this branch work. Recover and report an - error just in case. - */ - pthread_mutex_lock(&LOCK_open); - close_thread_table(thd, &thd->open_tables); - pthread_mutex_unlock(&LOCK_open); - table_list->table= 0; - unlink_all_closed_tables(); - my_error(ER_LOCK_DEADLOCK, MYF(0)); + unlink_all_closed_tables(thd, lock, reopen_count); + if (! thd->killed) + my_error(ER_LOCK_DEADLOCK, MYF(0)); return TRUE; } - thd->lock= lock; + thd->lock= merged_lock; } return FALSE; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 92b9f9f4611..4b6564fb9da 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1192,7 +1192,7 @@ private: Therefore, we can't allocate metadata locks on execution memory root -- as well as tables, the locks need to stay around till UNLOCK TABLES is called. - The locks are allocated in the memory root encapsulate in this + The locks are allocated in the memory root encapsulated in this class. Some SQL commands, like FLUSH TABLE or ALTER TABLE, demand that @@ -1211,10 +1211,21 @@ private: MEM_ROOT m_locked_tables_root; TABLE_LIST *m_locked_tables; TABLE_LIST **m_locked_tables_last; + /** An auxiliary array used only in reopen_tables(). */ + TABLE **m_reopen_array; + /** + Count the number of tables in m_locked_tables list. We can't + rely on thd->lock->table_count because it excludes + non-transactional temporary tables. We need to know + an exact number of TABLE objects. + */ + size_t m_locked_tables_count; public: Locked_tables_list() :m_locked_tables(NULL), - m_locked_tables_last(&m_locked_tables) + m_locked_tables_last(&m_locked_tables), + m_reopen_array(NULL), + m_locked_tables_count(0) { init_sql_alloc(&m_locked_tables_root, MEM_ROOT_BLOCK_SIZE, 0); } @@ -1228,7 +1239,9 @@ public: MEM_ROOT *locked_tables_root() { return &m_locked_tables_root; } void unlink_from_list(THD *thd, TABLE_LIST *table_list, bool remove_from_locked_tables); - void unlink_all_closed_tables(); + void unlink_all_closed_tables(THD *thd, + MYSQL_LOCK *lock, + size_t reopen_count); bool reopen_tables(THD *thd); }; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 8fdaa2cd93a..527095f2c88 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4515,7 +4515,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, } end: - thd->locked_tables_list.unlink_all_closed_tables(); + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); if (table == &tmp_table) { pthread_mutex_lock(&LOCK_open); @@ -7597,7 +7597,7 @@ err_with_mdl: remove all references to the altered table from the list of locked tables and release the exclusive metadata lock. */ - thd->locked_tables_list.unlink_all_closed_tables(); + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); if (target_mdl_request) { thd->mdl_context.release_lock(target_mdl_request->ticket); From ce5c87a3d359d528111d84b5c75bdb8acfe3fb9c Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 8 Dec 2009 12:57:07 +0300 Subject: [PATCH 075/212] Backport of: ---------------------------------------------------------- revno: 2617.69.20 committer: Konstantin Osipov branch nick: 5.4-4284-1-assert timestamp: Thu 2009-08-13 18:29:55 +0400 message: WL#4284 "Transactional DDL locking" A review fix. Since WL#4284 implementation separated MDL_request and MDL_ticket, MDL_request becamse a utility object necessary only to get a ticket. Store it by-value in TABLE_LIST with the intent to merge MDL_request::key with table_list->table_name and table_list->db in future. Change the MDL subsystem to not require MDL_requests to stay around till close_thread_tables(). Remove the list of requests from the MDL context. Requests for shared metadata locks acquired in open_tables() are only used as a list in recover_from_failed_open_table_attempt(), which calls mdl_context.wait_for_locks() for this list. To keep such list for recover_from_failed_open_table_attempt(), introduce a context class (Open_table_context), that collects all requests. A lot of minor cleanups and simplications that became possible with this change. sql/event_db_repository.cc: Remove alloc_mdl_requests(). Now MDL_request instance is a member of TABLE_LIST, and init_one_table() initializes it. sql/ha_ndbcluster_binlog.cc: Remove now unnecessary declaration and initialization of binlog_mdl_request. sql/lock.cc: No need to allocate MDL requests in lock_table_names() now. sql/log.cc: Use init_one_table() method, remove alloc_mdl_requests(), which is now unnecessary. sql/log_event.cc: No need to allocate mdl_request separately now. Use init_one_table() method. sql/log_event_old.cc: Update to the new signature of close_tables_for_reopen(). sql/mdl.cc: Update try_acquire_exclusive_lock() to be more easy to use. Function lock_table_name_if_not_cached() has been removed. Make acquire_shared_lock() signature consistent with try_acquire_exclusive_lock() signature. Remove methods that are no longer used. Update comments. sql/mdl.h: Implement an assignment operator that doesn't copy MDL_key (MDL_key::operator= is private and should remain private). This is a hack to work-around assignment of TABLE_LIST by value in several places. Such assignments violate encapsulation, since only perform a shallow copy. In most cases these assignments are a hack on their own. sql/mysql_priv.h: Update signatures of close_thread_tables() and close_tables_for_reopen(). sql/sp.cc: Allocate TABLE_LIST in thd->mem_root. Use init_one_table(). sql/sp_head.cc: Use init_one_table(). Remove thd->locked_tables_root, it's no longer needed. sql/sql_acl.cc: Use init_mdl_requests() and init_one_table(). sql/sql_base.cc: Update to new signatures of try_acquire_shared_lock() and try_acquire_exclusive_lock(). Remove lock_table_name_if_not_cached(). Fix a bug in open_ltable() that would not return ER_LOCK_DEADLOCK in case of a failed lock_tables() and a multi-statement transaction. Fix a bug in open_and_lock_tables_derived() that would not return ER_LOCK_DEADLOCK in case of a multi-statement transaction and a failure of lock_tables(). Move assignment of enum_open_table_action to a method of Open_table_context, a new class that maintains information for backoff actions. Minor rearrangements of the code. Remove alloc_mdl_requests() in functions that work with system tables: instead the patch ensures that callers always initialize TABLE_LIST argument. sql/sql_class.cc: THD::locked_tables_root is no more. sql/sql_class.h: THD::locked_tables_root is no more. Add a declaration for Open_table_context class. sql/sql_delete.cc: Update to use the simplified MDL API. sql/sql_handler.cc: TABLE_LIST::mdl_request is stored by-value now. Ensure that mdl_request.ticket is NULL for every request that is passed into MDL, to satisfy MDL asserts. @ sql/sql_help.cc Function open_system_tables_for_read() no longer initializes mdl_requests. Move TABLE_LIST::mdl_request initialization closer to TABLE_LIST initialization. sql/sql_help.cc: Function open_system_tables_for_read() no longer initializes mdl_requests. Move TABLE_LIST::mdl_request initialization closer to TABLE_LIST initialization. sql/sql_insert.cc: Remove assignment by-value of TABLE_LIST in TABLEOP_HOOKS. We can't carry over a granted MDL ticket from one table list to another. sql/sql_parse.cc: Change alloc_mdl_requests() -> init_mdl_requests(). @todo We can remove init_mdl_requests() altogether in some places: all places that call add_table_to_list() already have mdl requests initialized. sql/sql_plugin.cc: Use init_one_table(). THD::locked_tables_root is no more. sql/sql_servers.cc: Use init_one_table(). sql/sql_show.cc: Update acquire_high_priority_shared_lock() to use TABLE_LIST::mdl_request rather than allocate an own. Fix get_trigger_table_impl() to use init_one_table(), check for out of memory, follow the coding style. sql/sql_table.cc: Update to work with TABLE_LIST::mdl_request by-value. Remove lock_table_name_if_not_cached(). The code that used to delegate to it is quite simple and concise without it now. sql/sql_udf.cc: Use init_one_table(). sql/sql_update.cc: Update to use the new signature of close_tables_for_reopen(). sql/table.cc: Move re-setting of mdl_requests for prepared statements and stored procedures from close_thread_tables() to reinit_stmt_before_use(). Change alloc_mdl_requests() to init_mdl_requests(). init_mdl_requests() is a hack that can't be deleted until we don't have a list-aware TABLE_LIST constructor. Hopefully its use will be minimal sql/table.h: Change alloc_mdl_requests() to init_mdl_requests() TABLE_LIST::mdl_request is stored by value. sql/tztime.cc: We no longer initialize mdl requests in open_system_tables_for*() functions. Move this initialization closer to initialization of the rest of TABLE_LIST members. storage/myisammrg/ha_myisammrg.cc: Simplify mdl_request initialization. --- sql/event_db_repository.cc | 4 - sql/ha_ndbcluster_binlog.cc | 10 +- sql/lock.cc | 21 +-- sql/log.cc | 42 ++--- sql/log_event.cc | 18 +- sql/log_event_old.cc | 2 +- sql/mdl.cc | 242 +++++++------------------ sql/mdl.h | 85 +++++---- sql/mysql_priv.h | 8 +- sql/sp.cc | 15 +- sql/sp_head.cc | 10 +- sql/sql_acl.cc | 25 ++- sql/sql_base.cc | 283 ++++++++++++++++-------------- sql/sql_class.cc | 3 +- sql/sql_class.h | 53 ++++-- sql/sql_delete.cc | 41 ++--- sql/sql_handler.cc | 8 +- sql/sql_help.cc | 1 + sql/sql_insert.cc | 33 ++-- sql/sql_parse.cc | 10 +- sql/sql_plugin.cc | 19 +- sql/sql_servers.cc | 22 +-- sql/sql_show.cc | 78 ++++---- sql/sql_table.cc | 204 +++++++++------------ sql/sql_udf.cc | 26 +-- sql/sql_update.cc | 4 +- sql/table.cc | 14 +- sql/table.h | 8 +- sql/tztime.cc | 2 + storage/myisammrg/ha_myisammrg.cc | 8 +- 30 files changed, 566 insertions(+), 733 deletions(-) diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 134c8059e13..9f53a73a594 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -555,7 +555,6 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, DBUG_ENTER("Event_db_repository::open_event_table"); tables.init_one_table("mysql", 5, "event", 5, "event", lock_type); - alloc_mdl_requests(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1110,7 +1109,6 @@ Event_db_repository::check_system_tables(THD *thd) /* Check mysql.db */ tables.init_one_table("mysql", 5, "db", 2, "db", TL_READ); - alloc_mdl_requests(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1128,7 +1126,6 @@ Event_db_repository::check_system_tables(THD *thd) } /* Check mysql.user */ tables.init_one_table("mysql", 5, "user", 4, "user", TL_READ); - alloc_mdl_requests(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1149,7 +1146,6 @@ Event_db_repository::check_system_tables(THD *thd) } /* Check mysql.event */ tables.init_one_table("mysql", 5, "event", 5, "event", TL_READ); - alloc_mdl_requests(&tables, thd->mem_root); if (simple_open_n_lock_tables(thd, &tables)) { diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index 7ce318394d4..8b3764367c2 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -140,7 +140,6 @@ static Uint64 *p_latest_trans_gci= 0; */ static TABLE *ndb_binlog_index= 0; static TABLE_LIST binlog_tables; -static MDL_request binlog_mdl_request; /* Helper functions @@ -2337,13 +2336,10 @@ static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index) const char *save_proc_info= thd->proc_info; TABLE_LIST *tables= &binlog_tables; - bzero((char*) tables, sizeof(*tables)); - tables->db= repdb; - tables->alias= tables->table_name= reptable; - tables->lock_type= TL_WRITE; + tables->init_one_table(repdb, strlen(repdb), reptable, strlen(reptable), + reptable, TL_WRITE); thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE; - binlog_mdl_request.init(0, tables->db, tables->table_name); - tables->mdl_request= &binlog_mdl_request; + tables->required_type= FRMTYPE_TABLE; uint counter; thd->clear_error(); diff --git a/sql/lock.cc b/sql/lock.cc index 170007d8f66..aea1bfbd0e6 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -952,26 +952,18 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, bool lock_table_names(THD *thd, TABLE_LIST *table_list) { + MDL_request_list mdl_requests; TABLE_LIST *lock_table; - MDL_request *mdl_request; for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) { - mdl_request= MDL_request::create(0, lock_table->db, lock_table->table_name, - thd->mem_root); - if (!mdl_request) - goto end; - mdl_request->set_type(MDL_EXCLUSIVE); - thd->mdl_context.add_request(mdl_request); - lock_table->mdl_request= mdl_request; + lock_table->mdl_request.init(0, lock_table->db, lock_table->table_name, + MDL_EXCLUSIVE); + mdl_requests.push_front(&lock_table->mdl_request); } - if (thd->mdl_context.acquire_exclusive_locks()) - goto end; + if (thd->mdl_context.acquire_exclusive_locks(&mdl_requests)) + return 1; return 0; - -end: - thd->mdl_context.remove_all_requests(); - return 1; } @@ -987,7 +979,6 @@ void unlock_table_names(THD *thd) { DBUG_ENTER("unlock_table_names"); thd->mdl_context.release_all_locks(); - thd->mdl_context.remove_all_requests(); DBUG_VOID_RETURN; } diff --git a/sql/log.cc b/sql/log.cc index 48e509e9275..a74fc94d09d 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -423,14 +423,10 @@ bool Log_to_csv_event_handler:: save_thd_options= thd->options; thd->options&= ~OPTION_BIN_LOG; - bzero(& table_list, sizeof(TABLE_LIST)); - table_list.alias= table_list.table_name= GENERAL_LOG_NAME.str; - table_list.table_name_length= GENERAL_LOG_NAME.length; - - table_list.lock_type= TL_WRITE_CONCURRENT_INSERT; - - table_list.db= MYSQL_SCHEMA_NAME.str; - table_list.db_length= MYSQL_SCHEMA_NAME.length; + table_list.init_one_table(MYSQL_SCHEMA_NAME.str, MYSQL_SCHEMA_NAME.length, + GENERAL_LOG_NAME.str, GENERAL_LOG_NAME.length, + GENERAL_LOG_NAME.str, + TL_WRITE_CONCURRENT_INSERT); /* 1) open_performance_schema_table generates an error of the @@ -588,14 +584,10 @@ bool Log_to_csv_event_handler:: */ save_time_zone_used= thd->time_zone_used; - bzero(& table_list, sizeof(TABLE_LIST)); - table_list.alias= table_list.table_name= SLOW_LOG_NAME.str; - table_list.table_name_length= SLOW_LOG_NAME.length; - - table_list.lock_type= TL_WRITE_CONCURRENT_INSERT; - - table_list.db= MYSQL_SCHEMA_NAME.str; - table_list.db_length= MYSQL_SCHEMA_NAME.length; + table_list.init_one_table(MYSQL_SCHEMA_NAME.str, MYSQL_SCHEMA_NAME.length, + SLOW_LOG_NAME.str, SLOW_LOG_NAME.length, + SLOW_LOG_NAME.str, + TL_WRITE_CONCURRENT_INSERT); if (!(table= open_performance_schema_table(thd, & table_list, & open_tables_backup))) @@ -733,29 +725,25 @@ int Log_to_csv_event_handler:: { TABLE_LIST table_list; TABLE *table; + LEX_STRING *UNINIT_VAR(log_name); int result; Open_tables_state open_tables_backup; DBUG_ENTER("Log_to_csv_event_handler::activate_log"); - bzero(& table_list, sizeof(TABLE_LIST)); - if (log_table_type == QUERY_LOG_GENERAL) { - table_list.alias= table_list.table_name= GENERAL_LOG_NAME.str; - table_list.table_name_length= GENERAL_LOG_NAME.length; + log_name= &GENERAL_LOG_NAME; } else { DBUG_ASSERT(log_table_type == QUERY_LOG_SLOW); - table_list.alias= table_list.table_name= SLOW_LOG_NAME.str; - table_list.table_name_length= SLOW_LOG_NAME.length; + + log_name= &SLOW_LOG_NAME; } - - table_list.lock_type= TL_WRITE_CONCURRENT_INSERT; - - table_list.db= MYSQL_SCHEMA_NAME.str; - table_list.db_length= MYSQL_SCHEMA_NAME.length; + table_list.init_one_table(MYSQL_SCHEMA_NAME.str, MYSQL_SCHEMA_NAME.length, + log_name->str, log_name->length, log_name->str, + TL_WRITE_CONCURRENT_INSERT); table= open_performance_schema_table(thd, & table_list, & open_tables_backup); diff --git a/sql/log_event.cc b/sql/log_event.cc index aa8dd6e9bff..46d016b2c15 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -8067,7 +8067,6 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) { RPL_TABLE_LIST *table_list; char *db_mem, *tname_mem; - MDL_request *mdl_request; size_t dummy_len; void *memory; DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)"); @@ -8082,21 +8081,18 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) &table_list, (uint) sizeof(RPL_TABLE_LIST), &db_mem, (uint) NAME_LEN + 1, &tname_mem, (uint) NAME_LEN + 1, - &mdl_request, sizeof(MDL_request), NullS))) DBUG_RETURN(HA_ERR_OUT_OF_MEM); - bzero(table_list, sizeof(*table_list)); - table_list->db = db_mem; - table_list->alias= table_list->table_name = tname_mem; - table_list->lock_type= TL_WRITE; - table_list->next_global= table_list->next_local= 0; + strmov(db_mem, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len)); + strmov(tname_mem, m_tblnam); + + table_list->init_one_table(db_mem, strlen(db_mem), + tname_mem, strlen(tname_mem), + tname_mem, TL_WRITE); + table_list->table_id= m_table_id; table_list->updating= 1; - strmov(table_list->db, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len)); - strmov(table_list->table_name, m_tblnam); - mdl_request->init(0, table_list->db, table_list->table_name); - table_list->mdl_request= mdl_request; int error= 0; diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 90ea8b6cefe..942396fc3da 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -1506,7 +1506,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ thd->binlog_flush_pending_rows_event(false); TABLE_LIST *tables= rli->tables_to_lock; - close_tables_for_reopen(thd, &tables, FALSE); + close_tables_for_reopen(thd, &tables); uint tables_count= rli->tables_to_lock_count; if ((error= open_tables(thd, &tables, &tables_count, 0))) diff --git a/sql/mdl.cc b/sql/mdl.cc index eb8fcdb323e..566a7c96b3b 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -194,7 +194,6 @@ void MDL_context::init(THD *thd_arg) rely here on the default constructors of I_P_List to empty the list. */ - m_requests.empty(); m_tickets.empty(); } @@ -213,7 +212,6 @@ void MDL_context::init(THD *thd_arg) void MDL_context::destroy() { - DBUG_ASSERT(m_requests.is_empty()); DBUG_ASSERT(m_tickets.is_empty()); DBUG_ASSERT(! m_has_global_shared_lock); } @@ -231,10 +229,8 @@ void MDL_context::destroy() void MDL_context::backup_and_reset(MDL_context *backup) { - DBUG_ASSERT(backup->m_requests.is_empty()); DBUG_ASSERT(backup->m_tickets.is_empty()); - m_requests.swap(backup->m_requests); m_tickets.swap(backup->m_tickets); backup->m_has_global_shared_lock= m_has_global_shared_lock; @@ -254,11 +250,9 @@ void MDL_context::backup_and_reset(MDL_context *backup) void MDL_context::restore_from_backup(MDL_context *backup) { - DBUG_ASSERT(m_requests.is_empty()); DBUG_ASSERT(m_tickets.is_empty()); DBUG_ASSERT(m_has_global_shared_lock == FALSE); - m_requests.swap(backup->m_requests); m_tickets.swap(backup->m_tickets); m_has_global_shared_lock= backup->m_has_global_shared_lock; } @@ -271,18 +265,9 @@ void MDL_context::restore_from_backup(MDL_context *backup) void MDL_context::merge(MDL_context *src) { MDL_ticket *ticket; - MDL_request *mdl_request; DBUG_ASSERT(m_thd == src->m_thd); - if (!src->m_requests.is_empty()) - { - Request_iterator it(src->m_requests); - while ((mdl_request= it++)) - m_requests.push_front(mdl_request); - src->m_requests.empty(); - } - if (!src->m_tickets.is_empty()) { Ticket_iterator it(src->m_tickets); @@ -315,15 +300,11 @@ void MDL_context::merge(MDL_context *src) for example in the grant subsystem, to lock privilege tables. The MDL subsystem does not own or manage memory of lock requests. - Instead it assumes that the life time of every lock request (including - encompassed members db/name) encloses calls to MDL_context::add_request() - and MDL_context::remove_request() or MDL_context::remove_all_requests(). @param type Id of type of object to be locked @param db Name of database to which the object belongs @param name Name of of the object - - The initialized lock request will have MDL_SHARED type. + @param mdl_type The MDL lock type for the request. Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2 Note that tables and views must have the same lock type, since @@ -332,10 +313,11 @@ void MDL_context::merge(MDL_context *src) void MDL_request::init(unsigned char type_arg, const char *db_arg, - const char *name_arg) + const char *name_arg, + enum enum_mdl_type mdl_type_arg) { key.mdl_key_init(type_arg, db_arg, name_arg); - type= MDL_SHARED; + type= mdl_type_arg; ticket= NULL; } @@ -360,91 +342,20 @@ void MDL_request::init(unsigned char type_arg, MDL_request * MDL_request::create(unsigned char type, const char *db, - const char *name, MEM_ROOT *root) + const char *name, enum_mdl_type mdl_type, + MEM_ROOT *root) { MDL_request *mdl_request; if (!(mdl_request= (MDL_request*) alloc_root(root, sizeof(MDL_request)))) return NULL; - mdl_request->init(type, db, name); + mdl_request->init(type, db, name, mdl_type); return mdl_request; } -/** - Add a lock request to the list of lock requests of the context. - - The procedure to acquire metadata locks is: - - allocate and initialize lock requests - (MDL_request::create()) - - associate them with a context (MDL_context::add_request()) - - call MDL_context::acquire_shared_lock() and - MDL_context::release_lock() (maybe repeatedly). - - Associates a lock request with the given context. - There should be no more than one context per connection, to - avoid deadlocks. - - @param mdl_request The lock request to be added. -*/ - -void MDL_context::add_request(MDL_request *mdl_request) -{ - DBUG_ENTER("MDL_context::add_request"); - DBUG_ASSERT(mdl_request->ticket == NULL); - m_requests.push_front(mdl_request); - DBUG_VOID_RETURN; -} - - -/** - Remove a lock request from the list of lock requests. - - Disassociates a lock request from the given context. - - @param mdl_request The lock request to be removed. - - @pre The lock request being removed should correspond to a ticket that - was released or was not acquired. - - @note Resets lock request back to its initial state - (i.e. sets type to MDL_SHARED). -*/ - -void MDL_context::remove_request(MDL_request *mdl_request) -{ - DBUG_ENTER("MDL_context::remove_request"); - /* Reset lock request back to its initial state. */ - mdl_request->type= MDL_SHARED; - mdl_request->ticket= NULL; - m_requests.remove(mdl_request); - DBUG_VOID_RETURN; -} - - -/** - Clear all lock requests in the context. - Disassociates lock requests from the context. - - Also resets lock requests back to their initial state (i.e. MDL_SHARED). -*/ - -void MDL_context::remove_all_requests() -{ - MDL_request *mdl_request; - Request_iterator it(m_requests); - while ((mdl_request= it++)) - { - /* Reset lock request back to its initial state. */ - mdl_request->type= MDL_SHARED; - mdl_request->ticket= NULL; - } - m_requests.empty(); -} - - /** Auxiliary functions needed for creation/destruction of MDL_lock objects. @@ -636,8 +547,10 @@ MDL_global_lock::is_lock_type_compatible(enum_mdl_type type, /** Check if request for the lock can be satisfied given current state of lock. - @param lock Lock. - @param mdl_request Request for lock. + @param requestor_ctx The context that identifies the owner of the request. + @param type_arg The requested lock type. + @param is_upgrade Must be set to TRUE when we are upgrading + a shared upgradable lock to exclusive. @retval TRUE Lock request can be satisfied @retval FALSE There is some conflicting lock. @@ -731,7 +644,7 @@ MDL_lock::can_grant_lock(const MDL_context *requestor_ctx, enum_mdl_type type_ar /** Check whether the context already holds a compatible lock ticket - on a object. Only shared locks can be recursive. + on an object. @param mdl_request Lock request object for lock to be acquired @@ -744,8 +657,6 @@ MDL_context::find_ticket(MDL_request *mdl_request) MDL_ticket *ticket; Ticket_iterator it(m_tickets); - DBUG_ASSERT(mdl_request->is_shared()); - while ((ticket= it++)) { if (mdl_request->type == ticket->m_type && @@ -762,35 +673,33 @@ MDL_context::find_ticket(MDL_request *mdl_request) Unlike exclusive locks, shared locks are acquired one by one. This is interface is chosen to simplify introduction of - the new locking API to the system. MDL_context::acquire_shared_lock() + the new locking API to the system. MDL_context::try_acquire_shared_lock() is currently used from open_table(), and there we have only one table to work with. In future we may consider allocating multiple shared locks at once. - This function must be called after the lock is added to a context. + @param mdl_request [in/out] Lock request object for lock to be acquired - @param mdl_request [in] Lock request object for lock to be acquired - @param retry [out] Indicates that conflicting lock exists and another - attempt should be made after releasing all current - locks and waiting for conflicting lock go away - (using MDL_context::wait_for_locks()). - - @retval FALSE Success. - @retval TRUE Failure. Either error occurred or conflicting lock exists. - In the latter case "retry" parameter is set to TRUE. + @retval FALSE Success. The lock may have not been acquired. + Check the ticket, if it's NULL, a conflicting lock + exists and another attempt should be made after releasing + all current locks and waiting for conflicting lock go + away (using MDL_context::wait_for_locks()). + @retval TRUE Out of resources, an error has been reported. */ bool -MDL_context::acquire_shared_lock(MDL_request *mdl_request, bool *retry) +MDL_context::try_acquire_shared_lock(MDL_request *mdl_request) { MDL_lock *lock; MDL_key *key= &mdl_request->key; MDL_ticket *ticket; - *retry= FALSE; DBUG_ASSERT(mdl_request->is_shared() && mdl_request->ticket == NULL); + /* Don't take chances in production. */ + mdl_request->ticket= NULL; safe_mutex_assert_not_owner(&LOCK_open); if (m_has_global_shared_lock && @@ -807,6 +716,8 @@ MDL_context::acquire_shared_lock(MDL_request *mdl_request, bool *retry) if ((ticket= find_ticket(mdl_request))) { DBUG_ASSERT(ticket->m_state == MDL_ACQUIRED); + /* Only shared locks can be recursive. */ + DBUG_ASSERT(ticket->is_shared()); mdl_request->ticket= ticket; return FALSE; } @@ -816,8 +727,7 @@ MDL_context::acquire_shared_lock(MDL_request *mdl_request, bool *retry) if (!global_lock.is_lock_type_compatible(mdl_request->type, FALSE)) { pthread_mutex_unlock(&LOCK_mdl); - *retry= TRUE; - return TRUE; + return FALSE; } if (!(ticket= MDL_ticket::create(this, mdl_request->type))) @@ -854,13 +764,11 @@ MDL_context::acquire_shared_lock(MDL_request *mdl_request, bool *retry) { /* We can't get here if we allocated a new lock. */ DBUG_ASSERT(! lock->is_empty()); - *retry= TRUE; MDL_ticket::destroy(ticket); } - pthread_mutex_unlock(&LOCK_mdl); - return *retry; + return FALSE; } @@ -888,6 +796,19 @@ static bool notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) } +/** + Acquire a single exclusive lock. A convenience + wrapper around the method acquiring a list of locks. +*/ + +bool MDL_context::acquire_exclusive_lock(MDL_request *mdl_request) +{ + MDL_request_list mdl_requests; + mdl_requests.push_front(mdl_request); + return acquire_exclusive_locks(&mdl_requests); +} + + /** Acquire exclusive locks. The context must contain the list of locks to be acquired. There must be no granted locks in the @@ -903,7 +824,7 @@ static bool notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) @retval TRUE Failure */ -bool MDL_context::acquire_exclusive_locks() +bool MDL_context::acquire_exclusive_locks(MDL_request_list *mdl_requests) { MDL_lock *lock; bool signalled= FALSE; @@ -911,9 +832,11 @@ bool MDL_context::acquire_exclusive_locks() MDL_request *mdl_request; MDL_ticket *ticket; st_my_thread_var *mysys_var= my_thread_var; - Request_iterator it(m_requests); + MDL_request_list::Iterator it(*mdl_requests); safe_mutex_assert_not_owner(&LOCK_open); + /* Exclusive locks must always be acquired first, all at once. */ + DBUG_ASSERT(! has_locks()); if (m_has_global_shared_lock) { @@ -931,6 +854,9 @@ bool MDL_context::acquire_exclusive_locks() DBUG_ASSERT(mdl_request->type == MDL_EXCLUSIVE && mdl_request->ticket == NULL); + /* Don't take chances in production. */ + mdl_request->ticket= NULL; + /* Early allocation: ticket is used as a shortcut to the lock. */ if (!(ticket= MDL_ticket::create(this, mdl_request->type))) goto err; @@ -1024,10 +950,7 @@ bool MDL_context::acquire_exclusive_locks() return FALSE; err: - /* - Remove our pending lock requests from the locks. - Ignore those lock requests which were not made MDL_PENDING. - */ + /* Remove our pending tickets from the locks. */ it.rewind(); while ((mdl_request= it++) && mdl_request->ticket) { @@ -1163,17 +1086,16 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() @param mdl_request [in] The lock request @param conflict [out] Indicates that conflicting lock exists - @retval TRUE Failure either conflicting lock exists or some error - occurred (probably OOM). - @retval FALSE Success, lock was acquired. + @retval TRUE Failure: some error occurred (probably OOM). + @retval FALSE Success: the lock might have not been acquired, + check request.ticket to find out. FIXME: Compared to lock_table_name_if_not_cached() it gives slightly more false negatives. */ bool -MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request, - bool *conflict) +MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request) { MDL_lock *lock; MDL_ticket *ticket; @@ -1184,7 +1106,7 @@ MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request, safe_mutex_assert_not_owner(&LOCK_open); - *conflict= FALSE; + mdl_request->ticket= NULL; pthread_mutex_lock(&LOCK_mdl); @@ -1197,7 +1119,8 @@ MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request, { MDL_ticket::destroy(ticket); MDL_lock::destroy(lock); - goto err; + pthread_mutex_unlock(&LOCK_mdl); + return TRUE; } mdl_request->ticket= ticket; lock->type= MDL_lock::MDL_LOCK_EXCLUSIVE; @@ -1206,16 +1129,9 @@ MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request, ticket->m_state= MDL_ACQUIRED; ticket->m_lock= lock; global_lock.active_intention_exclusive++; - pthread_mutex_unlock(&LOCK_mdl); - return FALSE; } - - /* There is some lock for the object. */ - *conflict= TRUE; - -err: pthread_mutex_unlock(&LOCK_mdl); - return TRUE; + return FALSE; } @@ -1262,7 +1178,7 @@ bool MDL_context::acquire_global_shared_lock() /** Wait until there will be no locks that conflict with lock requests - in the context. + in the given list. This is a part of the locking protocol and must be used by the acquirer of shared locks after a back-off. @@ -1274,11 +1190,11 @@ bool MDL_context::acquire_global_shared_lock() */ bool -MDL_context::wait_for_locks() +MDL_context::wait_for_locks(MDL_request_list *mdl_requests) { MDL_lock *lock; MDL_request *mdl_request; - Request_iterator it(m_requests); + MDL_request_list::Iterator it(*mdl_requests); const char *old_msg; st_my_thread_var *mysys_var= my_thread_var; @@ -1380,8 +1296,7 @@ void MDL_context::release_ticket(MDL_ticket *ticket) /** - Release all locks associated with the context, but leave them - in the context as lock requests. + Release all locks associated with the context. This function is used to back off in case of a lock conflict. It is also used to release shared locks in the end of an SQL @@ -1396,15 +1311,6 @@ void MDL_context::release_all_locks() safe_mutex_assert_not_owner(&LOCK_open); - /* Detach lock tickets from the requests for back off. */ - { - MDL_request *mdl_request; - Request_iterator it(m_requests); - - while ((mdl_request= it++)) - mdl_request->ticket= NULL; - } - if (m_tickets.is_empty()) DBUG_VOID_RETURN; @@ -1444,7 +1350,7 @@ void MDL_context::release_lock(MDL_ticket *ticket) /** Release all locks in the context which correspond to the same name/ - object as this lock request, remove lock requests from the context. + object as this lock request. @param ticket One of the locks for the name/object for which all locks should be released. @@ -1455,19 +1361,6 @@ void MDL_context::release_all_locks_for_name(MDL_ticket *name) /* Use MDL_ticket::lock to identify other locks for the same object. */ MDL_lock *lock= name->m_lock; - /* Remove matching lock requests from the context. */ - MDL_request *mdl_request; - Request_iterator it_mdl_request(m_requests); - - while ((mdl_request= it_mdl_request++)) - { - DBUG_ASSERT(mdl_request->ticket && - mdl_request->ticket->m_state == MDL_ACQUIRED); - - if (mdl_request->ticket->m_lock == lock) - remove_request(mdl_request); - } - /* Remove matching lock tickets from the context. */ MDL_ticket *ticket; Ticket_iterator it_ticket(m_tickets); @@ -1537,16 +1430,11 @@ bool MDL_context::is_exclusive_lock_owner(unsigned char type, const char *db, const char *name) { - MDL_key key(type, db, name); - MDL_ticket *ticket; - MDL_context::Ticket_iterator it(m_tickets); + MDL_request mdl_request; + mdl_request.init(type, db, name, MDL_EXCLUSIVE); + MDL_ticket *ticket= find_ticket(&mdl_request); - while ((ticket= it++)) - { - if (ticket->m_lock->type == MDL_lock::MDL_LOCK_EXCLUSIVE && - ticket->m_lock->key.is_equal(&key)) - break; - } + DBUG_ASSERT(ticket == NULL || ticket->m_state == MDL_ACQUIRED); return ticket; } diff --git a/sql/mdl.h b/sql/mdl.h index e3e41652bf4..dc6f0a34443 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -109,7 +109,6 @@ public: mdl_key_init(type_arg, db_arg, name_arg); } MDL_key() {} /* To use when part of MDL_request. */ - private: char m_ptr[MAX_MDLKEY_LENGTH]; uint m_length; @@ -157,12 +156,20 @@ public: /** Pointers for participating in the list of lock requests for this context. */ - MDL_request *next_in_context; - MDL_request **prev_in_context; + MDL_request *next_in_list; + MDL_request **prev_in_list; + /** + Pointer to the lock ticket object for this lock request. + Valid only if this lock request is satisfied. + */ + MDL_ticket *ticket; + /** A lock is requested based on a fully qualified name and type. */ MDL_key key; - void init(unsigned char type_arg, const char *db_arg, const char *name_arg); +public: + void init(unsigned char type_arg, const char *db_arg, const char *name_arg, + enum_mdl_type mdl_type_arg); /** Set type of lock request. Can be only applied to pending locks. */ inline void set_type(enum_mdl_type type_arg) { @@ -171,15 +178,37 @@ public: } bool is_shared() const { return type < MDL_EXCLUSIVE; } - /** - Pointer to the lock ticket object for this lock request. - Valid only if this lock request is satisfied. - */ - MDL_ticket *ticket; - static MDL_request *create(unsigned char type, const char *db, - const char *name, MEM_ROOT *root); + const char *name, enum_mdl_type mdl_type, + MEM_ROOT *root); + /* + This is to work around the ugliness of TABLE_LIST + compiler-generated assignment operator. It is currently used + in several places to quickly copy "most" of the members of the + table list. These places currently never assume that the mdl + request is carried over to the new TABLE_LIST, or shared + between lists. + + This method does not initialize the instance being assigned! + Use of init() for initialization after this assignment operator + is mandatory. Can only be used before the request has been + granted. + */ + MDL_request& operator=(const MDL_request &rhs) + { + ticket= NULL; + /* Do nothing, in particular, don't try to copy the key. */ + return *this; + } + /* Another piece of ugliness for TABLE_LIST constructor */ + MDL_request() {} + + MDL_request(const MDL_request *rhs) + :type(rhs->type), + ticket(NULL), + key(&rhs->key) + {} }; @@ -248,6 +277,11 @@ private: }; +typedef I_P_List > + MDL_request_list; + /** Context of the owner of metadata locks. I.e. each server connection has such a context. @@ -256,14 +290,6 @@ private: class MDL_context { public: - typedef I_P_List > - Request_list; - - typedef Request_list::Iterator Request_iterator; - typedef I_P_Listprelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; - table->mdl_request= MDL_request::create(0, table->db, table->table_name, - thd->locked_tables_root ? - thd->locked_tables_root : - thd->mem_root); + table->mdl_request.init(0, table->db, table->table_name, MDL_SHARED); /* Everyting else should be zeroed */ @@ -4026,10 +4023,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->lock_type= locktype; table->select_lex= lex->current_select; table->cacheable_table= 1; - table->mdl_request= MDL_request::create(0, table->db, table->table_name, - thd->locked_tables_root ? - thd->locked_tables_root : - thd->mem_root); + table->mdl_request.init(0, table->db, table->table_name, MDL_SHARED); lex->add_to_query_tables(table); return table; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index f4a182b321f..fdcd68cc2ea 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -691,7 +691,7 @@ my_bool acl_reload(THD *thd) tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ; tables[0].skip_temporary= tables[1].skip_temporary= tables[2].skip_temporary= TRUE; - alloc_mdl_requests(tables, thd->mem_root); + init_mdl_requests(tables); if (simple_open_n_lock_tables(thd, tables)) { @@ -1596,10 +1596,7 @@ bool change_password(THD *thd, const char *host, const char *user, if (check_change_password(thd, host, user, new_password, new_password_len)) DBUG_RETURN(1); - bzero((char*) &tables, sizeof(tables)); - tables.alias= tables.table_name= (char*) "user"; - tables.db= (char*) "mysql"; - alloc_mdl_requests(&tables, thd->mem_root); + tables.init_one_table("mysql", 5, "user", 4, "user", TL_WRITE); #ifdef HAVE_REPLICATION /* @@ -3111,7 +3108,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, ? tables+2 : 0); tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_WRITE; tables[0].db=tables[1].db=tables[2].db=(char*) "mysql"; - alloc_mdl_requests(tables, thd->mem_root); + init_mdl_requests(tables); /* This statement will be replicated as a statement, even when using @@ -3329,7 +3326,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type=tables[1].lock_type=TL_WRITE; tables[0].db=tables[1].db=(char*) "mysql"; - alloc_mdl_requests(tables, thd->mem_root); + init_mdl_requests(tables); /* This statement will be replicated as a statement, even when using @@ -3468,7 +3465,7 @@ bool mysql_grant(THD *thd, const char *db, List &list, tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type=tables[1].lock_type=TL_WRITE; tables[0].db=tables[1].db=(char*) "mysql"; - alloc_mdl_requests(tables, thd->mem_root); + init_mdl_requests(tables); /* This statement will be replicated as a statement, even when using @@ -3797,12 +3794,10 @@ static my_bool grant_reload_procs_priv(THD *thd) my_bool return_val= FALSE; DBUG_ENTER("grant_reload_procs_priv"); - bzero((char*) &table, sizeof(table)); - table.alias= table.table_name= (char*) "procs_priv"; - table.db= (char *) "mysql"; - table.lock_type= TL_READ; + table.init_one_table("mysql", 5, "procs_priv", + strlen("procs_priv"), "procs_priv", + TL_READ); table.skip_temporary= 1; - alloc_mdl_requests(&table, thd->mem_root); if (simple_open_n_lock_tables(thd, &table)) { @@ -3869,7 +3864,7 @@ my_bool grant_reload(THD *thd) tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type= tables[1].lock_type= TL_READ; tables[0].skip_temporary= tables[1].skip_temporary= TRUE; - alloc_mdl_requests(tables, thd->mem_root); + init_mdl_requests(tables); /* To avoid deadlocks we should obtain table locks before @@ -5219,7 +5214,7 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) (tables+4)->lock_type= TL_WRITE; tables->db= (tables+1)->db= (tables+2)->db= (tables+3)->db= (tables+4)->db= (char*) "mysql"; - alloc_mdl_requests(tables, thd->mem_root); + init_mdl_requests(tables); #ifdef HAVE_REPLICATION /* diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 02871c118ca..7650d854efb 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -125,7 +125,8 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root); -static bool tdc_wait_for_old_versions(THD *thd, MDL_context *context); +static bool tdc_wait_for_old_versions(THD *thd, + MDL_request_list *mdl_requests); static bool has_write_table_with_auto_increment(TABLE_LIST *tables); @@ -1360,8 +1361,7 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, leave prelocked mode if needed. */ -void close_thread_tables(THD *thd, - bool is_back_off) +void close_thread_tables(THD *thd) { TABLE *table; DBUG_ENTER("close_thread_tables"); @@ -1471,10 +1471,6 @@ void close_thread_tables(THD *thd, thd->locked_tables_mode= LTM_NONE; - /* - Note that we are leaving prelocked mode so we don't need - to care about THD::locked_tables_root. - */ /* Fallthrough */ } @@ -1503,11 +1499,6 @@ void close_thread_tables(THD *thd, if (thd->open_tables) close_open_tables(thd); - if (!is_back_off) - { - thd->mdl_context.remove_all_requests(); - } - /* Defer the release of metadata locks until the current transaction is either committed or rolled back. This prevents other statements @@ -2316,10 +2307,10 @@ void table_share_release_hook(void *share) static bool open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, MDL_request *mdl_request, - uint flags, - enum_open_table_action *action) + Open_table_context *ot_ctx, + uint flags) { - thd->mdl_context.add_request(mdl_request); + ot_ctx->add_request(mdl_request); if (table_list->lock_strategy) { @@ -2333,16 +2324,13 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, enforced by asserts in metadata locking subsystem. */ mdl_request->set_type(MDL_EXCLUSIVE); - if (thd->mdl_context.acquire_exclusive_locks()) - { - thd->mdl_context.remove_request(mdl_request); + DBUG_ASSERT(! thd->mdl_context.has_locks()); + + if (thd->mdl_context.acquire_exclusive_lock(mdl_request)) return 1; - } } else { - bool retry; - /* There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we want to be sure that caller doesn't pass us both flags simultaneously. @@ -2356,12 +2344,11 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, if (flags & MYSQL_LOCK_IGNORE_FLUSH) mdl_request->set_type(MDL_SHARED_HIGH_PRIO); - if (thd->mdl_context.acquire_shared_lock(mdl_request, &retry)) + if (thd->mdl_context.try_acquire_shared_lock(mdl_request)) + return 1; + if (mdl_request->ticket == NULL) { - if (retry) - *action= OT_BACK_OFF_AND_RETRY; - else - thd->mdl_context.remove_request(mdl_request); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT); return 1; } } @@ -2416,7 +2403,7 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, - enum_open_table_action *action, uint flags) + Open_table_context *ot_ctx, uint flags) { reg1 TABLE *table; char key[MAX_DBKEY_LENGTH]; @@ -2428,11 +2415,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, TABLE_SHARE *share; DBUG_ENTER("open_table"); - /* Parsing of partitioning information from .frm needs thd->lex set up. */ - DBUG_ASSERT(thd->lex->is_lex_started); - - *action= OT_NO_ACTION; - /* an open table operation needs a lot of the stack space */ if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias)) DBUG_RETURN(TRUE); @@ -2602,12 +2584,15 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, This is the normal use case. */ - mdl_request= table_list->mdl_request; + mdl_request= &table_list->mdl_request; if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { - if (open_table_get_mdl_lock(thd, table_list, mdl_request, flags, - action)) + if (open_table_get_mdl_lock(thd, table_list, mdl_request, ot_ctx, flags)) + { + DEBUG_SYNC(thd, "before_open_table_wait_refresh"); DBUG_RETURN(TRUE); + } + DEBUG_SYNC(thd, "after_open_table_mdl_shared"); } /* @@ -2633,8 +2618,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ! (flags & MYSQL_LOCK_IGNORE_FLUSH)) { /* Someone did a refresh while thread was opening tables */ - *action= OT_BACK_OFF_AND_RETRY; pthread_mutex_unlock(&LOCK_open); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT); DBUG_RETURN(TRUE); } @@ -2766,9 +2751,9 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, separately in the caller for old table versions to go away (see tdc_wait_for_old_versions()). */ - *action= OT_BACK_OFF_AND_RETRY; release_table_share(share); pthread_mutex_unlock(&LOCK_open); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT); DBUG_RETURN(TRUE); } /* Force close at once after usage */ @@ -2808,12 +2793,12 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (error == 7) { share->version= 0; - *action= OT_DISCOVER; + (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER); } else if (share->crashed) { share->version= 0; - *action= OT_REPAIR; + (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR); } goto err_unlock; @@ -2891,10 +2876,8 @@ err_unlock: err_unlock2: pthread_mutex_unlock(&LOCK_open); if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) - { thd->mdl_context.release_lock(mdl_ticket); - thd->mdl_context.remove_request(mdl_request); - } + DBUG_RETURN(TRUE); } @@ -3004,6 +2987,9 @@ Locked_tables_list::init_locked_tables(THD *thd) return TRUE; } + memcpy(db, src_table_list->db, db_len + 1); + memcpy(table_name, src_table_list->table_name, table_name_len + 1); + memcpy(alias, src_table_list->alias, alias_len + 1); /** Sic: remember the *actual* table level lock type taken, to acquire the exact same type in reopen_tables(). @@ -3014,11 +3000,9 @@ Locked_tables_list::init_locked_tables(THD *thd) dst_table_list->init_one_table(db, db_len, table_name, table_name_len, alias, src_table_list->table->reginfo.lock_type); - dst_table_list->mdl_request= src_table_list->mdl_request; dst_table_list->table= table; - memcpy(db, src_table_list->db, db_len + 1); - memcpy(table_name, src_table_list->table_name, table_name_len + 1); - memcpy(alias, src_table_list->alias, alias_len + 1); + dst_table_list->mdl_request.ticket= src_table_list->mdl_request.ticket; + /* Link last into the list of tables */ *(dst_table_list->prev_global= m_locked_tables_last)= dst_table_list; m_locked_tables_last= &dst_table_list->next_global; @@ -3227,7 +3211,7 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) bool Locked_tables_list::reopen_tables(THD *thd) { - enum enum_open_table_action ot_action_unused; + Open_table_context ot_ctx_unused(thd); bool lt_refresh_unused; size_t reopen_count= 0; MYSQL_LOCK *lock; @@ -3240,7 +3224,7 @@ Locked_tables_list::reopen_tables(THD *thd) continue; /* Links into thd->open_tables upon success */ - if (open_table(thd, table_list, thd->mem_root, &ot_action_unused, + if (open_table(thd, table_list, thd->mem_root, &ot_ctx_unused, MYSQL_OPEN_REOPEN)) { unlink_all_closed_tables(thd, 0, reopen_count); @@ -3591,6 +3575,43 @@ end_with_lock_open: } +/** Open_table_context */ + +Open_table_context::Open_table_context(THD *thd) + :m_action(OT_NO_ACTION), + m_can_deadlock(thd->in_multi_stmt_transaction() && + thd->mdl_context.has_locks()) +{} + + +/** + Check if we can back-off and set back off action if we can. + Otherwise report and return error. + + @retval TRUE if back-off is impossible. + @retval FALSE if we can back off. Back off action has been set. +*/ + +bool +Open_table_context:: +request_backoff_action(enum_open_table_action action_arg) +{ + /* + We have met a exclusive metadata lock or a old version of + table and we are inside a transaction that already hold locks. + We can't follow the locking protocol in this scenario as it + might lead to deadlocks. + */ + if (m_can_deadlock) + { + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; + } + m_action= action_arg; + return FALSE; +} + + /** Recover from failed attempt ot open table by performing requested action. @@ -3604,57 +3625,58 @@ end_with_lock_open: @retval TRUE - Error */ -static bool -recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table, - enum_open_table_action action) +bool +Open_table_context:: +recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table) { bool result= FALSE; - MDL_request *mdl_request= table->mdl_request; - - switch (action) + /* Execute the action. */ + switch (m_action) { - case OT_BACK_OFF_AND_RETRY: - result= (thd->mdl_context.wait_for_locks() || - tdc_wait_for_old_versions(thd, &thd->mdl_context)); - thd->mdl_context.remove_all_requests(); + case OT_WAIT: + result= (thd->mdl_context.wait_for_locks(&m_mdl_requests) || + tdc_wait_for_old_versions(thd, &m_mdl_requests)); break; case OT_DISCOVER: - mdl_request->set_type(MDL_EXCLUSIVE); - thd->mdl_context.add_request(mdl_request); - if (thd->mdl_context.acquire_exclusive_locks()) { - thd->mdl_context.remove_request(mdl_request); - return TRUE; - } - pthread_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); - ha_create_table_from_engine(thd, table->db, table->table_name); - pthread_mutex_unlock(&LOCK_open); + MDL_request mdl_xlock_request(&table->mdl_request); + mdl_xlock_request.set_type(MDL_EXCLUSIVE); + if ((result= + thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request))) + break; + pthread_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); + ha_create_table_from_engine(thd, table->db, table->table_name); + pthread_mutex_unlock(&LOCK_open); - thd->warning_info->clear_warning_info(thd->query_id); - thd->clear_error(); // Clear error message - thd->mdl_context.release_lock(mdl_request->ticket); - thd->mdl_context.remove_request(mdl_request); - break; + thd->warning_info->clear_warning_info(thd->query_id); + thd->clear_error(); // Clear error message + thd->mdl_context.release_lock(mdl_xlock_request.ticket); + break; + } case OT_REPAIR: - mdl_request->set_type(MDL_EXCLUSIVE); - thd->mdl_context.add_request(mdl_request); - if (thd->mdl_context.acquire_exclusive_locks()) { - thd->mdl_context.remove_request(mdl_request); - return TRUE; - } - pthread_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); - pthread_mutex_unlock(&LOCK_open); + MDL_request mdl_xlock_request(&table->mdl_request); + mdl_xlock_request.set_type(MDL_EXCLUSIVE); + if ((result= + thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request))) + break; - result= auto_repair_table(thd, table); - thd->mdl_context.release_lock(mdl_request->ticket); - thd->mdl_context.remove_request(mdl_request); - break; + pthread_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); + pthread_mutex_unlock(&LOCK_open); + + result= auto_repair_table(thd, table); + thd->mdl_context.release_lock(mdl_xlock_request.ticket); + break; + } default: DBUG_ASSERT(0); } + /* Remove all old requests, they will be re-added. */ + m_mdl_requests.empty(); + /* Prepare for possible another back-off. */ + m_action= OT_NO_ACTION; return result; } @@ -3722,14 +3744,13 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) { TABLE_LIST *tables= NULL; - enum_open_table_action action; + Open_table_context ot_ctx(thd); int result=0; bool error; MEM_ROOT new_frm_mem; /* Also used for indicating that prelocking is need */ TABLE_LIST **query_tables_last_own; bool safe_to_ignore_table; - bool has_locks= thd->mdl_context.has_locks(); DBUG_ENTER("open_tables"); /* temporary mem_root for new .frm parsing. @@ -3856,31 +3877,19 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) */ Prelock_error_handler prelock_handler; thd->push_internal_handler(& prelock_handler); - error= open_table(thd, tables, &new_frm_mem, &action, flags); + error= open_table(thd, tables, &new_frm_mem, &ot_ctx, flags); thd->pop_internal_handler(); safe_to_ignore_table= prelock_handler.safely_trapped_errors(); } else - error= open_table(thd, tables, &new_frm_mem, &action, flags); + error= open_table(thd, tables, &new_frm_mem, &ot_ctx, flags); free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); if (error) { - if (action) + if (ot_ctx.can_recover_from_failed_open_table()) { - /* - We have met a exclusive metadata lock or a old version of table and - we are inside a transaction that already hold locks. We can't follow - the locking protocol in this scenario as it might lead to deadlocks. - */ - if (thd->in_multi_stmt_transaction() && has_locks) - { - my_error(ER_LOCK_DEADLOCK, MYF(0)); - result= -1; - goto err; - } - /* We have met exclusive metadata lock or old version of table. Now we have to close all tables which are not up to date/release metadata @@ -3897,13 +3906,13 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) */ if (query_tables_last_own) thd->lex->mark_as_requiring_prelocking(query_tables_last_own); - close_tables_for_reopen(thd, start, (action == OT_BACK_OFF_AND_RETRY)); + close_tables_for_reopen(thd, start); /* Here we rely on the fact that 'tables' still points to the valid TABLE_LIST element. Altough currently this assumption is valid it may change in future. */ - if (recover_from_failed_open_table_attempt(thd, tables, action)) + if (ot_ctx.recover_from_failed_open_table_attempt(thd, tables)) { result= -1; goto err; @@ -4210,7 +4219,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, uint lock_flags) { TABLE *table; - enum_open_table_action action; + Open_table_context ot_ctx(thd); bool refresh; bool error; DBUG_ENTER("open_ltable"); @@ -4224,16 +4233,18 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, table_list->required_type= FRMTYPE_TABLE; retry: - while ((error= open_table(thd, table_list, thd->mem_root, &action, 0)) && - action) + while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, 0)) && + ot_ctx.can_recover_from_failed_open_table()) { /* - Even altough we have failed to open table we still need to - call close_thread_tables() to release metadata locks which + Even though we have failed to open table we still need to + call release_all_locks() to release metadata locks which might have been acquired successfully. */ - close_thread_tables(thd, (action == OT_BACK_OFF_AND_RETRY)); - if (recover_from_failed_open_table_attempt(thd, table_list, action)) + if (! thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); + table_list->mdl_request.ticket= 0; + if (ot_ctx.recover_from_failed_open_table_attempt(thd, table_list)) break; } @@ -4272,8 +4283,20 @@ retry: { if (refresh) { - close_thread_tables(thd); - goto retry; + if (ot_ctx.can_deadlock()) + { + my_error(ER_LOCK_DEADLOCK, MYF(0)); + table= 0; + } + else + { + close_thread_tables(thd); + table_list->table= NULL; + table_list->mdl_request.ticket= NULL; + if (! thd->locked_tables_mode) + thd->mdl_context.release_all_locks(); + goto retry; + } } else table= 0; @@ -4283,7 +4306,7 @@ retry: else table= 0; - end: +end: thd_proc_info(thd, 0); DBUG_RETURN(table); } @@ -4320,6 +4343,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, { uint counter; bool need_reopen; + bool has_locks= thd->mdl_context.has_locks(); DBUG_ENTER("open_and_lock_tables_derived"); DBUG_PRINT("enter", ("derived handling: %d", derived)); @@ -4339,7 +4363,12 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, break; if (!need_reopen) DBUG_RETURN(-1); - close_tables_for_reopen(thd, &tables, FALSE); + if (thd->in_multi_stmt_transaction() && has_locks) + { + my_error(ER_LOCK_DEADLOCK, MYF(0)); + DBUG_RETURN(-1); + } + close_tables_for_reopen(thd, &tables); } if (derived && (mysql_handle_derived(thd->lex, &mysql_derived_prepare) || @@ -4677,10 +4706,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, table->table->query_id= thd->query_id; if (check_lock_and_start_stmt(thd, table->table, table->lock_type)) { - /* - This was an attempt to enter prelocked mode so there is no - need to care about THD::locked_tables_root here. - */ mysql_unlock_tables(thd, thd->lock); thd->lock= 0; DBUG_RETURN(-1); @@ -4767,7 +4792,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, */ -void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool is_back_off) +void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) { /* If table list consists only from tables from prelocking set, table list @@ -4778,8 +4803,11 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool is_back_off) thd->lex->chop_off_not_own_tables(); sp_remove_not_own_routines(thd->lex); for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global) + { tmp->table= 0; - close_thread_tables(thd, is_back_off); + tmp->mdl_request.ticket= NULL; + } + close_thread_tables(thd); if (!thd->locked_tables_mode) thd->mdl_context.release_all_locks(); } @@ -7855,7 +7883,8 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, @param context Metadata locking context with locks. */ -static bool tdc_wait_for_old_versions(THD *thd, MDL_context *mdl_context) +static bool +tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests) { TABLE_SHARE *share; const char *old_msg; @@ -7872,7 +7901,7 @@ static bool tdc_wait_for_old_versions(THD *thd, MDL_context *mdl_context) mysql_ha_flush(thd); pthread_mutex_lock(&LOCK_open); - MDL_context::Request_iterator it= mdl_context->get_requests(); + MDL_request_list::Iterator it(*mdl_requests); while ((mdl_request= it++)) { if ((share= get_cached_table_share(mdl_request->key.db_name(), @@ -8095,8 +8124,6 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, DBUG_ENTER("open_system_tables_for_read"); - alloc_mdl_requests(table_list, thd->mem_root); - /* Besides using new Open_tables_state for opening system tables, we also have to backup and reset/and then restore part of LEX @@ -8170,8 +8197,6 @@ open_system_table_for_update(THD *thd, TABLE_LIST *one_table) { DBUG_ENTER("open_system_table_for_update"); - alloc_mdl_requests(one_table, thd->mem_root); - TABLE *table= open_ltable(thd, one_table, one_table->lock_type, 0); if (table) { @@ -8208,7 +8233,6 @@ open_performance_schema_table(THD *thd, TABLE_LIST *one_table, thd->reset_n_backup_open_tables_state(backup); - alloc_mdl_requests(one_table, thd->mem_root); if ((table= open_ltable(thd, one_table, one_table->lock_type, flags))) { DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_PERFORMANCE); @@ -8286,7 +8310,6 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup) pthread_mutex_unlock(&LOCK_open); thd->mdl_context.release_all_locks(); - thd->mdl_context.remove_all_requests(); thd->restore_backup_open_tables_state(backup); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 7252f078f81..d83b60810ab 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -466,8 +466,7 @@ THD::THD() #if defined(ENABLED_DEBUG_SYNC) debug_sync_control(0), #endif /* defined(ENABLED_DEBUG_SYNC) */ - main_warning_info(0), - locked_tables_root(NULL) + main_warning_info(0) { ulong tmp; diff --git a/sql/sql_class.h b/sql/sql_class.h index 4b6564fb9da..9edb5f97713 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1180,6 +1180,49 @@ private: Internal_error_handler *m_err_handler; }; + +/** + A context of open_tables() function, used to recover + from a failed open_table() attempt. + + Implemented in sql_base.cc. +*/ + +class Open_table_context +{ +public: + enum enum_open_table_action + { + OT_NO_ACTION= 0, + OT_WAIT, + OT_DISCOVER, + OT_REPAIR + }; + Open_table_context(THD *thd); + + bool recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *tables); + bool request_backoff_action(enum_open_table_action action_arg); + + void add_request(MDL_request *request) + { m_mdl_requests.push_front(request); } + + bool can_recover_from_failed_open_table() const + { return m_action != OT_NO_ACTION; } + bool can_deadlock() const { return m_can_deadlock; } +private: + /** List of requests for all locks taken so far. Used for waiting on locks. */ + MDL_request_list m_mdl_requests; + /** Back off action. */ + enum enum_open_table_action m_action; + /** + Whether we had any locks when this context was created. + If we did, they are from the previous statement of a transaction, + and we can't safely do back-off (and release them). + */ + bool m_can_deadlock; +}; + + /** Tables that were locked with LOCK TABLES statement. @@ -1236,7 +1279,6 @@ public: } bool init_locked_tables(THD *thd); TABLE_LIST *locked_tables() { return m_locked_tables; } - MEM_ROOT *locked_tables_root() { return &m_locked_tables_root; } void unlink_from_list(THD *thd, TABLE_LIST *table_list, bool remove_from_locked_tables); void unlink_all_closed_tables(THD *thd, @@ -1895,15 +1937,6 @@ public: /* Debug Sync facility. See debug_sync.cc. */ struct st_debug_sync_control *debug_sync_control; #endif /* defined(ENABLED_DEBUG_SYNC) */ - - /** - Points to the memory root of Locked_tables_list if - we're locking the tables for LOCK TABLES. Otherwise is NULL. - This is necessary to ensure that metadata locks allocated for - tables used in triggers will persist after statement end. - */ - MEM_ROOT *locked_tables_root; - THD(); ~THD(); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index fc92dbee0a1..c3e205848de 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1100,7 +1100,8 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) TABLE *table; bool error; uint path_length; - MDL_request *mdl_request= NULL; + MDL_request mdl_request; + bool has_mdl_lock= FALSE; DBUG_ENTER("mysql_truncate"); bzero((char*) &create_info,sizeof(create_info)); @@ -1145,6 +1146,12 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) if (!dont_send_ok) { enum legacy_db_type table_type; + /* + FIXME: Code of TRUNCATE breaks the meta-data + locking protocol since it tries to find out the table storage + engine and therefore accesses table in some way without holding + any kind of meta-data lock. + */ mysql_frm_type(thd, path, &table_type); if (table_type == DB_TYPE_UNKNOWN) { @@ -1170,20 +1177,12 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) goto trunc_by_del; - /* - FIXME: Actually code of TRUNCATE breaks meta-data locking protocol since - tries to get table enging and therefore accesses table in some way - without holding any kind of meta-data lock. - */ - mdl_request= MDL_request::create(0, table_list->db, - table_list->table_name, thd->mem_root); - mdl_request->set_type(MDL_EXCLUSIVE); - thd->mdl_context.add_request(mdl_request); - if (thd->mdl_context.acquire_exclusive_locks()) - { - thd->mdl_context.remove_request(mdl_request); + mdl_request.init(0, table_list->db, table_list->table_name, MDL_EXCLUSIVE); + if (thd->mdl_context.acquire_exclusive_lock(&mdl_request)) DBUG_RETURN(TRUE); - } + + has_mdl_lock= TRUE; + pthread_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, table_list->table_name); @@ -1212,19 +1211,13 @@ end: write_bin_log(thd, TRUE, thd->query(), thd->query_length()); my_ok(thd); // This should return record count } - if (mdl_request) - { - thd->mdl_context.release_lock(mdl_request->ticket); - thd->mdl_context.remove_request(mdl_request); - } + if (has_mdl_lock) + thd->mdl_context.release_lock(mdl_request.ticket); } else if (error) { - if (mdl_request) - { - thd->mdl_context.release_lock(mdl_request->ticket); - thd->mdl_context.remove_request(mdl_request); - } + if (has_mdl_lock) + thd->mdl_context.release_lock(mdl_request.ticket); } DBUG_RETURN(error); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 4b07a00c779..1b7e45cec5d 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -150,7 +150,6 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) } pthread_mutex_unlock(&LOCK_open); thd->handler_mdl_context.release_lock(mdl_ticket); - thd->handler_mdl_context.remove_request(tables->mdl_request); } else if (tables->table) { @@ -163,6 +162,8 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) /* Mark table as closed, ready for re-open if necessary. */ tables->table= NULL; + /* Safety, cleanup the pointer to satisfy MDL assertions. */ + tables->mdl_request.ticket= NULL; } /* @@ -195,7 +196,6 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) int error; TABLE *backup_open_tables; MDL_context backup_mdl_context; - MDL_request *mdl_request; DBUG_ENTER("mysql_ha_open"); DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", tables->db, tables->table_name, tables->alias, @@ -246,7 +246,6 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) &db, (uint) dblen, &name, (uint) namelen, &alias, (uint) aliaslen, - &mdl_request, sizeof(MDL_request), NullS))) { DBUG_PRINT("exit",("ERROR")); @@ -260,8 +259,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) memcpy(hash_tables->db, tables->db, dblen); memcpy(hash_tables->table_name, tables->table_name, namelen); memcpy(hash_tables->alias, tables->alias, aliaslen); - mdl_request->init(0, db, name); - hash_tables->mdl_request= mdl_request; + hash_tables->mdl_request.init(0, db, name, MDL_SHARED); /* add to hash */ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) diff --git a/sql/sql_help.cc b/sql/sql_help.cc index 003741a7ddc..af67db45b36 100644 --- a/sql/sql_help.cc +++ b/sql/sql_help.cc @@ -653,6 +653,7 @@ bool mysqld_help(THD *thd, const char *mask) tables[3].alias= tables[3].table_name= (char*) "help_keyword"; tables[3].lock_type= TL_READ; tables[0].db= tables[1].db= tables[2].db= tables[3].db= (char*) "mysql"; + init_mdl_requests(tables); Open_tables_state open_tables_state_backup; if (open_system_tables_for_read(thd, tables, &open_tables_state_backup)) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 8553db8cf2b..e0537c75e07 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2399,7 +2399,7 @@ pthread_handler_t handle_delayed_insert(void *arg) thd->lex->set_stmt_unsafe(); thd->set_current_stmt_binlog_row_based_if_mixed(); - alloc_mdl_requests(&di->table_list, thd->mem_root); + init_mdl_requests(&di->table_list); if (di->open_and_lock_table()) goto err; @@ -3464,7 +3464,6 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, Item *item; Field *tmp_field; bool not_used; - enum_open_table_action not_used2; DBUG_ENTER("create_table_from_items"); tmp_table.alias= 0; @@ -3543,13 +3542,13 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - enum enum_open_table_action ot_action_unused; + Open_table_context ot_ctx_unused(thd); /* Here we open the destination table, on which we already have an exclusive metadata lock. */ if (open_table(thd, create_table, thd->mem_root, - &ot_action_unused, MYSQL_OPEN_REOPEN)) + &ot_ctx_unused, MYSQL_OPEN_REOPEN)) { pthread_mutex_lock(&LOCK_open); quick_rm_table(create_info->db_type, create_table->db, @@ -3562,7 +3561,8 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, } else { - if (open_table(thd, create_table, thd->mem_root, ¬_used2, + Open_table_context ot_ctx_unused(thd); + if (open_table(thd, create_table, thd->mem_root, &ot_ctx_unused, MYSQL_OPEN_TEMPORARY_ONLY) && !create_info->table_existed) { @@ -3637,18 +3637,28 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) */ class MY_HOOKS : public TABLEOP_HOOKS { public: - MY_HOOKS(select_create *x, TABLE_LIST *create_table, - TABLE_LIST *select_tables) - : ptr(x), all_tables(*create_table) + MY_HOOKS(select_create *x, TABLE_LIST *create_table_arg, + TABLE_LIST *select_tables_arg) + : ptr(x), + create_table(create_table_arg), + select_tables(select_tables_arg) { - all_tables.next_global= select_tables; } private: virtual int do_postlock(TABLE **tables, uint count) { + int error; THD *thd= const_cast(ptr->get_thd()); - if (int error= decide_logging_format(thd, &all_tables)) + TABLE_LIST *save_next_global= create_table->next_global; + + create_table->next_global= select_tables; + + error= decide_logging_format(thd, create_table); + + create_table->next_global= save_next_global; + + if (error) return error; TABLE const *const table = *tables; @@ -3662,7 +3672,8 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) } select_create *ptr; - TABLE_LIST all_tables; + TABLE_LIST *create_table; + TABLE_LIST *select_tables; }; MY_HOOKS hooks(this, create_table, select_tables); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index ddc163072a7..35c0973d103 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1107,7 +1107,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, select_lex.table_list.link_in_list((uchar*) &table_list, (uchar**) &table_list.next_local); thd->lex->add_to_query_tables(&table_list); - alloc_mdl_requests(&table_list, thd->mem_root); + init_mdl_requests(&table_list); /* switch on VIEW optimisation: do not fill temporary tables */ thd->lex->sql_command= SQLCOM_SHOW_FIELDS; @@ -3337,18 +3337,16 @@ end_with_restore_list: !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) goto error; - alloc_mdl_requests(all_tables, thd->locked_tables_list.locked_tables_root()); + init_mdl_requests(all_tables); thd->options|= OPTION_TABLE_LOCK; thd->in_lock_tables=1; - thd->locked_tables_root= thd->locked_tables_list.locked_tables_root(); res= (open_and_lock_tables_derived(thd, all_tables, FALSE, MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || thd->locked_tables_list.init_locked_tables(thd)); thd->in_lock_tables= 0; - thd->locked_tables_root= NULL; if (res) { @@ -6021,9 +6019,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); - ptr->mdl_request= - MDL_request::create(0, ptr->db, ptr->table_name, thd->locked_tables_root ? - thd->locked_tables_root : thd->mem_root); + ptr->mdl_request.init(0, ptr->db, ptr->table_name, MDL_SHARED); DBUG_RETURN(ptr); } diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index b7efe13e26e..7dd7dd0d4b9 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1349,7 +1349,6 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) #ifdef EMBEDDED_LIBRARY bool table_exists; #endif /* EMBEDDED_LIBRARY */ - MDL_request mdl_request; DBUG_ENTER("plugin_load"); if (!(new_thd= new THD)) @@ -1363,12 +1362,7 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) lex_start(new_thd); new_thd->db= my_strdup("mysql", MYF(0)); new_thd->db_length= 5; - bzero((uchar*)&tables, sizeof(tables)); - tables.alias= tables.table_name= (char*)"plugin"; - tables.lock_type= TL_READ; - tables.db= new_thd->db; - tables.mdl_request= &mdl_request; - mdl_request.init(0, tables.db, tables.table_name); + tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_READ); #ifdef EMBEDDED_LIBRARY /* @@ -1656,14 +1650,10 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, const LEX_STRING *dl struct st_plugin_int *tmp; DBUG_ENTER("mysql_install_plugin"); - bzero(&tables, sizeof(tables)); - tables.db= (char *)"mysql"; - tables.table_name= tables.alias= (char *)"plugin"; + tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_WRITE); if (check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE)) DBUG_RETURN(TRUE); - alloc_mdl_requests(&tables, thd->mem_root); - /* need to open before acquiring LOCK_plugin or it will deadlock */ if (! (table = open_ltable(thd, &tables, TL_WRITE, 0))) DBUG_RETURN(TRUE); @@ -1734,10 +1724,7 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name) struct st_plugin_int *plugin; DBUG_ENTER("mysql_uninstall_plugin"); - bzero(&tables, sizeof(tables)); - tables.db= (char *)"mysql"; - tables.table_name= tables.alias= (char *)"plugin"; - alloc_mdl_requests(&tables, thd->mem_root); + tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_WRITE); /* need to open before acquiring LOCK_plugin or it will deadlock */ if (! (table= open_ltable(thd, &tables, TL_WRITE, 0))) diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index c46a01cd534..8fafbbd8e70 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -228,11 +228,7 @@ bool servers_reload(THD *thd) DBUG_PRINT("info", ("locking servers_cache")); rw_wrlock(&THR_LOCK_servers); - bzero((char*) tables, sizeof(tables)); - tables[0].alias= tables[0].table_name= (char*) "servers"; - tables[0].db= (char*) "mysql"; - tables[0].lock_type= TL_READ; - alloc_mdl_requests(tables, thd->mem_root); + tables[0].init_one_table("mysql", 5, "servers", 7, "servers", TL_READ); if (simple_open_n_lock_tables(thd, tables)) { @@ -363,10 +359,7 @@ insert_server(THD *thd, FOREIGN_SERVER *server) DBUG_ENTER("insert_server"); - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.alias= tables.table_name= (char*) "servers"; - alloc_mdl_requests(&tables, thd->mem_root); + tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE); /* need to open before acquiring THR_LOCK_plugin or it will deadlock */ if (! (table= open_ltable(thd, &tables, TL_WRITE, 0))) @@ -582,10 +575,7 @@ int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options) DBUG_PRINT("info", ("server name server->server_name %s", server_options->server_name)); - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.alias= tables.table_name= (char*) "servers"; - alloc_mdl_requests(&tables, thd->mem_root); + tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE); rw_wrlock(&THR_LOCK_servers); @@ -707,10 +697,8 @@ int update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered) TABLE_LIST tables; DBUG_ENTER("update_server"); - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*)"mysql"; - tables.alias= tables.table_name= (char*)"servers"; - alloc_mdl_requests(&tables, thd->mem_root); + tables.init_one_table("mysql", 5, "servers", 7, "servers", + TL_WRITE); if (!(table= open_ltable(thd, &tables, TL_WRITE, 0))) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 9727498cb2e..6a5895f9446 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2934,7 +2934,7 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, table, res, db_name, table_name)); thd->temporary_tables= 0; - close_tables_for_reopen(thd, &show_table_list, FALSE); + close_tables_for_reopen(thd, &show_table_list); DBUG_RETURN(error); } @@ -3065,30 +3065,21 @@ uint get_table_open_method(TABLE_LIST *tables, */ static bool -acquire_high_prio_shared_mdl_lock(THD *thd, MDL_request *mdl_request, - TABLE_LIST *table) +acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table) { - bool retry; - - mdl_request->init(0, table->db, table->table_name); - table->mdl_request= mdl_request; - thd->mdl_context.add_request(mdl_request); - mdl_request->set_type(MDL_SHARED_HIGH_PRIO); - - while (1) + bool error; + table->mdl_request.init(0, table->db, table->table_name, + MDL_SHARED_HIGH_PRIO); + while (!(error= + thd->mdl_context.try_acquire_shared_lock(&table->mdl_request)) && + !table->mdl_request.ticket) { - if (thd->mdl_context.acquire_shared_lock(mdl_request, &retry)) - { - if (!retry || thd->mdl_context.wait_for_locks()) - { - thd->mdl_context.remove_all_requests(); - return TRUE; - } - continue; - } - break; + MDL_request_list mdl_requests; + mdl_requests.push_front(&table->mdl_request); + if ((error= thd->mdl_context.wait_for_locks(&mdl_requests))) + break; } - return FALSE; + return error; } @@ -3123,7 +3114,6 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, char key[MAX_DBKEY_LENGTH]; uint key_length; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; - MDL_request mdl_request; bzero((char*) &table_list, sizeof(TABLE_LIST)); bzero((char*) &tbl, sizeof(TABLE)); @@ -3153,7 +3143,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, simply obtaining internal lock of data-dictionary (ATM it is LOCK_open) instead of obtaning full-blown metadata lock. */ - if (acquire_high_prio_shared_mdl_lock(thd, &mdl_request, &table_list)) + if (acquire_high_prio_shared_mdl_lock(thd, &table_list)) { /* Some error occured (most probably we have been killed while @@ -3213,8 +3203,7 @@ err_share: err_unlock: pthread_mutex_unlock(&LOCK_open); - thd->mdl_context.release_lock(mdl_request.ticket); - thd->mdl_context.remove_request(&mdl_request); + thd->mdl_context.release_lock(table_list.mdl_request.ticket); thd->clear_error(); return res; } @@ -3462,7 +3451,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) res= schema_table->process_table(thd, show_table_list, table, res, &orig_db_name, &tmp_lex_string); - close_tables_for_reopen(thd, &show_table_list, FALSE); + close_tables_for_reopen(thd, &show_table_list); } DBUG_ASSERT(!lex->query_tables_own_last); if (res) @@ -7199,14 +7188,14 @@ static bool show_create_trigger_impl(THD *thd, - do not update Lex::query_tables in add_table_to_list(). */ -static TABLE_LIST *get_trigger_table_impl( - THD *thd, - const sp_name *trg_name) +static +TABLE_LIST *get_trigger_table_impl(THD *thd, const sp_name *trg_name) { char trn_path_buff[FN_REFLEN]; - LEX_STRING trn_path= { trn_path_buff, 0 }; + LEX_STRING db; LEX_STRING tbl_name; + TABLE_LIST *table; build_trn_path(thd, trg_name, &trn_path); @@ -7220,25 +7209,19 @@ static TABLE_LIST *get_trigger_table_impl( return NULL; /* We need to reset statement table list to be PS/SP friendly. */ - - TABLE_LIST *table; - - if (!(table= (TABLE_LIST *)thd->calloc(sizeof(TABLE_LIST)))) - { - my_error(ER_OUTOFMEMORY, MYF(0), sizeof(TABLE_LIST)); + if (!(table= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST)))) return NULL; - } - table->db_length= trg_name->m_db.length; - table->db= thd->strmake(trg_name->m_db.str, trg_name->m_db.length); + db= trg_name->m_db; - table->table_name_length= tbl_name.length; - table->table_name= thd->strmake(tbl_name.str, tbl_name.length); + db.str= thd->strmake(db.str, db.length); + tbl_name.str= thd->strmake(tbl_name.str, tbl_name.length); - table->alias= thd->strmake(tbl_name.str, tbl_name.length); + if (db.str == NULL || tbl_name.str == NULL) + return NULL; - table->lock_type= TL_IGNORE; - table->cacheable_table= 0; + table->init_one_table(db.str, db.length, tbl_name.str, tbl_name.length, + tbl_name.str, TL_IGNORE); return table; } @@ -7254,7 +7237,8 @@ static TABLE_LIST *get_trigger_table_impl( @return TABLE_LIST object corresponding to the base table. */ -static TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name) +static +TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name) { /* Acquire LOCK_open (stop the server). */ @@ -7310,8 +7294,6 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name) uint num_tables; /* NOTE: unused, only to pass to open_tables(). */ - alloc_mdl_requests(lst, thd->mem_root); - if (open_tables(thd, &lst, &num_tables, 0)) { my_error(ER_TRG_CANT_OPEN_TABLE, MYF(0), diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 527095f2c88..c389ef2aef3 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -16,6 +16,7 @@ /* drop and alter of tables */ #include "mysql_priv.h" +#include "debug_sync.h" #include #include #include @@ -1907,14 +1908,25 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, if (find_temporary_table(thd, table->db, table->table_name)) { /* - Since we don't acquire metadata lock if we have found temporary - table, we should do something to avoid releasing it at the end. + A temporary table. + + Don't try to find a corresponding MDL lock or assign it + to table->mdl_request.ticket. There can't be metadata + locks for temporary tables: they are local to the session. + + Later in this function we release the MDL lock only if + table->mdl_requeset.ticket is not NULL. Thus here we + ensure that we won't release the metadata lock on the base + table locked with LOCK TABLES as a side effect of temporary + table drop. */ - table->mdl_request= NULL; + DBUG_ASSERT(table->mdl_request.ticket == NULL); } else { /* + Not a temporary table. + Since 'tables' list can't contain duplicates (this is ensured by parser) it is safe to cache pointer to the TABLE instances in its elements. @@ -1923,7 +1935,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->table_name); if (!table->table) DBUG_RETURN(1); - table->mdl_request->ticket= table->table->mdl_ticket; + table->mdl_request.ticket= table->table->mdl_ticket; } } } @@ -2188,7 +2200,7 @@ err: Under LOCK TABLES we should release meta-data locks on the tables which were dropped. Otherwise we can rely on close_thread_tables() doing this. Unfortunately in this case we are likely to get more - false positives in lock_table_name_if_not_cached() function. So + false positives in try_acquire_exclusive_lock() function. So it makes sense to remove exclusive meta-data locks in all cases. Leave LOCK TABLES mode if we managed to drop all tables which were @@ -2203,14 +2215,14 @@ err: } for (table= tables; table; table= table->next_local) { - if (table->mdl_request) + if (table->mdl_request.ticket) { /* Under LOCK TABLES we may have several instances of table open and locked and therefore have to remove several metadata lock requests associated with them. */ - thd->mdl_context.release_all_locks_for_name(table->mdl_request->ticket); + thd->mdl_context.release_all_locks_for_name(table->mdl_request.ticket); } } } @@ -4094,47 +4106,6 @@ warn: } -/** - Auxiliary function which obtains exclusive meta-data lock on the - table if there are no shared or exclusive on it already. - - See mdl_try_acquire_exclusive_lock() function for more info. - - TODO: This function is here mostly to simplify current patch - and probably should be removed. - TODO: Investigate if it is kosher to leave lock request in the - context in the case when we fail to obtain the lock. -*/ - -static bool lock_table_name_if_not_cached(THD *thd, const char *db, - const char *table_name, - MDL_request **mdl_request) -{ - bool conflict; - - if (!(*mdl_request= MDL_request::create(0, db, table_name, thd->mem_root))) - return TRUE; - (*mdl_request)->set_type(MDL_EXCLUSIVE); - thd->mdl_context.add_request(*mdl_request); - if (thd->mdl_context.try_acquire_exclusive_lock(*mdl_request, &conflict)) - { - /* - To simplify our life under LOCK TABLES we remove unsatisfied - lock request from the context. - */ - thd->mdl_context.remove_request(*mdl_request); - if (!conflict) - { - /* Probably OOM. */ - return TRUE; - } - else - *mdl_request= NULL; - } - return FALSE; -} - - /* Database and name-locking aware wrapper for mysql_create_table_no_lock(), */ @@ -4145,7 +4116,8 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, bool internal_tmp_table, uint select_field_count) { - MDL_request *target_mdl_request= NULL; + MDL_request target_mdl_request; + bool has_target_mdl_lock= FALSE; bool result; DBUG_ENTER("mysql_create_table"); @@ -4168,13 +4140,15 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - if (lock_table_name_if_not_cached(thd, db, table_name, &target_mdl_request)) + target_mdl_request.init(0, db, table_name, MDL_EXCLUSIVE); + if (thd->mdl_context.try_acquire_exclusive_lock(&target_mdl_request)) { result= TRUE; goto unlock; } - if (!target_mdl_request) + if (target_mdl_request.ticket == NULL) { + /* Table exists and is locked by some other thread. */ if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) { push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, @@ -4191,6 +4165,9 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, } goto unlock; } + /* Got lock. */ + DEBUG_SYNC(thd, "locked_table_name"); + has_target_mdl_lock= TRUE; } result= mysql_create_table_no_lock(thd, db, table_name, create_info, @@ -4199,11 +4176,9 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, select_field_count); unlock: - if (target_mdl_request) - { - thd->mdl_context.release_lock(target_mdl_request->ticket); - thd->mdl_context.remove_request(target_mdl_request); - } + if (has_target_mdl_lock) + thd->mdl_context.release_lock(target_mdl_request.ticket); + pthread_mutex_lock(&LOCK_lock_db); if (!--creating_table && creating_database) pthread_cond_signal(&COND_refresh); @@ -4364,11 +4339,11 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, int error= 0; TABLE tmp_table, *table; TABLE_SHARE *share; + bool has_mdl_lock= FALSE; char from[FN_REFLEN],tmp[FN_REFLEN+32]; const char **ext; MY_STAT stat_info; - MDL_request *mdl_request= NULL; - enum enum_open_table_action ot_action_unused; + Open_table_context ot_ctx_unused(thd); DBUG_ENTER("prepare_for_repair"); uint reopen_for_repair_flags= (MYSQL_LOCK_IGNORE_FLUSH | MYSQL_OPEN_HAS_MDL_LOCK); @@ -4386,15 +4361,11 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, uint key_length; key_length= create_table_def_key(thd, key, table_list, 0); - mdl_request= MDL_request::create(0, table_list->db, - table_list->table_name, thd->mem_root); - mdl_request->set_type(MDL_EXCLUSIVE); - thd->mdl_context.add_request(mdl_request); - if (thd->mdl_context.acquire_exclusive_locks()) - { - thd->mdl_context.remove_request(mdl_request); + table_list->mdl_request.init(0, table_list->db, table_list->table_name, + MDL_EXCLUSIVE); + if (thd->mdl_context.acquire_exclusive_lock(&table_list->mdl_request)) DBUG_RETURN(0); - } + has_mdl_lock= TRUE; pthread_mutex_lock(&LOCK_open); if (!(share= (get_table_share(thd, table_list, key, key_length, 0, @@ -4412,7 +4383,6 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, } pthread_mutex_unlock(&LOCK_open); table= &tmp_table; - table_list->mdl_request= mdl_request; } /* A MERGE table must not come here. */ @@ -4507,7 +4477,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, to finish the repair in the handler later on. */ if (open_table(thd, table_list, thd->mem_root, - &ot_action_unused, reopen_for_repair_flags)) + &ot_ctx_unused, reopen_for_repair_flags)) { error= send_check_errmsg(thd, table_list, "repair", "Failed to open partially repaired table"); @@ -4523,11 +4493,9 @@ end: pthread_mutex_unlock(&LOCK_open); } /* In case of a temporary table there will be no metadata lock. */ - if (error && mdl_request) - { - thd->mdl_context.release_lock(mdl_request->ticket); - thd->mdl_context.remove_request(mdl_request); - } + if (error && has_mdl_lock) + thd->mdl_context.release_lock(table_list->mdl_request.ticket); + DBUG_RETURN(error); } @@ -4938,6 +4906,8 @@ send_result_message: thd->mdl_context.release_all_locks(); if (!result_code) // recreation went ok { + /* Clear the ticket released in close_thread_tables(). */ + table->mdl_request.ticket= NULL; if ((table->table= open_ltable(thd, table, lock_type, 0)) && ((result_code= table->table->file->ha_analyze(thd, check_opt)) > 0)) result_code= 0; // analyze went ok @@ -5241,9 +5211,9 @@ bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO *create_info) { - MDL_request *target_mdl_request= NULL; - char src_path[FN_REFLEN], dst_path[FN_REFLEN + 1]; + char src_path[FN_REFLEN + 1], dst_path[FN_REFLEN + 1]; uint dst_path_length; + bool has_mdl_lock= FALSE; char *db= table->db; char *table_name= table->table_name; int err; @@ -5298,19 +5268,20 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, } else { - if (lock_table_name_if_not_cached(thd, db, table_name, &target_mdl_request)) - goto err; - if (!target_mdl_request) + table->mdl_request.init(0, db, table_name, MDL_EXCLUSIVE); + if (thd->mdl_context.try_acquire_exclusive_lock(&table->mdl_request)) + DBUG_RETURN(TRUE); + + if (table->mdl_request.ticket == NULL) goto table_exists; + + DEBUG_SYNC(thd, "locked_table_name"); + has_mdl_lock= TRUE; + dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1, db, table_name, reg_ext, 0); if (!access(dst_path, F_OK)) goto table_exists; - /* - Make the metadata lock available to open_table() called to - reopen the table down the road. - */ - table->mdl_request= target_mdl_request; } DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000);); @@ -5440,14 +5411,14 @@ binlog: char buf[2048]; String query(buf, sizeof(buf), system_charset_info); query.length(0); // Have to zero it since constructor doesn't - enum enum_open_table_action ot_action_unused; + Open_table_context ot_ctx_unused(thd); /* Here we open the destination table, on which we already have exclusive metadata lock. This is needed for store_create_info() to work. The table will be closed by close_thread_table() at the end of this branch. */ - if (open_table(thd, table, thd->mem_root, &ot_action_unused, + if (open_table(thd, table, thd->mem_root, &ot_ctx_unused, MYSQL_OPEN_REOPEN)) goto err; @@ -5481,11 +5452,9 @@ binlog: res= FALSE; err: - if (target_mdl_request) - { - thd->mdl_context.release_lock(target_mdl_request->ticket); - thd->mdl_context.remove_request(target_mdl_request); - } + if (has_mdl_lock) + thd->mdl_context.release_lock(table->mdl_request.ticket); + DBUG_RETURN(res); } @@ -5969,7 +5938,6 @@ bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled, DBUG_RETURN(error); } - /** Prepare column and key definitions for CREATE TABLE in ALTER TABLE. @@ -6424,7 +6392,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, { TABLE *table, *new_table= 0; MDL_ticket *mdl_ticket; - MDL_request *target_mdl_request= NULL; + MDL_request target_mdl_request; + bool has_target_mdl_lock= FALSE; int error= 0; char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1]; char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias; @@ -6647,15 +6616,21 @@ view_err: } else { - if (lock_table_name_if_not_cached(thd, new_db, new_name, - &target_mdl_request)) + target_mdl_request.init(0, new_db, new_name, MDL_EXCLUSIVE); + if (thd->mdl_context.try_acquire_exclusive_lock(&target_mdl_request)) DBUG_RETURN(TRUE); - if (!target_mdl_request) + if (target_mdl_request.ticket == NULL) { + /* Table exists and is locked by some thread. */ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); DBUG_RETURN(TRUE); } - + DEBUG_SYNC(thd, "locked_table_name"); + has_target_mdl_lock= TRUE; + /* + Table maybe does not exist, but we got an exclusive lock + on the name, now we can safely try to find out for sure. + */ build_table_filename(new_name_buff, sizeof(new_name_buff) - 1, new_db, new_name_buff, reg_ext, 0); if (!access(new_name_buff, F_OK)) @@ -6843,8 +6818,7 @@ view_err: */ if (new_name != table_name || new_db != db) { - thd->mdl_context.release_lock(target_mdl_request->ticket); - thd->mdl_context.remove_request(target_mdl_request); + thd->mdl_context.release_lock(target_mdl_request.ticket); thd->mdl_context.release_all_locks_for_name(mdl_ticket); } else @@ -7081,7 +7055,6 @@ view_err: #ifdef WITH_PARTITION_STORAGE_ENGINE if (fast_alter_partition) { - DBUG_ASSERT(!target_mdl_request); DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, create_info, table_list, db, table_name, @@ -7161,13 +7134,13 @@ view_err: { if (table->s->tmp_table) { - enum_open_table_action not_used; + Open_table_context ot_ctx_unused(thd); TABLE_LIST tbl; bzero((void*) &tbl, sizeof(tbl)); tbl.db= new_db; tbl.table_name= tbl.alias= tmp_name; /* Table is in thd->temporary_tables */ - (void) open_table(thd, &tbl, thd->mem_root, ¬_used, + (void) open_table(thd, &tbl, thd->mem_root, &ot_ctx_unused, MYSQL_LOCK_IGNORE_FLUSH); new_table= tbl.table; } @@ -7439,7 +7412,7 @@ view_err: To do this we need to obtain a handler object for it. NO need to tamper with MERGE tables. The real open is done later. */ - enum enum_open_table_action ot_action_unused; + Open_table_context ot_ctx_unused(thd); TABLE *t_table; if (new_name != table_name || new_db != db) { @@ -7448,7 +7421,7 @@ view_err: table_list->table_name_length= strlen(new_name); table_list->db= new_db; table_list->db_length= strlen(new_db); - table_list->mdl_request= target_mdl_request; + table_list->mdl_request.ticket= target_mdl_request.ticket; } else { @@ -7457,10 +7430,10 @@ view_err: points to a different instance than the one set initially to request the lock. */ - table_list->mdl_request->ticket= mdl_ticket; + table_list->mdl_request.ticket= mdl_ticket; } if (open_table(thd, table_list, thd->mem_root, - &ot_action_unused, MYSQL_OPEN_REOPEN)) + &ot_ctx_unused, MYSQL_OPEN_REOPEN)) { goto err_with_mdl; } @@ -7523,8 +7496,7 @@ view_err: { if ((new_name != table_name || new_db != db)) { - thd->mdl_context.release_lock(target_mdl_request->ticket); - thd->mdl_context.remove_request(target_mdl_request); + thd->mdl_context.release_lock(target_mdl_request.ticket); thd->mdl_context.release_all_locks_for_name(mdl_ticket); } else @@ -7583,11 +7555,9 @@ err: alter_info->datetime_field->field_name); thd->abort_on_warning= save_abort_on_warning; } - if (target_mdl_request) - { - thd->mdl_context.release_lock(target_mdl_request->ticket); - thd->mdl_context.remove_request(target_mdl_request); - } + if (has_target_mdl_lock) + thd->mdl_context.release_lock(target_mdl_request.ticket); + DBUG_RETURN(TRUE); err_with_mdl: @@ -7598,11 +7568,9 @@ err_with_mdl: tables and release the exclusive metadata lock. */ thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); - if (target_mdl_request) - { - thd->mdl_context.release_lock(target_mdl_request->ticket); - thd->mdl_context.remove_request(target_mdl_request); - } + if (has_target_mdl_lock) + thd->mdl_context.release_lock(target_mdl_request.ticket); + thd->mdl_context.release_all_locks_for_name(mdl_ticket); DBUG_RETURN(TRUE); } @@ -7851,6 +7819,8 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list) uninitialized data. open_tables() could fail. */ table_list->table= NULL; + /* Same applies to MDL ticket. */ + table_list->mdl_request.ticket= NULL; bzero((char*) &create_info, sizeof(create_info)); create_info.row_type=ROW_TYPE_NOT_USED; diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc index 7e557c3ce68..d27473c1959 100644 --- a/sql/sql_udf.cc +++ b/sql/sql_udf.cc @@ -138,11 +138,7 @@ void udf_init() lex_start(new_thd); new_thd->set_db(db, sizeof(db)-1); - bzero((uchar*) &tables,sizeof(tables)); - tables.alias= tables.table_name= (char*) "func"; - tables.lock_type = TL_READ; - tables.db= db; - alloc_mdl_requests(&tables, new_thd->mem_root); + tables.init_one_table(db, sizeof(db)-1, "func", 4, "func", TL_READ); if (simple_open_n_lock_tables(new_thd, &tables)) { @@ -483,10 +479,7 @@ int mysql_create_function(THD *thd,udf_func *udf) /* create entry in mysql.func table */ - bzero((char*) &tables,sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "func"; - alloc_mdl_requests(&tables, thd->mem_root); + tables.init_one_table("mysql", 5, "func", 4, "func", TL_WRITE); /* Allow creation of functions even if we can't open func table */ if (!(table = open_ltable(thd, &tables, TL_WRITE, 0))) goto err; @@ -562,10 +555,8 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) if (udf->dlhandle && !find_udf_dl(udf->dl)) dlclose(udf->dlhandle); - bzero((char*) &tables,sizeof(tables)); - tables.db=(char*) "mysql"; - tables.table_name= tables.alias= (char*) "func"; - alloc_mdl_requests(&tables, thd->mem_root); + tables.init_one_table("mysql", 5, "func", 4, "func", TL_WRITE); + if (!(table = open_ltable(thd, &tables, TL_WRITE, 0))) goto err; table->use_all_columns(); @@ -579,15 +570,16 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) if ((error = table->file->ha_delete_row(table->record[0]))) table->file->print_error(error, MYF(0)); } - close_thread_tables(thd); - rw_unlock(&THR_LOCK_udf); - /* Binlog the drop function. */ + /* + Binlog the drop function. Keep the table open and locked + while binlogging, to avoid binlog inconsistency. + */ write_bin_log(thd, TRUE, thd->query(), thd->query_length()); DBUG_RETURN(0); - err: +err: rw_unlock(&THR_LOCK_udf); DBUG_RETURN(1); } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 7e033bc963a..b81fb30ec27 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -226,7 +226,7 @@ int mysql_update(THD *thd, break; if (!need_reopen) DBUG_RETURN(1); - close_tables_for_reopen(thd, &table_list, FALSE); + close_tables_for_reopen(thd, &table_list); } if (mysql_handle_derived(thd->lex, &mysql_derived_prepare) || @@ -1149,7 +1149,7 @@ reopen_tables: */ cleanup_items(thd->free_list); - close_tables_for_reopen(thd, &table_list, FALSE); + close_tables_for_reopen(thd, &table_list); goto reopen_tables; } diff --git a/sql/table.cc b/sql/table.cc index 8d28514e912..7fb9bbbd955 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4583,6 +4583,14 @@ void TABLE_LIST::reinit_before_use(THD *thd) } while (parent_embedding && parent_embedding->nested_join->join_list.head() == embedded); + + mdl_request.ticket= NULL; + /* + Not strictly necessary, but we manipulate with the type in open_table(), + so it's "safe" to reset the lock request type to the parser default, to + restore things back to first-execution state. + */ + mdl_request.set_type(MDL_SHARED); } /* @@ -4811,11 +4819,11 @@ size_t max_row_length(TABLE *table, const uchar *data) objects for all elements of table list. */ -void alloc_mdl_requests(TABLE_LIST *table_list, MEM_ROOT *root) +void init_mdl_requests(TABLE_LIST *table_list) { for ( ; table_list ; table_list= table_list->next_global) - table_list->mdl_request= - MDL_request::create(0, table_list->db, table_list->table_name, root); + table_list->mdl_request.init(0, table_list->db, table_list->table_name, + MDL_SHARED); } diff --git a/sql/table.h b/sql/table.h index 1762ae8785d..82498428a11 100644 --- a/sql/table.h +++ b/sql/table.h @@ -18,6 +18,7 @@ #include "sql_plist.h" +#include "mdl.h" /* Structs that defines the TABLE */ @@ -30,8 +31,6 @@ class st_select_lex; class partition_info; class COND_EQUAL; class Security_context; -class MDL_request; -class MDL_ticket; /*************************************************************************/ @@ -1126,6 +1125,7 @@ struct TABLE_LIST table_name_length= table_name_length_arg; alias= (char*) alias_arg; lock_type= lock_type_arg; + mdl_request.init(0, db, table_name, MDL_SHARED); } /* @@ -1429,7 +1429,7 @@ struct TABLE_LIST uint table_open_method; enum enum_schema_table_state schema_table_state; - MDL_request *mdl_request; + MDL_request mdl_request; void calc_md5(char *buffer); void set_underlying_merge(); @@ -1798,6 +1798,6 @@ static inline void dbug_tmp_restore_column_maps(MY_BITMAP *read_set, size_t max_row_length(TABLE *table, const uchar *data); -void alloc_mdl_requests(TABLE_LIST *table_list, MEM_ROOT *root); +void init_mdl_requests(TABLE_LIST *table_list); #endif /* TABLE_INCLUDED */ diff --git a/sql/tztime.cc b/sql/tztime.cc index 9c49c286662..c17b37e27fb 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -1637,6 +1637,7 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) tz_init_table_list(tz_tables+1); tz_tables[0].next_global= tz_tables[0].next_local= &tz_tables[1]; tz_tables[1].prev_global= &tz_tables[0].next_global; + init_mdl_requests(tz_tables); /* We need to open only mysql.time_zone_leap_second, but we try to @@ -2296,6 +2297,7 @@ my_tz_find(THD *thd, const String *name) Open_tables_state open_tables_state_backup; tz_init_table_list(tz_tables); + init_mdl_requests(tz_tables); if (!open_system_tables_for_read(thd, tz_tables, &open_tables_state_backup)) { diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index 21a7e2c43d3..e68971975bc 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -388,7 +388,6 @@ int ha_myisammrg::add_children_list(void) { TABLE_LIST *parent_l= this->table->pos_in_table_list; TABLE_LIST *child_l; - THD *thd= current_thd; DBUG_ENTER("ha_myisammrg::add_children_list"); DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", this->table->s->db.str, this->table->s->table_name.str, (long) this->table)); @@ -434,15 +433,12 @@ int ha_myisammrg::add_children_list(void) /* Copy select_lex. Used in unique_table() at least. */ child_l->select_lex= parent_l->select_lex; - child_l->mdl_request= NULL; /* Safety, if alloc_mdl_requests fails. */ - /* Break when this was the last child. */ if (&child_l->next_global == this->children_last_l) break; } - alloc_mdl_requests(children_l, thd->locked_tables_root ? - thd->locked_tables_root : thd->mem_root); + init_mdl_requests(children_l); /* Insert children into the table list. */ if (parent_l->next_global) @@ -819,6 +815,8 @@ int ha_myisammrg::detach_children(void) Clear the table reference. */ child_l->table= NULL; + /* Similarly, clear the ticket reference. */ + child_l->mdl_request.ticket= NULL; /* Break when this was the last child. */ if (&child_l->next_global == this->children_last_l) From a164203fea122604d4167886102e61fbff6ccd1f Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Tue, 8 Dec 2009 14:14:05 +0100 Subject: [PATCH 076/212] Backport of revno: 2617.56.21 Bug #45066 FLUSH TABLES WITH READ LOCK deadlocks against LOCK TABLE Test coverage for combinations of LOCK TABLE READ / WRITE and FLUSH TABLES / FLUSH TABLES WITH READ LOCK added to lock.test. LOCK and FLUSH are executed sequentially from one connection. --- mysql-test/r/lock.result | 22 ++++++++++++++++++++++ mysql-test/t/lock.test | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index 46ce618b99b..afb444f8ae9 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -319,5 +319,27 @@ alter table t1 add column j int; unlock tables; drop table t1; # +# Bug#45066 FLUSH TABLES WITH READ LOCK deadlocks against +# LOCK TABLE +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1(a INT); +LOCK TABLE t1 READ; +FLUSH TABLES; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +LOCK TABLE t1 WRITE; +FLUSH TABLES; +# +# If you allow the next combination, you reintroduce bug Bug#45066 +# +LOCK TABLE t1 READ; +FLUSH TABLES WITH READ LOCK; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +LOCK TABLE t1 WRITE; +FLUSH TABLES WITH READ LOCK; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +UNLOCK TABLES; +DROP TABLE t1; +# # End of 6.0 tests. # diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index eaba2693904..4d610559077 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -385,6 +385,38 @@ alter table t1 add column j int; unlock tables; drop table t1; +--echo # +--echo # Bug#45066 FLUSH TABLES WITH READ LOCK deadlocks against +--echo # LOCK TABLE +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1(a INT); + +LOCK TABLE t1 READ; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +FLUSH TABLES; + +LOCK TABLE t1 WRITE; +FLUSH TABLES; + +--echo # +--echo # If you allow the next combination, you reintroduce bug Bug#45066 +--echo # +LOCK TABLE t1 READ; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +FLUSH TABLES WITH READ LOCK; + +LOCK TABLE t1 WRITE; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +FLUSH TABLES WITH READ LOCK; + +UNLOCK TABLES; +DROP TABLE t1; + --echo # --echo # End of 6.0 tests. --echo # From 8da6d448b4451cf272ff1d3f1600e2a63e33e0c7 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Tue, 8 Dec 2009 14:22:26 +0100 Subject: [PATCH 077/212] Backport of revno: 2617.62.1 Bug #39675 rename tables on innodb tables with pending transactions causes slave data issue Bug was already fixed as part of patch for Bug#989 (If DROP TABLE while there's an active transaction, wrong binlog order) Test case added to rpl_innodb.test. --- mysql-test/suite/rpl/r/rpl_innodb.result | 45 ++++++++++++++++ mysql-test/suite/rpl/t/rpl_innodb.test | 67 +++++++++++++++++++++++- 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/rpl/r/rpl_innodb.result b/mysql-test/suite/rpl/r/rpl_innodb.result index bf6c3cb8c86..0d83f29d0fb 100644 --- a/mysql-test/suite/rpl/r/rpl_innodb.result +++ b/mysql-test/suite/rpl/r/rpl_innodb.result @@ -82,3 +82,48 @@ FLUSH LOGS; FLUSH LOGS; DROP DATABASE mysqltest1; End of 5.1 tests +# +# Bug#39675 rename tables on innodb tables with pending +# transactions causes slave data issue. +# +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t3; +CREATE TABLE t1 ( +id INT PRIMARY KEY auto_increment, +b INT DEFAULT NULL +) ENGINE=InnoDB; +CREATE TABLE t2 ( +id INT PRIMARY KEY auto_increment, +b INT DEFAULT NULL +) ENGINE=InnoDB; +INSERT INTO t1 (b) VALUES (1),(2),(3); +BEGIN; +INSERT INTO t1(b) VALUES (4); +-------- switch to master1 -------- +RENAME TABLE t1 TO t3, t2 TO t1;; +-------- switch to master -------- +COMMIT; +-------- switch to master1 -------- +-------- switch to master -------- +SELECT * FROM t1; +id b +SELECT * FROM t3; +id b +1 1 +2 2 +3 3 +4 4 +-------- switch to slave -------- +SELECT * FROM t1; +id b +SELECT * FROM t3; +id b +1 1 +2 2 +3 3 +4 4 +-------- switch to master -------- +DROP TABLE t1; +DROP TABLE t3; +End of 6.0 tests diff --git a/mysql-test/suite/rpl/t/rpl_innodb.test b/mysql-test/suite/rpl/t/rpl_innodb.test index 64a85d27c88..7ee65027abc 100644 --- a/mysql-test/suite/rpl/t/rpl_innodb.test +++ b/mysql-test/suite/rpl/t/rpl_innodb.test @@ -120,6 +120,71 @@ connection master; FLUSH LOGS; DROP DATABASE mysqltest1; --- source include/master-slave-end.inc --echo End of 5.1 tests + +--echo # +--echo # Bug#39675 rename tables on innodb tables with pending +--echo # transactions causes slave data issue. +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t3; +--enable_warnings + +CREATE TABLE t1 ( + id INT PRIMARY KEY auto_increment, + b INT DEFAULT NULL +) ENGINE=InnoDB; + +CREATE TABLE t2 ( + id INT PRIMARY KEY auto_increment, + b INT DEFAULT NULL +) ENGINE=InnoDB; + +INSERT INTO t1 (b) VALUES (1),(2),(3); + +BEGIN; +INSERT INTO t1(b) VALUES (4); + +--echo -------- switch to master1 -------- +connection master1; +--send RENAME TABLE t1 TO t3, t2 TO t1; + +--echo -------- switch to master -------- +connection master; +# Need to wait until RENAME is received +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE info = "RENAME TABLE t1 TO t3, t2 TO t1" and + state = "Waiting for table"; +--source include/wait_condition.inc + +COMMIT; + +--echo -------- switch to master1 -------- +connection master1; +--reap + +--echo -------- switch to master -------- +connection master; +SELECT * FROM t1; +SELECT * FROM t3; + +sync_slave_with_master; + +--echo -------- switch to slave -------- +connection slave; +SELECT * FROM t1; +SELECT * FROM t3; + +--echo -------- switch to master -------- +connection master; +DROP TABLE t1; +DROP TABLE t3; + +--echo End of 6.0 tests + +--source include/master-slave-end.inc From 5ce2421bb14740fe3d647e0fab04a5d44aaa4d1f Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Tue, 8 Dec 2009 14:27:33 +0100 Subject: [PATCH 078/212] Backport of revno: 2617.65.1 Bug #22876 Four-way deadlock This bug was fixed as a part of Bug#989 "If DROP TABLE while there's an active transaction, wrong binlog order" A statement which would have caused circular wait will now be aborted with ER_LOCK_DEADLOCK. Test case based on bug description added to innodb_mysql_lock.test. Note that innodb_lock_wait_timeout is set to 5 mins to prevent race conditions in the test. --- mysql-test/r/innodb_mysql_lock.result | 24 ++++++++++ mysql-test/t/innodb_mysql_lock-master.opt | 1 + mysql-test/t/innodb_mysql_lock.test | 58 +++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 mysql-test/r/innodb_mysql_lock.result create mode 100644 mysql-test/t/innodb_mysql_lock-master.opt create mode 100644 mysql-test/t/innodb_mysql_lock.test diff --git a/mysql-test/r/innodb_mysql_lock.result b/mysql-test/r/innodb_mysql_lock.result new file mode 100644 index 00000000000..147267d5550 --- /dev/null +++ b/mysql-test/r/innodb_mysql_lock.result @@ -0,0 +1,24 @@ +# +# Bug #22876 Four-way deadlock +# +DROP TABLE IF EXISTS t1; +# Connection 1 +set @@autocommit=0; +CREATE TABLE t1(s1 INT UNIQUE) ENGINE=innodb; +INSERT INTO t1 VALUES (1); +# Connection 2 +set @@autocommit=0; +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (1); +# Connection 3 +set @@autocommit=0; +DROP TABLE t1; +# Connection 1 +# Connection 1 is now holding the lock. +# Issuing insert from connection 1 while connection 2&3 +# is waiting for the lock should give a deadlock error. +INSERT INTO t1 VALUES (2); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Cleanup +commit; +commit; diff --git a/mysql-test/t/innodb_mysql_lock-master.opt b/mysql-test/t/innodb_mysql_lock-master.opt new file mode 100644 index 00000000000..0041949b829 --- /dev/null +++ b/mysql-test/t/innodb_mysql_lock-master.opt @@ -0,0 +1 @@ +--innodb_lock_wait_timeout=300 diff --git a/mysql-test/t/innodb_mysql_lock.test b/mysql-test/t/innodb_mysql_lock.test new file mode 100644 index 00000000000..daee94bedb5 --- /dev/null +++ b/mysql-test/t/innodb_mysql_lock.test @@ -0,0 +1,58 @@ +-- source include/have_innodb.inc + +--echo # +--echo # Bug #22876 Four-way deadlock +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +connect (con1,localhost,root,,); +connect (con2,localhost,root,,); +connect (con3,localhost,root,,); + +--echo # Connection 1 +connection con1; +set @@autocommit=0; +CREATE TABLE t1(s1 INT UNIQUE) ENGINE=innodb; +INSERT INTO t1 VALUES (1); + +--echo # Connection 2 +connection con2; +set @@autocommit=0; +INSERT INTO t1 VALUES (2); +--send INSERT INTO t1 VALUES (1) + +--echo # Connection 3 +connection con3; +set @@autocommit=0; +--send DROP TABLE t1 + +--echo # Connection 1 +connection con1; +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE info = "INSERT INTO t1 VALUES (1)" and + state = "update"; +--source include/wait_condition.inc +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE info = "DROP TABLE t1" and + state = "Waiting for table"; +--source include/wait_condition.inc +--echo # Connection 1 is now holding the lock. +--echo # Issuing insert from connection 1 while connection 2&3 +--echo # is waiting for the lock should give a deadlock error. +--error ER_LOCK_DEADLOCK +INSERT INTO t1 VALUES (2); + +--echo # Cleanup +connection con2; +--reap +commit; +connection con1; +commit; +connection con3; +--reap +connection default; From 463dcbb51ccea25b07e7b6d0d3787120c4fa895d Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 8 Dec 2009 16:57:25 +0300 Subject: [PATCH 079/212] Backport of revid 2617.69.21, 2617.69.22, 2617.29.23: ---------------------------------------------------------- revno: 2617.69.21 committer: Konstantin Osipov branch nick: 5.4-4284-1-assert timestamp: Thu 2009-08-13 20:13:55 +0400 message: A fix and a test case for Bug#46610 "MySQL 5.4.4: MyISAM MRG engine crash on auto-repair of child". Also fixes Bug#42862 "Crash on failed attempt to open a children of a merge table". MERGE engine needs to extend the global table list with TABLE_LIST elements for child tables, so that they are opened and locked. Previously these table list elements were allocated in memory of ha_myisammrg object (MERGE engine handler). That would lead to access to freed memory in recover_from_failed_open_table_attempt(), which would try to recover a MERGE table child (MyISAM table) and use for that TABLE_LIST of that child. But by the time recover_from_failed_open_table_attempt() is invoked, ha_myisammrg object that owns this TABLE_LIST may be destroyed, and thus TABLE_LIST memory freed. The fix is to ensure that TABLE_LIST elements that are added to the global table list (lex->query_tables) are always allocated in thd->mem_root, which is not destroyed until end of execution. If previously TABLE_LIST elements were allocated at ha_myisammrg::open() (i.e. when the TABLE object was created and added to the table cache), now they are allocated in ha_myisammrg::add_chidlren_list() (i.e. right after "open" of the merge parent in open_tables()). We still create a list of children names at ha_myisammrg::open() to use as a basis for creation of TABLE_LISTs, that allows to avoid reading the merge handler data file on every execution. mysql-test/r/merge_recover.result: Test results for Bug#46610. mysql-test/t/merge_recover-master.opt: Option file for Bug#46610 test (need a new test because of that option, which is not tested anywhere else). mysql-test/t/merge_recover.test: Add a test case for Bug#46610. sql/table.h: MERGE child child_def_version is now moved from TABLE_LIST to MERGE engine specific data structure. storage/myisammrg/ha_myisammrg.cc: Introduce an auxiliary structure to keep MERGE child name and definition version. A list of Mrg_child_def is created in ha_myisammrg::open() and reused in ha_myisammrg::add_children_list(). --- mysql-test/r/merge_recover.result | 103 ++++++++ mysql-test/t/merge_recover-master.opt | 1 + mysql-test/t/merge_recover.test | 113 +++++++++ sql/table.h | 30 +-- storage/myisammrg/ha_myisammrg.cc | 326 +++++++++++++++----------- storage/myisammrg/ha_myisammrg.h | 50 +++- 6 files changed, 459 insertions(+), 164 deletions(-) create mode 100644 mysql-test/r/merge_recover.result create mode 100644 mysql-test/t/merge_recover-master.opt create mode 100644 mysql-test/t/merge_recover.test diff --git a/mysql-test/r/merge_recover.result b/mysql-test/r/merge_recover.result new file mode 100644 index 00000000000..871c12ca4c0 --- /dev/null +++ b/mysql-test/r/merge_recover.result @@ -0,0 +1,103 @@ +# +# Test of MyISAM MRG tables with corrupted children. +# Run with --myisam-recover=force option. +# +# Preparation: we need to make sure that the merge parent +# is never left in the table cache when closed, since this may +# have effect on merge children. +# For that, we set the table cache to minimal size and populate it +# in a concurrent connection. +# +# Switching to connection con1 +# +# +# Minimal values. +# +call mtr.add_suppression("Got an error from thread_id=.*ha_myisam.cc:"); +call mtr.add_suppression("MySQL thread id .*, query id .* localhost.*root Checking table"); +call mtr.add_suppression(" '\..test.t1'"); +set global table_open_cache=256; +set global table_definition_cache=400; +drop procedure if exists p_create; +create procedure p_create() +begin +declare i int default 1; +set @lock_table_stmt="lock table "; +set @drop_table_stmt="drop table "; +while i < @@global.table_definition_cache + 1 do +set @table_name=concat("t_", i); +set @opt_comma=if(i=1, "", ", "); +set @lock_table_stmt=concat(@lock_table_stmt, @opt_comma, +@table_name, " read"); +set @drop_table_stmt=concat(@drop_table_stmt, @opt_comma, @table_name); +set @create_table_stmt=concat("create table if not exists ", +@table_name, " (a int)"); +prepare stmt from @create_table_stmt; +execute stmt; +deallocate prepare stmt; +set i= i+1; +end while; +end| +call p_create(); +drop procedure p_create; +# +# Switching to connection 'default' +# +# +# We have to disable the ps-protocol, to avoid +# "Prepared statement needs to be re-prepared" errors +# -- table def versions change all the time with full table cache. +# +drop table if exists t1, t1_mrg, t1_copy; +# +# Prepare a MERGE engine table, that refers to a corrupted +# child. +# +create table t1 (a int, key(a)) engine=myisam; +create table t1_mrg (a int) union (t1) engine=merge; +# +# Create a table with a corrupted index file: +# save an old index file, insert more rows, +# overwrite the new index file with the old one. +# +insert into t1 (a) values (1), (2), (3); +flush table t1; +insert into t1 (a) values (4), (5), (6); +flush table t1; +# check table is needed to mark the table as crashed. +check table t1; +Table Op Msg_type Msg_text +test.t1 check warning Size of datafile is: 42 Should be: 21 +test.t1 check error Record-count is not ok; is 6 Should be: 3 +test.t1 check warning Found 6 key parts. Should be: 3 +test.t1 check error Corrupt +# +# At this point we have a merge table t1_mrg pointing to t1, +# and t1 is corrupted, and will be auto-repaired at open. +# Check that this doesn't lead to memory corruption. +# +select * from t1_mrg; +a +1 +2 +3 +4 +5 +6 +Warnings: +Error 145 Table 't1' is marked as crashed and should be repaired +Error 1194 Table 't1' is marked as crashed and should be repaired +Error 1034 Number of rows changed from 3 to 6 +# +# Cleanup +# +drop table t1, t1_mrg; +# +# Switching to connection con1 +# +unlock tables; +prepare stmt from @drop_table_stmt; +execute stmt; +deallocate prepare stmt; +set @@global.table_definition_cache=default; +set @@global.table_open_cache=default; diff --git a/mysql-test/t/merge_recover-master.opt b/mysql-test/t/merge_recover-master.opt new file mode 100644 index 00000000000..875a25ad513 --- /dev/null +++ b/mysql-test/t/merge_recover-master.opt @@ -0,0 +1 @@ +--myisam-recover=force diff --git a/mysql-test/t/merge_recover.test b/mysql-test/t/merge_recover.test new file mode 100644 index 00000000000..f2cb204eeb6 --- /dev/null +++ b/mysql-test/t/merge_recover.test @@ -0,0 +1,113 @@ +--echo # +--echo # Test of MyISAM MRG tables with corrupted children. +--echo # Run with --myisam-recover=force option. +--echo # +--echo # Preparation: we need to make sure that the merge parent +--echo # is never left in the table cache when closed, since this may +--echo # have effect on merge children. +--echo # For that, we set the table cache to minimal size and populate it +--echo # in a concurrent connection. +connect(con1,localhost,root,,test,,); +--echo # +--echo # Switching to connection con1 +--echo # +connection con1; +--echo # +--echo # Minimal values. +--echo # + +call mtr.add_suppression("Got an error from thread_id=.*ha_myisam.cc:"); +call mtr.add_suppression("MySQL thread id .*, query id .* localhost.*root Checking table"); +call mtr.add_suppression(" '\..test.t1'"); + +set global table_open_cache=256; +set global table_definition_cache=400; +--disable_warnings +drop procedure if exists p_create; +--enable_warnings +delimiter |; +create procedure p_create() +begin + declare i int default 1; + set @lock_table_stmt="lock table "; + set @drop_table_stmt="drop table "; + while i < @@global.table_definition_cache + 1 do + set @table_name=concat("t_", i); + set @opt_comma=if(i=1, "", ", "); + set @lock_table_stmt=concat(@lock_table_stmt, @opt_comma, + @table_name, " read"); + set @drop_table_stmt=concat(@drop_table_stmt, @opt_comma, @table_name); + set @create_table_stmt=concat("create table if not exists ", + @table_name, " (a int)"); + prepare stmt from @create_table_stmt; + execute stmt; + deallocate prepare stmt; + set i= i+1; + end while; +end| +delimiter ;| +call p_create(); +drop procedure p_create; +--disable_query_log +let $lock=`select @lock_table_stmt`; +eval $lock; +--enable_query_log +--echo # +--echo # Switching to connection 'default' +--echo # +connection default; +--echo # +--echo # We have to disable the ps-protocol, to avoid +--echo # "Prepared statement needs to be re-prepared" errors +--echo # -- table def versions change all the time with full table cache. +--echo # +--disable_ps_protocol +--disable_warnings +drop table if exists t1, t1_mrg, t1_copy; +--enable_warnings +let $MYSQLD_DATADIR=`select @@datadir`; +--echo # +--echo # Prepare a MERGE engine table, that refers to a corrupted +--echo # child. +--echo # +create table t1 (a int, key(a)) engine=myisam; +create table t1_mrg (a int) union (t1) engine=merge; +--echo # +--echo # Create a table with a corrupted index file: +--echo # save an old index file, insert more rows, +--echo # overwrite the new index file with the old one. +--echo # +insert into t1 (a) values (1), (2), (3); +flush table t1; +--copy_file $MYSQLD_DATADIR/test/t1.MYI $MYSQLD_DATADIR/test/t1_copy.MYI +insert into t1 (a) values (4), (5), (6); +flush table t1; +--remove_file $MYSQLD_DATADIR/test/t1.MYI +--copy_file $MYSQLD_DATADIR/test/t1_copy.MYI $MYSQLD_DATADIR/test/t1.MYI +--remove_file $MYSQLD_DATADIR/test/t1_copy.MYI +--echo # check table is needed to mark the table as crashed. +check table t1; +--echo # +--echo # At this point we have a merge table t1_mrg pointing to t1, +--echo # and t1 is corrupted, and will be auto-repaired at open. +--echo # Check that this doesn't lead to memory corruption. +--echo # +--replace_regex /'.*[\/\\]/'/ +select * from t1_mrg; +--echo # +--echo # Cleanup +--echo # +drop table t1, t1_mrg; +--echo # +--echo # Switching to connection con1 +--echo # +connection con1; +unlock tables; +prepare stmt from @drop_table_stmt; +execute stmt; +deallocate prepare stmt; +set @@global.table_definition_cache=default; +set @@global.table_open_cache=default; +disconnect con1; +connection default; +--enable_ps_protocol diff --git a/sql/table.h b/sql/table.h index 82498428a11..21e7bd3142c 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1486,20 +1486,6 @@ struct TABLE_LIST */ bool process_index_hints(TABLE *table); - /* Access MERGE child def version. See top comment in ha_myisammrg.cc */ - inline ulong get_child_def_version() - { - return child_def_version; - } - inline void set_child_def_version(ulong version) - { - child_def_version= version; - } - inline void init_child_def_version() - { - child_def_version= ~0UL; - } - /** Compare the version of metadata from the previous execution (if any) with values obtained from the current table @@ -1522,9 +1508,14 @@ struct TABLE_LIST */ inline void set_table_ref_id(TABLE_SHARE *s) + { set_table_ref_id(s->get_table_ref_type(), s->get_table_ref_version()); } + + inline + void set_table_ref_id(enum_table_ref_type table_ref_type_arg, + ulong table_ref_version_arg) { - m_table_ref_type= s->get_table_ref_type(); - m_table_ref_version= s->get_table_ref_version(); + m_table_ref_type= table_ref_type_arg; + m_table_ref_version= table_ref_version_arg; } /** @@ -1550,13 +1541,6 @@ struct TABLE_LIST private: bool prep_check_option(THD *thd, uint8 check_opt_type); bool prep_where(THD *thd, Item **conds, bool no_where_clause); - /* - Cleanup for re-execution in a prepared statement or a stored - procedure. - */ - - /* Remembered MERGE child def version. See top comment in ha_myisammrg.cc */ - ulong child_def_version; /** See comments for set_metadata_id() */ enum enum_table_ref_type m_table_ref_type; /** See comments for set_metadata_id() */ diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index e68971975bc..760639e2c6b 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -114,8 +114,8 @@ static handler *myisammrg_create_handler(handlerton *hton, ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg) :handler(hton, table_arg), file(0), is_cloned(0) { - init_sql_alloc(&children_mem_root, max(4 * sizeof(TABLE_LIST), FN_REFLEN) + - ALLOC_ROOT_MIN_BLOCK_SIZE, 0); + init_sql_alloc(&children_mem_root, + FN_REFLEN + ALLOC_ROOT_MIN_BLOCK_SIZE, 0); } @@ -220,10 +220,11 @@ static int myisammrg_parent_open_callback(void *callback_param, const char *filename) { ha_myisammrg *ha_myrg= (ha_myisammrg*) callback_param; - TABLE_LIST *child_l; - const char *db; - const char *table_name; - size_t dirlen; + Mrg_child_def *mrg_child_def; + char *db; + char *table_name; + uint dirlen; + uint table_name_length; char dir_path[FN_REFLEN]; DBUG_ENTER("myisammrg_parent_open_callback"); @@ -237,7 +238,7 @@ static int myisammrg_parent_open_callback(void *callback_param, DBUG_RETURN(1); /* purecov: end */ } - table_name= filename + dirlen; + table_name= (char*) filename + dirlen; dirlen--; /* Strip off trailing '/'. */ memcpy(dir_path, filename, dirlen); dir_path[dirlen]= '\0'; @@ -245,49 +246,33 @@ static int myisammrg_parent_open_callback(void *callback_param, dirlen-= db - dir_path; /* This is now the length of 'db'. */ DBUG_PRINT("myrg", ("open: '%s'.'%s'", db, table_name)); - /* Get a TABLE_LIST object. */ - if (!(child_l= (TABLE_LIST*) alloc_root(&ha_myrg->children_mem_root, - sizeof(TABLE_LIST)))) - { - /* purecov: begin inspected */ - DBUG_PRINT("error", ("my_malloc error: %d", my_errno)); - DBUG_RETURN(1); - /* purecov: end */ - } - bzero((char*) child_l, sizeof(TABLE_LIST)); - /* Set database (schema) name. */ - child_l->db_length= dirlen; - child_l->db= strmake_root(&ha_myrg->children_mem_root, db, dirlen); + db= strmake_root(&ha_myrg->children_mem_root, db, dirlen); /* Set table name. */ - child_l->table_name_length= strlen(table_name); - child_l->table_name= strmake_root(&ha_myrg->children_mem_root, table_name, - child_l->table_name_length); + table_name_length= strlen(table_name); + table_name= strmake_root(&ha_myrg->children_mem_root, table_name, + table_name_length); + + if (! db || ! table_name) + DBUG_RETURN(1); + /* Convert to lowercase if required. */ - if (lower_case_table_names && child_l->table_name_length) + if (lower_case_table_names && table_name_length) { /* purecov: begin tested */ - child_l->table_name_length= my_casedn_str(files_charset_info, - child_l->table_name); + table_name_length= my_casedn_str(files_charset_info, table_name); /* purecov: end */ } - /* Set alias. */ - child_l->alias= child_l->table_name; - /* Initialize table map to 'undefined'. */ - child_l->init_child_def_version(); + mrg_child_def= new (&ha_myrg->children_mem_root) + Mrg_child_def(db, dirlen, table_name, table_name_length); - /* Link TABLE_LIST object into the children list. */ - if (ha_myrg->children_last_l) - child_l->prev_global= ha_myrg->children_last_l; - else + if (! mrg_child_def || + ha_myrg->child_def_list.push_back(mrg_child_def, + &ha_myrg->children_mem_root)) { - /* Initialize ha_myrg->children_last_l when handling first child. */ - ha_myrg->children_last_l= &ha_myrg->children_l; + DBUG_RETURN(1); } - *ha_myrg->children_last_l= child_l; - ha_myrg->children_last_l= &child_l->next_global; - DBUG_RETURN(0); } @@ -297,7 +282,7 @@ static int myisammrg_parent_open_callback(void *callback_param, @param[in] name MERGE table path name @param[in] mode read/write mode, unused - @param[in] test_if_locked open flags + @param[in] test_if_locked_arg open flags @return status @retval 0 OK @@ -309,17 +294,17 @@ static int myisammrg_parent_open_callback(void *callback_param, */ int ha_myisammrg::open(const char *name, int mode __attribute__((unused)), - uint test_if_locked) + uint test_if_locked_arg) { DBUG_ENTER("ha_myisammrg::open"); DBUG_PRINT("myrg", ("name: '%s' table: 0x%lx", name, (long) table)); - DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked)); + DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked_arg)); /* Must not be used when table is open. */ DBUG_ASSERT(!this->file); /* Save for later use. */ - this->test_if_locked= test_if_locked; + test_if_locked= test_if_locked_arg; /* In case this handler was open and closed before, free old data. */ free_root(&this->children_mem_root, MYF(MY_MARK_BLOCKS_FREE)); @@ -334,6 +319,7 @@ int ha_myisammrg::open(const char *name, int mode __attribute__((unused)), */ children_l= NULL; children_last_l= NULL; + child_def_list.empty(); my_errno= 0; /* retrieve children table list. */ @@ -379,7 +365,7 @@ int ha_myisammrg::open(const char *name, int mode __attribute__((unused)), @detail When a MERGE parent table has just been opened, insert the - TABLE_LIST chain from the MERGE handle into the table list used for + TABLE_LIST chain from the MERGE handler into the table list used for opening tables for this statement. This lets the children be opened too. */ @@ -387,7 +373,9 @@ int ha_myisammrg::open(const char *name, int mode __attribute__((unused)), int ha_myisammrg::add_children_list(void) { TABLE_LIST *parent_l= this->table->pos_in_table_list; - TABLE_LIST *child_l; + THD *thd= table->in_use; + List_iterator_fast it(child_def_list); + Mrg_child_def *mrg_child_def; DBUG_ENTER("ha_myisammrg::add_children_list"); DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", this->table->s->db.str, this->table->s->table_name.str, (long) this->table)); @@ -406,7 +394,7 @@ int ha_myisammrg::add_children_list(void) DBUG_ASSERT(!this->file->children_attached); /* Must not call this with children list in place. */ - DBUG_ASSERT(parent_l->next_global != this->children_l); + DBUG_ASSERT(this->children_l == NULL); /* Prevent inclusion of another MERGE table, which could make infinite @@ -418,40 +406,103 @@ int ha_myisammrg::add_children_list(void) DBUG_RETURN(1); } - /* Fix children. */ - DBUG_ASSERT(this->children_l); - for (child_l= this->children_l; ; child_l= child_l->next_global) + while ((mrg_child_def= it++)) { - DBUG_ASSERT(!child_l->table); + TABLE_LIST *child_l; + char *db; + char *table_name; - /* Set lock type. */ - child_l->lock_type= parent_l->lock_type; + child_l= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST)); + db= (char*) thd->memdup(mrg_child_def->db.str, mrg_child_def->db.length+1); + table_name= (char*) thd->memdup(mrg_child_def->name.str, + mrg_child_def->name.length+1); + if (child_l == NULL || db == NULL || table_name == NULL) + DBUG_RETURN(1); + + child_l->init_one_table(db, mrg_child_def->db.length, + table_name, mrg_child_def->name.length, + table_name, parent_l->lock_type); /* Set parent reference. Used to detect MERGE in children list. */ child_l->parent_l= parent_l; - /* Copy select_lex. Used in unique_table() at least. */ child_l->select_lex= parent_l->select_lex; - - /* Break when this was the last child. */ - if (&child_l->next_global == this->children_last_l) - break; + /* + Set the expected table version, to not cause spurious re-prepare. + @todo: revise after the fix for Bug#36171 + */ + child_l->set_table_ref_id(mrg_child_def->get_child_table_ref_type(), + mrg_child_def->get_child_def_version()); + /* Link TABLE_LIST object into the children list. */ + if (this->children_last_l) + child_l->prev_global= this->children_last_l; + else + { + /* Initialize children_last_l when handling first child. */ + this->children_last_l= &this->children_l; + } + *this->children_last_l= child_l; + this->children_last_l= &child_l->next_global; } - init_mdl_requests(children_l); - /* Insert children into the table list. */ if (parent_l->next_global) parent_l->next_global->prev_global= this->children_last_l; *this->children_last_l= parent_l->next_global; parent_l->next_global= this->children_l; this->children_l->prev_global= &parent_l->next_global; + /* + We have to update LEX::query_tables_last if children are added to + the tail of the table list in order to be able correctly add more + elements to it (e.g. as part of prelocking process). + */ + if (thd->lex->query_tables_last == &parent_l->next_global) + thd->lex->query_tables_last= this->children_last_l; end: DBUG_RETURN(0); } +/** + A context of myrg_attach_children() callback. +*/ + +class Mrg_attach_children_callback_param +{ +public: + /** + 'need_compat_check' is set by myisammrg_attach_children_callback() + if a child fails the table def version check. + */ + bool need_compat_check; + /** TABLE_LIST identifying this merge parent. */ + TABLE_LIST *parent_l; + /** Iterator position, the current child to attach. */ + TABLE_LIST *next_child_attach; + List_iterator_fast def_it; + Mrg_child_def *mrg_child_def; +public: + Mrg_attach_children_callback_param(TABLE_LIST *parent_l_arg, + TABLE_LIST *first_child, + List &child_def_list) + :need_compat_check(FALSE), + parent_l(parent_l_arg), + next_child_attach(first_child), + def_it(child_def_list), + mrg_child_def(def_it++) + {} + void next() + { + next_child_attach= next_child_attach->next_global; + if (next_child_attach && next_child_attach->parent_l != parent_l) + next_child_attach= NULL; + if (mrg_child_def) + mrg_child_def= def_it++; + } +}; + + /** Callback function for attaching a MERGE child table. @@ -470,48 +521,38 @@ end: static MI_INFO *myisammrg_attach_children_callback(void *callback_param) { - ha_myisammrg *ha_myrg= (ha_myisammrg*) callback_param; - TABLE *parent= ha_myrg->table_ptr(); + Mrg_attach_children_callback_param *param= + (Mrg_attach_children_callback_param*) callback_param; + TABLE *parent= param->parent_l->table; TABLE *child; - TABLE_LIST *child_l; - MI_INFO *myisam; + TABLE_LIST *child_l= param->next_child_attach; + Mrg_child_def *mrg_child_def= param->mrg_child_def; + MI_INFO *myisam= NULL; DBUG_ENTER("myisammrg_attach_children_callback"); - my_errno= 0; - - /* Get child list item. */ - child_l= ha_myrg->next_child_attach; if (!child_l) { DBUG_PRINT("myrg", ("No more children to attach")); - DBUG_RETURN(NULL); + my_errno= 0; /* Ok, no more child tables. */ + goto end; } child= child_l->table; - /* - Prepare for next child. Used as child_l in next call to this function. - We cannot rely on a NULL-terminated chain. - */ - if (&child_l->next_global == ha_myrg->children_last_l) - { - DBUG_PRINT("myrg", ("attaching last child")); - ha_myrg->next_child_attach= NULL; - } - else - ha_myrg->next_child_attach= child_l->next_global; + /* Prepare for next child. */ + param->next(); /* Do a quick compatibility check. The table def version is set when the table share is created. The child def version is copied - from the table def version after a sucessful compatibility check. + from the table def version after a successful compatibility check. We need to repeat the compatibility check only if a child is opened from a different share than last time it was used with this MERGE table. */ DBUG_PRINT("myrg", ("table_def_version last: %lu current: %lu", - (ulong) child_l->get_child_def_version(), + (ulong) mrg_child_def->get_child_def_version(), (ulong) child->s->get_table_def_version())); - if (child_l->get_child_def_version() != child->s->get_table_def_version()) - ha_myrg->need_compat_check= TRUE; + if (mrg_child_def->get_child_def_version() != child->s->get_table_def_version()) + param->need_compat_check= TRUE; /* If parent is temporary, children must be temporary too and vice @@ -527,7 +568,7 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) DBUG_PRINT("error", ("temporary table mismatch parent: %d child: %d", parent->s->tmp_table, child->s->tmp_table)); my_errno= HA_ERR_WRONG_MRG_TABLE_DEF; - goto err; + goto end; } /* Extract the MyISAM table structure pointer from the handler object. */ @@ -542,8 +583,8 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) DBUG_PRINT("myrg", ("MyISAM handle: 0x%lx my_errno: %d", (long) myisam, my_errno)); - err: - DBUG_RETURN(my_errno ? NULL : myisam); + end: + DBUG_RETURN(myisam); } @@ -625,7 +666,9 @@ int ha_myisammrg::attach_children(void) MI_KEYDEF *keyinfo; uint recs; uint keys= table->s->keys; + TABLE_LIST *parent_l= table->pos_in_table_list; int error; + Mrg_attach_children_callback_param param(parent_l, this->children_l, child_def_list); DBUG_ENTER("ha_myisammrg::attach_children"); DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str, table->s->table_name.str, (long) table)); @@ -652,25 +695,15 @@ int ha_myisammrg::attach_children(void) DBUG_ASSERT(this->table->pos_in_table_list->next_global == this->children_l); /* - Initialize variables that are used, modified, and/or set by - myisammrg_attach_children_callback(). - 'next_child_attach' traverses the chain of TABLE_LIST objects - that has been compiled during myrg_parent_open(). Every call - to myisammrg_attach_children_callback() moves the pointer to - the next object. - 'need_compat_check' is set by myisammrg_attach_children_callback() - if a child fails the table def version check. 'my_errno' is set by myisammrg_attach_children_callback() in case of an error. */ - next_child_attach= this->children_l; - need_compat_check= FALSE; my_errno= 0; if (myrg_attach_children(this->file, this->test_if_locked | current_thd->open_options, - myisammrg_attach_children_callback, this, - (my_bool *) &need_compat_check)) + myisammrg_attach_children_callback, ¶m, + (my_bool *) ¶m.need_compat_check)) { error= my_errno; goto err; @@ -690,8 +723,8 @@ int ha_myisammrg::attach_children(void) always happen at the first attach because the reference child def version is initialized to 'undefined' at open. */ - DBUG_PRINT("myrg", ("need_compat_check: %d", need_compat_check)); - if (need_compat_check) + DBUG_PRINT("myrg", ("need_compat_check: %d", param.need_compat_check)); + if (param.need_compat_check) { TABLE_LIST *child_l; @@ -744,11 +777,13 @@ int ha_myisammrg::attach_children(void) if (error == HA_ERR_WRONG_MRG_TABLE_DEF) goto err; /* purecov: inspected */ - /* All checks passed so far. Now update child def version. */ + List_iterator_fast def_it(child_def_list); DBUG_ASSERT(this->children_l); for (child_l= this->children_l; ; child_l= child_l->next_global) { - child_l->set_child_def_version( + Mrg_child_def *mrg_child_def= def_it++; + mrg_child_def->set_child_def_version( + child_l->table->s->get_table_ref_type(), child_l->table->s->get_table_def_version()); if (&child_l->next_global == this->children_last_l) @@ -804,49 +839,64 @@ int ha_myisammrg::detach_children(void) goto end; } - /* Clear TABLE references. */ - DBUG_ASSERT(this->children_l); - for (child_l= this->children_l; ; child_l= child_l->next_global) + if (this->children_l) { + THD *thd= table->in_use; + + /* Clear TABLE references. */ + for (child_l= this->children_l; ; child_l= child_l->next_global) + { + /* + Do not DBUG_ASSERT(child_l->table); open_tables might be + incomplete. + + Clear the table reference. + */ + child_l->table= NULL; + /* Similarly, clear the ticket reference. */ + child_l->mdl_request.ticket= NULL; + + /* Break when this was the last child. */ + if (&child_l->next_global == this->children_last_l) + break; + } /* - Do not DBUG_ASSERT(child_l->table); open_tables might be - incomplete. + Remove children from the table list. This won't fail if called + twice. The list is terminated after removal. - Clear the table reference. + If the parent is LEX::query_tables_own_last and pre-locked tables + follow (tables used by stored functions or triggers), the children + are inserted behind the parent and before the pre-locked tables. But + we do not adjust LEX::query_tables_own_last. The pre-locked tables + could have chopped off the list by clearing + *LEX::query_tables_own_last. This did also chop off the children. If + we would copy the reference from *this->children_last_l in this + case, we would put the chopped off pre-locked tables back to the + list. So we refrain from copying it back, if the destination has + been set to NULL meanwhile. */ - child_l->table= NULL; - /* Similarly, clear the ticket reference. */ - child_l->mdl_request.ticket= NULL; + if (this->children_l->prev_global && *this->children_l->prev_global) + *this->children_l->prev_global= *this->children_last_l; + if (*this->children_last_l) + (*this->children_last_l)->prev_global= this->children_l->prev_global; - /* Break when this was the last child. */ - if (&child_l->next_global == this->children_last_l) - break; + /* + If table elements being removed are at the end of table list we + need to adjust LEX::query_tables_last member to point to the + new last element of the list. + */ + if (thd->lex->query_tables_last == this->children_last_l) + thd->lex->query_tables_last= this->children_l->prev_global; + + /* Terminate child list. So it cannot be tried to remove again. */ + *this->children_last_l= NULL; + this->children_l->prev_global= NULL; + + /* Forget about the children, we don't own their memory. */ + this->children_l= NULL; + this->children_last_l= NULL; } - /* - Remove children from the table list. This won't fail if called - twice. The list is terminated after removal. - - If the parent is LEX::query_tables_own_last and pre-locked tables - follow (tables used by stored functions or triggers), the children - are inserted behind the parent and before the pre-locked tables. But - we do not adjust LEX::query_tables_own_last. The pre-locked tables - could have chopped off the list by clearing - *LEX::query_tables_own_last. This did also chop off the children. If - we would copy the reference from *this->children_last_l in this - case, we would put the chopped off pre-locked tables back to the - list. So we refrain from copying it back, if the destination has - been set to NULL meanwhile. - */ - if (this->children_l->prev_global && *this->children_l->prev_global) - *this->children_l->prev_global= *this->children_last_l; - if (*this->children_last_l) - (*this->children_last_l)->prev_global= this->children_l->prev_global; - - /* Terminate child list. So it cannot be tried to remove again. */ - *this->children_last_l= NULL; - this->children_l->prev_global= NULL; - if (!this->file->children_attached) { DBUG_PRINT("myrg", ("merge children are already detached")); diff --git a/storage/myisammrg/ha_myisammrg.h b/storage/myisammrg/ha_myisammrg.h index c3803eb584b..4ff24c69071 100644 --- a/storage/myisammrg/ha_myisammrg.h +++ b/storage/myisammrg/ha_myisammrg.h @@ -22,18 +22,62 @@ #include +/** + Represents one name of a MERGE child. + + @todo: Add MYRG_SHARE and store chlidren names in the + share. +*/ + +class Mrg_child_def: public Sql_alloc +{ + /* Remembered MERGE child def version. See top comment in ha_myisammrg.cc */ + enum_table_ref_type m_child_table_ref_type; + ulong m_child_def_version; +public: + LEX_STRING db; + LEX_STRING name; + + /* Access MERGE child def version. See top comment in ha_myisammrg.cc */ + inline enum_table_ref_type get_child_table_ref_type() + { + return m_child_table_ref_type; + } + inline ulong get_child_def_version() + { + return m_child_def_version; + } + inline void set_child_def_version(enum_table_ref_type child_table_ref_type, + ulong version) + { + m_child_table_ref_type= child_table_ref_type; + m_child_def_version= version; + } + + Mrg_child_def(char *db_arg, size_t db_len_arg, + char *table_name_arg, size_t table_name_len_arg) + { + db.str= db_arg; + db.length= db_len_arg; + name.str= table_name_arg; + name.length= table_name_len_arg; + m_child_def_version= ~0UL; + m_child_table_ref_type= TABLE_REF_NULL; + } +}; + + class ha_myisammrg: public handler { MYRG_INFO *file; my_bool is_cloned; /* This instance has been cloned */ - public: +public: MEM_ROOT children_mem_root; /* mem root for children list */ + List child_def_list; TABLE_LIST *children_l; /* children list */ TABLE_LIST **children_last_l; /* children list end */ - TABLE_LIST *next_child_attach; /* next child to attach */ uint test_if_locked; /* flags from ::open() */ - bool need_compat_check; /* if need compatibility check */ ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg); ~ha_myisammrg(); From 302352723e8fbf69b9d02604c84a79fa56e69b7b Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 8 Dec 2009 17:13:12 +0300 Subject: [PATCH 080/212] Backport of: ---------------------------------------------------------- revno: 2617.69.24 committer: Konstantin Osipov branch nick: 5.4-42546 timestamp: Fri 2009-08-14 19:22:05 +0400 message: A pre-requisite for a fix for Bug#42546 "Backup: RESTORE fails, thinking it finds an existing table" Back-port from WL 148 "Foreign keys" feature tree a patch that introduced Prelocking_strategy class -- a way to parameterize open_tables() behaviour, implemented by Dmitry Lenev. (Part of WL#4284). sql/sql_base.cc: Implement different prelocking strategies. Use an instance of prelocking_strategy in open_tables(). sql/sql_class.h: Add declarations for class Prelocking_strategy. sql/sql_lex.h: Add a helper method to access last table of the global table list (lex->query_tables). sql/sql_parse.cc: Use a special prelocking strategy when locking tables for LOCK TABLES. sql/sql_table.cc: Use normal open_and_lock_tables_derived() in ALTER TABLE. sql/sql_yacc.yy: Modify the grammar to not pollute the global table list with tables that should not be opened. --- sql/mysql_priv.h | 22 ++- sql/sp.cc | 416 +++++++++++------------------------------- sql/sp.h | 52 ++++-- sql/sp_head.h | 8 +- sql/sql_base.cc | 440 +++++++++++++++++++++++++++++++++++++++------ sql/sql_class.h | 84 +++++++++ sql/sql_lex.h | 17 ++ sql/sql_parse.cc | 11 +- sql/sql_table.cc | 34 +++- sql/sql_trigger.cc | 52 ++++++ sql/sql_trigger.h | 6 +- sql/sql_yacc.yy | 66 +++++-- 12 files changed, 790 insertions(+), 418 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index d65850e0d18..c956a0bfd12 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1474,10 +1474,26 @@ int setup_ftfuncs(SELECT_LEX* select); int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order); void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond); -int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags); +int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags, + Prelocking_strategy *prelocking_strategy); +inline int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags) +{ + DML_prelocking_strategy prelocking_strategy; + + return open_tables(thd, tables, counter, flags, &prelocking_strategy); +} /* open_and_lock_tables with optional derived handling */ -int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, - uint flags); +int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, + bool derived, uint flags, + Prelocking_strategy *prelocking_strategy); +inline int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, + bool derived, uint flags) +{ + DML_prelocking_strategy prelocking_strategy; + + return open_and_lock_tables_derived(thd, tables, derived, flags, + &prelocking_strategy); +} /* simple open_and_lock_tables without derived handling */ inline int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables) { diff --git a/sql/sp.cc b/sql/sp.cc index 68c8ad395a1..19fe00594bd 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -17,7 +17,6 @@ #include "sp.h" #include "sp_head.h" #include "sp_cache.h" -#include "sql_trigger.h" #include @@ -1388,32 +1387,6 @@ sp_routine_exists_in_table(THD *thd, int type, sp_name *name) } -/** - Structure that represents element in the set of stored routines - used by statement or routine. -*/ -struct Sroutine_hash_entry; - -struct Sroutine_hash_entry -{ - /** - Set key consisting of one-byte routine type and quoted routine name. - */ - LEX_STRING key; - /** - Next element in list linking all routines in set. See also comments - for LEX::sroutine/sroutine_list and sp_head::m_sroutines. - */ - Sroutine_hash_entry *next; - /** - Uppermost view which directly or indirectly uses this routine. - 0 if routine is not used in view. Note that it also can be 0 if - statement uses routine both via view and directly. - */ - TABLE_LIST *belong_to_view; -}; - - extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, my_bool first) { @@ -1423,52 +1396,17 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, } -/** - Check if - - current statement (the one in thd->lex) needs table prelocking - - first routine in thd->lex->sroutines_list needs to execute its body in - prelocked mode. - - @param thd Current thread, thd->lex is the statement to be - checked. - @param[out] need_prelocking TRUE - prelocked mode should be activated - before executing the statement; - FALSE - Don't activate prelocking - @param[out] first_no_prelocking TRUE - Tables used by first routine in - thd->lex->sroutines_list should be - prelocked. FALSE - Otherwise. - - @note - This function assumes that for any "CALL proc(...)" statement routines_list - will have 'proc' as first element (it may have several, consider e.g. - "proc(sp_func(...)))". This property is currently guaranted by the parser. -*/ - -void sp_get_prelocking_info(THD *thd, bool *need_prelocking, - bool *first_no_prelocking) -{ - Sroutine_hash_entry *routine; - routine= (Sroutine_hash_entry*)thd->lex->sroutines_list.first; - - DBUG_ASSERT(routine); - bool first_is_procedure= (routine->key.str[0] == TYPE_ENUM_PROCEDURE); - - *first_no_prelocking= first_is_procedure; - *need_prelocking= !first_is_procedure || test(routine->next); -} - - /** Auxilary function that adds new element to the set of stored routines used by statement. In case when statement uses stored routines but does not need prelocking (i.e. it does not use any tables) we will access the - elements of LEX::sroutines set on prepared statement re-execution. - Because of this we have to allocate memory for both hash element - and copy of its key in persistent arena. + elements of Query_tables_list::sroutines set on prepared statement + re-execution. Because of this we have to allocate memory for both + hash element and copy of its key in persistent arena. - @param lex LEX representing statement + @param prelocking_ctx Prelocking context of the statement @param arena Arena in which memory for new element will be allocated @param key Key for the hash representing set @@ -1476,7 +1414,7 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking, (0 if routine is not used by view) @note - Will also add element to end of 'LEX::sroutines_list' list. + Will also add element to end of 'Query_tables_list::sroutines_list' list. @todo When we will got rid of these accesses on re-executions we will be @@ -1491,15 +1429,15 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking, the set). */ -static bool add_used_routine(LEX *lex, Query_arena *arena, - const LEX_STRING *key, - TABLE_LIST *belong_to_view) +bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, + const LEX_STRING *key, TABLE_LIST *belong_to_view) { - my_hash_init_opt(&lex->sroutines, system_charset_info, + my_hash_init_opt(&prelocking_ctx->sroutines, system_charset_info, Query_tables_list::START_SROUTINES_HASH_SIZE, 0, 0, sp_sroutine_key, 0, 0); - if (!my_hash_search(&lex->sroutines, (uchar *)key->str, key->length)) + if (!my_hash_search(&prelocking_ctx->sroutines, (uchar *)key->str, + key->length)) { Sroutine_hash_entry *rn= (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry) + @@ -1509,8 +1447,8 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, rn->key.length= key->length; rn->key.str= (char *)rn + sizeof(Sroutine_hash_entry); memcpy(rn->key.str, key->str, key->length + 1); - my_hash_insert(&lex->sroutines, (uchar *)rn); - lex->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next); + my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn); + prelocking_ctx->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next); rn->belong_to_view= belong_to_view; return TRUE; } @@ -1525,24 +1463,25 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, To be friendly towards prepared statements one should pass persistent arena as second argument. - @param lex LEX representing statement - @param arena arena in which memory for new element of the set - will be allocated - @param rt routine name - @param rt_type routine type (one of TYPE_ENUM_PROCEDURE/...) + @param prelocking_ctx Prelocking context of the statement + @param arena Arena in which memory for new element of the set + will be allocated + @param rt Routine name + @param rt_type Routine type (one of TYPE_ENUM_PROCEDURE/...) @note - Will also add element to end of 'LEX::sroutines_list' list (and will - take into account that this is explicitly used routine). + Will also add element to end of 'Query_tables_list::sroutines_list' list + (and will take into account that this is an explicitly used routine). */ -void sp_add_used_routine(LEX *lex, Query_arena *arena, +void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, sp_name *rt, char rt_type) { rt->set_routine_type(rt_type); - (void)add_used_routine(lex, arena, &rt->m_sroutines_key, 0); - lex->sroutines_list_own_last= lex->sroutines_list.next; - lex->sroutines_list_own_elements= lex->sroutines_list.elements; + (void)sp_add_used_routine(prelocking_ctx, arena, &rt->m_sroutines_key, 0); + prelocking_ctx->sroutines_list_own_last= prelocking_ctx->sroutines_list.next; + prelocking_ctx->sroutines_list_own_elements= + prelocking_ctx->sroutines_list.elements; } @@ -1550,13 +1489,14 @@ void sp_add_used_routine(LEX *lex, Query_arena *arena, Remove routines which are only indirectly used by statement from the set of routines used by this statement. - @param lex LEX representing statement + @param prelocking_ctx Prelocking context of the statement */ -void sp_remove_not_own_routines(LEX *lex) +void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx) { Sroutine_hash_entry *not_own_rt, *next_rt; - for (not_own_rt= *(Sroutine_hash_entry **)lex->sroutines_list_own_last; + for (not_own_rt= + *(Sroutine_hash_entry **)prelocking_ctx->sroutines_list_own_last; not_own_rt; not_own_rt= next_rt) { /* @@ -1564,12 +1504,13 @@ void sp_remove_not_own_routines(LEX *lex) but we want to be more future-proof. */ next_rt= not_own_rt->next; - my_hash_delete(&lex->sroutines, (uchar *)not_own_rt); + my_hash_delete(&prelocking_ctx->sroutines, (uchar *)not_own_rt); } - *(Sroutine_hash_entry **)lex->sroutines_list_own_last= NULL; - lex->sroutines_list.next= lex->sroutines_list_own_last; - lex->sroutines_list.elements= lex->sroutines_list_own_elements; + *(Sroutine_hash_entry **)prelocking_ctx->sroutines_list_own_last= NULL; + prelocking_ctx->sroutines_list.next= prelocking_ctx->sroutines_list_own_last; + prelocking_ctx->sroutines_list.elements= + prelocking_ctx->sroutines_list_own_elements; } @@ -1605,23 +1546,24 @@ void sp_update_sp_used_routines(HASH *dst, HASH *src) routines used by statement. @param thd Thread context - @param lex LEX representing statement + @param prelocking_ctx Prelocking context of the statement @param src Hash representing set from which routines will be added @param belong_to_view Uppermost view which uses these routines, 0 if none - @note - It will also add elements to end of 'LEX::sroutines_list' list. + @note It will also add elements to end of + 'Query_tables_list::sroutines_list' list. */ -static void -sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src, - TABLE_LIST *belong_to_view) +void +sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, + HASH *src, TABLE_LIST *belong_to_view) { for (uint i=0 ; i < src->records ; i++) { Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i); - (void)add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view); + (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &rt->key, + belong_to_view); } } @@ -1631,254 +1573,104 @@ sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src, routines used by statement. @param thd Thread context - @param lex LEX representing statement + @param prelocking_ctx Prelocking context of the statement @param src List representing set from which routines will be added @param belong_to_view Uppermost view which uses these routines, 0 if none - @note - It will also add elements to end of 'LEX::sroutines_list' list. + @note It will also add elements to end of + 'Query_tables_list::sroutines_list' list. */ -static void sp_update_stmt_used_routines(THD *thd, LEX *lex, SQL_LIST *src, - TABLE_LIST *belong_to_view) +void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, + SQL_LIST *src, TABLE_LIST *belong_to_view) { for (Sroutine_hash_entry *rt= (Sroutine_hash_entry *)src->first; rt; rt= rt->next) - (void)add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view); + (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &rt->key, + belong_to_view); } /** - Cache sub-set of routines used by statement, add tables used by these - routines to statement table list. Do the same for all routines used - by these routines. + Ensure that routine is present in cache by loading it from the mysql.proc + table if needed. Emit an appropriate error if there was a problem during + loading. - @param thd thread context - @param lex LEX representing statement - @param start first routine from the list of routines to be cached - (this list defines mentioned sub-set). - @param first_no_prelock If true, don't add tables or cache routines used by - the body of the first routine (i.e. *start) - will be executed in non-prelocked mode. - @param tabs_changed Set to TRUE some tables were added, FALSE otherwise + @param[in] thd Thread context. + @param[in] type Type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE). + @param[in] name Name of routine. + @param[out] sp Pointer to sp_head object for routine, NULL if routine was + not found, - @note - If some function is missing this won't be reported here. - Instead this fact will be discovered during query execution. - - @retval - 0 success - @retval - non-0 failure + @retval 0 Either routine is found and was succesfully loaded into cache + or it does not exist. + @retval non-0 Error while loading routine from mysql,proc table. */ -static int -sp_cache_routines_and_add_tables_aux(THD *thd, LEX *lex, - Sroutine_hash_entry *start, - bool first_no_prelock) +int sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp) { int ret= 0; - bool first= TRUE; - DBUG_ENTER("sp_cache_routines_and_add_tables_aux"); - for (Sroutine_hash_entry *rt= start; rt; rt= rt->next) - { - sp_name name(thd, rt->key.str, rt->key.length); - int type= rt->key.str[0]; - sp_head *sp; + DBUG_ENTER("sp_cache_routine"); - /* - Triggers can't happen here: their bodies are always processed - in sp_cache_routines_and_add_tables_for_triggers(). - */ - DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE); + DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE); - if (!(sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ? + if (!(*sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ? &thd->sp_func_cache : &thd->sp_proc_cache), - &name))) + name))) + { + switch ((ret= db_find_routine(thd, type, name, sp))) { - switch ((ret= db_find_routine(thd, type, &name, &sp))) + case SP_OK: + if (type == TYPE_ENUM_FUNCTION) + sp_cache_insert(&thd->sp_func_cache, *sp); + else + sp_cache_insert(&thd->sp_proc_cache, *sp); + break; + case SP_KEY_NOT_FOUND: + ret= SP_OK; + break; + default: + /* Query might have been killed, don't set error. */ + if (thd->killed) + break; + /* + Any error when loading an existing routine is either some problem + with the mysql.proc table, or a parse error because the contents + has been tampered with (in which case we clear that error). + */ + if (ret == SP_PARSE_ERROR) + thd->clear_error(); + /* + If we cleared the parse error, or when db_find_routine() flagged + an error with it's return value without calling my_error(), we + set the generic "mysql.proc table corrupt" error here. + */ + if (! thd->is_error()) { - case SP_OK: - { - if (type == TYPE_ENUM_FUNCTION) - sp_cache_insert(&thd->sp_func_cache, sp); - else - sp_cache_insert(&thd->sp_proc_cache, sp); - } - break; - case SP_KEY_NOT_FOUND: - ret= SP_OK; - break; - default: - /* Query might have been killed, don't set error. */ - if (thd->killed) - break; /* - Any error when loading an existing routine is either some problem - with the mysql.proc table, or a parse error because the contents - has been tampered with (in which case we clear that error). + SP allows full NAME_LEN chars thus he have to allocate enough + size in bytes. Otherwise there is stack overrun could happen + if multibyte sequence is `name`. `db` is still safe because the + rest of the server checks agains NAME_LEN bytes and not chars. + Hence, the overrun happens only if the name is in length > 32 and + uses multibyte (cyrillic, greek, etc.) */ - if (ret == SP_PARSE_ERROR) - thd->clear_error(); - /* - If we cleared the parse error, or when db_find_routine() flagged - an error with it's return value without calling my_error(), we - set the generic "mysql.proc table corrupt" error here. - */ - if (! thd->is_error()) - { - /* - SP allows full NAME_LEN chars thus he have to allocate enough - size in bytes. Otherwise there is stack overrun could happen - if multibyte sequence is `name`. `db` is still safe because the - rest of the server checks agains NAME_LEN bytes and not chars. - Hence, the overrun happens only if the name is in length > 32 and - uses multibyte (cyrillic, greek, etc.) - */ - char n[NAME_LEN*2+2]; + char n[NAME_LEN*2+2]; - /* m_qname.str is not always \0 terminated */ - memcpy(n, name.m_qname.str, name.m_qname.length); - n[name.m_qname.length]= '\0'; - my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret); - } - break; + /* m_qname.str is not always \0 terminated */ + memcpy(n, name->m_qname.str, name->m_qname.length); + n[name->m_qname.length]= '\0'; + my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret); } + break; } - if (sp) - { - if (!(first && first_no_prelock)) - { - sp_update_stmt_used_routines(thd, lex, &sp->m_sroutines, - rt->belong_to_view); - (void)sp->add_used_tables_to_table_list(thd, &lex->query_tables_last, - rt->belong_to_view); - } - sp->propagate_attributes(lex); - } - first= FALSE; } DBUG_RETURN(ret); } -/** - Cache all routines from the set of used by statement, add tables used - by those routines to statement table list. Do the same for all routines - used by those routines. - - @param thd thread context - @param lex LEX representing statement - @param first_no_prelock If true, don't add tables or cache routines used by - the body of the first routine (i.e. *start) - - @retval - 0 success - @retval - non-0 failure -*/ - -int -sp_cache_routines_and_add_tables(THD *thd, LEX *lex, bool first_no_prelock) -{ - return sp_cache_routines_and_add_tables_aux(thd, lex, - (Sroutine_hash_entry *)lex->sroutines_list.first, - first_no_prelock); -} - - -/** - Add all routines used by view to the set of routines used by - statement. - - Add tables used by those routines to statement table list. Do the same - for all routines used by these routines. - - @param thd Thread context - @param lex LEX representing statement - @param view Table list element representing view - - @retval - 0 success - @retval - non-0 failure -*/ - -int -sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex, TABLE_LIST *view) -{ - Sroutine_hash_entry **last_cached_routine_ptr= - (Sroutine_hash_entry **)lex->sroutines_list.next; - sp_update_stmt_used_routines(thd, lex, &view->view->sroutines_list, - view->top_table()); - return sp_cache_routines_and_add_tables_aux(thd, lex, - *last_cached_routine_ptr, FALSE); -} - - -/** - Add triggers for table to the set of routines used by statement. - Add tables used by them to statement table list. Do the same for - all implicitly used routines. - - @param thd thread context - @param lex LEX respresenting statement - @param table Table list element for table with trigger - - @retval - 0 success - @retval - non-0 failure -*/ - -int -sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex, - TABLE_LIST *table) -{ - if (static_cast(table->lock_type) >= - static_cast(TL_WRITE_ALLOW_WRITE)) - { - for (int i= 0; i < (int)TRG_EVENT_MAX; i++) - { - if (table->trg_event_map & - static_cast(1 << static_cast(i))) - { - for (int j= 0; j < (int)TRG_ACTION_MAX; j++) - { - /* We can have only one trigger per action type currently */ - sp_head *trigger= table->table->triggers->bodies[i][j]; - if (trigger && - add_used_routine(lex, thd->stmt_arena, &trigger->m_sroutines_key, - table->belong_to_view)) - { - int ret; - /* Sic: excludes the trigger key from processing */ - Sroutine_hash_entry **last_cached_routine_ptr= - (Sroutine_hash_entry **)lex->sroutines_list.next; - - trigger->add_used_tables_to_table_list(thd, &lex->query_tables_last, - table->belong_to_view); - trigger->propagate_attributes(lex); - sp_update_stmt_used_routines(thd, lex, - &trigger->m_sroutines, - table->belong_to_view); - - ret= sp_cache_routines_and_add_tables_aux(thd, lex, - *last_cached_routine_ptr, - FALSE); - if (ret) - return ret; - } - } - } - } - } - return 0; -} - - /** Generates the CREATE... string from the table information. diff --git a/sql/sp.h b/sql/sp.h index 5a190c5480e..cf8f72d2cef 100644 --- a/sql/sp.h +++ b/sql/sp.h @@ -42,6 +42,9 @@ sp_head * sp_find_routine(THD *thd, int type, sp_name *name, sp_cache **cp, bool cache_only); +int +sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp); + bool sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any); @@ -60,22 +63,45 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics); int sp_drop_routine(THD *thd, int type, sp_name *name); -/* - Procedures for pre-caching of stored routines and building table list - for prelocking. + +/** + Structure that represents element in the set of stored routines + used by statement or routine. */ -void sp_get_prelocking_info(THD *thd, bool *need_prelocking, - bool *first_no_prelocking); -void sp_add_used_routine(LEX *lex, Query_arena *arena, + +struct Sroutine_hash_entry +{ + /** + Set key consisting of one-byte routine type and quoted routine name. + */ + LEX_STRING key; + /** + Next element in list linking all routines in set. See also comments + for LEX::sroutine/sroutine_list and sp_head::m_sroutines. + */ + Sroutine_hash_entry *next; + /** + Uppermost view which directly or indirectly uses this routine. + 0 if routine is not used in view. Note that it also can be 0 if + statement uses routine both via view and directly. + */ + TABLE_LIST *belong_to_view; +}; + + +/* + Procedures for handling sets of stored routines used by statement or routine. +*/ +void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, sp_name *rt, char rt_type); -void sp_remove_not_own_routines(LEX *lex); +bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, + const LEX_STRING *key, TABLE_LIST *belong_to_view); +void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx); void sp_update_sp_used_routines(HASH *dst, HASH *src); -int sp_cache_routines_and_add_tables(THD *thd, LEX *lex, - bool first_no_prelock); -int sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex, - TABLE_LIST *view); -int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex, - TABLE_LIST *table); +void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, + HASH *src, TABLE_LIST *belong_to_view); +void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, + SQL_LIST *src, TABLE_LIST *belong_to_view); extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, my_bool first); diff --git a/sql/sp_head.h b/sql/sp_head.h index dd11f8693ac..5610ecd2a72 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -454,10 +454,10 @@ public: /* This method is intended for attributes of a routine which need - to propagate upwards to the LEX of the caller (when a property of a - sp_head needs to "taint" the caller). + to propagate upwards to the Query_tables_list of the caller (when + a property of a sp_head needs to "taint" the calling statement). */ - void propagate_attributes(LEX *lex) + void propagate_attributes(Query_tables_list *prelocking_ctx) { /* If this routine needs row-based binary logging, the entire top statement @@ -466,7 +466,7 @@ public: the substatements not). */ if (m_flags & BINLOG_ROW_BASED_IF_MIXED) - lex->set_stmt_unsafe(); + prelocking_ctx->set_stmt_unsafe(); } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 7650d854efb..4be80f4c8a5 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3714,34 +3714,99 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) /* + Perform steps of prelocking algorithm for elements of the + prelocking set other than tables. E.g. cache routines and, if + prelocking strategy prescribes so, extend the prelocking set with + tables and routines used by them. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context. + @param[in] start First element in the list representing + subset of the prelocking set to be + processed. + @param[in] prelocking_strategy Strategy which specifies how the + prelocking set should be extended when + one of its elements is processed. + @param[out] need_prelocking Set to TRUE if it was detected that this + statement will require prelocked mode for + its execution, not touched otherwise. + + @retval FALSE Success. + @retval TRUE Failure (Conflicting metadata lock, OOM, other errors). +*/ + +static bool +open_routines(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *start, + Prelocking_strategy *prelocking_strategy, + bool *need_prelocking) +{ + DBUG_ENTER("open_routines"); + + for (Sroutine_hash_entry *rt= start; rt; rt= rt->next) + { + int type= rt->key.str[0]; + + switch (type) + { + case TYPE_ENUM_FUNCTION: + case TYPE_ENUM_PROCEDURE: + { + sp_name name(thd, rt->key.str, rt->key.length); + sp_head *sp; + + if (sp_cache_routine(thd, type, &name, &sp)) + DBUG_RETURN(TRUE); + + if (sp) + { + prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp, + need_prelocking); + } + } + break; + case TYPE_ENUM_TRIGGER: + break; + default: + /* Impossible type value. */ + DBUG_ASSERT(0); + } + } + DBUG_RETURN(FALSE); +} + + +/** Open all tables in list - SYNOPSIS - open_tables() - thd - thread handler - start - list of tables in/out - counter - number of opened tables will be return using this parameter - flags - bitmap of flags to modify how the tables will be open: - MYSQL_LOCK_IGNORE_FLUSH - open table even if someone has - done a flush or namelock on it. + @param[in] thd Thread context. + @param[in,out] start List of tables to be open (it can be adjusted for + statement that uses tables only implicitly, e.g. + for "SELECT f1()"). + @param[out] counter Number of tables which were open. + @param[in] flags Bitmap of flags to modify how the tables will be + open, see open_table() description for details. + @param[in] prelocking_strategy Strategy which specifies how prelocking + algorithm should work for this statement. - NOTE - Unless we are already in prelocked mode, this function will also precache - all SP/SFs explicitly or implicitly (via views and triggers) used by the - query and add tables needed for their execution to table list. If resulting - tables list will be non empty it will mark query as requiring precaching. + @note + Unless we are already in prelocked mode and prelocking strategy prescribes + so this function will also precache all SP/SFs explicitly or implicitly + (via views and triggers) used by the query and add tables needed for their + execution to table list. Statement that uses SFs, invokes triggers or + requires foreign key checks will be marked as requiring prelocking. Prelocked mode will be enabled for such query during lock_tables() call. If query for which we are opening tables is already marked as requiring prelocking it won't do such precaching and will simply reuse table list which is already built. - RETURN - 0 - OK - -1 - error + @retval 0 OK + @retval -1 Error. */ -int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) +int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, + Prelocking_strategy *prelocking_strategy) { TABLE_LIST *tables= NULL; Open_table_context ot_ctx(thd); @@ -3786,13 +3851,14 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) !thd->lex->requires_prelocking() && thd->lex->uses_stored_routines()) { - bool first_no_prelocking, need_prelocking; + bool need_prelocking= FALSE; TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; DBUG_ASSERT(thd->lex->query_tables == *start); - sp_get_prelocking_info(thd, &need_prelocking, &first_no_prelocking); - if (sp_cache_routines_and_add_tables(thd, thd->lex, first_no_prelocking)) + if (open_routines(thd, thd->lex, + (Sroutine_hash_entry *)thd->lex->sroutines_list.first, + prelocking_strategy, &need_prelocking)) { /* Serious error during reading stored routines from mysql.proc table. @@ -3973,27 +4039,54 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) /* If we are not already in prelocked mode and extended table list is not - yet built and we have trigger for table being opened then we should - cache all routines used by its triggers and add their tables to - prelocking list. - If we lock table for reading we won't update it so there is no need to - process its triggers since they never will be activated. + yet built we might have to build the prelocking set for this statement. + + Since currently no prelocking strategy prescribes doing anything for + tables which are only read, we do below checks only if table is going + to be changed. */ if (thd->locked_tables_mode <= LTM_LOCK_TABLES && !thd->lex->requires_prelocking() && - tables->trg_event_map && tables->table->triggers && tables->lock_type >= TL_WRITE_ALLOW_WRITE) { - if (!query_tables_last_own) - query_tables_last_own= thd->lex->query_tables_last; - if (sp_cache_routines_and_add_tables_for_triggers(thd, thd->lex, - tables)) + bool need_prelocking= FALSE; + bool not_used; + TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; + Sroutine_hash_entry **sroutines_next= + (Sroutine_hash_entry **)thd->lex->sroutines_list.next; + + /* + Extend statement's table list and the prelocking set with + tables and routines according to the current prelocking + strategy. + + For example, for DML statements we need to add tables and routines + used by triggers which are going to be invoked for this element of + table list and also add tables required for handling of foreign keys. + */ + error= prelocking_strategy->handle_table(thd, thd->lex, tables, + &need_prelocking); + + if (need_prelocking && ! query_tables_last_own) + query_tables_last_own= save_query_tables_last; + + if (error) + { + result= -1; + goto err; + } + + /* + Process elements of the prelocking set which were added + by the above invocation of Prelocking_strategy method. + + For example, if new element is a routine, cache it and then, if + prelocking strategy prescribes so, add tables it uses to the table + list and routines it might invoke to the prelocking set. + */ + if (open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy, + ¬_used)) { - /* - Serious error during reading stored routines from mysql.proc table. - Something's wrong with the table or its contents, and an error has - been emitted; we must abort. - */ result= -1; goto err; } @@ -4039,13 +4132,27 @@ process_view_routines: */ if (tables->view && thd->locked_tables_mode <= LTM_LOCK_TABLES && - !thd->lex->requires_prelocking() && - tables->view->uses_stored_routines()) + !thd->lex->requires_prelocking()) { - /* We have at least one table in TL here. */ - if (!query_tables_last_own) - query_tables_last_own= thd->lex->query_tables_last; - if (sp_cache_routines_and_add_tables_for_view(thd, thd->lex, tables)) + bool need_prelocking= FALSE; + bool not_used; + TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; + Sroutine_hash_entry **sroutines_next= + (Sroutine_hash_entry **)thd->lex->sroutines_list.next; + + error= prelocking_strategy->handle_view(thd, thd->lex, tables, + &need_prelocking); + + if (need_prelocking && ! query_tables_last_own) + query_tables_last_own= save_query_tables_last; + + if (error) + { + result= -1; + goto err; + } + if (open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy, + ¬_used)) { /* Serious error during reading stored routines from mysql.proc table. @@ -4097,6 +4204,220 @@ process_view_routines: } +/** + Defines how prelocking algorithm for DML statements should handle routines: + - For CALL statements we do unrolling (i.e. open and lock tables for each + sub-statement individually). So for such statements prelocking is enabled + only if stored functions are used in parameter list and only for period + during which we calculate values of parameters. Thus in this strategy we + ignore procedure which is directly called by such statement and extend + the prelocking set only with tables/functions used by SF called from the + parameter list. + - For any other statement any routine which is directly or indirectly called + by statement is going to be executed in prelocked mode. So in this case we + simply add all tables and routines used by it to the prelocking set. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] rt Prelocking set element describing routine. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool DML_prelocking_strategy:: +handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking) +{ + /* + We assume that for any "CALL proc(...)" statement sroutines_list will + have 'proc' as first element (it may have several, consider e.g. + "proc(sp_func(...)))". This property is currently guaranted by the + parser. + */ + + if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first || + rt->key.str[0] != TYPE_ENUM_PROCEDURE) + { + *need_prelocking= TRUE; + sp_update_stmt_used_routines(thd, prelocking_ctx, &sp->m_sroutines, + rt->belong_to_view); + (void)sp->add_used_tables_to_table_list(thd, + &prelocking_ctx->query_tables_last, + rt->belong_to_view); + } + sp->propagate_attributes(prelocking_ctx); + return FALSE; +} + + +/** + Defines how prelocking algorithm for DML statements should handle table list + elements: + - If table has triggers we should add all tables and routines + used by them to the prelocking set. + + We do not need to acquire metadata locks on trigger names + in DML statements, since all DDL statements + that change trigger metadata always lock their + subject tables. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] table_list Table list element for table. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool DML_prelocking_strategy:: +handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + /* We rely on a caller to check that table is going to be changed. */ + DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE); + + if (table_list->trg_event_map) + { + if (table_list->table->triggers) + { + *need_prelocking= TRUE; + + if (table_list->table->triggers-> + add_tables_and_routines_for_triggers(thd, prelocking_ctx, table_list)) + return TRUE; + } + } + + return FALSE; +} + + +/** + Defines how prelocking algorithm for DML statements should handle view - + all view routines should be added to the prelocking set. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] table_list Table list element for view. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool DML_prelocking_strategy:: +handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + if (table_list->view->uses_stored_routines()) + { + *need_prelocking= TRUE; + + sp_update_stmt_used_routines(thd, prelocking_ctx, + &table_list->view->sroutines_list, + table_list->top_table()); + } + return FALSE; +} + + +/** + Defines how prelocking algorithm for LOCK TABLES statement should handle + table list elements. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] table_list Table list element for table. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool Lock_tables_prelocking_strategy:: +handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + if (DML_prelocking_strategy::handle_table(thd, prelocking_ctx, table_list, + need_prelocking)) + return TRUE; + + /* We rely on a caller to check that table is going to be changed. */ + DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE); + + return FALSE; +} + + +/** + Defines how prelocking algorithm for ALTER TABLE statement should handle + routines - do nothing as this statement is not supposed to call routines. + + We still can end up in this method when someone tries + to define a foreign key referencing a view, and not just + a simple view, but one that uses stored routines. +*/ + +bool Alter_table_prelocking_strategy:: +handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking) +{ + return FALSE; +} + + +/** + Defines how prelocking algorithm for ALTER TABLE statement should handle + table list elements. + + Unlike in DML, we do not process triggers here. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] table_list Table list element for table. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool Alter_table_prelocking_strategy:: +handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + return FALSE; +} + + +/** + Defines how prelocking algorithm for ALTER TABLE statement + should handle view - do nothing. We don't need to add view + routines to the prelocking set in this case as view is not going + to be materialized. +*/ + +bool Alter_table_prelocking_strategy:: +handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + return FALSE; +} + + /* Check that lock is ok for tables; Call start stmt if ok @@ -4312,34 +4633,35 @@ end: } -/* +/** Open all tables in list, locks them and optionally process derived tables. - SYNOPSIS - open_and_lock_tables_derived() - thd - thread handler - tables - list of tables for open&locking - flags - set of options to be used to open and lock tables (see - open_tables() and mysql_lock_tables() for details). - derived - if to handle derived tables + @param thd Thread context. + @param tables List of tables for open and locking. + @param derived If to handle derived tables. + @param flags Bitmap of options to be used to open and lock + tables (see open_tables() and mysql_lock_tables() + for details). + @param prelocking_strategy Strategy which specifies how prelocking algorithm + should work for this statement. - RETURN - FALSE - ok - TRUE - error - - NOTE + @note The lock will automaticaly be freed by close_thread_tables() - NOTE - There are two convenience functions: + @note + There are several convenience functions, e.g. : - simple_open_n_lock_tables(thd, tables) without derived handling - open_and_lock_tables(thd, tables) with derived handling Both inline functions call open_and_lock_tables_derived() with the third argument set appropriately. + + @retval FALSE OK. + @retval TRUE Error */ -int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, - uint flags) +int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, + bool derived, uint flags, + Prelocking_strategy *prelocking_strategy) { uint counter; bool need_reopen; @@ -4349,7 +4671,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, for ( ; ; ) { - if (open_tables(thd, &tables, &counter, flags)) + if (open_tables(thd, &tables, &counter, flags, prelocking_strategy)) DBUG_RETURN(-1); DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", { diff --git a/sql/sql_class.h b/sql/sql_class.h index 9edb5f97713..67fa6306283 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -38,6 +38,7 @@ class sp_rcontext; class sp_cache; class Parser_state; class Rows_log_event; +class Sroutine_hash_entry; enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE }; enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY, RNEXT_SAME }; @@ -1181,6 +1182,89 @@ private: }; +/** + An abstract class for a strategy specifying how the prelocking + algorithm should extend the prelocking set while processing + already existing elements in the set. +*/ + +class Prelocking_strategy +{ +public: + virtual ~Prelocking_strategy() { } + + virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, + bool *need_prelocking) = 0; + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) = 0; + virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking)= 0; +}; + + +/** + A Strategy for prelocking algorithm suitable for DML statements. + + Ensures that all tables used by all statement's SF/SP/triggers and + required for foreign key checks are prelocked and SF/SPs used are + cached. +*/ + +class DML_prelocking_strategy : public Prelocking_strategy +{ +public: + virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, + bool *need_prelocking); + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); + virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); +}; + + +/** + A strategy for prelocking algorithm to be used for LOCK TABLES + statement. +*/ + +class Lock_tables_prelocking_strategy : public DML_prelocking_strategy +{ + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); +}; + + +/** + Strategy for prelocking algorithm to be used for ALTER TABLE statements. + + Unlike DML or LOCK TABLES strategy, it doesn't + prelock triggers, views or stored routines, since they are not + used during ALTER. +*/ + +class Alter_table_prelocking_strategy : public Prelocking_strategy +{ +public: + + Alter_table_prelocking_strategy(Alter_info *alter_info) + : m_alter_info(alter_info) + {} + + virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, + bool *need_prelocking); + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); + virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); + +private: + Alter_info *m_alter_info; +}; + + /** A context of open_tables() function, used to recover from a failed open_table() attempt. diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 6b0c9699032..c10e0fc20fe 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1052,6 +1052,17 @@ public: } } + /** Return a pointer to the last element in query table list. */ + TABLE_LIST *last_table() + { + /* Don't use offsetof() macro in order to avoid warnings. */ + return query_tables ? + (TABLE_LIST*) ((char*) query_tables_last - + ((char*) &(query_tables->next_global) - + (char*) query_tables)) : + 0; + } + /** Has the parser/scanner detected that this statement is unsafe? */ @@ -1751,6 +1762,12 @@ struct LEX: public Query_tables_list bool subqueries, ignore; st_parsing_options parsing_options; Alter_info alter_info; + /* + For CREATE TABLE statement last element of table list which is not + part of SELECT or LIKE part (i.e. either element for table we are + creating or last of tables referenced by foreign keys). + */ + TABLE_LIST *create_last_non_select_table; /* Prepared statements SQL syntax:*/ LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */ /* diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 35c0973d103..2f1753bcdaa 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3342,9 +3342,14 @@ end_with_restore_list: thd->options|= OPTION_TABLE_LOCK; thd->in_lock_tables=1; - res= (open_and_lock_tables_derived(thd, all_tables, FALSE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || - thd->locked_tables_list.init_locked_tables(thd)); + { + Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; + + res= (open_and_lock_tables_derived(thd, all_tables, FALSE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL, + &lock_tables_prelocking_strategy) || + thd->locked_tables_list.init_locked_tables(thd)); + } thd->in_lock_tables= 0; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index c389ef2aef3..532cb1465c6 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -6560,9 +6560,26 @@ view_err: DBUG_RETURN(error); } - if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL))) + + /* + Code below can handle only base tables so ensure that we won't open a view. + Note that RENAME TABLE the only ALTER clause which is supported for views + has been already processed. + */ + table_list->required_type= FRMTYPE_TABLE; + + Alter_table_prelocking_strategy alter_prelocking_strategy(alter_info); + + error= open_and_lock_tables_derived(thd, table_list, FALSE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL, + &alter_prelocking_strategy); + + if (error) + { DBUG_RETURN(TRUE); + } + + table= table_list->table; table->use_all_columns(); mdl_ticket= table->mdl_ticket; @@ -6572,7 +6589,8 @@ view_err: set of tables from the old table or to open a new TABLE object for an extended list and verify that they belong to locked tables. */ - if (thd->locked_tables_mode && + if ((thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) && (create_info->used_fields & HA_CREATE_USED_UNION) && (table->s->tmp_table == NO_TMP_TABLE)) { @@ -6806,7 +6824,8 @@ view_err: table_list->table= NULL; // For query cache query_cache_invalidate3(thd, table_list, 0); - if (thd->locked_tables_mode) + if ((thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) { /* Under LOCK TABLES we should adjust meta-data locks before finishing @@ -7290,7 +7309,9 @@ view_err: if (table->s->tmp_table != NO_TMP_TABLE) { /* Close lock if this is a transactional table */ - if (thd->lock && ! thd->locked_tables_mode) + if (thd->lock && + ! (thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) { mysql_unlock_tables(thd, thd->lock); thd->lock=0; @@ -7492,7 +7513,8 @@ view_err: table_list->table=0; // For query cache query_cache_invalidate3(thd, table_list, 0); - if (thd->locked_tables_mode) + if (thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) { if ((new_name != table_name || new_db != db)) { diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 93b89b869d6..b8a4edaa963 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -19,6 +19,7 @@ #include "sp_head.h" #include "sql_trigger.h" #include "parse_file.h" +#include "sp.h" /*************************************************************************/ @@ -2017,6 +2018,57 @@ bool Table_triggers_list::process_triggers(THD *thd, } +/** + Add triggers for table to the set of routines used by statement. + Add tables used by them to statement table list. Do the same for + routines used by triggers. + + @param thd Thread context. + @param prelocking_ctx Prelocking context of the statement. + @param table_list Table list element for table with trigger. + + @retval FALSE Success. + @retval TRUE Failure. +*/ + +bool +Table_triggers_list:: +add_tables_and_routines_for_triggers(THD *thd, + Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list) +{ + DBUG_ASSERT(static_cast(table_list->lock_type) >= + static_cast(TL_WRITE_ALLOW_WRITE)); + + for (int i= 0; i < (int)TRG_EVENT_MAX; i++) + { + if (table_list->trg_event_map & + static_cast(1 << static_cast(i))) + { + for (int j= 0; j < (int)TRG_ACTION_MAX; j++) + { + /* We can have only one trigger per action type currently */ + sp_head *trigger= table_list->table->triggers->bodies[i][j]; + + if (trigger && sp_add_used_routine(prelocking_ctx, thd->stmt_arena, + &trigger->m_sroutines_key, + table_list->belong_to_view)) + { + trigger->add_used_tables_to_table_list(thd, + &prelocking_ctx->query_tables_last, + table_list->belong_to_view); + sp_update_stmt_used_routines(thd, prelocking_ctx, + &trigger->m_sroutines, + table_list->belong_to_view); + trigger->propagate_attributes(prelocking_ctx); + } + } + } + } + return FALSE; +} + + /** Mark fields of subject table which we read/set in its triggers as such. diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h index b411acf2ac5..85b2dbe5f21 100644 --- a/sql/sql_trigger.h +++ b/sql/sql_trigger.h @@ -144,8 +144,10 @@ public: void mark_fields_used(trg_event_type event); friend class Item_trigger_field; - friend int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex, - TABLE_LIST *table); + + bool add_tables_and_routines_for_triggers(THD *thd, + Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list); private: bool prepare_record1_accessors(TABLE *table); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 24800f805d0..577c60b4c10 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1766,6 +1766,7 @@ create: lex->create_info.default_table_charset= NULL; lex->name.str= 0; lex->name.length= 0; + lex->create_last_non_select_table= lex->last_table(); } create2 { @@ -1788,7 +1789,8 @@ create: lex->sql_command= SQLCOM_CREATE_INDEX; if (!lex->current_select->add_table_to_list(lex->thd, $7, NULL, - TL_OPTION_UPDATING)) + TL_OPTION_UPDATING, + TL_WRITE_ALLOW_READ)) MYSQL_YYABORT; lex->alter_info.reset(); lex->alter_info.flags= ALTER_ADD_INDEX; @@ -3952,7 +3954,7 @@ create2: ; create2a: - field_list ')' opt_create_table_options + create_field_list ')' opt_create_table_options opt_partitioning create3 {} | opt_partitioning @@ -4802,19 +4804,30 @@ create_table_option: Lex->create_info.row_type= $3; Lex->create_info.used_fields|= HA_CREATE_USED_ROW_FORMAT; } - | UNION_SYM opt_equal '(' opt_table_list ')' + | UNION_SYM opt_equal { - /* Move the union list to the merge_list */ + Lex->select_lex.table_list.save_and_clear(&Lex->save_list); + } + '(' opt_table_list ')' + { + /* + Move the union list to the merge_list and exclude its tables + from the global list. + */ LEX *lex=Lex; - TABLE_LIST *table_list= lex->select_lex.get_table_list(); lex->create_info.merge_list= lex->select_lex.table_list; - lex->create_info.merge_list.elements--; - lex->create_info.merge_list.first= - (uchar*) (table_list->next_local); - lex->select_lex.table_list.elements=1; - lex->select_lex.table_list.next= - (uchar**) &(table_list->next_local); - table_list->next_local= 0; + lex->select_lex.table_list= lex->save_list; + /* + When excluding union list from the global list we assume that + elements of the former immediately follow elements which represent + table being created/altered and parent tables. + */ + TABLE_LIST *last_non_sel_table= lex->create_last_non_select_table; + DBUG_ASSERT(last_non_sel_table->next_global == + (TABLE_LIST *)lex->create_info.merge_list.first); + last_non_sel_table->next_global= 0; + Lex->query_tables_last= &last_non_sel_table->next_global; + lex->create_info.used_fields|= HA_CREATE_USED_UNION; } | default_charset @@ -4952,6 +4965,14 @@ udf_type: | INT_SYM {$$ = (int) INT_RESULT; } ; + +create_field_list: + field_list + { + Lex->create_last_non_select_table= Lex->last_table(); + } + ; + field_list: field_list_item | field_list ',' field_list_item @@ -5743,7 +5764,8 @@ alter: lex->sql_command= SQLCOM_ALTER_TABLE; lex->duplicates= DUP_ERROR; if (!lex->select_lex.add_table_to_list(thd, $4, NULL, - TL_OPTION_UPDATING)) + TL_OPTION_UPDATING, + TL_WRITE_ALLOW_READ)) MYSQL_YYABORT; lex->col_list.empty(); lex->select_lex.init_order(); @@ -5756,6 +5778,7 @@ alter: lex->alter_info.reset(); lex->no_write_to_binlog= 0; lex->create_info.storage_media= HA_SM_DEFAULT; + lex->create_last_non_select_table= lex->last_table(); } alter_commands {} @@ -6139,12 +6162,16 @@ add_column: ; alter_list_item: - add_column column_def opt_place { } + add_column column_def opt_place + { + Lex->create_last_non_select_table= Lex->last_table(); + } | ADD key_def { + Lex->create_last_non_select_table= Lex->last_table(); Lex->alter_info.flags|= ALTER_ADD_INDEX; } - | add_column '(' field_list ')' + | add_column '(' create_field_list ')' { Lex->alter_info.flags|= ALTER_ADD_COLUMN | ALTER_ADD_INDEX; } @@ -6155,6 +6182,9 @@ alter_list_item: lex->alter_info.flags|= ALTER_CHANGE_COLUMN; } field_spec opt_place + { + Lex->create_last_non_select_table= Lex->last_table(); + } | MODIFY_SYM opt_column field_ident { LEX *lex=Lex; @@ -6177,6 +6207,9 @@ alter_list_item: MYSQL_YYABORT; } opt_place + { + Lex->create_last_non_select_table= Lex->last_table(); + } | DROP opt_column field_ident opt_restrict { LEX *lex=Lex; @@ -9607,7 +9640,8 @@ drop: lex->alter_info.flags= ALTER_DROP_INDEX; lex->alter_info.drop_list.push_back(ad); if (!lex->current_select->add_table_to_list(lex->thd, $5, NULL, - TL_OPTION_UPDATING)) + TL_OPTION_UPDATING, + TL_WRITE_ALLOW_READ)) MYSQL_YYABORT; } | DROP DATABASE if_exists ident From c0b78cc47a2da2710cdf580ddbb3b31f5f691a23 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Tue, 8 Dec 2009 15:38:58 +0100 Subject: [PATCH 081/212] Backport of revno: 2617.65.10 Bug #45067 Assertion `stmt_da->is_error()' in Delayed_insert::open_and_lock_table The assert was triggered when delayed insert was killed by another connection using mysql_notify_thread_having_shared_lock(). During handling of thd->killed, thd.fatal_error() was called without a previous call to my_error() which triggered the assert. This patch allows the assert to pass if thd->killed has been set. --- sql/sql_class.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_class.h b/sql/sql_class.h index 67fa6306283..f4504bfed0d 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2216,7 +2216,7 @@ public: */ inline void fatal_error() { - DBUG_ASSERT(main_da.is_error()); + DBUG_ASSERT(stmt_da->is_error() || killed); is_fatal_error= 1; DBUG_PRINT("error",("Fatal error set")); } From 19ff2445b156a11cbd76e7c741ed05e4eb33dc62 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Tue, 8 Dec 2009 15:56:06 +0100 Subject: [PATCH 082/212] Backport of revno: 2617.68.9 Bug #43272 HANDLER SQL command does not work under LOCK TABLES HANDLER commands are now explicitly disallowed in LOCK TABLES mode. Before, HANDLER OPEN gave the misleading error message: "Table x was not locked with LOCK TABLES". This patch changes HANDLER OPEN/READ/CLOSE to give ER_LOCK_OR_ACTIVE_TRANSACTION "Can't execute the given command because you have active locked tables or an active transaction" in LOCK TABLES mode. Test case added to lock.test. --- mysql-test/r/lock.result | 15 +++++++++++++++ mysql-test/t/lock.test | 24 ++++++++++++++++++++++++ sql/sql_handler.cc | 16 ++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index afb444f8ae9..a542d70c5b9 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -319,6 +319,21 @@ alter table t1 add column j int; unlock tables; drop table t1; # +# Bug #43272 HANDLER SQL command does not work under LOCK TABLES +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT); +LOCK TABLE t1 WRITE; +# HANDLER commands are not allowed in LOCK TABLES mode +HANDLER t1 OPEN; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +HANDLER t1 READ FIRST; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +HANDLER t1 CLOSE; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +UNLOCK TABLES; +DROP TABLE t1; +# # Bug#45066 FLUSH TABLES WITH READ LOCK deadlocks against # LOCK TABLE # diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index 4d610559077..64003c9d861 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -385,6 +385,30 @@ alter table t1 add column j int; unlock tables; drop table t1; + +--echo # +--echo # Bug #43272 HANDLER SQL command does not work under LOCK TABLES +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (a INT); +LOCK TABLE t1 WRITE; + +--echo # HANDLER commands are not allowed in LOCK TABLES mode +--error ER_LOCK_OR_ACTIVE_TRANSACTION +HANDLER t1 OPEN; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +HANDLER t1 READ FIRST; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +HANDLER t1 CLOSE; + +UNLOCK TABLES; +DROP TABLE t1; + + --echo # --echo # Bug#45066 FLUSH TABLES WITH READ LOCK deadlocks against --echo # LOCK TABLE diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 1b7e45cec5d..5bdf8611f5e 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -201,6 +201,11 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) tables->db, tables->table_name, tables->alias, (int) reopen)); + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } if (tables->schema_table) { my_error(ER_WRONG_USAGE, MYF(0), "HANDLER OPEN", @@ -386,6 +391,11 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables) DBUG_PRINT("enter",("'%s'.'%s' as '%s'", tables->db, tables->table_name, tables->alias)); + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } if ((hash_tables= (TABLE_LIST*) my_hash_search(&thd->handler_tables_hash, (uchar*) tables->alias, strlen(tables->alias) + 1))) @@ -448,6 +458,12 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, DBUG_PRINT("enter",("'%s'.'%s' as '%s'", tables->db, tables->table_name, tables->alias)); + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } + thd->lex->select_lex.context.resolve_in_table_list_only(tables); list.push_front(new Item_field(&thd->lex->select_lex.context, NULL, NULL, "*")); From 4235167fcf9de9b8398336c46c1e6f63a764907a Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 09:32:29 +0100 Subject: [PATCH 083/212] Backport of revno: 2599.169.2 Bug #42074 concurrent optimize table and alter table = Assertion failed: thd->is_error() This assertion could occur if OPTIMIZE TABLE was started on a InnoDB table and the table was altered to different storage engine after OPTIMIZE had started. This allowed OPTIMIZE to pass the initial checks for storage engine support, but fail once it reached "recreate+analyze" if this operation was not supported by the new storage engine. The bug had no consequences for non-debug builds of the server. In detail, the assertion was triggered when ha_analyze() returned HA_ADMIN_NOT_IMPLEMENTED. This led to a code path which included an assert checking for diagnostics area contents. Since this area had not been filled, the assertion was triggered. The diagnostics area is in this case only used to provide more detailed information about why optimize failed. The triggered code path sends this information to the client and clears the diagnostic area. This patch fixed the problem by adding an error message to the diagnostic area if ha_analyze() fails. This error message contains the error code returned by ha_analyze(). Test case added to innodb_mysql_sync.test. --- mysql-test/r/innodb_mysql_sync.result | 26 +++++++++++++++ mysql-test/t/innodb_mysql_sync.test | 48 +++++++++++++++++++++++++++ sql/sql_table.cc | 3 ++ 3 files changed, 77 insertions(+) create mode 100644 mysql-test/r/innodb_mysql_sync.result create mode 100644 mysql-test/t/innodb_mysql_sync.test diff --git a/mysql-test/r/innodb_mysql_sync.result b/mysql-test/r/innodb_mysql_sync.result new file mode 100644 index 00000000000..039d8b74c07 --- /dev/null +++ b/mysql-test/r/innodb_mysql_sync.result @@ -0,0 +1,26 @@ +# +# Bug 42074 concurrent optimize table and +# alter table = Assertion failed: thd->is_error() +# +DROP TABLE IF EXISTS t1; +# Create InnoDB table +CREATE TABLE t1 (id INT) engine=innodb; +# Connection 1 +# Start optimizing table +SET DEBUG_SYNC='ha_admin_try_alter SIGNAL optimize_started WAIT_FOR table_altered'; +OPTIMIZE TABLE t1; +# Connection 2 +# Change table to engine=memory +SET DEBUG_SYNC='now WAIT_FOR optimize_started'; +ALTER TABLE t1 engine=memory; +SET DEBUG_SYNC='now SIGNAL table_altered'; +# Connection 1 +# Complete optimization +Table Op Msg_type Msg_text +test.t1 optimize note Table does not support optimize, doing recreate + analyze instead +test.t1 optimize error Got error -1 from storage engine +test.t1 optimize status Operation failed +Warnings: +Error 1030 Got error -1 from storage engine +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; diff --git a/mysql-test/t/innodb_mysql_sync.test b/mysql-test/t/innodb_mysql_sync.test new file mode 100644 index 00000000000..3f061e30293 --- /dev/null +++ b/mysql-test/t/innodb_mysql_sync.test @@ -0,0 +1,48 @@ +# +# Test file for InnoDB tests that require the debug sync facility +# +--source include/have_innodb.inc +--source include/have_debug_sync.inc +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + + +--echo # +--echo # Bug 42074 concurrent optimize table and +--echo # alter table = Assertion failed: thd->is_error() +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +--echo # Create InnoDB table +CREATE TABLE t1 (id INT) engine=innodb; +connect (con2, localhost, root); + +--echo # Connection 1 +--echo # Start optimizing table +connection default; +SET DEBUG_SYNC='ha_admin_try_alter SIGNAL optimize_started WAIT_FOR table_altered'; +--send OPTIMIZE TABLE t1 + +--echo # Connection 2 +--echo # Change table to engine=memory +connection con2; +SET DEBUG_SYNC='now WAIT_FOR optimize_started'; +ALTER TABLE t1 engine=memory; +SET DEBUG_SYNC='now SIGNAL table_altered'; + +--echo # Connection 1 +--echo # Complete optimization +connection default; +--reap + +disconnect con2; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; + + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 532cb1465c6..6630331ad00 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4878,6 +4878,7 @@ send_result_message: close_thread_tables(thd); if (!thd->locked_tables_mode) thd->mdl_context.release_all_locks(); + DEBUG_SYNC(thd, "ha_admin_try_alter"); protocol->store(STRING_WITH_LEN("note"), system_charset_info); protocol->store(STRING_WITH_LEN( "Table does not support optimize, doing recreate + analyze instead"), @@ -4911,6 +4912,8 @@ send_result_message: if ((table->table= open_ltable(thd, table, lock_type, 0)) && ((result_code= table->table->file->ha_analyze(thd, check_opt)) > 0)) result_code= 0; // analyze went ok + if (result_code) // analyze failed + table->table->file->print_error(result_code, MYF(0)); } /* Start a new row for the final status row */ protocol->prepare_for_resend(); From 546e16ebd8685f490d90e376df6116175b62fcba Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 09:51:20 +0100 Subject: [PATCH 084/212] Backport of revno: 2617.69.40 A pre-requisite patch for Bug#30977 "Concurrent statement using stored function and DROP FUNCTION breaks SBR". This patch changes the MDL API by introducing a namespace for lock keys: MDL_TABLE for tables and views and MDL_PROCEDURE for stored procedures and functions. The latter is needed for the fix for Bug#30977. --- sql/lock.cc | 2 +- sql/mdl.cc | 48 +++++++++++++++++++++------------------------- sql/mdl.h | 37 +++++++++++++++++++++-------------- sql/sp_head.cc | 4 ++-- sql/sql_base.cc | 6 +++--- sql/sql_delete.cc | 2 +- sql/sql_handler.cc | 2 +- sql/sql_parse.cc | 2 +- sql/sql_show.cc | 2 +- sql/sql_table.cc | 8 ++++---- sql/sql_trigger.cc | 2 +- sql/table.cc | 2 +- sql/table.h | 2 +- 13 files changed, 62 insertions(+), 57 deletions(-) diff --git a/sql/lock.cc b/sql/lock.cc index aea1bfbd0e6..8d314c4ad19 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -957,7 +957,7 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list) for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) { - lock_table->mdl_request.init(0, lock_table->db, lock_table->table_name, + lock_table->mdl_request.init(MDL_TABLE, lock_table->db, lock_table->table_name, MDL_EXCLUSIVE); mdl_requests.push_front(&lock_table->mdl_request); } diff --git a/sql/mdl.cc b/sql/mdl.cc index 566a7c96b3b..f9b52b17f5f 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -301,22 +301,18 @@ void MDL_context::merge(MDL_context *src) The MDL subsystem does not own or manage memory of lock requests. - @param type Id of type of object to be locked - @param db Name of database to which the object belongs - @param name Name of of the object - @param mdl_type The MDL lock type for the request. - - Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2 - Note that tables and views must have the same lock type, since - they share the same name space in the SQL standard. + @param mdl_namespace Id of namespace of object to be locked + @param db Name of database to which the object belongs + @param name Name of of the object + @param mdl_type The MDL lock type for the request. */ -void MDL_request::init(unsigned char type_arg, +void MDL_request::init(enum_mdl_namespace mdl_namespace, const char *db_arg, const char *name_arg, enum enum_mdl_type mdl_type_arg) { - key.mdl_key_init(type_arg, db_arg, name_arg); + key.mdl_key_init(mdl_namespace, db_arg, name_arg); type= mdl_type_arg; ticket= NULL; } @@ -329,10 +325,10 @@ void MDL_request::init(unsigned char type_arg, on a memory root. Necessary to lock ad-hoc tables, e.g. mysql.* tables of grant and data dictionary subsystems. - @param type Id of type of object to be locked - @param db Name of database to which object belongs - @param name Name of of object - @param root MEM_ROOT on which object should be allocated + @param mdl_namespace Id of namespace of object to be locked + @param db Name of database to which object belongs + @param name Name of of object + @param root MEM_ROOT on which object should be allocated @note The allocated lock request will have MDL_SHARED type. @@ -341,7 +337,7 @@ void MDL_request::init(unsigned char type_arg, */ MDL_request * -MDL_request::create(unsigned char type, const char *db, +MDL_request::create(enum_mdl_namespace mdl_namespace, const char *db, const char *name, enum_mdl_type mdl_type, MEM_ROOT *root) { @@ -350,7 +346,7 @@ MDL_request::create(unsigned char type, const char *db, if (!(mdl_request= (MDL_request*) alloc_root(root, sizeof(MDL_request)))) return NULL; - mdl_request->init(type, db, name, mdl_type); + mdl_request->init(mdl_namespace, db, name, mdl_type); return mdl_request; } @@ -1418,20 +1414,20 @@ void MDL_context::release_global_shared_lock() Auxiliary function which allows to check if we have exclusive lock on the object. - @param type Id of object type - @param db Name of the database - @param name Name of the object + @param mdl_namespace Id of object namespace + @param db Name of the database + @param name Name of the object @return TRUE if current context contains exclusive lock for the object, FALSE otherwise. */ bool -MDL_context::is_exclusive_lock_owner(unsigned char type, +MDL_context::is_exclusive_lock_owner(enum_mdl_namespace mdl_namespace, const char *db, const char *name) { MDL_request mdl_request; - mdl_request.init(type, db, name, MDL_EXCLUSIVE); + mdl_request.init(mdl_namespace, db, name, MDL_EXCLUSIVE); MDL_ticket *ticket= find_ticket(&mdl_request); DBUG_ASSERT(ticket == NULL || ticket->m_state == MDL_ACQUIRED); @@ -1444,19 +1440,19 @@ MDL_context::is_exclusive_lock_owner(unsigned char type, Auxiliary function which allows to check if we have some kind of lock on a object. - @param type Id of object type - @param db Name of the database - @param name Name of the object + @param mdl_namespace Id of object namespace + @param db Name of the database + @param name Name of the object @return TRUE if current context contains satisfied lock for the object, FALSE otherwise. */ bool -MDL_context::is_lock_owner(unsigned char type, +MDL_context::is_lock_owner(enum_mdl_namespace mdl_namespace, const char *db, const char *name) { - MDL_key key(type, db, name); + MDL_key key(mdl_namespace, db, name); MDL_ticket *ticket; MDL_context::Ticket_iterator it(m_tickets); diff --git a/sql/mdl.h b/sql/mdl.h index dc6f0a34443..87982bc6af1 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -47,6 +47,14 @@ enum enum_mdl_type {MDL_SHARED=0, MDL_SHARED_HIGH_PRIO, enum enum_mdl_state { MDL_PENDING, MDL_ACQUIRED }; +/** + Object namespaces + + Different types of objects exist in different namespaces + - MDL_TABLE is for tables and views. + - MDL_PROCEDURE is for stored procedures, stored functions and UDFs. +*/ +enum enum_mdl_namespace { MDL_TABLE=0, MDL_PROCEDURE }; /** Maximal length of key for metadata locking subsystem. */ #define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1) @@ -74,18 +82,18 @@ public: uint table_name_length() const { return m_length - m_db_name_length - 3; } /** - Construct a metadata lock key from a triplet (type, database and name). + Construct a metadata lock key from a triplet (mdl_namespace, database and name). - @remark The key for a table is <0 (=table)>++
+ @remark The key for a table is ++
- @param type Id of type of object to be locked - @param db Name of database to which the object belongs - @param name Name of of the object - @param key Where to store the the MDL key. + @param mdl_namespace Id of namespace of object to be locked + @param db Name of database to which the object belongs + @param name Name of of the object + @param key Where to store the the MDL key. */ - void mdl_key_init(char type, const char *db, const char *name) + void mdl_key_init(enum_mdl_namespace mdl_namespace, const char *db, const char *name) { - m_ptr[0]= type; + m_ptr[0]= (char) mdl_namespace; m_db_name_length= (uint) (strmov(m_ptr + 1, db) - m_ptr - 1); m_length= (uint) (strmov(m_ptr + m_db_name_length + 2, name) - m_ptr + 1); } @@ -104,11 +112,12 @@ public: { mdl_key_init(rhs); } - MDL_key(char type_arg, const char *db_arg, const char *name_arg) + MDL_key(enum_mdl_namespace namespace_arg, const char *db_arg, const char *name_arg) { - mdl_key_init(type_arg, db_arg, name_arg); + mdl_key_init(namespace_arg, db_arg, name_arg); } MDL_key() {} /* To use when part of MDL_request. */ + private: char m_ptr[MAX_MDLKEY_LENGTH]; uint m_length; @@ -168,7 +177,7 @@ public: MDL_key key; public: - void init(unsigned char type_arg, const char *db_arg, const char *name_arg, + void init(enum_mdl_namespace namespace_arg, const char *db_arg, const char *name_arg, enum_mdl_type mdl_type_arg); /** Set type of lock request. Can be only applied to pending locks. */ inline void set_type(enum_mdl_type type_arg) @@ -178,7 +187,7 @@ public: } bool is_shared() const { return type < MDL_EXCLUSIVE; } - static MDL_request *create(unsigned char type, const char *db, + static MDL_request *create(enum_mdl_namespace mdl_namespace, const char *db, const char *name, enum_mdl_type mdl_type, MEM_ROOT *root); @@ -318,10 +327,10 @@ public: void release_lock(MDL_ticket *ticket); void release_global_shared_lock(); - bool is_exclusive_lock_owner(unsigned char type, + bool is_exclusive_lock_owner(enum_mdl_namespace mdl_namespace, const char *db, const char *name); - bool is_lock_owner(unsigned char type, const char *db, const char *name); + bool is_lock_owner(enum_mdl_namespace mdl_namespace, const char *db, const char *name); inline bool has_locks() const { diff --git a/sql/sp_head.cc b/sql/sp_head.cc index ae798d063ea..0744e5be930 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3981,7 +3981,7 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; - table->mdl_request.init(0, table->db, table->table_name, MDL_SHARED); + table->mdl_request.init(MDL_TABLE, table->db, table->table_name, MDL_SHARED); /* Everyting else should be zeroed */ @@ -4023,7 +4023,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->lock_type= locktype; table->select_lex= lex->current_select; table->cacheable_table= 1; - table->mdl_request.init(0, table->db, table->table_name, MDL_SHARED); + table->mdl_request.init(MDL_TABLE, table->db, table->table_name, MDL_SHARED); lex->add_to_query_tables(table); return table; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 4be80f4c8a5..6ed97c468ca 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -474,7 +474,7 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, To be able perform any operation on table we should own some kind of metadata lock on it. */ - DBUG_ASSERT(thd->mdl_context.is_lock_owner(0, table_list->db, + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_TABLE, table_list->db, table_list->table_name)); /* Read table definition from cache */ @@ -2541,7 +2541,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, TABLES breaks metadata locking protocol (potentially can lead to deadlocks) it should be disallowed. */ - if (thd->mdl_context.is_lock_owner(0, table_list->db, + if (thd->mdl_context.is_lock_owner(MDL_TABLE, table_list->db, table_list->table_name)) { char path[FN_REFLEN + 1]; @@ -8158,7 +8158,7 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, safe_mutex_assert_owner(&LOCK_open); DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || - thd->mdl_context.is_exclusive_lock_owner(0, db, table_name)); + thd->mdl_context.is_exclusive_lock_owner(MDL_TABLE, db, table_name)); key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index c3e205848de..03be0382150 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1177,7 +1177,7 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) goto trunc_by_del; - mdl_request.init(0, table_list->db, table_list->table_name, MDL_EXCLUSIVE); + mdl_request.init(MDL_TABLE, table_list->db, table_list->table_name, MDL_EXCLUSIVE); if (thd->mdl_context.acquire_exclusive_lock(&mdl_request)) DBUG_RETURN(TRUE); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 5bdf8611f5e..0bc9623c31c 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -264,7 +264,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) memcpy(hash_tables->db, tables->db, dblen); memcpy(hash_tables->table_name, tables->table_name, namelen); memcpy(hash_tables->alias, tables->alias, aliaslen); - hash_tables->mdl_request.init(0, db, name, MDL_SHARED); + hash_tables->mdl_request.init(MDL_TABLE, db, name, MDL_SHARED); /* add to hash */ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2f1753bcdaa..2c5f72c8cf4 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -6024,7 +6024,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); - ptr->mdl_request.init(0, ptr->db, ptr->table_name, MDL_SHARED); + ptr->mdl_request.init(MDL_TABLE, ptr->db, ptr->table_name, MDL_SHARED); DBUG_RETURN(ptr); } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 6a5895f9446..d346bae5258 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3068,7 +3068,7 @@ static bool acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table) { bool error; - table->mdl_request.init(0, table->db, table->table_name, + table->mdl_request.init(MDL_TABLE, table->db, table->table_name, MDL_SHARED_HIGH_PRIO); while (!(error= thd->mdl_context.try_acquire_shared_lock(&table->mdl_request)) && diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 6630331ad00..ddb53dd3754 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4140,7 +4140,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - target_mdl_request.init(0, db, table_name, MDL_EXCLUSIVE); + target_mdl_request.init(MDL_TABLE, db, table_name, MDL_EXCLUSIVE); if (thd->mdl_context.try_acquire_exclusive_lock(&target_mdl_request)) { result= TRUE; @@ -4361,7 +4361,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, uint key_length; key_length= create_table_def_key(thd, key, table_list, 0); - table_list->mdl_request.init(0, table_list->db, table_list->table_name, + table_list->mdl_request.init(MDL_TABLE, table_list->db, table_list->table_name, MDL_EXCLUSIVE); if (thd->mdl_context.acquire_exclusive_lock(&table_list->mdl_request)) DBUG_RETURN(0); @@ -5271,7 +5271,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, } else { - table->mdl_request.init(0, db, table_name, MDL_EXCLUSIVE); + table->mdl_request.init(MDL_TABLE, db, table_name, MDL_EXCLUSIVE); if (thd->mdl_context.try_acquire_exclusive_lock(&table->mdl_request)) DBUG_RETURN(TRUE); @@ -6637,7 +6637,7 @@ view_err: } else { - target_mdl_request.init(0, new_db, new_name, MDL_EXCLUSIVE); + target_mdl_request.init(MDL_TABLE, new_db, new_name, MDL_EXCLUSIVE); if (thd->mdl_context.try_acquire_exclusive_lock(&target_mdl_request)) DBUG_RETURN(TRUE); if (target_mdl_request.ticket == NULL) diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index b8a4edaa963..f9d99b3c119 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -1883,7 +1883,7 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, In the future, only an exclusive metadata lock will be enough. */ #ifndef DBUG_OFF - if (thd->mdl_context.is_exclusive_lock_owner(0, db, old_table)) + if (thd->mdl_context.is_exclusive_lock_owner(MDL_TABLE, db, old_table)) safe_mutex_assert_owner(&LOCK_open); #endif diff --git a/sql/table.cc b/sql/table.cc index 7fb9bbbd955..c66610e5693 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4822,7 +4822,7 @@ size_t max_row_length(TABLE *table, const uchar *data) void init_mdl_requests(TABLE_LIST *table_list) { for ( ; table_list ; table_list= table_list->next_global) - table_list->mdl_request.init(0, table_list->db, table_list->table_name, + table_list->mdl_request.init(MDL_TABLE, table_list->db, table_list->table_name, MDL_SHARED); } diff --git a/sql/table.h b/sql/table.h index 21e7bd3142c..6a2bef4c610 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1125,7 +1125,7 @@ struct TABLE_LIST table_name_length= table_name_length_arg; alias= (char*) alias_arg; lock_type= lock_type_arg; - mdl_request.init(0, db, table_name, MDL_SHARED); + mdl_request.init(MDL_TABLE, db, table_name, MDL_SHARED); } /* From 351b912e8549037e5c5006d184b5092707ae7c5a Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 9 Dec 2009 12:04:55 +0300 Subject: [PATCH 085/212] Backport of: ---------------------------------------------------------- revno: 2617.69.25 committer: Konstantin Osipov branch nick: 5.4-42546 timestamp: Fri 2009-08-14 23:52:00 +0400 message: A cleanup in open_tables() and lock_tables(): change return type of these functions to bool from int, to follow convention in the rest of the code. (Part of WL#4284 review fixes). sql/mysql_priv.h: Change return type of open_talbes() and lock_tables() from int to bool. sql/sql_base.cc: Change return type of open_tables() and lock_tables() from int to bool. sql/sql_handler.cc: Use bool now that open_tables() returns bool. --- sql/mysql_priv.h | 17 ++++---- sql/sql_base.cc | 104 ++++++++++++++++++++------------------------- sql/sql_handler.cc | 6 +-- 3 files changed, 58 insertions(+), 69 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index c956a0bfd12..e61c9a923c3 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1474,19 +1474,20 @@ int setup_ftfuncs(SELECT_LEX* select); int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order); void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond); -int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags, +bool open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags, Prelocking_strategy *prelocking_strategy); -inline int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags) +inline bool +open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags) { DML_prelocking_strategy prelocking_strategy; return open_tables(thd, tables, counter, flags, &prelocking_strategy); } /* open_and_lock_tables with optional derived handling */ -int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, +bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, uint flags, Prelocking_strategy *prelocking_strategy); -inline int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, +inline bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, uint flags) { DML_prelocking_strategy prelocking_strategy; @@ -1495,12 +1496,12 @@ inline int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, &prelocking_strategy); } /* simple open_and_lock_tables without derived handling */ -inline int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables) +inline bool simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables) { return open_and_lock_tables_derived(thd, tables, FALSE, 0); } /* open_and_lock_tables with derived handling */ -inline int open_and_lock_tables(THD *thd, TABLE_LIST *tables) +inline bool open_and_lock_tables(THD *thd, TABLE_LIST *tables) { return open_and_lock_tables_derived(thd, tables, TRUE, 0); } @@ -1508,9 +1509,9 @@ inline int open_and_lock_tables(THD *thd, TABLE_LIST *tables) TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, thr_lock_type lock_type, uint flags); bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags); -int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags, +bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags, bool *need_reopen); -int decide_logging_format(THD *thd, TABLE_LIST *tables); +bool decide_logging_format(THD *thd, TABLE_LIST *tables); TABLE *open_temporary_table(THD *thd, const char *path, const char *db, const char *table_name, bool link_in_list); bool rm_temporary_table(handlerton *base, char *path); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 6ed97c468ca..0a5e89b279f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3801,17 +3801,16 @@ open_routines(THD *thd, Query_tables_list *prelocking_ctx, prelocking it won't do such precaching and will simply reuse table list which is already built. - @retval 0 OK - @retval -1 Error. + @retval FALSE Success. + @retval TRUE Error, reported. */ -int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, +bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, Prelocking_strategy *prelocking_strategy) { TABLE_LIST *tables= NULL; Open_table_context ot_ctx(thd); - int result=0; - bool error; + bool error= FALSE; MEM_ROOT new_frm_mem; /* Also used for indicating that prelocking is need */ TABLE_LIST **query_tables_last_own; @@ -3856,16 +3855,16 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, DBUG_ASSERT(thd->lex->query_tables == *start); - if (open_routines(thd, thd->lex, - (Sroutine_hash_entry *)thd->lex->sroutines_list.first, - prelocking_strategy, &need_prelocking)) + error= open_routines(thd, thd->lex, + (Sroutine_hash_entry *)thd->lex->sroutines_list.first, + prelocking_strategy, &need_prelocking); + if (error) { /* Serious error during reading stored routines from mysql.proc table. Something's wrong with the table or its contents, and an error has been emitted; we must abort. */ - result= -1; goto err; } else if (need_prelocking) @@ -3924,7 +3923,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, { continue; } - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } DBUG_PRINT("tcache", ("opening table: '%s'.'%s' item: 0x%lx", tables->db, tables->table_name, (long) tables)); @@ -3979,10 +3978,9 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, it may change in future. */ if (ot_ctx.recover_from_failed_open_table_attempt(thd, tables)) - { - result= -1; goto err; - } + + error= FALSE; goto restart; } @@ -3990,10 +3988,9 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, { DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'", tables->db, tables->alias)); + error= FALSE; continue; } - - result= -1; // Fatal error goto err; } @@ -4071,10 +4068,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, query_tables_last_own= save_query_tables_last; if (error) - { - result= -1; goto err; - } /* Process elements of the prelocking set which were added @@ -4084,12 +4078,10 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, prelocking strategy prescribes so, add tables it uses to the table list and routines it might invoke to the prelocking set. */ - if (open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy, - ¬_used)) - { - result= -1; + error= open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy, + ¬_used); + if (error) goto err; - } } if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables_mode) @@ -4105,12 +4097,10 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, tables->table->grant= tables->grant; /* Check and update metadata version of a base table. */ - if (check_and_update_table_version(thd, tables, tables->table->s)) - { - result= -1; - goto err; - } + error= check_and_update_table_version(thd, tables, tables->table->s); + if (error) + goto err; /* After opening a MERGE table add the children to the query list of tables, so that they are opened too. @@ -4121,7 +4111,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, /* Non-MERGE tables ignore this call. */ if (tables->table->file->extra(HA_EXTRA_ADD_CHILDREN_LIST)) { - result= -1; + error= TRUE; goto err; } @@ -4147,19 +4137,18 @@ process_view_routines: query_tables_last_own= save_query_tables_last; if (error) - { - result= -1; goto err; - } - if (open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy, - ¬_used)) + + error= open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy, + ¬_used); + + if (error) { /* Serious error during reading stored routines from mysql.proc table. Something is wrong with the table or its contents, and an error has been emitted; we must abort. */ - result= -1; goto err; } } @@ -4182,25 +4171,25 @@ process_view_routines: DBUG_ASSERT(tbl->pos_in_table_list == tables); if (tbl->file->extra(HA_EXTRA_ATTACH_CHILDREN)) { - result= -1; + error= TRUE; goto err; } } } - err: +err: thd_proc_info(thd, 0); free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block if (query_tables_last_own) thd->lex->mark_as_requiring_prelocking(query_tables_last_own); - if (result && tables) + if (error && tables) { tables->table= NULL; } - DBUG_PRINT("tcache", ("returning: %d", result)); - DBUG_RETURN(result); + DBUG_PRINT("open_tables", ("returning: %d", (int) error)); + DBUG_RETURN(error); } @@ -4659,9 +4648,9 @@ end: @retval TRUE Error */ -int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, - bool derived, uint flags, - Prelocking_strategy *prelocking_strategy) +bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, + bool derived, uint flags, + Prelocking_strategy *prelocking_strategy) { uint counter; bool need_reopen; @@ -4672,8 +4661,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, for ( ; ; ) { if (open_tables(thd, &tables, &counter, flags, prelocking_strategy)) - DBUG_RETURN(-1); - + DBUG_RETURN(TRUE); DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", { const char *old_proc_info= thd->proc_info; thd->proc_info= "DBUG sleep"; @@ -4684,11 +4672,11 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, &need_reopen)) break; if (!need_reopen) - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); if (thd->in_multi_stmt_transaction() && has_locks) { my_error(ER_LOCK_DEADLOCK, MYF(0)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } close_tables_for_reopen(thd, &tables); } @@ -4697,7 +4685,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, (thd->fill_derived_tables() && mysql_handle_derived(thd->lex, &mysql_derived_filling)))) DBUG_RETURN(TRUE); /* purecov: inspected */ - DBUG_RETURN(0); + DBUG_RETURN(FALSE); } @@ -4791,7 +4779,7 @@ static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table) @param tables Tables involved in the query */ -int decide_logging_format(THD *thd, TABLE_LIST *tables) +bool decide_logging_format(THD *thd, TABLE_LIST *tables) { /* In SBR mode, we are only proceeding if we are binlogging this @@ -4889,7 +4877,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) DBUG_PRINT("info", ("error: %d", error)); if (error) - return -1; + return TRUE; /* We switch to row-based format if we are in mixed mode and one of @@ -4910,7 +4898,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) } } - return 0; + return FALSE; } /* @@ -4942,8 +4930,8 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) -1 Error */ -int lock_tables(THD *thd, TABLE_LIST *tables, uint count, - uint flags, bool *need_reopen) +bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, + uint flags, bool *need_reopen) { TABLE_LIST *table; @@ -4975,7 +4963,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, TABLE **start,**ptr; if (!(ptr=start=(TABLE**) thd->alloc(sizeof(TABLE*)*count))) - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); for (table= tables; table; table= table->next_global) { if (!table->placeholder()) @@ -5002,7 +4990,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, if (! (thd->lock= mysql_lock_tables(thd, start, (uint) (ptr - start), flags, need_reopen))) - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); if (thd->lex->requires_prelocking() && thd->lex->sql_command != SQLCOM_LOCK_TABLES) @@ -5030,7 +5018,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, { mysql_unlock_tables(thd, thd->lock); thd->lock= 0; - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } } @@ -5075,14 +5063,14 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, { my_error(ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG, MYF(0), table->table->s->table_name.str); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } } if (check_lock_and_start_stmt(thd, table->table, table->lock_type)) { - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } /* diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 0bc9623c31c..94f6b248e45 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -193,7 +193,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) TABLE_LIST *hash_tables = NULL; char *db, *name, *alias; uint dblen, namelen, aliaslen, counter; - int error; + bool error; TABLE *backup_open_tables; MDL_context backup_mdl_context; DBUG_ENTER("mysql_ha_open"); @@ -321,8 +321,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) can close a single table only. */ close_thread_tables(thd); - my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); - error= 1; + my_error(ER_ILLEGAL_HA, MYF(0), hash_tables->alias); + error= TRUE; } else { From cb4e624f95d3227ad63d42c77a42182532b64456 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 9 Dec 2009 12:17:17 +0300 Subject: [PATCH 086/212] Backpo ---------------------------------------------------------- revno: 2617.69.28 committer: Konstantin Osipov branch nick: 5.4-azalea-bugfixing timestamp: Tue 2009-08-18 15:27:35 +0400 message: An attempt to fix a link failure on Windows -- Sroutine_hash_entry is forward-declared as class. (Part of WL#4284). --- sql/sp.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sql/sp.h b/sql/sp.h index cf8f72d2cef..1de41eb3920 100644 --- a/sql/sp.h +++ b/sql/sp.h @@ -69,8 +69,9 @@ sp_drop_routine(THD *thd, int type, sp_name *name); used by statement or routine. */ -struct Sroutine_hash_entry +class Sroutine_hash_entry { +public: /** Set key consisting of one-byte routine type and quoted routine name. */ From c03174458a46f382926abfcd669e8edd521dd59c Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 9 Dec 2009 12:29:36 +0300 Subject: [PATCH 087/212] ---------------------------------------------------------- revno: 2617.69.33 committer: Konstantin Osipov branch nick: mysql-next-46452 timestamp: Wed 2009-08-19 18:39:31 +0400 message: Bug#46452 "Crash in MDL, HANDLER OPEN + TRUNCATE TABLE". Flush open HANDLER tables before TRUNCATE, which is a DDL. mysql-test/r/truncate.result: Update results for Bug#46452. mysql-test/t/truncate.test: Add a test case for Bug#46452 "Crash in MDL, HANDLER OPEN + TRUNCATE TABLE". --- mysql-test/r/truncate.result | 14 ++++++++++++++ mysql-test/t/truncate.test | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/mysql-test/r/truncate.result b/mysql-test/r/truncate.result index b194f9b7dc6..8ce2ad9be21 100644 --- a/mysql-test/r/truncate.result +++ b/mysql-test/r/truncate.result @@ -60,3 +60,17 @@ truncate table v1; ERROR 42S02: Table 'test.v1' doesn't exist drop view v1; drop table t1; +# +# Bug#46452 Crash in MDL, HANDLER OPEN + TRUNCATE TABLE +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 AS SELECT 1 AS f1; +HANDLER t1 OPEN; +# Here comes the crash. +TRUNCATE t1; +# Currently TRUNCATE, just like other DDL, implicitly closes +# open HANDLER table. +HANDLER t1 READ FIRST; +ERROR 42S02: Unknown table 't1' in HANDLER +DROP TABLE t1; +# End of 6.0 tests diff --git a/mysql-test/t/truncate.test b/mysql-test/t/truncate.test index ba5364bd324..feec4051e35 100644 --- a/mysql-test/t/truncate.test +++ b/mysql-test/t/truncate.test @@ -69,3 +69,26 @@ drop table t1; # End of 5.0 tests +--echo # +--echo # Bug#46452 Crash in MDL, HANDLER OPEN + TRUNCATE TABLE +--echo # +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 AS SELECT 1 AS f1; + +HANDLER t1 OPEN; +--echo # Here comes the crash. +TRUNCATE t1; + +--echo # Currently TRUNCATE, just like other DDL, implicitly closes +--echo # open HANDLER table. +--error ER_UNKNOWN_TABLE +HANDLER t1 READ FIRST; + +# Cleanup +DROP TABLE t1; + +--echo # End of 6.0 tests + From 59f82702a18799621a19f1a880e9f80cb374b1ce Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 9 Dec 2009 12:37:54 +0300 Subject: [PATCH 088/212] Backport of: ------------------------------------------------------------ revno: 2617.69.32 committer: Dmitry Lenev branch nick: mysql-next-bg46747 timestamp: Wed 2009-08-19 18:12:27 +0400 message: Fix for bug #46747 "Crash in MDL_ticket::upgrade_shared_lock_to_exclusive on TRIGGER + TEMP table". Server crashed when one tried to drop trigger which had its subject table shadowed by a temporary table with the same name. This problem occured because in such situation DROP TRIGGER has opened temporary table instead of base table on which trigger was defined. Attempt to upgrade metadata lock on this temporary table led to crash (we don't acquire metadata locks for temporary tables). This fix ensures that DROP TRIGGER ignores temporary tables when trying to open table on which trigger to be dropped is defined. mysql-test/r/trigger.result: Added test case for bug #46747 "Crash in MDL_ticket::upgrade_shared_lock_to_exclusive on TRIGGER + TEMP table". mysql-test/t/trigger.test: Added test case for bug #46747 "Crash in MDL_ticket::upgrade_shared_lock_to_exclusive on TRIGGER + TEMP table". sql/sql_trigger.cc: Prevent DROP TRIGGER from opening temporary table which might shadow base table on which trigger to be dropped is defined. --- mysql-test/r/trigger.result | 20 ++++++++++++++++++++ mysql-test/t/trigger.test | 25 +++++++++++++++++++++++++ sql/sql_trigger.cc | 5 +++++ 3 files changed, 50 insertions(+) diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index 47b50b233b3..5c63604a325 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -2162,3 +2162,23 @@ Warning 1265 Data truncated for column 'trg2' at row 1 DROP TRIGGER trg1; DROP TRIGGER trg2; DROP TABLE t1; +# +# Bug #46747 "Crash in MDL_ticket::upgrade_shared_lock_to_exclusive +# on TRIGGER + TEMP table". +# +drop trigger if exists t1_bi; +drop temporary table if exists t1; +drop table if exists t1; +create table t1 (i int); +create trigger t1_bi before insert on t1 for each row set @a:=1; +# Create temporary table which shadows base table with trigger. +create temporary table t1 (j int); +# Dropping of trigger should succeed. +drop trigger t1_bi; +select trigger_name from information_schema.triggers +where event_object_schema = 'test' and event_object_table = 't1'; +trigger_name +# Clean-up. +drop temporary table t1; +drop table t1; +End of 6.0 tests. diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index f3b9d6bb91e..7ff61bd96e1 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -2489,3 +2489,28 @@ DROP TRIGGER trg1; DROP TRIGGER trg2; DROP TABLE t1; + +--echo # +--echo # Bug #46747 "Crash in MDL_ticket::upgrade_shared_lock_to_exclusive +--echo # on TRIGGER + TEMP table". +--echo # + +--disable_warnings +drop trigger if exists t1_bi; +drop temporary table if exists t1; +drop table if exists t1; +--enable_warnings + +create table t1 (i int); +create trigger t1_bi before insert on t1 for each row set @a:=1; +--echo # Create temporary table which shadows base table with trigger. +create temporary table t1 (j int); +--echo # Dropping of trigger should succeed. +drop trigger t1_bi; +select trigger_name from information_schema.triggers + where event_object_schema = 'test' and event_object_table = 't1'; +--echo # Clean-up. +drop temporary table t1; +drop table t1; + +--echo End of 6.0 tests. diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index f9d99b3c119..015e0d4daa1 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -443,6 +443,11 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) /* We also don't allow creation of triggers on views. */ tables->required_type= FRMTYPE_TABLE; + /* + Also prevent DROP TRIGGER from opening temporary table which might + shadow base table on which trigger to be dropped is defined. + */ + tables->skip_temporary= TRUE; /* Keep consistent with respect to other DDL statements */ mysql_ha_rm_tables(thd, tables); From fcae99271a82b8ab16883a135fa679711ce97b62 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 10:44:01 +0100 Subject: [PATCH 089/212] Backport of revno: 2617.68.39 Bug #47249 assert in MDL_global_lock::is_lock_type_compatible This assert could be triggered if LOCK TABLES were used to lock both a table and a view that used the same table. The table would have to be first WRITE locked and then READ locked. So "LOCK TABLES v1 WRITE, t1 READ" would eventually trigger the assert, "LOCK TABLES v1 READ, t1 WRITE" would not. The reason is that the ordering of locks in the interal representation made a difference when executing FLUSH TABLE on the table. During FLUSH TABLE, a lock was upgraded to exclusive. If this lock was of type MDL_SHARED and not MDL_SHARED_UPGRADABLE, an internal counter in the MDL subsystem would get out of sync. This would happen if the *last* mention of the table in LOCK TABLES was a READ lock. The counter in question is the number exclusive locks (active or intention). This is used to make sure a global metadata lock is only taken when the counter is zero (= no conflicts). The counter is increased when a MDL_EXCLUSIVE or MDL_SHARED_UPGRADABLE lock is taken, but not when upgrade_shared_lock_to_exclusive() is used to upgrade directly from MDL_SHARED to MDL_EXCLUSIVE. This patch fixes the problem by searching for a TABLE instance locked with MDL_SHARED_UPGRADABLE or MDL_EXCLUSIVE before calling upgrade_shared_lock_to_exclusive(). The patch also adds an assert checking that only MDL_SHARED_UPGRADABLE locks are upgraded to exclusive. Test case added to lock_multi.test. --- mysql-test/r/lock_multi.result | 31 ++++++++++++++++++ mysql-test/t/lock_multi.test | 58 ++++++++++++++++++++++++++++++++++ sql/mdl.cc | 3 ++ sql/mdl.h | 4 +++ sql/sql_base.cc | 34 ++++++++++++++++++-- 5 files changed, 128 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/lock_multi.result b/mysql-test/r/lock_multi.result index 930983bae27..ef9292ad8c0 100644 --- a/mysql-test/r/lock_multi.result +++ b/mysql-test/r/lock_multi.result @@ -219,3 +219,34 @@ flush tables with read lock;; connection: default flush tables; drop table t1; +# +# Bug#47249 assert in MDL_global_lock::is_lock_type_compatible +# +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +# +# Test 1: LOCK TABLES v1 WRITE, t1 READ; +# +CREATE TABLE t1 ( f1 integer ); +CREATE VIEW v1 AS SELECT f1 FROM t1 ; +# Connection 2 +LOCK TABLES v1 WRITE, t1 READ; +FLUSH TABLE t1; +# Connection 1 +LOCK TABLES t1 WRITE; +FLUSH TABLE t1; +DROP TABLE t1; +DROP VIEW v1; +# +# Test 2: LOCK TABLES t1 WRITE, v1 READ; +# +CREATE TABLE t1 ( f1 integer ); +CREATE VIEW v1 AS SELECT f1 FROM t1 ; +# Connection 2 +LOCK TABLES t1 WRITE, v1 READ; +FLUSH TABLE t1; +# Connection 1 +LOCK TABLES t1 WRITE; +FLUSH TABLE t1; +DROP TABLE t1; +DROP VIEW v1; diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test index c82e351dfef..cbb99c04967 100644 --- a/mysql-test/t/lock_multi.test +++ b/mysql-test/t/lock_multi.test @@ -664,5 +664,63 @@ connection flush; --reap connection default; disconnect flush; + + +--echo # +--echo # Bug#47249 assert in MDL_global_lock::is_lock_type_compatible +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +--enable_warnings + +--echo # +--echo # Test 1: LOCK TABLES v1 WRITE, t1 READ; +--echo # + +CREATE TABLE t1 ( f1 integer ); +CREATE VIEW v1 AS SELECT f1 FROM t1 ; + +--echo # Connection 2 +connect (con2,localhost,root); +LOCK TABLES v1 WRITE, t1 READ; +FLUSH TABLE t1; +disconnect con2; +--source include/wait_until_disconnected.inc + +--echo # Connection 1 +connection default; +LOCK TABLES t1 WRITE; +FLUSH TABLE t1; # Assertion happened here + +# Cleanup +DROP TABLE t1; +DROP VIEW v1; + +--echo # +--echo # Test 2: LOCK TABLES t1 WRITE, v1 READ; +--echo # + +CREATE TABLE t1 ( f1 integer ); +CREATE VIEW v1 AS SELECT f1 FROM t1 ; + +--echo # Connection 2 +connect (con2,localhost,root); +LOCK TABLES t1 WRITE, v1 READ; +FLUSH TABLE t1; +disconnect con2; +--source include/wait_until_disconnected.inc + +--echo # Connection 1 +connection default; +LOCK TABLES t1 WRITE; +FLUSH TABLE t1; # Assertion happened here + +# Cleanup +DROP TABLE t1; +DROP VIEW v1; + + # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc diff --git a/sql/mdl.cc b/sql/mdl.cc index f9b52b17f5f..879b12a4cac 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -1005,6 +1005,9 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() if (m_type == MDL_EXCLUSIVE) DBUG_RETURN(FALSE); + /* Only allow upgrades from MDL_SHARED_UPGRADABLE */ + DBUG_ASSERT(m_type == MDL_SHARED_UPGRADABLE); + pthread_mutex_lock(&LOCK_mdl); old_msg= MDL_ENTER_COND(thd, mysys_var); diff --git a/sql/mdl.h b/sql/mdl.h index 87982bc6af1..03631bb9dd6 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -254,6 +254,10 @@ public: mdl_cached_object_release_hook release_hook); const MDL_context *get_ctx() const { return m_ctx; } bool is_shared() const { return m_type < MDL_EXCLUSIVE; } + bool is_upgradable_or_exclusive() const + { + return m_type == MDL_SHARED_UPGRADABLE || m_type == MDL_EXCLUSIVE; + } bool upgrade_shared_lock_to_exclusive(); void downgrade_exclusive_lock(); private: diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 0a5e89b279f..140e9e14568 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -130,6 +130,8 @@ static bool tdc_wait_for_old_versions(THD *thd, static bool has_write_table_with_auto_increment(TABLE_LIST *tables); +TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db, + const char *table_name); uint cached_open_tables(void) { @@ -999,8 +1001,8 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, table_list= table_list->next_global) { /* A check that the table was locked for write is done by the caller. */ - TABLE *table= find_locked_table(thd->open_tables, table_list->db, - table_list->table_name); + TABLE *table= find_table_for_mdl_upgrade(thd->open_tables, table_list->db, + table_list->table_name); /* May return NULL if this table has already been closed via an alias. */ if (! table) @@ -2942,6 +2944,34 @@ TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_na } +/** + Find instance of TABLE with MDL_SHARED_UPGRADABLE or + MDL_EXCLUSIVE lock from the list of open tables. + + @param list List of TABLE objects to be searched + @param db Database name. + @param table_name Name of table. + + @return Pointer to MDL_SHARED_UPGRADABLE or MDL_EXCLUSIVE + TABLE instance, NULL otherwise. +*/ + +TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db, + const char *table_name) +{ + TABLE *tab= find_locked_table(list, db, table_name); + + while (tab != NULL) + { + if (tab->mdl_ticket != NULL && + tab->mdl_ticket->is_upgradable_or_exclusive()) + return tab; + tab= find_locked_table(tab->next, db, table_name); + } + return NULL; +} + + /*********************************************************************** class Locked_tables_list implementation. Declared in sql_class.h ************************************************************************/ From 736db89a7665730e5adaa23289c8245a9251cfd6 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 9 Dec 2009 12:44:05 +0300 Subject: [PATCH 090/212] Backport of: ------------------------------------------------------------ revno: 2617.69.37 committer: Dmitry Lenev branch nick: mysql-next-bg46748 timestamp: Fri 2009-08-21 18:17:02 +0400 message: Fix for bug #46748 "Assertion in MDL_context::wait_for_locks() on INSERT + CREATE TRIGGER". Concurrent execution of statements involving stored functions or triggers which were using several tables and DDL statements which affected those tables on debug build of server might have led to assertion failures in MDL_context::wait_for_locks(). Non-debug build was not affected. The problem was that during back-off which happens when open_tables() encounters conflicting metadata lock for one of the tables being open we didn't reset MDL_request::ticket value for requests which correspond to tables from extended prelocking set. Since these requests are part of of list of requests to be waited for in Open_table_context this broke assumption that ticket value for them is 0 in MDL_context::wait_for_locks() and caused assertion failure. This fix ensures that close_tables_for_reopen(), which performs this back-off resets MDL_request::ticket value not only for tables directly used by the statement but also for tables from extended prelocking set, thus satisfying assumption described above. mysql-test/r/mdl_sync.result: Added test case for bug #46748 "Assertion in MDL_context::wait_for_locks() on INSERT + CREATE TRIGGER". mysql-test/t/mdl_sync.test: Added test case for bug #46748 "Assertion in MDL_context::wait_for_locks() on INSERT + CREATE TRIGGER". sql/sql_base.cc: Since metadata lock requests for tables from extended part of prelocking set are also part of list of requests to be waited for in Open_table_context in close_tables_for_reopen() we have to reset MDL_request::ticket values for them to assumptions in MDL_context::wait_for_locks(). --- mysql-test/r/mdl_sync.result | 43 ++++++++++++++++++++ mysql-test/t/mdl_sync.test | 77 ++++++++++++++++++++++++++++++++++++ sql/sql_base.cc | 16 +++++++- 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index 37f18746c57..36451985a86 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -19,3 +19,46 @@ connection: con2 ERROR 42S02: Unknown table 't1' drop table t3; SET DEBUG_SYNC= 'RESET'; +# +# Test for bug #46748 "Assertion in MDL_context::wait_for_locks() +# on INSERT + CREATE TRIGGER". +# +drop tables if exists t1, t2, t3, t4, t5; +# Let us simulate scenario in which we open some tables from extended +# part of prelocking set but then encounter conflicting metadata lock, +# so have to back-off and wait for it to go away. +create table t1 (i int); +create table t2 (j int); +create table t3 (k int); +create table t4 (l int); +create trigger t1_bi before insert on t1 for each row +insert into t2 values (new.i); +create trigger t2_bi before insert on t2 for each row +insert into t3 values (new.j); +# +# Switching to connection 'con1root'. +lock tables t4 read; +# +# Switching to connection 'con2root'. +# Send : +rename table t3 to t5, t4 to t3;; +# +# Switching to connection 'default'. +# Wait until the above RENAME TABLE adds pending requests for exclusive +# metadata lock on its tables and blocks due to 't4' being used by LOCK +# TABLES. +# Send : +insert into t1 values (1);; +# +# Switching to connection 'con1root'. +# Wait until INSERT statement waits due to encountering pending +# exclusive metadata lock on 't3'. +unlock tables; +# +# Switching to connection 'con2root'. +# Reap RENAME TABLE. +# +# Switching to connection 'default'. +# Reap INSERT. +# Clean-up. +drop tables t1, t2, t3, t5; diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test index ed03a9a5cb0..124e76bbc1f 100644 --- a/mysql-test/t/mdl_sync.test +++ b/mysql-test/t/mdl_sync.test @@ -3,6 +3,10 @@ # --source include/have_debug_sync.inc +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + + # Clean up resources used in this test case. --disable_warnings SET DEBUG_SYNC= 'RESET'; @@ -67,3 +71,76 @@ disconnect con3; --disable_warnings SET DEBUG_SYNC= 'RESET'; --enable_warnings + + +--echo # +--echo # Test for bug #46748 "Assertion in MDL_context::wait_for_locks() +--echo # on INSERT + CREATE TRIGGER". +--echo # +--disable_warnings +drop tables if exists t1, t2, t3, t4, t5; +--enable_warnings +--echo # Let us simulate scenario in which we open some tables from extended +--echo # part of prelocking set but then encounter conflicting metadata lock, +--echo # so have to back-off and wait for it to go away. +connect (con1root,localhost,root,,test,,); +connect (con2root,localhost,root,,test,,); +connection default; +create table t1 (i int); +create table t2 (j int); +create table t3 (k int); +create table t4 (l int); +create trigger t1_bi before insert on t1 for each row + insert into t2 values (new.i); +create trigger t2_bi before insert on t2 for each row + insert into t3 values (new.j); +--echo # +--echo # Switching to connection 'con1root'. +connection con1root; +lock tables t4 read; +--echo # +--echo # Switching to connection 'con2root'. +connection con2root; +--echo # Send : +--send rename table t3 to t5, t4 to t3; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until the above RENAME TABLE adds pending requests for exclusive +--echo # metadata lock on its tables and blocks due to 't4' being used by LOCK +--echo # TABLES. +let $wait_condition= select count(*)= 1 from information_schema.processlist + where state= 'Waiting for table' and + info='rename table t3 to t5, t4 to t3'; +--source include/wait_condition.inc +--echo # Send : +--send insert into t1 values (1); +--echo # +--echo # Switching to connection 'con1root'. +connection con1root; +--echo # Wait until INSERT statement waits due to encountering pending +--echo # exclusive metadata lock on 't3'. +let $wait_condition= select count(*)= 1 from information_schema.processlist + where state= 'Waiting for table' and + info='insert into t1 values (1)'; +--source include/wait_condition.inc +unlock tables; +--echo # +--echo # Switching to connection 'con2root'. +connection con2root; +--echo # Reap RENAME TABLE. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap INSERT. +--reap +--echo # Clean-up. +disconnect con1root; +disconnect con2root; +drop tables t1, t2, t3, t5; + + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 0a5e89b279f..42f987133dd 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -5104,19 +5104,31 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) { + TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table(); + TABLE_LIST *tmp; + /* If table list consists only from tables from prelocking set, table list for new attempt should be empty, so we have to update list's root pointer. */ - if (thd->lex->first_not_own_table() == *tables) + if (first_not_own_table == *tables) *tables= 0; thd->lex->chop_off_not_own_tables(); sp_remove_not_own_routines(thd->lex); - for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global) + for (tmp= *tables; tmp; tmp= tmp->next_global) { tmp->table= 0; tmp->mdl_request.ticket= NULL; } + /* + Metadata lock requests for tables from extended part of prelocking set + are part of list of requests to be waited for in Open_table_context. + So to satisfy assumptions in MDL_context::wait_for_locks(), which will + performs the waiting, we have to reset MDL_request::ticket values for + them as well. + */ + for (tmp= first_not_own_table; tmp; tmp= tmp->next_global) + tmp->mdl_request.ticket= NULL; close_thread_tables(thd); if (!thd->locked_tables_mode) thd->mdl_context.release_all_locks(); From 3d1433cff6b291cde59b85b1bbeb7a07c1dcfd41 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 12:51:45 +0100 Subject: [PATCH 091/212] Backport of revno: 2617.69.34 Bug #45949 Assertion `!tables->table' in open_tables() on ALTER + INSERT DELAYED The assertion was caused by improperly closing tables when INSERT DELAYED needed to reopen tables. This patch replaces the call to close_thread_tables with close_tables_for_reopen which fixes the problem. The only way I was able to trigger the reopen code path and thus the assertion, was if ALTER TABLE killed the delayed insert thread and the delayed insert thread was able to enter the reopen code path before it noticed that thd->killed had been set. Note that in these cases reopen will always fail since open_table() will check thd->killed and return. This patch therefore adds two more thd->killed checks to minimize the chance of entering the reopen code path without hope for success. The patch also changes it so that if the delayed insert is killed using KILL_CONNECTION, the error message that is copied to the connection thread is ER_QUERY_INTERRUPTED rather than ER_SERVER_SHUTDOWN. This means that if INSERT DELAYED fails, the user will now see "Query execution was interrupted" rather than the misleading "Server shutdown in progress". No test case is supplied. This is for two reasons: 1) Unable to reproduce the error without having the delayed insert thread in a killed state which means that reopen is futile and was not supposed to be attempted. 2) Difficulty of using sync points in other threads than the connection thread. The patch has been successfully tested with the RQG and the grammar supplied in the bug description. --- sql/sql_insert.cc | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index e0537c75e07..666d6400d32 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1974,6 +1974,11 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) } pthread_mutex_unlock(&di->mutex); thd_proc_info(thd, "got old table"); + if (thd->killed) + { + di->unlock(); + goto end_create; + } if (di->thd.killed) { if (di->thd.is_error()) @@ -1981,20 +1986,19 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) /* Copy the error message. Note that we don't treat fatal errors in the delayed thread as fatal errors in the - main thread. Use of my_message will enable stored - procedures continue handlers. + main thread. If delayed thread was killed, we don't + want to send "Server shutdown in progress" in the + INSERT THREAD. */ - my_message(di->thd.stmt_da->sql_errno(), di->thd.stmt_da->message(), - MYF(0)); - } - di->unlock(); + if (di->thd.stmt_da->sql_errno() == ER_SERVER_SHUTDOWN) + my_message(ER_QUERY_INTERRUPTED, ER(ER_QUERY_INTERRUPTED), MYF(0)); + else + my_message(di->thd.stmt_da->sql_errno(), di->thd.stmt_da->message(), + MYF(0)); + } + di->unlock(); goto end_create; } - if (thd->killed) - { - di->unlock(); - goto end_create; - } pthread_mutex_lock(&LOCK_delayed_insert); delayed_threads.append(di); pthread_mutex_unlock(&LOCK_delayed_insert); @@ -2061,8 +2065,11 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) goto error; if (dead) { - my_message(thd.stmt_da->sql_errno(), thd.stmt_da->message(), MYF(0)); - goto error; + /* Don't copy over "Server shutdown in progress". */ + if (thd.stmt_da->sql_errno() == ER_SERVER_SHUTDOWN) + my_message(ER_QUERY_INTERRUPTED, ER(ER_QUERY_INTERRUPTED), MYF(0)); + else + my_message(thd.stmt_da->sql_errno(), thd.stmt_da->message(), MYF(0)); } } share= table->s; @@ -2412,7 +2419,7 @@ pthread_handler_t handle_delayed_insert(void *arg) for (;;) { - if (thd->killed == THD::KILL_CONNECTION) + if (thd->killed) { uint lock_count; /* @@ -2474,7 +2481,7 @@ pthread_handler_t handle_delayed_insert(void *arg) } thd_proc_info(&(di->thd), 0); - if (di->tables_in_use && ! thd->lock) + if (di->tables_in_use && ! thd->lock && !thd->killed) { bool need_reopen; /* @@ -2491,14 +2498,15 @@ pthread_handler_t handle_delayed_insert(void *arg) MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK, &need_reopen))) { - if (need_reopen) + if (need_reopen && !thd->killed) { /* We were waiting to obtain TL_WRITE_DELAYED (probably due to someone having or requesting TL_WRITE_ALLOW_READ) and got aborted. Try to reopen table and if it fails die. */ - close_thread_tables(thd); + TABLE_LIST *tl_ptr = &di->table_list; + close_tables_for_reopen(thd, &tl_ptr); di->table= 0; if (di->open_and_lock_table()) { From d40ef57d80639815fb5c64ef07a37d826b943cb8 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 12:58:52 +0100 Subject: [PATCH 092/212] Backport of revno: 3711.1.1 Bug #48725 Assert !thd->is_error() in delayed_get_table() This bug is a regression introduced by the patch for Bug #45949. If the handler thread for INSERT DELAYED was killed by e.g. FLUSH TABLES, the error message is copied from the handler thread to the connection thread. But the error was not reacted on, so the connection thread continued as normal, leading to an eventual assert. No test case added as it would have required sync points to work for handler threads. The plan is to add this in the scope of Bug #48725 / Bug #48541. The patch has been tested with the non-deterministic test case given in the bug description. --- sql/sql_insert.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 666d6400d32..171c5e2cee0 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2070,6 +2070,7 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) my_message(ER_QUERY_INTERRUPTED, ER(ER_QUERY_INTERRUPTED), MYF(0)); else my_message(thd.stmt_da->sql_errno(), thd.stmt_da->message(), MYF(0)); + goto error; } } share= table->s; From 05a126eda1d0fc34440323d707921d4c836c3628 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 13:15:35 +0100 Subject: [PATCH 093/212] Backport of revno: 2617.76.2 Bug #47107 assert in notify_shared_lock on incorrect CREATE TABLE , HANDLER Attempts to create a table (using CREATE TABLE, CREATE TABLE LIKE or CREATE TABLE SELECT statements) which already existed and was opened by the same connection through HANDLER statement, led to a stalled connection (for production builds of the server) or to the server being aborted due to an assertion failure (for debug builds of the server). This problem was introduced by the new implementation of a metadata locking subsystem and didn't affect earlier versions of the server. The cause of the problem was that the HANDLER was not closed by CREATE TABLE before CREATE tried to open and lock the table. Acquiring an exclusive MDL lock on the table to be created would therefore fail since HANDLER already had a shared MDL lock. This triggered an assert as the HANDLER and CREATE statements came from the same thread (self-deadlock). This patch resolves the issue by closing any open HANDLERs on tables to be created by CREATE TABLE, similar to what is already done for DROP and ALTER TABLE. Test case added to create.test. --- mysql-test/r/create.result | 18 ++++++++++++++++++ mysql-test/t/create.test | 30 ++++++++++++++++++++++++++++++ sql/sql_parse.cc | 6 ++++++ 3 files changed, 54 insertions(+) diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result index cf424b8b058..90502f94f44 100644 --- a/mysql-test/r/create.result +++ b/mysql-test/r/create.result @@ -1961,3 +1961,21 @@ END ;| ERROR 42000: This version of MySQL doesn't yet support 'multiple triggers with the same action time and event for one table' DROP TABLE t1; DROP TABLE B; +# +# Bug #47107 assert in notify_shared_lock on incorrect +# CREATE TABLE , HANDLER +DROP TABLE IF EXISTS t1; +CREATE TABLE t1(f1 integer); +# The following CREATE TABLEs before gave an assert. +HANDLER t1 OPEN AS A; +CREATE TABLE t1 SELECT 1 AS f2; +ERROR 42S01: Table 't1' already exists +HANDLER t1 OPEN AS A; +CREATE TABLE t1(f1 integer); +ERROR 42S01: Table 't1' already exists +CREATE TABLE t2(f1 integer); +HANDLER t1 OPEN AS A; +CREATE TABLE t1 LIKE t2; +ERROR 42S01: Table 't1' already exists +DROP TABLE t2; +DROP TABLE t1; diff --git a/mysql-test/t/create.test b/mysql-test/t/create.test index c07014bfc19..21778e00ab9 100644 --- a/mysql-test/t/create.test +++ b/mysql-test/t/create.test @@ -1639,3 +1639,33 @@ END ;| DROP TABLE t1; DROP TABLE B; + + +--echo # +--echo # Bug #47107 assert in notify_shared_lock on incorrect +--echo # CREATE TABLE , HANDLER +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1(f1 integer); + +--echo # The following CREATE TABLEs before gave an assert. + +HANDLER t1 OPEN AS A; +--error ER_TABLE_EXISTS_ERROR +CREATE TABLE t1 SELECT 1 AS f2; + +HANDLER t1 OPEN AS A; +--error ER_TABLE_EXISTS_ERROR +CREATE TABLE t1(f1 integer); + +CREATE TABLE t2(f1 integer); +HANDLER t1 OPEN AS A; +--error ER_TABLE_EXISTS_ERROR +CREATE TABLE t1 LIKE t2; + +DROP TABLE t2; +DROP TABLE t1; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2c5f72c8cf4..056a947f3fa 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2311,6 +2311,12 @@ case SQLCOM_PREPARE: thd->work_part_info= part_info; } #endif + + /* + Close any open handlers for the table + */ + mysql_ha_rm_tables(thd, create_table); + if (select_lex->item_list.elements) // With select { select_result *result; From dd848dfa914e1f01eb3a8b2a5ce16af0cb7055c7 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 13:27:24 +0100 Subject: [PATCH 094/212] Backport of revno: 2617.76.3 Bug#47107 Add missing line in previous change set. mysql-test/r/create.result: Bug#47107 Add missing line in previous change set. --- mysql-test/r/create.result | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result index 90502f94f44..66439593c7c 100644 --- a/mysql-test/r/create.result +++ b/mysql-test/r/create.result @@ -1964,6 +1964,7 @@ DROP TABLE B; # # Bug #47107 assert in notify_shared_lock on incorrect # CREATE TABLE , HANDLER +# DROP TABLE IF EXISTS t1; CREATE TABLE t1(f1 integer); # The following CREATE TABLEs before gave an assert. From 334eb7e0af014d8930726ccdce208fd6135c43a5 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 14:03:37 +0100 Subject: [PATCH 095/212] Backport of revno: 3702 Bug #48248 assert in MDL_ticket::upgrade_shared_lock_to_exclusive The assert would happen if REPAIR TABLE was used on a table already locked by LOCK TABLES READ. REPAIR mistakenly tried to upgrade the read-lock to exclusive, thereby triggering the assert. The cause of the problem was that REPAIR TABLE ignored errors from opening and locking tables. This is by design, as REPAIR can be used to broken tables that cannot be opened. However, repair also ignored logical errors such as the inability to exclusivly lock a table due to conflicting LOCK TABLES. This patch fixes the problem by not ignoring errors from opening and locking tables if inside LOCK TABLES mode. In LOCK TABLES we already know that the table can be opened, so that the failure to open must be a logical error. Test added to repair.test. --- mysql-test/r/lock.result | 2 +- mysql-test/r/repair.result | 12 ++++++++++++ mysql-test/r/view.result | 6 +++--- mysql-test/t/repair.test | 15 +++++++++++++++ sql/sql_table.cc | 15 +++++++++++++-- 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index a542d70c5b9..8f680858fdc 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -41,7 +41,7 @@ lock tables t1 write; check table t2; Table Op Msg_type Msg_text test.t2 check Error Table 't2' was not locked with LOCK TABLES -test.t2 check error Corrupt +test.t2 check status Operation failed insert into t1 select index1,nr from t1; ERROR HY000: Table 't1' was not locked with LOCK TABLES unlock tables; diff --git a/mysql-test/r/repair.result b/mysql-test/r/repair.result index 5bb3dd76fed..77eb927a21f 100644 --- a/mysql-test/r/repair.result +++ b/mysql-test/r/repair.result @@ -157,3 +157,15 @@ REPAIR TABLE tt1 USE_FRM; Table Op Msg_type Msg_text tt1 repair error Cannot repair temporary table from .frm file DROP TABLE tt1; +# +# Bug #48248 assert in MDL_ticket::upgrade_shared_lock_to_exclusive +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1(a INT); +LOCK TABLES t1 READ; +REPAIR TABLE t1; +Table Op Msg_type Msg_text +test.t1 repair Error Table 't1' was locked with a READ lock and can't be updated +test.t1 repair status Operation failed +UNLOCK TABLES; +DROP TABLE t1; diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index 5f16d88a0dc..7e9739173df 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -1955,15 +1955,15 @@ CHECK TABLE v1, v2, v3, v4, v5, v6; Table Op Msg_type Msg_text test.v1 check Error FUNCTION test.f1 does not exist test.v1 check Error View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them -test.v1 check error Corrupt +test.v1 check status Operation failed test.v2 check status OK test.v3 check Error FUNCTION test.f1 does not exist test.v3 check Error View 'test.v3' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them -test.v3 check error Corrupt +test.v3 check status Operation failed test.v4 check status OK test.v5 check Error FUNCTION test.f1 does not exist test.v5 check Error View 'test.v5' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them -test.v5 check error Corrupt +test.v5 check status Operation failed test.v6 check status OK create function f1 () returns int return (select max(col1) from t1); DROP TABLE t1; diff --git a/mysql-test/t/repair.test b/mysql-test/t/repair.test index eb2ca7992a6..ec4c9b3cae8 100644 --- a/mysql-test/t/repair.test +++ b/mysql-test/t/repair.test @@ -158,3 +158,18 @@ CREATE TEMPORARY TABLE tt1 (c1 INT); REPAIR TABLE tt1 USE_FRM; DROP TABLE tt1; + +--echo # +--echo # Bug #48248 assert in MDL_ticket::upgrade_shared_lock_to_exclusive +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1(a INT); +LOCK TABLES t1 READ; +REPAIR TABLE t1; + +UNLOCK TABLES; +DROP TABLE t1; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index ddb53dd3754..341d2d16757 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4548,6 +4548,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, char table_name[NAME_LEN*2+2]; char* db = table->db; bool fatal_error=0; + bool open_error; DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name)); DBUG_PRINT("admin", ("extra_open_options: %u", extra_open_options)); @@ -4575,12 +4576,22 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, if (view_operator_func == NULL) table->required_type=FRMTYPE_TABLE; - open_and_lock_tables_derived(thd, table, TRUE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL); + open_error= open_and_lock_tables_derived(thd, table, TRUE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL); thd->no_warnings_for_error= 0; table->next_global= save_next_global; table->next_local= save_next_local; thd->open_options&= ~extra_open_options; + /* + Under locked tables, we know that the table can be opened, + so any errors opening the table are logical errors. + In these cases it does not make sense to try to repair. + */ + if (open_error && thd->locked_tables_mode) + { + result_code= HA_ADMIN_FAILED; + goto send_result; + } #ifdef WITH_PARTITION_STORAGE_ENGINE if (table->table) { From 527813b41344c3e7a3c67f195393d1de3e0f6ed2 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 14:23:31 +0100 Subject: [PATCH 096/212] Backport of revno: 2617.31.30 Bug #21793 Missing CF_CHANGES_DATA and CF_STATUS_COMMAND for handful of commands CF_CHANGES_DATA and CF_STATUS_COMMAND flags added to the commands mentioned in the bug description. With the following two exceptions: 1) 4 commands do not exist: SQLCOM_RENAME_DB SQLCOM_LOAD_MASTER_DATA SQLCOM_LOAD_MASTER_TABLE SQLCOM_SHOW_COLUMN_TYPES 2) All SQLCOM_SHOW_* commands already had CF_STATUS_COMMAND, leaving only SQLCOM_BINLOG_BASE64_EVENT. Further, check_prepared_statement() in sql_prepare.cc has been simplified by taking advantage of the CF_STATUS_COMMAND flag. Note that no test case has been added. --- sql/sql_parse.cc | 91 ++++++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 37 deletions(-) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 056a947f3fa..06f29cd6039 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -197,6 +197,8 @@ void init_update_queries(void) sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; @@ -221,8 +223,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE | - CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; @@ -237,34 +238,35 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_VARIABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_CHARSETS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_COLLATIONS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SHOW_NEW_MASTER]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_BINLOGS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_NEW_MASTER]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_BINLOGS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_SLAVE_HOSTS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_BINLOG_EVENTS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_STORAGE_ENGINES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_AUTHORS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_AUTHORS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CONTRIBUTORS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_PRIVILEGES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_WARNS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; - sql_command_flags[SQLCOM_SHOW_ERRORS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; + sql_command_flags[SQLCOM_SHOW_PRIVILEGES]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_WARNS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; + sql_command_flags[SQLCOM_SHOW_ERRORS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; sql_command_flags[SQLCOM_SHOW_ENGINE_STATUS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_MUTEX]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_LOGS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROCESSLIST]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_MASTER_STAT]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_MASTER_STAT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_SLAVE_STAT]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_TRIGGER]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_BINLOG_BASE64_EVENT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_TABLES]= (CF_STATUS_COMMAND | CF_SHOW_TABLE_COMMAND | @@ -273,37 +275,52 @@ void init_update_queries(void) CF_SHOW_TABLE_COMMAND | CF_REEXECUTION_FRAGILE); + + sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA; + /* The following is used to preserver CF_ROW_COUNT during the a CALL or EXECUTE statement, so the value generated by the last called (or executed) statement is preserved. See mysql_execute_command() for how CF_ROW_COUNT is used. */ - sql_command_flags[SQLCOM_CALL]= CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_EXECUTE]= CF_HAS_ROW_COUNT; + sql_command_flags[SQLCOM_CALL]= CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_EXECUTE]= CF_HAS_ROW_COUNT; /* The following admin table operations are allowed on log tables. */ - sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_OPTIMIZE]= CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_CREATE_USER]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_DROP_USER]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_RENAME_USER]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_REVOKE]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_GRANT]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_USER]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_USER]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_RENAME_USER]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_GRANT]|= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_PROCEDURE]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_FUNCTION]|= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_PRELOAD_KEYS]= CF_AUTO_COMMIT_TRANS; From 832ad46641822b64fbf0e7f0e70b62ef7af504ac Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 9 Dec 2009 14:41:56 +0100 Subject: [PATCH 097/212] Backport of revno: 2617.68.36 --------------------------------------------- This is a patch for bug#47098 assert in MDL_context::destroy on HANDLER OPEN. The assert occurs in MDL_context::destroy when the connection is terminated, because all mdl_tickets have not been released. MERGE tables do not support being opened using the HANDLER ... OPEN command, and trying to do so will result in an error. In the event of an error, all tables that are opened, should be closed again. The fix for bug#45781 made sure that this also works for MERGE tables, which causes multiple tables to be opened. This fix extends the fix for bug#45781, by ensuring that also all locks are released, when MERGE tables are involved. mysql-test/r/merge.result: The result of the test. mysql-test/t/merge.test: Added a test based on the bug report, as well as a test of the more general scenario. sql/sql_handler.cc: Added code to release all the locks. --- mysql-test/r/merge.result | 30 ++++++++++++++++++++++++++++++ mysql-test/t/merge.test | 39 ++++++++++++++++++++++++++++++++++++++- sql/sql_handler.cc | 1 + 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/merge.result b/mysql-test/r/merge.result index 479645744ec..3a6cd4d3f5a 100644 --- a/mysql-test/r/merge.result +++ b/mysql-test/r/merge.result @@ -2502,4 +2502,34 @@ c1 DROP TRIGGER t2_au; DROP FUNCTION f1; DROP TABLE tm1, t1, t2, t3, t4, t5; +# +# Bug47098 assert in MDL_context::destroy on HANDLER +# OPEN +# +# Test that merge tables are closed correctly when opened using +# HANDLER ... OPEN. +# The general case. +DROP TABLE IF EXISTS t1, t2, t3; +# Connection con1. +CREATE TABLE t1 (c1 int); +CREATE TABLE t2 (c1 int); +CREATE TABLE t3 (c1 int) ENGINE = MERGE UNION (t1,t2); +START TRANSACTION; +HANDLER t3 OPEN; +ERROR HY000: Table storage engine for 't3' doesn't have this option +DROP TABLE t1, t2, t3; +# Connection default. +# Disconnecting con1, all mdl_tickets must have been released. +# The bug-specific case. +# Connection con1. +CREATE TABLE t1 (c1 int); +CREATE TABLE t2 (c1 int); +CREATE TABLE t3 (c1 int) ENGINE = MERGE UNION (t1,t2); +DROP TABLE t2; +START TRANSACTION; +HANDLER t3 OPEN; +ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist +DROP TABLE t1, t3; +# Connection default. +# Disconnecting con1, all mdl_tickets must have been released. End of 6.0 tests diff --git a/mysql-test/t/merge.test b/mysql-test/t/merge.test index f7ce6ba700b..2738f79247f 100644 --- a/mysql-test/t/merge.test +++ b/mysql-test/t/merge.test @@ -1985,6 +1985,43 @@ DROP TRIGGER t2_au; DROP FUNCTION f1; DROP TABLE tm1, t1, t2, t3, t4, t5; - +--echo # +--echo # Bug47098 assert in MDL_context::destroy on HANDLER +--echo # OPEN +--echo # +--echo # Test that merge tables are closed correctly when opened using +--echo # HANDLER ... OPEN. +--echo # The general case. +--disable_warnings +DROP TABLE IF EXISTS t1, t2, t3; +--enable_warnings +--echo # Connection con1. +connect (con1,localhost,root,,); +CREATE TABLE t1 (c1 int); +CREATE TABLE t2 (c1 int); +CREATE TABLE t3 (c1 int) ENGINE = MERGE UNION (t1,t2); +START TRANSACTION; +--error ER_ILLEGAL_HA +HANDLER t3 OPEN; +DROP TABLE t1, t2, t3; +--echo # Connection default. +connection default; +--echo # Disconnecting con1, all mdl_tickets must have been released. +disconnect con1; +--echo # The bug-specific case. +--echo # Connection con1. +connect (con1,localhost,root,,); +CREATE TABLE t1 (c1 int); +CREATE TABLE t2 (c1 int); +CREATE TABLE t3 (c1 int) ENGINE = MERGE UNION (t1,t2); +DROP TABLE t2; +START TRANSACTION; +--error ER_WRONG_MRG_TABLE +HANDLER t3 OPEN; +DROP TABLE t1, t3; +--echo # Connection default. +connection default; +--echo # Disconnecting con1, all mdl_tickets must have been released. +disconnect con1; --echo End of 6.0 tests diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 94f6b248e45..cf178342b51 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -321,6 +321,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) can close a single table only. */ close_thread_tables(thd); + thd->mdl_context.release_all_locks(); my_error(ER_ILLEGAL_HA, MYF(0), hash_tables->alias); error= TRUE; } From f3e9b392ac8dcdd79951a1e16b63415fc48fce0a Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 15:25:48 +0100 Subject: [PATCH 098/212] Backport of revno: 2617.68.13 Introduce a counter for protection against global read lock on thread level. The functions for protection against global read lock sometimes need a local variable to signal when the protection is set, and hence need to be released. It would be better to control this behaviour via a counter on the THD struct, telling how many times the protection has been claimed by the current thread. A side-effect of the fix is that if protection is claimed twice for a thread, only a simple increment is required for the second claim, instead of a mutex-protected increment of the global variable protect_against_global_read_lock. sql/lock.cc: Count how many times that we have claimed protection against global read lock. Assert that we really have the protection when releasing it. Added comments to all functions operating on global_read_lock. sql/sql_class.cc: Added the counter variable global_read_lock_protection. sql/sql_class.h: Added the counter variable global_read_lock_protection. sql/sql_parse.cc: Replaced test on local variable need_start_waiting with test on thd->global_read_lock_protection. sql/sql_table.cc: Replaced test on local variable need_start_waiting with test on thd->global_read_lock_protection. sql/sql_trigger.cc: Inserted test on thd->global_read_lock_protection. --- sql/lock.cc | 93 ++++++++++++++++++++++++++++++++++++++++++++++ sql/sql_class.cc | 1 + sql/sql_class.h | 1 + sql/sql_parse.cc | 37 +++++++----------- sql/sql_table.cc | 9 ++--- sql/sql_trigger.cc | 6 +-- 6 files changed, 115 insertions(+), 32 deletions(-) diff --git a/sql/lock.cc b/sql/lock.cc index 8d314c4ad19..420868a25c4 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -1098,6 +1098,19 @@ static volatile uint waiting_for_read_lock=0; #define GOT_GLOBAL_READ_LOCK 1 #define MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT 2 +/** + Take global read lock, wait if there is protection against lock. + + If the global read lock is already taken by this thread, then nothing is done. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. + + @retval False Success, global read lock set, commits are NOT blocked. + @retval True Failure, thread was killed. +*/ + bool lock_global_read_lock(THD *thd) { DBUG_ENTER("lock_global_read_lock"); @@ -1164,6 +1177,16 @@ bool lock_global_read_lock(THD *thd) } +/** + Unlock global read lock. + + Commits may or may not be blocked when this function is called. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. +*/ + void unlock_global_read_lock(THD *thd) { uint tmp; @@ -1190,6 +1213,25 @@ void unlock_global_read_lock(THD *thd) DBUG_VOID_RETURN; } +/** + Wait if the global read lock is set, and optionally seek protection against + global read lock. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. + @param abort_on_refresh If True, abort waiting if a refresh occurs, + do NOT seek protection against GRL. + If False, wait until the GRL is released and seek + protection against GRL. + @param is_not_commit If False, called from a commit operation, + wait only if commit blocking is also enabled. + + @retval False Success, protection against global read lock is set + (if !abort_on_refresh) + @retval True Failure, wait was aborted or thread was killed. +*/ + #define must_wait (global_read_lock && \ (is_not_commit || \ global_read_lock_blocks_commit)) @@ -1201,6 +1243,16 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, bool result= 0, need_exit_cond; DBUG_ENTER("wait_if_global_read_lock"); + /* + If we already have protection against global read lock, + just increment the counter. + */ + if (unlikely(thd->global_read_lock_protection > 0)) + { + if (!abort_on_refresh) + thd->global_read_lock_protection++; + DBUG_RETURN(FALSE); + } /* Assert that we do not own LOCK_open. If we would own it, other threads could not close their tables. This would make a pretty @@ -1237,7 +1289,12 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, result=1; } if (!abort_on_refresh && !result) + { + thd->global_read_lock_protection++; protect_against_global_read_lock++; + DBUG_PRINT("sql_lock", ("protect_against_global_read_lock incr: %u", + protect_against_global_read_lock)); + } /* The following is only true in case of a global read locks (which is rare) and if old_message is set @@ -1250,10 +1307,31 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, } +/** + Release protection against global read lock and restart + global read lock waiters. + + Should only be called if we have protection against global read lock. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. +*/ + void start_waiting_global_read_lock(THD *thd) { bool tmp; DBUG_ENTER("start_waiting_global_read_lock"); + /* + Ignore request if we do not have protection against global read lock. + (Note that this is a violation of the interface contract, hence the assert). + */ + DBUG_ASSERT(thd->global_read_lock_protection > 0); + if (unlikely(thd->global_read_lock_protection == 0)) + DBUG_VOID_RETURN; + /* Decrement local read lock protection counter, return if we still have it */ + if (unlikely(--thd->global_read_lock_protection > 0)) + DBUG_VOID_RETURN; if (unlikely(thd->global_read_lock)) DBUG_VOID_RETURN; (void) pthread_mutex_lock(&LOCK_global_read_lock); @@ -1267,6 +1345,21 @@ void start_waiting_global_read_lock(THD *thd) } +/** + Make global read lock also block commits. + + The scenario is: + - This thread has the global read lock. + - Global read lock blocking of commits is not set. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. + + @retval False Success, global read lock set, commits are blocked. + @retval True Failure, thread was killed. +*/ + bool make_global_read_lock_block_commit(THD *thd) { bool error; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index d83b60810ab..d42a06b3811 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -452,6 +452,7 @@ THD::THD() examined_row_count(0), warning_info(&main_warning_info), stmt_da(&main_da), + global_read_lock_protection(0), global_read_lock(0), is_fatal_error(0), transaction_rollback_request(0), diff --git a/sql/sql_class.h b/sql/sql_class.h index f4504bfed0d..889d7c5472b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1874,6 +1874,7 @@ public: ulong rand_saved_seed1, rand_saved_seed2; pthread_t real_id; /* For debugging */ my_thread_id thread_id; + uint global_read_lock_protection;// GRL protection count uint tmp_table, global_read_lock; uint server_status,open_options; enum enum_thread_type system_thread; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 06f29cd6039..211788768f9 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1762,7 +1762,6 @@ int mysql_execute_command(THD *thd) { int res= FALSE; - bool need_start_waiting= FALSE; // have protection against global read lock int up_result= 0; LEX *lex= thd->lex; /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ @@ -2039,7 +2038,7 @@ mysql_execute_command(THD *thd) break; if (!thd->locked_tables_mode && lex->protect_against_global_read_lock && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + wait_if_global_read_lock(thd, 0, 1)) break; res= execute_sqlcom_select(thd, all_tables); @@ -2309,10 +2308,9 @@ case SQLCOM_PREPARE: read lock when it succeeds. This needs to be released by start_waiting_global_read_lock(). We protect the normal CREATE TABLE in the same way. That way we avoid that a new table is - created during a gobal read lock. + created during a global read lock. */ - if (!thd->locked_tables_mode && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) { res= 1; goto end_with_restore_list; @@ -2617,8 +2615,7 @@ end_with_restore_list: "INDEX DIRECTORY"); create_info.data_file_name= create_info.index_file_name= NULL; - if (!thd->locked_tables_mode && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) { res= 1; break; @@ -2852,8 +2849,7 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (update_precheck(thd, all_tables)) break; - if (!thd->locked_tables_mode && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) goto error; DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); @@ -2891,7 +2887,7 @@ end_with_restore_list: */ if (!thd->locked_tables_mode && lex->sql_command == SQLCOM_UPDATE_MULTI && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + wait_if_global_read_lock(thd, 0, 1)) goto error; res= mysql_multi_update_prepare(thd); @@ -2993,8 +2989,7 @@ end_with_restore_list: if ((res= insert_precheck(thd, all_tables))) break; - if (!thd->locked_tables_mode && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) { res= 1; break; @@ -3033,8 +3028,7 @@ end_with_restore_list: unit->set_limit(select_lex); - if (! thd->locked_tables_mode && - ! (need_start_waiting= ! wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) { res= 1; break; @@ -3104,7 +3098,7 @@ end_with_restore_list: ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); goto error; } - if (!(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (wait_if_global_read_lock(thd, 0, 1)) goto error; res= mysql_truncate(thd, first_table, 0); break; @@ -3116,8 +3110,7 @@ end_with_restore_list: DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); - if (!thd->locked_tables_mode && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) { res= 1; break; @@ -3137,8 +3130,7 @@ end_with_restore_list: (TABLE_LIST *)thd->lex->auxiliary_table_list.first; multi_delete *del_result; - if (!thd->locked_tables_mode && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) { res= 1; break; @@ -3282,8 +3274,7 @@ end_with_restore_list: if (check_one_table_access(thd, privilege, all_tables)) goto error; - if (!thd->locked_tables_mode && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) goto error; res= mysql_load(thd, lex->exchange, first_table, lex->field_list, @@ -3357,7 +3348,7 @@ end_with_restore_list: FALSE, UINT_MAX, FALSE)) goto error; if (lex->protect_against_global_read_lock && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + wait_if_global_read_lock(thd, 0, 1)) goto error; init_mdl_requests(all_tables); @@ -4575,7 +4566,7 @@ error: res= TRUE; finish: - if (need_start_waiting) + if (thd->global_read_lock_protection > 0) { /* Release the protection against the global read lock and wake diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 341d2d16757..5500bc286b8 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1780,16 +1780,16 @@ void write_bin_log(THD *thd, bool clear_error, bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, my_bool drop_temporary) { - bool error= FALSE, need_start_waiting= FALSE; + bool error; Drop_table_error_handler err_handler(thd->get_internal_handler()); + DBUG_ENTER("mysql_rm_table"); /* mark for close and remove all cached entries */ if (!drop_temporary) { - if (!thd->locked_tables_mode && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) DBUG_RETURN(TRUE); } @@ -1797,8 +1797,7 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0); thd->pop_internal_handler(); - - if (need_start_waiting) + if (thd->global_read_lock_protection > 0) start_waiting_global_read_lock(thd); if (error) diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 015e0d4daa1..b3108cae3d9 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -328,7 +328,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) TABLE *table; bool result= TRUE; String stmt_query; - bool need_start_waiting= FALSE; bool lock_upgrade_done= FALSE; MDL_ticket *mdl_ticket= NULL; @@ -386,8 +385,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) LOCK_open is not enough because global read lock is held without holding LOCK_open). */ - if (!thd->locked_tables_mode && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) DBUG_RETURN(TRUE); if (!create) @@ -521,7 +519,7 @@ end: if (thd->locked_tables_mode && tables && lock_upgrade_done) mdl_ticket->downgrade_exclusive_lock(); - if (need_start_waiting) + if (thd->global_read_lock_protection > 0) start_waiting_global_read_lock(thd); if (!result) From 0bd574102208166774c79c8ffda40c6fc3779389 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 9 Dec 2009 16:13:00 +0100 Subject: [PATCH 099/212] Backport of revno: 2617.68.18 Bug #42147 Concurrent DML and LOCK TABLE ... READ for InnoDB table cause warnings in errlog Concurrent execution of LOCK TABLES ... READ statement and DML statements affecting the same InnoDB table on debug builds of MySQL server might lead to "Found lock of type 6 that is write and read locked" warnings appearing in error log. The problem is that the table-level locking code allows a thread to acquire TL_READ_NO_INSERT lock on a table even if there is another thread which holds TL_WRITE_ALLOW_WRITE lock on the same table. At the same time, the locking code assumes that that such locks are incompatible (for example, see check_locks()). This doesn't lead to any problems other than warnings in error log for debug builds of server since for InnoDB tables TL_READ_NO_INSERT type of lock is only used for LOCK TABLES and for this statement InnoDB also performs its own table-level locking. Unfortunately, the table lock compatibility matrix cannot be updated to disallow TL_READ_NO_INSERT when another thread holds TL_WRITE_ALLOW_WRITE without causing starvation of LOCK TABLE READ in InnoDB under high write load. This patch therefore contains no code changes. The issue will be fixed later when LOCK TABLE READ has been updated to not use table locks. This bug will therefore be marked as "To be fixed later". Code comment in thr_lock.c expanded to clarify the issue and a test case based on the bug description added to innodb_mysql_lock.test. Note that a global suppression rule has been added to both MTR v1 and v2 for the "Found lock of type 6 that is write and read locked" warning. These suppression rules must be removed once this bug is properly fixed. --- mysql-test/include/mtr_warnings.sql | 8 ++++ mysql-test/lib/v1/mtr_report.pl | 3 ++ mysql-test/r/innodb_mysql_lock.result | 32 ++++++++++++++ mysql-test/t/innodb_mysql_lock.test | 63 +++++++++++++++++++++++++++ mysys/thr_lock.c | 32 +++++++++++--- 5 files changed, 131 insertions(+), 7 deletions(-) diff --git a/mysql-test/include/mtr_warnings.sql b/mysql-test/include/mtr_warnings.sql index e03e83efac2..14f1dd97830 100644 --- a/mysql-test/include/mtr_warnings.sql +++ b/mysql-test/include/mtr_warnings.sql @@ -186,6 +186,14 @@ INSERT INTO global_suppressions VALUES (": The MySQL server is running with the --secure-backup-file-priv option so it cannot execute this statement"), ("Slave: Unknown table 't1' Error_code: 1051"), + /* + BUG#42147 - Concurrent DML and LOCK TABLE ... READ for InnoDB + table cause warnings in errlog + Note: This is a temporary suppression until Bug#42147 can be + fixed properly. See bug page for more information. + */ + ("Found lock of type 6 that is write and read locked"), + ("THE_LAST_SUPPRESSION")|| diff --git a/mysql-test/lib/v1/mtr_report.pl b/mysql-test/lib/v1/mtr_report.pl index 3c78c3ca064..36aba983c34 100644 --- a/mysql-test/lib/v1/mtr_report.pl +++ b/mysql-test/lib/v1/mtr_report.pl @@ -376,6 +376,9 @@ sub mtr_report_stats ($) { /Slave: Can't DROP 'c7'.* 1091/ or /Slave: Key column 'c6'.* 1072/ or + # Warnings generated until bug#42147 is properly resolved + /Found lock of type 6 that is write and read locked/ or + # rpl_idempotency.test produces warnings for the slave. ($testname eq 'rpl.rpl_idempotency' and (/Slave: Can\'t find record in \'t1\' Error_code: 1032/ or diff --git a/mysql-test/r/innodb_mysql_lock.result b/mysql-test/r/innodb_mysql_lock.result index 147267d5550..374f67358eb 100644 --- a/mysql-test/r/innodb_mysql_lock.result +++ b/mysql-test/r/innodb_mysql_lock.result @@ -21,4 +21,36 @@ INSERT INTO t1 VALUES (2); ERROR 40001: Deadlock found when trying to get lock; try restarting transaction # Cleanup commit; +set @@autocommit=1; commit; +set @@autocommit=1; +set @@autocommit=1; +# +# Bug #42147 Concurrent DML and LOCK TABLE ... READ for InnoDB +# table cause warnings in errlog +# +# +# Note that this test for now relies on a global suppression of +# the warning "Found lock of type 6 that is write and read locked" +# This suppression rule can be removed once Bug#42147 is properly +# fixed. See bug page for more info. +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (i INT) engine= innodb; +# Connection 2 +# Get user-level lock +SELECT get_lock('bug42147_lock', 60); +get_lock('bug42147_lock', 60) +1 +# Connection 1 +INSERT INTO t1 SELECT get_lock('bug42147_lock', 60); +# Connection 2 +LOCK TABLES t1 READ; +SELECT release_lock('bug42147_lock'); +release_lock('bug42147_lock') +1 +# Connection 1 +# Connection 2 +UNLOCK TABLES; +# Connection 1 +DROP TABLE t1; diff --git a/mysql-test/t/innodb_mysql_lock.test b/mysql-test/t/innodb_mysql_lock.test index daee94bedb5..c8c38cd1ab1 100644 --- a/mysql-test/t/innodb_mysql_lock.test +++ b/mysql-test/t/innodb_mysql_lock.test @@ -1,5 +1,8 @@ -- source include/have_innodb.inc +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + --echo # --echo # Bug #22876 Four-way deadlock --echo # @@ -51,8 +54,68 @@ INSERT INTO t1 VALUES (2); connection con2; --reap commit; +set @@autocommit=1; connection con1; commit; +set @@autocommit=1; connection con3; --reap +set @@autocommit=1; connection default; + +disconnect con1; +disconnect con3; + +--echo # +--echo # Bug #42147 Concurrent DML and LOCK TABLE ... READ for InnoDB +--echo # table cause warnings in errlog +--echo # + +--echo # +--echo # Note that this test for now relies on a global suppression of +--echo # the warning "Found lock of type 6 that is write and read locked" +--echo # This suppression rule can be removed once Bug#42147 is properly +--echo # fixed. See bug page for more info. +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (i INT) engine= innodb; + +--echo # Connection 2 +--echo # Get user-level lock +connection con2; +SELECT get_lock('bug42147_lock', 60); + +--echo # Connection 1 +connection default; +--send INSERT INTO t1 SELECT get_lock('bug42147_lock', 60) + +--echo # Connection 2 +connection con2; +let $wait_condition= + SELECT COUNT(*) > 0 FROM information_schema.processlist + WHERE state = 'User lock' + AND info = 'INSERT INTO t1 SELECT get_lock(\'bug42147_lock\', 60)'; +--source include/wait_condition.inc +LOCK TABLES t1 READ; +SELECT release_lock('bug42147_lock'); + +--echo # Connection 1 +connection default; +--reap + +--echo # Connection 2 +connection con2; +UNLOCK TABLES; + +--echo # Connection 1 +connection default; +disconnect con2; +DROP TABLE t1; + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index 6433c04a96f..a3f3e9ee080 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -540,13 +540,31 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, /* Request for READ lock */ if (lock->write.data) { - /* We can allow a read lock even if there is already a write lock - on the table in one the following cases: - - This thread alread have a write lock on the table - - The write lock is TL_WRITE_ALLOW_READ or TL_WRITE_DELAYED - and the read lock is TL_READ_HIGH_PRIORITY or TL_READ - - The write lock is TL_WRITE_CONCURRENT_INSERT or TL_WRITE_ALLOW_WRITE - and the read lock is not TL_READ_NO_INSERT + /* + We can allow a read lock even if there is already a + write lock on the table if they are owned by the same + thread or if they satisfy the following lock + compatibility matrix: + + Request + /------- + H|++++ WRITE_ALLOW_WRITE + e|+++- WRITE_ALLOW_READ + l|+++- WRITE_CONCURRENT_INSERT + d|++++ WRITE_DELAYED + |||| + |||\= READ_NO_INSERT + ||\ = READ_HIGH_PRIORITY + |\ = READ_WITH_SHARED_LOCKS + \ = READ + + + = Request can be satisified. + - = Request cannot be satisified. + + READ_NO_INSERT and WRITE_ALLOW_WRITE should in principle + be incompatible. However this will cause starvation of + LOCK TABLE READ in InnoDB under high write load. + See Bug#42147 for more information. */ DBUG_PRINT("lock",("write locked 1 by thread: 0x%lx", From 2f26574026f7520b922aae5e4d62b45c137563a9 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 9 Dec 2009 18:48:42 +0300 Subject: [PATCH 100/212] Backport of: ------------------------------------------------------------ revno: 2617.68.7 committer: Dmitry Lenev branch nick: mysql-next-bg46044 timestamp: Thu 2009-08-27 10:22:17 +0400 message: Fix for bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY FOR UPDATE". Deadlock occured when during execution of query to I_S we tried to open a table or its .FRM in order to get information about it and had to wait because we have encountered exclusive metadata lock on this table held by a DDL operation from another connection which in its turn waited for some resource currently owned by connection executing this I_S query. For example, this might have happened if one under LOCK TABLES executed I_S query targeted to particular table (which was not among locked) and also concurrently tried to create this table using CREATE TABLE SELECT which had to wait for one of tables locked by the first connection. Another situation in which deadlock might have occured is when I_S query, which was executed as part of transaction, tried to get information about table which just has been dropped by concurrent DROP TABLES executed under LOCK TABLES and this DROP TABLES for its completion also had to wait transaction from the first connection. This problem stemmed from the fact that opening of tables/.FRMs for I_S filling is happening outside of connection's main MDL_context so code which tries to detect deadlocks due to conflicting metadata locks doesn't work in this case. Indeed, this led to deadlocks when during I_S filling we tried to wait for conflicting metadata lock to go away, while its owner was waiting for some resource held by connection executing I_S query. This patch solves this problem by avoiding waiting in such situation. Instead we skip this table and produce warning that information about it was omitted from I_S due to concurrent DDL operation. We still wait for conflicting metadata lock to go away when it is known that deadlock is not possible (i.e. when connection executing I_S query does not hold any metadata or table-level locks). Basically, we apply our standard deadlock avoidance technique for metadata locks to the process of filling of I_S tables but replace ER_LOCK_DEADLOCK error with a warning. Note that this change is supposed to be safe for 'mysqldump' since the only its mode which is affected by this change is --single-transaction mode is not safe in the presence of concurrent DDL anyway (and this fact is documented). Other modes are unaffected because they either use SHOW TABLES/SELECT * FROM I_S.TABLE_NAMES which do not take any metadata locks in the process of I_S table filling and thus cannot skip tables or execute I_S queries for tables which were previously locked by LOCK TABLES (or in the presence of global read lock) which excludes possibility of encountering conflicting metadata lock. mysql-test/r/mdl_sync.result: Added test for bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY FOR UPDATE". mysql-test/t/mdl_sync.test: Added test for bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY FOR UPDATE". sql/mysql_priv.h: Added a new flag for open_table() call which allows it to fail with an error in cases when conflicting metadata lock is discovered instead of waiting until this lock goes away. sql/share/errmsg-utf8.txt: Added error/warning message to be generated in cases when information about table is omitted from I_S since there is conflicting metadata lock on the table. sql/share/errmsg.txt: Added error/warning message to be generated in cases when information about table is omitted from I_S since there is conflicting metadata lock on the table. sql/sql_base.cc: Added a new flag for open_table() call which allows it to fail with an error in cases when conflicting metadata lock is discovered instead of waiting until this lock goes away. sql/sql_show.cc: When we are opening a table (or just .FRM) in order to fill I_S with information about this table and encounter conflicting metadata lock waiting for this lock to go away can lead to a deadlock in some situations (under LOCK TABLES, within transaction, etc.). To avoid these deadlocks we detect such situations and don't do waiting. Instead, we skip table for which we have conflicting metadata lock, thus omitting information about it from I_S table, and produce an appropriate warning. --- mysql-test/r/mdl_sync.result | 111 +++++++++++++++++++ mysql-test/t/mdl_sync.test | 202 +++++++++++++++++++++++++++++++++++ sql/mysql_priv.h | 2 + sql/share/errmsg-utf8.txt | 5 +- sql/share/errmsg.txt | 2 + sql/sql_base.cc | 5 +- sql/sql_show.cc | 84 ++++++++++++--- 7 files changed, 391 insertions(+), 20 deletions(-) diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index 36451985a86..f63179b893a 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -62,3 +62,114 @@ unlock tables; # Reap INSERT. # Clean-up. drop tables t1, t2, t3, t5; +# +# Bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY +# FOR UPDATE" +# +drop tables if exists t1, t2; +create table t1 (i int); +# Let us check that we won't deadlock if during filling +# of I_S table we encounter conflicting metadata lock +# which owner is in its turn waiting for our connection. +lock tables t1 write; +# Switching to connection 'con46044'. +# Sending: +create table t2 select * from t1;; +# Switching to connection 'default'. +# Waiting until CREATE TABLE ... SELECT ... is blocked. +# First let us check that SHOW FIELDS/DESCRIBE doesn't +# gets blocked and emits and error. +show fields from t2; +ERROR HY000: Table 'test'.'t2' was skipped since its definition is being modified by concurrent DDL statement +# Now test for I_S query which reads only .FRMs. +# +# Query below should only emit a warning. +select column_name from information_schema.columns +where table_schema='test' and table_name='t2'; +column_name +Warnings: +Warning 1652 Table 'test'.'t2' was skipped since its definition is being modified by concurrent DDL statement +# Finally, test for I_S query which does full-blown table open. +# +# Query below should not be blocked. Warning message should be +# stored in the 'table_comment' column. +select table_name, table_type, auto_increment, table_comment +from information_schema.tables where table_schema='test' and table_name='t2'; +table_name table_type auto_increment table_comment +t2 BASE TABLE NULL Table 'test'.'t2' was skipped since its definition is being modified by concurre +# Switching to connection 'default'. +unlock tables; +# Switching to connection 'con46044'. +# Reaping CREATE TABLE ... SELECT ... . +drop table t2; +# +# Let us also check that queries to I_S wait for conflicting metadata +# locks to go away instead of skipping table with a warning in cases +# when deadlock is not possible. This is a nice thing from compatibility +# and ease of use points of view. +# +# We check same three queries to I_S in this new situation. +# Switching to connection 'con46044_2'. +lock tables t1 write; +# Switching to connection 'con46044'. +# Sending: +create table t2 select * from t1;; +# Switching to connection 'default'. +# Waiting until CREATE TABLE ... SELECT ... is blocked. +# Let us check that SHOW FIELDS/DESCRIBE gets blocked. +# Sending: +show fields from t2;; +# Switching to connection 'con46044_2'. +# Wait until SHOW FIELDS gets blocked. +unlock tables; +# Switching to connection 'con46044'. +# Reaping CREATE TABLE ... SELECT ... . +# Switching to connection 'default'. +# Reaping SHOW FIELDS ... +Field Type Null Key Default Extra +i int(11) YES NULL +drop table t2; +# Switching to connection 'con46044_2'. +lock tables t1 write; +# Switching to connection 'con46044'. +# Sending: +create table t2 select * from t1;; +# Switching to connection 'default'. +# Waiting until CREATE TABLE ... SELECT ... is blocked. +# Check that I_S query which reads only .FRMs gets blocked. +# Sending: +select column_name from information_schema.columns where table_schema='test' and table_name='t2';; +# Switching to connection 'con46044_2'. +# Wait until SELECT COLUMN_NAME FROM I_S.COLUMNS gets blocked. +unlock tables; +# Switching to connection 'con46044'. +# Reaping CREATE TABLE ... SELECT ... . +# Switching to connection 'default'. +# Reaping SELECT COLUMN_NAME FROM I_S.COLUMNS +column_name +i +drop table t2; +# Switching to connection 'con46044_2'. +lock tables t1 write; +# Switching to connection 'con46044'. +# Sending: +create table t2 select * from t1;; +# Switching to connection 'default'. +# Waiting until CREATE TABLE ... SELECT ... is blocked. +# Finally, check that I_S query which does full-blown table open +# also gets blocked. +# Sending: +select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t2';; +# Switching to connection 'con46044_2'. +# Wait until SELECT ... FROM I_S.TABLES gets blocked. +unlock tables; +# Switching to connection 'con46044'. +# Reaping CREATE TABLE ... SELECT ... . +# Switching to connection 'default'. +# Reaping SELECT ... FROM I_S.TABLES +table_name table_type auto_increment table_comment +t2 BASE TABLE NULL +drop table t2; +# Switching to connection 'default'. +# Clean-up. +drop table t1; diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test index 124e76bbc1f..cc03cc67f95 100644 --- a/mysql-test/t/mdl_sync.test +++ b/mysql-test/t/mdl_sync.test @@ -141,6 +141,208 @@ disconnect con2root; drop tables t1, t2, t3, t5; +--echo # +--echo # Bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY +--echo # FOR UPDATE" +--echo # +--disable_warnings +drop tables if exists t1, t2; +--enable_warnings +connect (con46044, localhost, root,,); +connect (con46044_2, localhost, root,,); +connection default; +create table t1 (i int); + +--echo # Let us check that we won't deadlock if during filling +--echo # of I_S table we encounter conflicting metadata lock +--echo # which owner is in its turn waiting for our connection. +lock tables t1 write; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Sending: +--send create table t2 select * from t1; + +--echo # Switching to connection 'default'. +connection default; +--echo # Waiting until CREATE TABLE ... SELECT ... is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "create table t2 select * from t1"; +--source include/wait_condition.inc + +--echo # First let us check that SHOW FIELDS/DESCRIBE doesn't +--echo # gets blocked and emits and error. +--error ER_WARN_I_S_SKIPPED_TABLE +show fields from t2; + +--echo # Now test for I_S query which reads only .FRMs. +--echo # +--echo # Query below should only emit a warning. +select column_name from information_schema.columns + where table_schema='test' and table_name='t2'; + +--echo # Finally, test for I_S query which does full-blown table open. +--echo # +--echo # Query below should not be blocked. Warning message should be +--echo # stored in the 'table_comment' column. +select table_name, table_type, auto_increment, table_comment + from information_schema.tables where table_schema='test' and table_name='t2'; + +--echo # Switching to connection 'default'. +connection default; +unlock tables; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Reaping CREATE TABLE ... SELECT ... . +--reap +drop table t2; + +--echo # +--echo # Let us also check that queries to I_S wait for conflicting metadata +--echo # locks to go away instead of skipping table with a warning in cases +--echo # when deadlock is not possible. This is a nice thing from compatibility +--echo # and ease of use points of view. +--echo # +--echo # We check same three queries to I_S in this new situation. + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +lock tables t1 write; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Sending: +--send create table t2 select * from t1; + +--echo # Switching to connection 'default'. +connection default; +--echo # Waiting until CREATE TABLE ... SELECT ... is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "create table t2 select * from t1"; +--source include/wait_condition.inc + +--echo # Let us check that SHOW FIELDS/DESCRIBE gets blocked. +--echo # Sending: +--send show fields from t2; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +--echo # Wait until SHOW FIELDS gets blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "show fields from t2"; +--source include/wait_condition.inc + +unlock tables; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Reaping CREATE TABLE ... SELECT ... . +--reap + +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SHOW FIELDS ... +--reap +drop table t2; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +lock tables t1 write; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Sending: +--send create table t2 select * from t1; + +--echo # Switching to connection 'default'. +connection default; +--echo # Waiting until CREATE TABLE ... SELECT ... is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "create table t2 select * from t1"; +--source include/wait_condition.inc + +--echo # Check that I_S query which reads only .FRMs gets blocked. +--echo # Sending: +--send select column_name from information_schema.columns where table_schema='test' and table_name='t2'; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +--echo # Wait until SELECT COLUMN_NAME FROM I_S.COLUMNS gets blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info like "select column_name from information_schema.columns%"; +--source include/wait_condition.inc + +unlock tables; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Reaping CREATE TABLE ... SELECT ... . +--reap + +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SELECT COLUMN_NAME FROM I_S.COLUMNS +--reap +drop table t2; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +lock tables t1 write; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Sending: +--send create table t2 select * from t1; + +--echo # Switching to connection 'default'. +connection default; +--echo # Waiting until CREATE TABLE ... SELECT ... is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "create table t2 select * from t1"; +--source include/wait_condition.inc + +--echo # Finally, check that I_S query which does full-blown table open +--echo # also gets blocked. +--echo # Sending: +--send select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t2'; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +--echo # Wait until SELECT ... FROM I_S.TABLES gets blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info like "select table_name, table_type, auto_increment, table_comment from information_schema.tables%"; +--source include/wait_condition.inc + +unlock tables; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Reaping CREATE TABLE ... SELECT ... . +--reap + +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SELECT ... FROM I_S.TABLES +--reap +drop table t2; + +--echo # Switching to connection 'default'. +connection default; +--echo # Clean-up. +disconnect con46044; +disconnect con46044_2; +drop table t1; + # Check that all connections opened by test cases in this file are really # gone so execution of other tests won't be affected by their presence. --source include/wait_until_count_sessions.inc diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index e61c9a923c3..03a4462f199 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -2071,6 +2071,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, #define MYSQL_OPEN_GET_NEW_TABLE 0x0080 /** Don't look up the table in the list of temporary tables. */ #define MYSQL_OPEN_SKIP_TEMPORARY 0x0100 +/** Fail instead of waiting when conficting metadata lock is discovered. */ +#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0200 /** Please refer to the internals manual. */ #define MYSQL_OPEN_REOPEN (MYSQL_LOCK_IGNORE_FLUSH |\ diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 4260efdeb56..70fae60d051 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -6236,12 +6236,13 @@ ER_COND_ITEM_TOO_LONG eng "Data too long for condition item '%s'" ER_UNKNOWN_LOCALE - eng "Unknown locale: '%-.64s'" - + eng "Unknown locale: '%-.64s'" ER_SLAVE_IGNORE_SERVER_IDS eng "The requested server id %d clashes with the slave startup option --replicate-same-server-id" ER_QUERY_CACHE_DISABLED eng "Query cache is disabled; restart the server with query_cache_type=1 to enable it" +ER_WARN_I_S_SKIPPED_TABLE + eng "Table '%s'.'%s' was skipped since its definition is being modified by concurrent DDL statement" ER_SAME_NAME_PARTITION_FIELD eng "Duplicate partition field name '%-.192s'" ER_PARTITION_COLUMN_LIST_ERROR diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 93385292c24..0f8a43407fa 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -6242,6 +6242,8 @@ ER_UNKNOWN_LOCALE ER_SLAVE_IGNORE_SERVER_IDS eng "The requested server id %d clashes with the slave startup option --replicate-same-server-id" +ER_WARN_I_S_SKIPPED_TABLE + eng "Table '%s'.'%s' was skipped since its definition is being modified by concurrent DDL statement" ER_SAME_NAME_PARTITION_FIELD eng "Duplicate partition field name '%-.192s'" ER_PARTITION_COLUMN_LIST_ERROR diff --git a/sql/sql_base.cc b/sql/sql_base.cc index f9514a055ca..d1ce4aefb1f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2350,7 +2350,10 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, return 1; if (mdl_request->ticket == NULL) { - (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT); + if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT) + my_error(ER_WARN_I_S_SKIPPED_TABLE, MYF(0), table_list->db, table_list->table_name); + else + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT); return 1; } } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index d346bae5258..fe941321dcd 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2867,6 +2867,10 @@ make_table_name_list(THD *thd, List *table_names, LEX *lex, @param[in] thd thread handler @param[in] tables TABLE_LIST for I_S table @param[in] schema_table pointer to I_S structure + @param[in] can_deadlock Indicates that deadlocks are possible + due to metadata locks, so to avoid + them we should not wait in case if + conflicting lock is present. @param[in] open_tables_state_backup pointer to Open_tables_state object which is used to save|restore original status of variables related to @@ -2880,6 +2884,7 @@ make_table_name_list(THD *thd, List *table_names, LEX *lex, static int fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, ST_SCHEMA_TABLE *schema_table, + bool can_deadlock, Open_tables_state *open_tables_state_backup) { LEX *lex= thd->lex; @@ -2908,7 +2913,9 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, */ lex->sql_command= SQLCOM_SHOW_FIELDS; res= open_normal_and_derived_tables(thd, show_table_list, - MYSQL_LOCK_IGNORE_FLUSH); + (MYSQL_LOCK_IGNORE_FLUSH | + (can_deadlock ? + MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0))); lex->sql_command= save_sql_command; /* get_all_tables() returns 1 on failure and 0 on success thus @@ -3047,12 +3054,16 @@ uint get_table_open_method(TABLE_LIST *tables, /** - Acquire high priority share metadata lock on a table. + Try acquire high priority share metadata lock on a table (with + optional wait for conflicting locks to go away). @param thd Thread context. @param mdl_request Pointer to memory to be used for MDL_request object for a lock request. @param table Table list element for the table + @param can_deadlock Indicates that deadlocks are possible due to + metadata locks, so to avoid them we should not + wait in case if conflicting lock is present. @note This is an auxiliary function to be used in cases when we want to access table's description by looking up info in TABLE_SHARE without @@ -3060,19 +3071,21 @@ uint get_table_open_method(TABLE_LIST *tables, @note This function assumes that there are no other metadata lock requests in the current metadata locking context. - @retval FALSE Success + @retval FALSE No error, if lock was obtained TABLE_LIST::mdl_request::ticket + is set to non-NULL value. @retval TRUE Some error occured (probably thread was killed). */ static bool -acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table) +try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table, + bool can_deadlock) { bool error; table->mdl_request.init(MDL_TABLE, table->db, table->table_name, MDL_SHARED_HIGH_PRIO); while (!(error= thd->mdl_context.try_acquire_shared_lock(&table->mdl_request)) && - !table->mdl_request.ticket) + !table->mdl_request.ticket && !can_deadlock) { MDL_request_list mdl_requests; mdl_requests.push_front(&table->mdl_request); @@ -3092,6 +3105,10 @@ acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table) @param[in] db_name database name @param[in] table_name table name @param[in] schema_table_idx I_S table index + @param[in] can_deadlock Indicates that deadlocks are possible + due to metadata locks, so to avoid + them we should not wait in case if + conflicting lock is present. @return Operation status @retval 0 Table is processed and we can continue @@ -3104,13 +3121,14 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, ST_SCHEMA_TABLE *schema_table, LEX_STRING *db_name, LEX_STRING *table_name, - enum enum_schema_tables schema_table_idx) + enum enum_schema_tables schema_table_idx, + bool can_deadlock) { TABLE_SHARE *share; TABLE tbl; TABLE_LIST table_list; uint res= 0; - int error; + int not_used; char key[MAX_DBKEY_LENGTH]; uint key_length; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; @@ -3143,7 +3161,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, simply obtaining internal lock of data-dictionary (ATM it is LOCK_open) instead of obtaning full-blown metadata lock. */ - if (acquire_high_prio_shared_mdl_lock(thd, &table_list)) + if (try_acquire_high_prio_shared_mdl_lock(thd, &table_list, can_deadlock)) { /* Some error occured (most probably we have been killed while @@ -3153,14 +3171,30 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, return 1; } + if (! table_list.mdl_request.ticket) + { + /* + We are in situation when we have encountered conflicting metadata + lock and deadlocks can occur due to waiting for it to go away. + So instead of waiting skip this table with an appropriate warning. + */ + DBUG_ASSERT(can_deadlock); + + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_I_S_SKIPPED_TABLE, + ER(ER_WARN_I_S_SKIPPED_TABLE), + table_list.db, table_list.table_name); + return 0; + } + key_length= create_table_def_key(thd, key, &table_list, 0); pthread_mutex_lock(&LOCK_open); share= get_table_share(thd, &table_list, key, - key_length, OPEN_VIEW, &error); + key_length, OPEN_VIEW, ¬_used); if (!share) { res= 0; - goto err_unlock; + goto end_unlock; } if (share->is_view) @@ -3169,7 +3203,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, { /* skip view processing */ res= 0; - goto err_share; + goto end_share; } else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL) { @@ -3178,7 +3212,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, open_normal_and_derived_tables() */ res= 1; - goto err_share; + goto end_share; } } @@ -3194,15 +3228,16 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, res= schema_table->process_table(thd, &table_list, table, res, db_name, table_name); closefrm(&tbl, true); - goto err_unlock; + goto end_unlock; } -err_share: +end_share: release_table_share(share); -err_unlock: +end_unlock: pthread_mutex_unlock(&LOCK_open); +end: thd->mdl_context.release_lock(table_list.mdl_request.ticket); thd->clear_error(); return res; @@ -3254,8 +3289,20 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) Security_context *sctx= thd->security_ctx; #endif uint table_open_method; + bool can_deadlock; DBUG_ENTER("get_all_tables"); + /* + In cases when SELECT from I_S table being filled by this call is + part of statement which also uses other tables or is being executed + under LOCK TABLES or is part of transaction which also uses other + tables waiting for metadata locks which happens below might result + in deadlocks. + To avoid them we don't wait if conflicting metadata lock is + encountered and skip table with emitting an appropriate warning. + */ + can_deadlock= thd->mdl_context.has_locks(); + lex->view_prepare_mode= TRUE; lex->reset_n_backup_query_tables_list(&query_tables_list_backup); @@ -3275,6 +3322,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) if (lsel && lsel->table_list.first) { error= fill_schema_show_cols_or_idxs(thd, tables, schema_table, + can_deadlock, &open_tables_state_backup); goto err; } @@ -3390,7 +3438,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) !with_i_schema) { if (!fill_schema_table_from_frm(thd, table, schema_table, db_name, - table_name, schema_table_idx)) + table_name, schema_table_idx, + can_deadlock)) continue; } @@ -3415,7 +3464,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) show_table_list->i_s_requested_object= schema_table->i_s_requested_object; res= open_normal_and_derived_tables(thd, show_table_list, - MYSQL_LOCK_IGNORE_FLUSH); + (MYSQL_LOCK_IGNORE_FLUSH | + (can_deadlock ? MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0))); lex->sql_command= save_sql_command; /* XXX: show_table_list has a flag i_is_requested, From e001a9f0d076b50dd35eb21d5f03619dffc14410 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 9 Dec 2009 18:56:34 +0300 Subject: [PATCH 101/212] Backport of: ------------------------------------------------------------ revno: 2617.68.10 committer: Dmitry Lenev branch nick: mysql-next-bg46673 timestamp: Tue 2009-09-01 19:57:05 +0400 message: Fix for bug #46673 "Deadlock between FLUSH TABLES WITH READ LOCK and DML". Deadlocks occured when one concurrently executed transactions with several statements modifying data and FLUSH TABLES WITH READ LOCK statement or SET READ_ONLY=1 statement. These deadlocks were introduced by the patch for WL 4284: "Transactional DDL locking"/Bug 989: "If DROP TABLE while there's an active transaction, wrong binlog order" which has changed FLUSH TABLES WITH READ LOCK/SET READ_ONLY=1 to wait for pending transactions. What happened was that FLUSH TABLES WITH READ LOCK blocked all further statements changing tables by setting global_read_lock global variable and has started waiting for all pending transactions to complete. Then one of those transactions tried to executed DML, detected that global_read_lock non-zero and tried to wait until global read lock will be released (i.e. global_read_lock becomes 0), indeed, this led to a deadlock. Proper solution for this problem should probably involve full integration of global read lock with metadata locking subsystem (which will allow to implement waiting for pending transactions without blocking DML in them). But since it requires significant changes another, short-term solution for the problem is implemented in this patch. Basically, this patch restores behavior of FLUSH TABLES WITH READ LOCK/ SET READ_ONLY=1 before the patch for WL 4284/bug 989. By ensuring that extra references to TABLE_SHARE are not stored for active metadata locks it changes these statements not to wait for pending transactions. As result deadlock is eliminated. Note that this does not change the fact that active FLUSH TABLES WITH READ LOCK lock or SET READ_ONLY=1 prevent modifications to tables as they also block transaction commits. mysql-test/r/flush_block_commit.result: Adjusted test case after change in FLUSH TABLES WITH READ LOCK behavior - it is no longer blocked by a pending transaction. mysql-test/r/mdl_sync.result: Added test for bug #46673 "Deadlock between FLUSH TABLES WITH READ LOCK and DML". mysql-test/r/read_only_innodb.result: Adjusted test case after change in SET READ_ONLY behavior - it is no longer blocked by a pending transaction. mysql-test/t/flush_block_commit.test: Adjusted test case after change in FLUSH TABLES WITH READ LOCK behavior - it is no longer blocked by a pending transaction. mysql-test/t/mdl_sync.test: Added test for bug #46673 "Deadlock between FLUSH TABLES WITH READ LOCK and DML". mysql-test/t/read_only_innodb.test: Adjusted test case after change in SET READ_ONLY behavior - it is no longer blocked by a pending transaction. sql/sql_base.cc: Disable caching of pointers to TABLE_SHARE objects in MDL subsystem. This means that transactions holding metadata lock on the table will no longer have extra reference to the TABLE_SHARE (due to this lock) and will no longer block concurrent FLUSH TABLES/FLUSH TABLES WITH READ LOCK. Note that this does not change the fact that FLUSH TABLES WITH READ LOCK prevents concurrent transactions from modifying data as it also blocks all commits. --- mysql-test/r/flush_block_commit.result | 2 +- mysql-test/r/mdl_sync.result | 21 +++++++++++++++ mysql-test/r/read_only_innodb.result | 6 +++-- mysql-test/t/flush_block_commit.test | 17 ++++++------ mysql-test/t/mdl_sync.test | 37 ++++++++++++++++++++++++++ mysql-test/t/read_only_innodb.test | 14 ++-------- sql/sql_base.cc | 6 +++++ 7 files changed, 79 insertions(+), 24 deletions(-) diff --git a/mysql-test/r/flush_block_commit.result b/mysql-test/r/flush_block_commit.result index da09d07b813..7062d05c2d7 100644 --- a/mysql-test/r/flush_block_commit.result +++ b/mysql-test/r/flush_block_commit.result @@ -17,9 +17,9 @@ COMMIT; # Verify that 'con1' was blocked and data did not move. SELECT * FROM t1; a -1 UNLOCK TABLES; # Switch to connection con1 +# Reaping COMMIT # Switch to connection con1 BEGIN; SELECT * FROM t1 FOR UPDATE; diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index f63179b893a..d409157a70b 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -173,3 +173,24 @@ drop table t2; # Switching to connection 'default'. # Clean-up. drop table t1; +# +# Test for bug #46673 "Deadlock between FLUSH TABLES WITH READ LOCK +# and DML". +# +drop tables if exists t1; +create table t1 (i int); +# Switching to connection 'con46673'. +begin; +insert into t1 values (1); +# Switching to connection 'default'. +# Statement below should not get blocked. And if after some +# changes to code it is there should not be a deadlock between +# it and transaction from connection 'con46673'. +flush tables with read lock; +unlock tables; +# Switching to connection 'con46673'. +delete from t1 where i = 1; +commit; +# Switching to connection 'default'. +# Clean-up +drop table t1; diff --git a/mysql-test/r/read_only_innodb.result b/mysql-test/r/read_only_innodb.result index 4cba98900a1..690de085bf9 100644 --- a/mysql-test/r/read_only_innodb.result +++ b/mysql-test/r/read_only_innodb.result @@ -7,10 +7,12 @@ insert into table_11733 values(11733); set global read_only=1; select @@global.read_only; @@global.read_only -0 +1 select * from table_11733 ; -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +a +11733 COMMIT; +ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement set global read_only=0; drop table table_11733 ; drop user test@localhost; diff --git a/mysql-test/t/flush_block_commit.test b/mysql-test/t/flush_block_commit.test index 98bca8cdad7..0b3bede1684 100644 --- a/mysql-test/t/flush_block_commit.test +++ b/mysql-test/t/flush_block_commit.test @@ -29,26 +29,25 @@ BEGIN; INSERT INTO t1 VALUES(1); --echo # Switch to connection con2 connection con2; ---send FLUSH TABLES WITH READ LOCK +FLUSH TABLES WITH READ LOCK; --echo # Switch to connection con1 connection con1; --echo # Sending: -COMMIT; +--send COMMIT --echo # Switch to connection con2 connection con2; ---reap --echo # Wait until COMMIT gets blocked. -#let $wait_condition= -# select count(*) = 1 from information_schema.processlist -# where state = "Waiting for release of readlock" and info = "COMMIT"; -#--source include/wait_condition.inc +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for release of readlock" and info = "COMMIT"; +--source include/wait_condition.inc --echo # Verify that 'con1' was blocked and data did not move. SELECT * FROM t1; UNLOCK TABLES; --echo # Switch to connection con1 connection con1; -#--echo # Reaping COMMIT -#--reap +--echo # Reaping COMMIT +--reap # No deadlock ? diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test index cc03cc67f95..d50c056fda3 100644 --- a/mysql-test/t/mdl_sync.test +++ b/mysql-test/t/mdl_sync.test @@ -343,6 +343,43 @@ disconnect con46044; disconnect con46044_2; drop table t1; + +--echo # +--echo # Test for bug #46673 "Deadlock between FLUSH TABLES WITH READ LOCK +--echo # and DML". +--echo # +--disable_warnings +drop tables if exists t1; +--enable_warnings +connect (con46673, localhost, root,,); +connection default; +create table t1 (i int); + +--echo # Switching to connection 'con46673'. +connection con46673; +begin; +insert into t1 values (1); + +--echo # Switching to connection 'default'. +connection default; +--echo # Statement below should not get blocked. And if after some +--echo # changes to code it is there should not be a deadlock between +--echo # it and transaction from connection 'con46673'. +flush tables with read lock; +unlock tables; + +--echo # Switching to connection 'con46673'. +connection con46673; +delete from t1 where i = 1; +commit; + +--echo # Switching to connection 'default'. +connection default; +--echo # Clean-up +disconnect con46673; +drop table t1; + + # Check that all connections opened by test cases in this file are really # gone so execution of other tests won't be affected by their presence. --source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/read_only_innodb.test b/mysql-test/t/read_only_innodb.test index 98e704e25c7..9e001f2b997 100644 --- a/mysql-test/t/read_only_innodb.test +++ b/mysql-test/t/read_only_innodb.test @@ -16,8 +16,6 @@ DROP TABLE IF EXISTS table_11733 ; grant CREATE, SELECT, DROP on *.* to test@localhost; connect (con1,localhost,test,,test); -connect (con2,localhost,root,,); - connection default; set global read_only=0; @@ -30,22 +28,15 @@ BEGIN; insert into table_11733 values(11733); connection default; -send set global read_only=1; - -connection con2; -let $wait_condition= - select count(*) = 1 from information_schema.processlist - where state = "Flushing tables" and info = "set global read_only=1"; ---source include/wait_condition.inc +set global read_only=1; connection con1; select @@global.read_only; ---error ER_LOCK_DEADLOCK select * from table_11733 ; +--error ER_OPTION_PREVENTS_STATEMENT COMMIT; connection default; -reap; set global read_only=0; drop table table_11733 ; drop user test@localhost; @@ -90,6 +81,5 @@ DROP TABLE t1; DROP USER test@localhost; disconnect con1; -disconnect con2; --echo echo End of 5.1 tests diff --git a/sql/sql_base.cc b/sql/sql_base.cc index d1ce4aefb1f..ad618eb59fe 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2648,7 +2648,9 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, DBUG_RETURN(FALSE); } +#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL if (!(share= (TABLE_SHARE *) mdl_ticket->get_cached_object())) +#endif { if (!(share= get_table_share_with_create(thd, table_list, key, key_length, OPEN_VIEW, @@ -2714,13 +2716,16 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) goto err_unlock; +#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL /* We are going to to store extra reference to the share in MDL-subsystem so we need to increase reference counter; */ reference_table_share(share); mdl_ticket->set_cached_object(share, table_share_release_hook); +#endif } +#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL else { if (table_list->view) @@ -2741,6 +2746,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, */ reference_table_share(share); } +#endif if (share->version != refresh_version) { From b9d2f55a9d98c2a8fe4d67de51e7e7b84d2b7440 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 9 Dec 2009 19:00:46 +0300 Subject: [PATCH 102/212] ------------------------------------------------------------ revno: 2617.68.23 committer: Dmitry Lenev branch nick: mysql-next-bg-pre1 timestamp: Wed 2009-09-16 09:34:42 +0400 message: Pre-requisite patch for fixing bug #30977 "Concurrent statement using stored function and DROP FUNCTION breaks SBR". CREATE TABLE SELECT statements take exclusive metadata lock on table being created. Invariant of metadata locking subsystem states that such lock should be taken before taking any kind of shared locks. Once metadata locks on stored routines are introduced statements like "CREATE TABLE ... SELECT f1()" will break this invariant by taking shared locks on routines before exclusive lock on target table. To avoid this, open_tables() is reworked to process tables which are directly used by the statement before stored routines are processed. sql/sql_base.cc: Refactored open_tables() implementation to process stored routines only after tables which are directly used by statement were processed. To achieve this moved handling of routines in open_tables() out of loop which iterates over tables to a new separate loop. And in its turn this allowed to split handling of particular table or view to an auxiliary function, which made code in open_tables() simpler and more easy to understand. --- sql/sql_base.cc | 724 +++++++++++++++++++++++++----------------------- 1 file changed, 380 insertions(+), 344 deletions(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index ad618eb59fe..20353f0c9f8 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3753,16 +3753,13 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) /* - Perform steps of prelocking algorithm for elements of the - prelocking set other than tables. E.g. cache routines and, if - prelocking strategy prescribes so, extend the prelocking set with - tables and routines used by them. + Handle element of prelocking set other than table. E.g. cache routine + and, if prelocking strategy prescribes so, extend the prelocking set + with tables and routines used by it. @param[in] thd Thread context. @param[in] prelocking_ctx Prelocking context. - @param[in] start First element in the list representing - subset of the prelocking set to be - processed. + @param[in] rt Element of prelocking set to be processed. @param[in] prelocking_strategy Strategy which specifies how the prelocking set should be extended when one of its elements is processed. @@ -3775,46 +3772,293 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) */ static bool -open_routines(THD *thd, Query_tables_list *prelocking_ctx, - Sroutine_hash_entry *start, - Prelocking_strategy *prelocking_strategy, - bool *need_prelocking) +open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, + Prelocking_strategy *prelocking_strategy, + bool *need_prelocking) { - DBUG_ENTER("open_routines"); + int type= rt->key.str[0]; - for (Sroutine_hash_entry *rt= start; rt; rt= rt->next) + DBUG_ENTER("open_and_process_routine"); + + switch (type) { - int type= rt->key.str[0]; - - switch (type) + case TYPE_ENUM_FUNCTION: + case TYPE_ENUM_PROCEDURE: { - case TYPE_ENUM_FUNCTION: - case TYPE_ENUM_PROCEDURE: + sp_name name(thd, rt->key.str, rt->key.length); + sp_head *sp; + + if (sp_cache_routine(thd, type, &name, &sp)) + DBUG_RETURN(TRUE); + + if (sp) { - sp_name name(thd, rt->key.str, rt->key.length); - sp_head *sp; - - if (sp_cache_routine(thd, type, &name, &sp)) - DBUG_RETURN(TRUE); - - if (sp) - { - prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp, - need_prelocking); - } + prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp, + need_prelocking); } - break; - case TYPE_ENUM_TRIGGER: - break; - default: - /* Impossible type value. */ - DBUG_ASSERT(0); } + break; + case TYPE_ENUM_TRIGGER: + break; + default: + /* Impossible type value. */ + DBUG_ASSERT(0); } DBUG_RETURN(FALSE); } +/** + Handle table list element by obtaining metadata lock, opening table or view + and, if prelocking strategy prescribes so, extending the prelocking set with + tables and routines used by it. + + @param[in] thd Thread context. + @param[in] lex LEX structure for statement. + @param[in] tables Table list element to be processed. + @param[in,out] counter Number of tables which are open. + @param[in] flags Bitmap of flags to modify how the tables + will be open, see open_table() description + for details. + @param[in] prelocking_strategy Strategy which specifies how the + prelocking set should be extended + when table or view is processed. + @param[in] has_prelocking_list Indicates that prelocking set/list for + this statement has already been built. + @param[in] ot_ctx Context used to recover from a failed + open_table() attempt. + @param[in] new_frm_mem Temporary MEM_ROOT to be used for + parsing .FRMs for views. + + @retval FALSE Success. + @retval TRUE Error, reported unless there is a chance to recover from it. +*/ + +static bool +open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, + uint *counter, uint flags, + Prelocking_strategy *prelocking_strategy, + bool has_prelocking_list, + Open_table_context *ot_ctx, + MEM_ROOT *new_frm_mem) +{ + bool error= FALSE; + bool safe_to_ignore_table= FALSE; + DBUG_ENTER("open_and_process_table"); + + /* + Ignore placeholders for derived tables. After derived tables + processing, link to created temporary table will be put here. + If this is derived table for view then we still want to process + routines used by this view. + */ + if (tables->derived) + { + if (!tables->view) + goto end; + /* + We restore view's name and database wiped out by derived tables + processing and fall back to standard open process in order to + obtain proper metadata locks and do other necessary steps like + stored routine processing. + */ + tables->db= tables->view_db.str; + tables->db_length= tables->view_db.length; + tables->table_name= tables->view_name.str; + tables->table_name_length= tables->view_name.length; + } + /* + If this TABLE_LIST object is a placeholder for an information_schema + table, create a temporary table to represent the information_schema + table in the query. Do not fill it yet - will be filled during + execution. + */ + if (tables->schema_table) + { + /* + If this information_schema table is merged into a mergeable + view, ignore it for now -- it will be filled when its respective + TABLE_LIST is processed. This code works only during re-execution. + */ + if (tables->view) + goto process_view_routines; + if (!mysql_schema_table(thd, lex, tables) && + !check_and_update_table_version(thd, tables, tables->table->s)) + { + goto end; + } + error= TRUE; + goto end; + } + DBUG_PRINT("tcache", ("opening table: '%s'.'%s' item: %p", + tables->db, tables->table_name, tables)); //psergey: invalid read of size 1 here + (*counter)++; + + /* Not a placeholder: must be a base table or a view. Let us open it. */ + DBUG_ASSERT(!tables->table); + + if (tables->prelocking_placeholder) + { + /* + For the tables added by the pre-locking code, attempt to open + the table but fail silently if the table does not exist. + The real failure will occur when/if a statement attempts to use + that table. + */ + Prelock_error_handler prelock_handler; + thd->push_internal_handler(& prelock_handler); + error= open_table(thd, tables, new_frm_mem, ot_ctx, flags); + thd->pop_internal_handler(); + safe_to_ignore_table= prelock_handler.safely_trapped_errors(); + } + else + error= open_table(thd, tables, new_frm_mem, ot_ctx, flags); + + free_root(new_frm_mem, MYF(MY_KEEP_PREALLOC)); + + if (error) + { + if (! ot_ctx->can_recover_from_failed_open_table() && safe_to_ignore_table) + { + DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'", + tables->db, tables->alias)); + error= FALSE; + } + goto end; + } + + /* + We can't rely on simple check for TABLE_LIST::view to determine + that this is a view since during re-execution we might reopen + ordinary table in place of view and thus have TABLE_LIST::view + set from repvious execution and TABLE_LIST::table set from + current. + */ + if (!tables->table && tables->view) + { + /* VIEW placeholder */ + (*counter)--; + + /* + tables->next_global list consists of two parts: + 1) Query tables and underlying tables of views. + 2) Tables used by all stored routines that this statement invokes on + execution. + We need to know where the bound between these two parts is. If we've + just opened a view, which was the last table in part #1, and it + has added its base tables after itself, adjust the boundary pointer + accordingly. + */ + if (lex->query_tables_own_last == &(tables->next_global) && + tables->view->query_tables) + lex->query_tables_own_last= tables->view->query_tables_last; + /* + Let us free memory used by 'sroutines' hash here since we never + call destructor for this LEX. + */ + my_hash_free(&tables->view->sroutines); + goto process_view_routines; + } + + /* + Special types of open can succeed but still don't set + TABLE_LIST::table to anything. + */ + if (tables->open_strategy && !tables->table) + goto end; + + /* + If we are not already in prelocked mode and extended table list is not + yet built we might have to build the prelocking set for this statement. + + Since currently no prelocking strategy prescribes doing anything for + tables which are only read, we do below checks only if table is going + to be changed. + */ + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && + ! has_prelocking_list && + tables->lock_type >= TL_WRITE_ALLOW_WRITE) + { + bool need_prelocking= FALSE; + TABLE_LIST **save_query_tables_last= lex->query_tables_last; + /* + Extend statement's table list and the prelocking set with + tables and routines according to the current prelocking + strategy. + + For example, for DML statements we need to add tables and routines + used by triggers which are going to be invoked for this element of + table list and also add tables required for handling of foreign keys. + */ + error= prelocking_strategy->handle_table(thd, lex, tables, + &need_prelocking); + + if (need_prelocking && ! lex->requires_prelocking()) + lex->mark_as_requiring_prelocking(save_query_tables_last); + + if (error) + goto end; + } + + if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables_mode) + { + if (tables->lock_type == TL_WRITE_DEFAULT) + tables->table->reginfo.lock_type= thd->update_lock_default; + else if (tables->lock_type == TL_READ_DEFAULT) + tables->table->reginfo.lock_type= + read_lock_type_for_table(thd, tables->table); + else + tables->table->reginfo.lock_type= tables->lock_type; + } + tables->table->grant= tables->grant; + + /* Check and update metadata version of a base table. */ + error= check_and_update_table_version(thd, tables, tables->table->s); + + if (error) + goto end; + /* + After opening a MERGE table add the children to the query list of + tables, so that they are opened too. + Note that placeholders don't have the handler open. + */ + /* MERGE tables need to access parent and child TABLE_LISTs. */ + DBUG_ASSERT(tables->table->pos_in_table_list == tables); + /* Non-MERGE tables ignore this call. */ + if (tables->table->file->extra(HA_EXTRA_ADD_CHILDREN_LIST)) + { + error= TRUE; + goto end; + } + +process_view_routines: + /* + Again we may need cache all routines used by this view and add + tables used by them to table list. + */ + if (tables->view && + thd->locked_tables_mode <= LTM_LOCK_TABLES && + ! has_prelocking_list) + { + bool need_prelocking= FALSE; + TABLE_LIST **save_query_tables_last= lex->query_tables_last; + + error= prelocking_strategy->handle_view(thd, lex, tables, + &need_prelocking); + + if (need_prelocking && ! lex->requires_prelocking()) + lex->mark_as_requiring_prelocking(save_query_tables_last); + + if (error) + goto end; + } + +end: + DBUG_RETURN(error); +} + + /** Open all tables in list @@ -3847,26 +4091,21 @@ open_routines(THD *thd, Query_tables_list *prelocking_ctx, bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, Prelocking_strategy *prelocking_strategy) { - TABLE_LIST *tables= NULL; + /* + We use pointers to "next_global" member in the last processed TABLE_LIST + element and to the "next" member in the last processed Sroutine_hash_entry + element as iterators over, correspondingly, the table list and stored routines + list which stay valid and allow to continue iteration when new elements are + added to the tail of the lists. + */ + TABLE_LIST **table_to_open; + Sroutine_hash_entry **sroutine_to_open; + TABLE_LIST *tables; Open_table_context ot_ctx(thd); bool error= FALSE; MEM_ROOT new_frm_mem; - /* Also used for indicating that prelocking is need */ - TABLE_LIST **query_tables_last_own; - bool safe_to_ignore_table; + bool has_prelocking_list= thd->lex->requires_prelocking(); DBUG_ENTER("open_tables"); - /* - temporary mem_root for new .frm parsing. - TODO: variables for size - */ - init_sql_alloc(&new_frm_mem, 8024, 8024); - - thd->current_tablenr= 0; - restart: - *counter= 0; - query_tables_last_own= 0; - thd_proc_info(thd, "Opening tables"); - /* Close HANDLER tables which are marked for flush or against which there are pending exclusive metadata locks. Note that we do this not to avoid @@ -3880,316 +4119,116 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, mysql_ha_flush(thd); /* - If we are not already executing prelocked statement and don't have - statement for which table list for prelocking is already built, let - us cache routines and try to build such table list. + temporary mem_root for new .frm parsing. + TODO: variables for size */ + init_sql_alloc(&new_frm_mem, 8024, 8024); - if (thd->locked_tables_mode <= LTM_LOCK_TABLES && - !thd->lex->requires_prelocking() && - thd->lex->uses_stored_routines()) - { - bool need_prelocking= FALSE; - TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; - - DBUG_ASSERT(thd->lex->query_tables == *start); - - error= open_routines(thd, thd->lex, - (Sroutine_hash_entry *)thd->lex->sroutines_list.first, - prelocking_strategy, &need_prelocking); - if (error) - { - /* - Serious error during reading stored routines from mysql.proc table. - Something's wrong with the table or its contents, and an error has - been emitted; we must abort. - */ - goto err; - } - else if (need_prelocking) - { - query_tables_last_own= save_query_tables_last; - *start= thd->lex->query_tables; - } - } + thd->current_tablenr= 0; + restart: + table_to_open= start; + sroutine_to_open= (Sroutine_hash_entry**) &thd->lex->sroutines_list.first; + *counter= 0; + thd_proc_info(thd, "Opening tables"); /* - For every table in the list of tables to open, try to find or open - a table. + Perform steps of prelocking algorithm until there are unprocessed + elements in prelocking list/set. */ - for (tables= *start; tables ;tables= tables->next_global) + while (*table_to_open || + (thd->locked_tables_mode <= LTM_LOCK_TABLES && + ! has_prelocking_list && + *sroutine_to_open)) { - safe_to_ignore_table= FALSE; - /* - Ignore placeholders for derived tables. After derived tables - processing, link to created temporary table will be put here. - If this is derived table for view then we still want to process - routines used by this view. - */ - if (tables->derived) - { - if (!tables->view) - continue; - /* - We restore view's name and database wiped out by derived tables - processing and fall back to standard open process in order to - obtain proper metadata locks and do other necessary steps like - stored routine processing. - */ - tables->db= tables->view_db.str; - tables->db_length= tables->view_db.length; - tables->table_name= tables->view_name.str; - tables->table_name_length= tables->view_name.length; - } - /* - If this TABLE_LIST object is a placeholder for an information_schema - table, create a temporary table to represent the information_schema - table in the query. Do not fill it yet - will be filled during - execution. + For every table in the list of tables to open, try to find or open + a table. */ - if (tables->schema_table) + for (tables= *table_to_open; tables; + table_to_open= &tables->next_global, tables= tables->next_global) { - /* - If this information_schema table is merged into a mergeable - view, ignore it for now -- it will be filled when its respective - TABLE_LIST is processed. This code works only during re-execution. - */ - if (tables->view) - goto process_view_routines; - if (!mysql_schema_table(thd, thd->lex, tables) && - !check_and_update_table_version(thd, tables, tables->table->s)) + error= open_and_process_table(thd, thd->lex, tables, counter, + flags, prelocking_strategy, + has_prelocking_list, &ot_ctx, + &new_frm_mem); + + if (error) { - continue; + if (ot_ctx.can_recover_from_failed_open_table()) + { + /* + We have met exclusive metadata lock or old version of table. + Now we have to close all tables and release metadata locks. + We also have to throw away set of prelocked tables (and thus + close tables from this set that were open by now) since it + is possible that one of tables which determined its content + was changed. + + Instead of implementing complex/non-robust logic mentioned + above we simply close and then reopen all tables. + + We have to save pointer to table list element for table which we + have failed to open since closing tables can trigger removal of + elements from the table list (if MERGE tables are involved), + */ + TABLE_LIST *failed_table= *table_to_open; + close_tables_for_reopen(thd, start); + /* + Here we rely on the fact that 'tables' still points to the valid + TABLE_LIST element. Altough currently this assumption is valid + it may change in future. + */ + if (ot_ctx.recover_from_failed_open_table_attempt(thd, failed_table)) + goto err; + + error= FALSE; + goto restart; + } + goto err; } - DBUG_RETURN(TRUE); } - DBUG_PRINT("tcache", ("opening table: '%s'.'%s' item: 0x%lx", - tables->db, tables->table_name, (long) tables)); - (*counter)++; - /* Not a placeholder: must be a base table or a view. Let us open it. */ - DBUG_ASSERT(!tables->table); - - if (tables->prelocking_placeholder) + /* + If we are not already in prelocked mode and extended table list is + not yet built for our statement we need to cache routines it uses + and build the prelocking list for it. + */ + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && ! has_prelocking_list) { + bool need_prelocking= FALSE; + TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; /* - For the tables added by the pre-locking code, attempt to open - the table but fail silently if the table does not exist. - The real failure will occur when/if a statement attempts to use - that table. + Process elements of the prelocking set which are present there + since parsing stage or were added to it by invocations of + Prelocking_strategy methods in the above loop over tables. + + For example, if element is a routine, cache it and then, + if prelocking strategy prescribes so, add tables it uses to the + table list and routines it might invoke to the prelocking set. */ - Prelock_error_handler prelock_handler; - thd->push_internal_handler(& prelock_handler); - error= open_table(thd, tables, &new_frm_mem, &ot_ctx, flags); - thd->pop_internal_handler(); - safe_to_ignore_table= prelock_handler.safely_trapped_errors(); - } - else - error= open_table(thd, tables, &new_frm_mem, &ot_ctx, flags); - - free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); - - if (error) - { - if (ot_ctx.can_recover_from_failed_open_table()) + for (Sroutine_hash_entry *rt= *sroutine_to_open; rt; + sroutine_to_open= &rt->next, rt= rt->next) { - /* - We have met exclusive metadata lock or old version of table. Now we - have to close all tables which are not up to date/release metadata - locks. We also have to throw away set of prelocked tables (and thus - close tables from this set that were open by now) since it possible - that one of tables which determined its content was changed. + error= open_and_process_routine(thd, thd->lex, rt, + prelocking_strategy, + &need_prelocking); - Instead of implementing complex/non-robust logic mentioned - above we simply close and then reopen all tables. - - In order to prepare for recalculation of set of prelocked tables - we pretend that we have finished calculation which we were doing - currently. - */ - if (query_tables_last_own) - thd->lex->mark_as_requiring_prelocking(query_tables_last_own); - close_tables_for_reopen(thd, start); - /* - Here we rely on the fact that 'tables' still points to the valid - TABLE_LIST element. Altough currently this assumption is valid - it may change in future. - */ - if (ot_ctx.recover_from_failed_open_table_attempt(thd, tables)) + if (error) + { + /* + Serious error during reading stored routines from mysql.proc table. + Something is wrong with the table or its contents, and an error has + been emitted; we must abort. + */ goto err; - - error= FALSE; - goto restart; + } } - if (safe_to_ignore_table) - { - DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'", - tables->db, tables->alias)); - error= FALSE; - continue; - } - goto err; - } + if (need_prelocking && ! thd->lex->requires_prelocking()) + thd->lex->mark_as_requiring_prelocking(save_query_tables_last); - /* - We can't rely on simple check for TABLE_LIST::view to determine - that this is a view since during re-execution we might reopen - ordinary table in place of view and thus have TABLE_LIST::view - set from repvious execution and TABLE_LIST::table set from - current. - */ - if (!tables->table && tables->view) - { - /* VIEW placeholder */ - (*counter)--; - - /* - tables->next_global list consists of two parts: - 1) Query tables and underlying tables of views. - 2) Tables used by all stored routines that this statement invokes on - execution. - We need to know where the bound between these two parts is. If we've - just opened a view, which was the last table in part #1, and it - has added its base tables after itself, adjust the boundary pointer - accordingly. - */ - if (query_tables_last_own == &(tables->next_global) && - tables->view->query_tables) - query_tables_last_own= tables->view->query_tables_last; - /* - Let us free memory used by 'sroutines' hash here since we never - call destructor for this LEX. - */ - my_hash_free(&tables->view->sroutines); - goto process_view_routines; - } - - /* - Special types of open can succeed but still don't set - TABLE_LIST::table to anything. - */ - if (tables->open_strategy && !tables->table) - continue; - - /* - If we are not already in prelocked mode and extended table list is not - yet built we might have to build the prelocking set for this statement. - - Since currently no prelocking strategy prescribes doing anything for - tables which are only read, we do below checks only if table is going - to be changed. - */ - if (thd->locked_tables_mode <= LTM_LOCK_TABLES && - !thd->lex->requires_prelocking() && - tables->lock_type >= TL_WRITE_ALLOW_WRITE) - { - bool need_prelocking= FALSE; - bool not_used; - TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; - Sroutine_hash_entry **sroutines_next= - (Sroutine_hash_entry **)thd->lex->sroutines_list.next; - - /* - Extend statement's table list and the prelocking set with - tables and routines according to the current prelocking - strategy. - - For example, for DML statements we need to add tables and routines - used by triggers which are going to be invoked for this element of - table list and also add tables required for handling of foreign keys. - */ - error= prelocking_strategy->handle_table(thd, thd->lex, tables, - &need_prelocking); - - if (need_prelocking && ! query_tables_last_own) - query_tables_last_own= save_query_tables_last; - - if (error) - goto err; - - /* - Process elements of the prelocking set which were added - by the above invocation of Prelocking_strategy method. - - For example, if new element is a routine, cache it and then, if - prelocking strategy prescribes so, add tables it uses to the table - list and routines it might invoke to the prelocking set. - */ - error= open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy, - ¬_used); - if (error) - goto err; - } - - if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables_mode) - { - if (tables->lock_type == TL_WRITE_DEFAULT) - tables->table->reginfo.lock_type= thd->update_lock_default; - else if (tables->lock_type == TL_READ_DEFAULT) - tables->table->reginfo.lock_type= - read_lock_type_for_table(thd, tables->table); - else - tables->table->reginfo.lock_type= tables->lock_type; - } - tables->table->grant= tables->grant; - - /* Check and update metadata version of a base table. */ - error= check_and_update_table_version(thd, tables, tables->table->s); - - if (error) - goto err; - /* - After opening a MERGE table add the children to the query list of - tables, so that they are opened too. - Note that placeholders don't have the handler open. - */ - /* MERGE tables need to access parent and child TABLE_LISTs. */ - DBUG_ASSERT(tables->table->pos_in_table_list == tables); - /* Non-MERGE tables ignore this call. */ - if (tables->table->file->extra(HA_EXTRA_ADD_CHILDREN_LIST)) - { - error= TRUE; - goto err; - } - -process_view_routines: - /* - Again we may need cache all routines used by this view and add - tables used by them to table list. - */ - if (tables->view && - thd->locked_tables_mode <= LTM_LOCK_TABLES && - !thd->lex->requires_prelocking()) - { - bool need_prelocking= FALSE; - bool not_used; - TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; - Sroutine_hash_entry **sroutines_next= - (Sroutine_hash_entry **)thd->lex->sroutines_list.next; - - error= prelocking_strategy->handle_view(thd, thd->lex, tables, - &need_prelocking); - - if (need_prelocking && ! query_tables_last_own) - query_tables_last_own= save_query_tables_last; - - if (error) - goto err; - - error= open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy, - ¬_used); - - if (error) - { - /* - Serious error during reading stored routines from mysql.proc table. - Something is wrong with the table or its contents, and an error has - been emitted; we must abort. - */ - goto err; - } + if (need_prelocking && ! *start) + *start= thd->lex->query_tables; } } @@ -4220,12 +4259,9 @@ err: thd_proc_info(thd, 0); free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block - if (query_tables_last_own) - thd->lex->mark_as_requiring_prelocking(query_tables_last_own); - - if (error && tables) + if (error && *table_to_open) { - tables->table= NULL; + (*table_to_open)->table= NULL; } DBUG_PRINT("open_tables", ("returning: %d", (int) error)); DBUG_RETURN(error); From 634a81094288b9e47649ceed749d59af18a97e26 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 9 Dec 2009 19:11:26 +0300 Subject: [PATCH 103/212] Backport of: ------------------------------------------------------------ revno: 2617.68.24 committer: Dmitry Lenev branch nick: mysql-next-bg-pre2-2 timestamp: Wed 2009-09-16 17:25:29 +0400 message: Pre-requisite patch for fixing bug #30977 "Concurrent statement using stored function and DROP FUNCTION breaks SBR". Added MDL_request for stored routine as member to Sroutine_hash_entry in order to be able perform metadata locking for stored routines in future (Sroutine_hash_entry is an equivalent of TABLE_LIST class for stored routines). (WL#4284, follow up fixes). sql/mdl.cc: Introduced version of MDL_request::init() method which initializes lock request using pre-built MDL key. MDL_key::table_name/table_name_length() getters were renamed to reflect the fact that MDL_key objects are now created not only for tables. sql/mdl.h: Extended enum_mdl_namespace enum with values which correspond to namespaces for stored functions and triggers. Renamed MDL_key::table_name/table_name_length() getters to MDL_key::name() and name_length() correspondingly to reflect the fact that MDL_key objects are now created not only for tables. Added MDL_key::mdl_namespace() getter. Also added version of MDL_request::init() method which initializes lock request using pre-built MDL key. sql/sp.cc: Added MDL_request for stored routine as member to Sroutine_hash_entry. Changed code to use MDL_key from this request as a key for LEX::sroutines set. Removed separate "key" member from Sroutine_hash_entry as it became unnecessary. sql/sp.h: Added MDL_request for stored routine as member to Sroutine_hash_entry in order to be able perform metadata locking for stored routines in future (Sroutine_hash_entry is an equivalent of TABLE_LIST class for stored routines). Removed Sroutine_hash_entry::key member as now we can use MDL_key from this request as a key for LEX::sroutines set. sql/sp_head.cc: Removed sp_name::m_sroutines_key member and set_routine_type() method. Since key for routine in LEX::sroutines set has no longer sp_name::m_qname as suffix we won't save anything by creating it at sp_name construction time. Adjusted sp_name constructor used for creating temporary objects for lookups in SP-cache to accept MDL_key as parameter and to avoid any memory allocation. Finally, removed sp_head::m_soutines_key member for reasons similar to why sp_name::m_sroutines_key was removed sql/sp_head.h: Removed sp_name::m_sroutines_key member and set_routine_type() method. Since key for routine in LEX::sroutines set has no longer sp_name::m_qname as suffix we won't save anything by creating it at sp_name construction time. Adjusted sp_name constructor used for creating temporary objects for lookups in SP-cache to accept MDL_key as parameter and to avoid any memory allocation. Finally, removed sp_head::m_soutines_key member for reasons similar to why sp_name::m_sroutines_key was removed. sql/sql_base.cc: Adjusted code to the fact that we now use MDL_key from Sroutine_hash_entry::mdl_request as a key for LEX::sroutines set. MDL_key::table_name/table_name_length() getters were renamed to reflect the fact that MDL_key objects are now created not only for tables. sql/sql_trigger.cc: sp_add_used_routine() now takes MDL_key as parameter as now we use instance of this class as a key for LEX::sroutines set. --- sql/mdl.cc | 22 ++++++++++++++-- sql/mdl.h | 17 +++++++++--- sql/sp.cc | 32 +++++++++++------------ sql/sp.h | 7 ++--- sql/sp_head.cc | 64 +++++++++++++++++++++------------------------- sql/sp_head.h | 29 +++------------------ sql/sql_base.cc | 19 +++++++------- sql/sql_trigger.cc | 24 +++++++++-------- 8 files changed, 109 insertions(+), 105 deletions(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index 879b12a4cac..5bda56193f5 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -318,6 +318,24 @@ void MDL_request::init(enum_mdl_namespace mdl_namespace, } +/** + Initialize a lock request using pre-built MDL_key. + + @sa MDL_request::init(namespace, db, name, type). + + @param key_arg The pre-built MDL key for the request. + @param mdl_type_arg The MDL lock type for the request. +*/ + +void MDL_request::init(const MDL_key *key_arg, + enum enum_mdl_type mdl_type_arg) +{ + key.mdl_key_init(key_arg); + type= mdl_type_arg; + ticket= NULL; +} + + /** Allocate and initialize one lock request. @@ -1254,7 +1272,7 @@ void MDL_context::release_ticket(MDL_ticket *ticket) MDL_lock *lock= ticket->m_lock; DBUG_ENTER("release_ticket"); DBUG_PRINT("enter", ("db=%s name=%s", lock->key.db_name(), - lock->key.table_name())); + lock->key.name())); safe_mutex_assert_owner(&LOCK_mdl); @@ -1526,7 +1544,7 @@ MDL_ticket::set_cached_object(void *cached_object, { DBUG_ENTER("mdl_set_cached_object"); DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", - m_lock->key.db_name(), m_lock->key.table_name(), + m_lock->key.db_name(), m_lock->key.name(), cached_object)); /* TODO: This assumption works now since we do get_cached_object() diff --git a/sql/mdl.h b/sql/mdl.h index 03631bb9dd6..868e22d0834 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -52,9 +52,14 @@ enum enum_mdl_state { MDL_PENDING, MDL_ACQUIRED }; Different types of objects exist in different namespaces - MDL_TABLE is for tables and views. - - MDL_PROCEDURE is for stored procedures, stored functions and UDFs. + - MDL_FUNCTION is for stored functions. + - MDL_PROCEDURE is for stored procedures. + - MDL_TRIGGER is for triggers. + Note that although there isn't metadata locking on triggers, + it's necessary to have a separate namespace for them since + MDL_key is also used outside of the MDL subsystem. */ -enum enum_mdl_namespace { MDL_TABLE=0, MDL_PROCEDURE }; +enum enum_mdl_namespace { MDL_TABLE=0, MDL_FUNCTION, MDL_PROCEDURE, MDL_TRIGGER }; /** Maximal length of key for metadata locking subsystem. */ #define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1) @@ -78,8 +83,11 @@ public: const char *db_name() const { return m_ptr + 1; } uint db_name_length() const { return m_db_name_length; } - const char *table_name() const { return m_ptr + m_db_name_length + 2; } - uint table_name_length() const { return m_length - m_db_name_length - 3; } + const char *name() const { return m_ptr + m_db_name_length + 2; } + uint name_length() const { return m_length - m_db_name_length - 3; } + + enum_mdl_namespace mdl_namespace() const + { return (enum_mdl_namespace)(m_ptr[0]); } /** Construct a metadata lock key from a triplet (mdl_namespace, database and name). @@ -179,6 +187,7 @@ public: public: void init(enum_mdl_namespace namespace_arg, const char *db_arg, const char *name_arg, enum_mdl_type mdl_type_arg); + void init(const MDL_key *key_arg, enum_mdl_type mdl_type_arg); /** Set type of lock request. Can be only applied to pending locks. */ inline void set_type(enum_mdl_type type_arg) { diff --git a/sql/sp.cc b/sql/sp.cc index 19fe00594bd..93defd1b401 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -1391,8 +1391,8 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, my_bool first) { Sroutine_hash_entry *rn= (Sroutine_hash_entry *)ptr; - *plen= rn->key.length; - return (uchar *)rn->key.str; + *plen= rn->mdl_request.key.length(); + return (uchar *)rn->mdl_request.key.ptr(); } @@ -1430,23 +1430,19 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, */ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, - const LEX_STRING *key, TABLE_LIST *belong_to_view) + const MDL_key *key, TABLE_LIST *belong_to_view) { my_hash_init_opt(&prelocking_ctx->sroutines, system_charset_info, Query_tables_list::START_SROUTINES_HASH_SIZE, 0, 0, sp_sroutine_key, 0, 0); - if (!my_hash_search(&prelocking_ctx->sroutines, (uchar *)key->str, - key->length)) + if (!my_hash_search(&prelocking_ctx->sroutines, key->ptr(), key->length())) { Sroutine_hash_entry *rn= - (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry) + - key->length + 1); + (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry)); if (!rn) // OOM. Error will be reported using fatal_error(). return FALSE; - rn->key.length= key->length; - rn->key.str= (char *)rn + sizeof(Sroutine_hash_entry); - memcpy(rn->key.str, key->str, key->length + 1); + rn->mdl_request.init(key, MDL_SHARED); my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn); prelocking_ctx->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next); rn->belong_to_view= belong_to_view; @@ -1477,8 +1473,9 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, sp_name *rt, char rt_type) { - rt->set_routine_type(rt_type); - (void)sp_add_used_routine(prelocking_ctx, arena, &rt->m_sroutines_key, 0); + MDL_key key((rt_type == TYPE_ENUM_FUNCTION) ? MDL_FUNCTION : MDL_PROCEDURE, + rt->m_db.str, rt->m_name.str); + (void)sp_add_used_routine(prelocking_ctx, arena, &key, 0); prelocking_ctx->sroutines_list_own_last= prelocking_ctx->sroutines_list.next; prelocking_ctx->sroutines_list_own_elements= prelocking_ctx->sroutines_list.elements; @@ -1535,7 +1532,8 @@ void sp_update_sp_used_routines(HASH *dst, HASH *src) for (uint i=0 ; i < src->records ; i++) { Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i); - if (!my_hash_search(dst, (uchar *)rt->key.str, rt->key.length)) + if (!my_hash_search(dst, (uchar *)rt->mdl_request.key.ptr(), + rt->mdl_request.key.length())) my_hash_insert(dst, (uchar *)rt); } } @@ -1562,8 +1560,8 @@ sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, for (uint i=0 ; i < src->records ; i++) { Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i); - (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &rt->key, - belong_to_view); + (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, + &rt->mdl_request.key, belong_to_view); } } @@ -1587,8 +1585,8 @@ void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, { for (Sroutine_hash_entry *rt= (Sroutine_hash_entry *)src->first; rt; rt= rt->next) - (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &rt->key, - belong_to_view); + (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, + &rt->mdl_request.key, belong_to_view); } diff --git a/sql/sp.h b/sql/sp.h index 1de41eb3920..a66d72d0e9e 100644 --- a/sql/sp.h +++ b/sql/sp.h @@ -73,9 +73,10 @@ class Sroutine_hash_entry { public: /** - Set key consisting of one-byte routine type and quoted routine name. + Metadata lock request for routine. + MDL_key in this request is also used as a key for set. */ - LEX_STRING key; + MDL_request mdl_request; /** Next element in list linking all routines in set. See also comments for LEX::sroutine/sroutine_list and sp_head::m_sroutines. @@ -96,7 +97,7 @@ public: void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, sp_name *rt, char rt_type); bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, - const LEX_STRING *key, TABLE_LIST *belong_to_view); + const MDL_key *key, TABLE_LIST *belong_to_view); void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx); void sp_update_sp_used_routines(HASH *dst, HASH *src); void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 0744e5be930..c9e13e6b830 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -382,31 +382,34 @@ error: } -/* - * - * sp_name - * - */ +/** + Create temporary sp_name object from MDL key. -sp_name::sp_name(THD *thd, char *key, uint key_len) + @note The lifetime of this object is bound to the lifetime of the MDL_key. + This should be fine as sp_name objects created by this constructor + are mainly used for SP-cache lookups. + + @param key MDL key containing database and routine name. + @param qname_buff Buffer to be used for storing quoted routine name + (should be at least 2*NAME_LEN+1+1 bytes). +*/ + +sp_name::sp_name(const MDL_key *key, char *qname_buff) { - m_sroutines_key.str= key; - m_sroutines_key.length= key_len; - m_qname.str= ++key; - m_qname.length= key_len - 1; - if ((m_name.str= strchr(m_qname.str, '.'))) + m_db.str= (char*)key->db_name(); + m_db.length= key->db_name_length(); + m_name.str= (char*)key->name(); + m_name.length= key->name_length(); + m_qname.str= qname_buff; + if (m_db.length) { - m_db.length= m_name.str - key; - m_db.str= strmake_root(thd->mem_root, key, m_db.length); - m_name.str++; - m_name.length= m_qname.length - m_db.length - 1; + strxmov(qname_buff, m_db.str, ".", m_name.str, NullS); + m_qname.length= m_db.length + 1 + m_name.length; } else { - m_name.str= m_qname.str; - m_name.length= m_qname.length; - m_db.str= 0; - m_db.length= 0; + strmov(qname_buff, m_name.str); + m_qname.length= m_name.length; } m_explicit_name= false; } @@ -419,12 +422,10 @@ void sp_name::init_qname(THD *thd) { const uint dot= !!m_db.length; - /* m_sroutines format: m_type + [database + dot] + name + nul */ - m_sroutines_key.length= 1 + m_db.length + dot + m_name.length; - if (!(m_sroutines_key.str= (char*) thd->alloc(m_sroutines_key.length + 1))) + /* m_qname format: [database + dot] + name + '\0' */ + m_qname.length= m_db.length + dot + m_name.length; + if (!(m_qname.str= (char*) thd->alloc(m_qname.length + 1))) return; - m_qname.length= m_sroutines_key.length - 1; - m_qname.str= m_sroutines_key.str + 1; sprintf(m_qname.str, "%.*s%.*s%.*s", (int) m_db.length, (m_db.length ? m_db.str : ""), dot, ".", @@ -585,9 +586,6 @@ sp_head::init(LEX *lex) m_defstr.str= NULL; m_defstr.length= 0; - m_sroutines_key.str= NULL; - m_sroutines_key.length= 0; - m_return_field_def.charset= NULL; DBUG_VOID_RETURN; @@ -617,14 +615,10 @@ sp_head::init_sp_name(THD *thd, sp_name *spname) if (spname->m_qname.length == 0) spname->init_qname(thd); - m_sroutines_key.length= spname->m_sroutines_key.length; - m_sroutines_key.str= (char*) memdup_root(thd->mem_root, - spname->m_sroutines_key.str, - spname->m_sroutines_key.length + 1); - m_sroutines_key.str[0]= static_cast(m_type); - - m_qname.length= m_sroutines_key.length - 1; - m_qname.str= m_sroutines_key.str + 1; + m_qname.length= spname->m_qname.length; + m_qname.str= (char*) memdup_root(thd->mem_root, + spname->m_qname.str, + spname->m_qname.length + 1); DBUG_VOID_RETURN; } diff --git a/sql/sp_head.h b/sql/sp_head.h index 5610ecd2a72..74fcd03180e 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -109,36 +109,21 @@ public: LEX_STRING m_db; LEX_STRING m_name; LEX_STRING m_qname; - /** - Key representing routine in the set of stored routines used by statement. - Consists of 1-byte routine type and m_qname (which usually refences to - same buffer). Note that one must complete initialization of the key by - calling set_routine_type(). - */ - LEX_STRING m_sroutines_key; bool m_explicit_name; /**< Prepend the db name? */ sp_name(LEX_STRING db, LEX_STRING name, bool use_explicit_name) : m_db(db), m_name(name), m_explicit_name(use_explicit_name) { - m_qname.str= m_sroutines_key.str= 0; - m_qname.length= m_sroutines_key.length= 0; + m_qname.str= 0; + m_qname.length= 0; } - /** - Creates temporary sp_name object from key, used mainly - for SP-cache lookups. - */ - sp_name(THD *thd, char *key, uint key_len); + /** Create temporary sp_name object from MDL key. */ + sp_name(const MDL_key *key, char *qname_buff); // Init. the qualified name from the db and name. void init_qname(THD *thd); // thd for memroot allocation - void set_routine_type(char type) - { - m_sroutines_key.str[0]= type; - } - ~sp_name() {} }; @@ -181,12 +166,6 @@ public: ulong m_sql_mode; ///< For SHOW CREATE and execution LEX_STRING m_qname; ///< db.name bool m_explicit_name; ///< Prepend the db name? */ - /** - Key representing routine in the set of stored routines used by statement. - [routine_type]db.name - @sa sp_name::m_sroutines_key - */ - LEX_STRING m_sroutines_key; LEX_STRING m_db; LEX_STRING m_name; LEX_STRING m_params; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 20353f0c9f8..16b9342889a 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3777,17 +3777,18 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, Prelocking_strategy *prelocking_strategy, bool *need_prelocking) { - int type= rt->key.str[0]; - DBUG_ENTER("open_and_process_routine"); - switch (type) + switch (rt->mdl_request.key.mdl_namespace()) { - case TYPE_ENUM_FUNCTION: - case TYPE_ENUM_PROCEDURE: + case MDL_FUNCTION: + case MDL_PROCEDURE: { - sp_name name(thd, rt->key.str, rt->key.length); + char qname_buff[NAME_LEN*2+1+1]; + sp_name name(&rt->mdl_request.key, qname_buff); sp_head *sp; + int type= (rt->mdl_request.key.mdl_namespace() == MDL_FUNCTION) ? + TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE; if (sp_cache_routine(thd, type, &name, &sp)) DBUG_RETURN(TRUE); @@ -3799,7 +3800,7 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, } } break; - case TYPE_ENUM_TRIGGER: + case MDL_TRIGGER: break; default: /* Impossible type value. */ @@ -4304,7 +4305,7 @@ handle_routine(THD *thd, Query_tables_list *prelocking_ctx, */ if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first || - rt->key.str[0] != TYPE_ENUM_PROCEDURE) + rt->mdl_request.key.mdl_namespace() != MDL_PROCEDURE) { *need_prelocking= TRUE; sp_update_stmt_used_routines(thd, prelocking_ctx, &sp->m_sroutines, @@ -8302,7 +8303,7 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests) while ((mdl_request= it++)) { if ((share= get_cached_table_share(mdl_request->key.db_name(), - mdl_request->key.table_name())) && + mdl_request->key.name())) && share->version != refresh_version && !share->used_tables.is_empty()) break; diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 015e0d4daa1..676875ecc75 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -2055,17 +2055,21 @@ add_tables_and_routines_for_triggers(THD *thd, /* We can have only one trigger per action type currently */ sp_head *trigger= table_list->table->triggers->bodies[i][j]; - if (trigger && sp_add_used_routine(prelocking_ctx, thd->stmt_arena, - &trigger->m_sroutines_key, - table_list->belong_to_view)) + if (trigger) { - trigger->add_used_tables_to_table_list(thd, - &prelocking_ctx->query_tables_last, - table_list->belong_to_view); - sp_update_stmt_used_routines(thd, prelocking_ctx, - &trigger->m_sroutines, - table_list->belong_to_view); - trigger->propagate_attributes(prelocking_ctx); + MDL_key key(MDL_TRIGGER, trigger->m_db.str, trigger->m_name.str); + + if (sp_add_used_routine(prelocking_ctx, thd->stmt_arena, + &key, table_list->belong_to_view)) + { + trigger->add_used_tables_to_table_list(thd, + &prelocking_ctx->query_tables_last, + table_list->belong_to_view); + sp_update_stmt_used_routines(thd, prelocking_ctx, + &trigger->m_sroutines, + table_list->belong_to_view); + trigger->propagate_attributes(prelocking_ctx); + } } } } From f26f632b44dfe9a6cc239a10f8697ba0ba50675d Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 10 Dec 2009 11:21:38 +0300 Subject: [PATCH 104/212] Backport of: ------------------------------------------------------------ revno: 2617.68.25 committer: Dmitry Lenev branch nick: mysql-next-bg-pre2-2 timestamp: Wed 2009-09-16 18:26:50 +0400 message: Follow-up for one of pre-requisite patches for fixing bug #30977 "Concurrent statement using stored function and DROP FUNCTION breaks SBR". Made enum_mdl_namespace enum part of MDL_key class and removed MDL_ prefix from the names of enum members. In order to do the latter changed name of PROCEDURE symbol to PROCEDURE_SYM (otherwise macro which was automatically generated for this symbol conflicted with MDL_key::PROCEDURE enum member). --- sql/lex.h | 2 +- sql/lock.cc | 3 ++- sql/mdl.cc | 8 ++++---- sql/mdl.h | 45 +++++++++++++++++++++++++-------------------- sql/sp.cc | 3 ++- sql/sp_head.cc | 6 ++++-- sql/sql_base.cc | 17 +++++++++-------- sql/sql_delete.cc | 2 +- sql/sql_handler.cc | 2 +- sql/sql_parse.cc | 2 +- sql/sql_show.cc | 2 +- sql/sql_table.cc | 10 ++++++---- sql/sql_trigger.cc | 4 ++-- sql/sql_yacc.yy | 20 ++++++++++---------- sql/table.cc | 3 ++- sql/table.h | 2 +- 16 files changed, 72 insertions(+), 59 deletions(-) diff --git a/sql/lex.h b/sql/lex.h index a12cf0c4b3e..91ef5287a1e 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -416,7 +416,7 @@ static SYMBOL symbols[] = { { "PREV", SYM(PREV_SYM)}, { "PRIMARY", SYM(PRIMARY_SYM)}, { "PRIVILEGES", SYM(PRIVILEGES)}, - { "PROCEDURE", SYM(PROCEDURE)}, + { "PROCEDURE", SYM(PROCEDURE_SYM)}, { "PROCESS" , SYM(PROCESS)}, { "PROCESSLIST", SYM(PROCESSLIST_SYM)}, { "PROFILE", SYM(PROFILE_SYM)}, diff --git a/sql/lock.cc b/sql/lock.cc index 8d314c4ad19..648fecf9b69 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -957,7 +957,8 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list) for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) { - lock_table->mdl_request.init(MDL_TABLE, lock_table->db, lock_table->table_name, + lock_table->mdl_request.init(MDL_key::TABLE, + lock_table->db, lock_table->table_name, MDL_EXCLUSIVE); mdl_requests.push_front(&lock_table->mdl_request); } diff --git a/sql/mdl.cc b/sql/mdl.cc index 5bda56193f5..b624b0658ed 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -307,7 +307,7 @@ void MDL_context::merge(MDL_context *src) @param mdl_type The MDL lock type for the request. */ -void MDL_request::init(enum_mdl_namespace mdl_namespace, +void MDL_request::init(MDL_key::enum_mdl_namespace mdl_namespace, const char *db_arg, const char *name_arg, enum enum_mdl_type mdl_type_arg) @@ -355,7 +355,7 @@ void MDL_request::init(const MDL_key *key_arg, */ MDL_request * -MDL_request::create(enum_mdl_namespace mdl_namespace, const char *db, +MDL_request::create(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, const char *name, enum_mdl_type mdl_type, MEM_ROOT *root) { @@ -1444,7 +1444,7 @@ void MDL_context::release_global_shared_lock() */ bool -MDL_context::is_exclusive_lock_owner(enum_mdl_namespace mdl_namespace, +MDL_context::is_exclusive_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, const char *name) { MDL_request mdl_request; @@ -1470,7 +1470,7 @@ MDL_context::is_exclusive_lock_owner(enum_mdl_namespace mdl_namespace, */ bool -MDL_context::is_lock_owner(enum_mdl_namespace mdl_namespace, +MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, const char *name) { MDL_key key(mdl_namespace, db, name); diff --git a/sql/mdl.h b/sql/mdl.h index 868e22d0834..2758bd3a8e6 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -47,20 +47,6 @@ enum enum_mdl_type {MDL_SHARED=0, MDL_SHARED_HIGH_PRIO, enum enum_mdl_state { MDL_PENDING, MDL_ACQUIRED }; -/** - Object namespaces - - Different types of objects exist in different namespaces - - MDL_TABLE is for tables and views. - - MDL_FUNCTION is for stored functions. - - MDL_PROCEDURE is for stored procedures. - - MDL_TRIGGER is for triggers. - Note that although there isn't metadata locking on triggers, - it's necessary to have a separate namespace for them since - MDL_key is also used outside of the MDL subsystem. -*/ -enum enum_mdl_namespace { MDL_TABLE=0, MDL_FUNCTION, MDL_PROCEDURE, MDL_TRIGGER }; - /** Maximal length of key for metadata locking subsystem. */ #define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1) @@ -77,6 +63,23 @@ enum enum_mdl_namespace { MDL_TABLE=0, MDL_FUNCTION, MDL_PROCEDURE, MDL_TRIGGER class MDL_key { public: + /** + Object namespaces + + Different types of objects exist in different namespaces + - TABLE is for tables and views. + - FUNCTION is for stored functions. + - PROCEDURE is for stored procedures. + - TRIGGER is for triggers. + Note that although there isn't metadata locking on triggers, + it's necessary to have a separate namespace for them since + MDL_key is also used outside of the MDL subsystem. + */ + enum enum_mdl_namespace { TABLE=0, + FUNCTION, + PROCEDURE, + TRIGGER }; + const uchar *ptr() const { return (uchar*) m_ptr; } uint length() const { return m_length; } @@ -185,7 +188,8 @@ public: MDL_key key; public: - void init(enum_mdl_namespace namespace_arg, const char *db_arg, const char *name_arg, + void init(MDL_key::enum_mdl_namespace namespace_arg, + const char *db_arg, const char *name_arg, enum_mdl_type mdl_type_arg); void init(const MDL_key *key_arg, enum_mdl_type mdl_type_arg); /** Set type of lock request. Can be only applied to pending locks. */ @@ -196,9 +200,9 @@ public: } bool is_shared() const { return type < MDL_EXCLUSIVE; } - static MDL_request *create(enum_mdl_namespace mdl_namespace, const char *db, - const char *name, enum_mdl_type mdl_type, - MEM_ROOT *root); + static MDL_request *create(MDL_key::enum_mdl_namespace mdl_namespace, + const char *db, const char *name, + enum_mdl_type mdl_type, MEM_ROOT *root); /* This is to work around the ugliness of TABLE_LIST @@ -340,10 +344,11 @@ public: void release_lock(MDL_ticket *ticket); void release_global_shared_lock(); - bool is_exclusive_lock_owner(enum_mdl_namespace mdl_namespace, + bool is_exclusive_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, const char *name); - bool is_lock_owner(enum_mdl_namespace mdl_namespace, const char *db, const char *name); + bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, + const char *db, const char *name); inline bool has_locks() const { diff --git a/sql/sp.cc b/sql/sp.cc index 93defd1b401..a8bd419cdcc 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -1473,7 +1473,8 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, sp_name *rt, char rt_type) { - MDL_key key((rt_type == TYPE_ENUM_FUNCTION) ? MDL_FUNCTION : MDL_PROCEDURE, + MDL_key key((rt_type == TYPE_ENUM_FUNCTION) ? MDL_key::FUNCTION : + MDL_key::PROCEDURE, rt->m_db.str, rt->m_name.str); (void)sp_add_used_routine(prelocking_ctx, arena, &key, 0); prelocking_ctx->sroutines_list_own_last= prelocking_ctx->sroutines_list.next; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index c9e13e6b830..7316bcb2a46 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3975,7 +3975,8 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; - table->mdl_request.init(MDL_TABLE, table->db, table->table_name, MDL_SHARED); + table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, + MDL_SHARED); /* Everyting else should be zeroed */ @@ -4017,7 +4018,8 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->lock_type= locktype; table->select_lex= lex->current_select; table->cacheable_table= 1; - table->mdl_request.init(MDL_TABLE, table->db, table->table_name, MDL_SHARED); + table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, + MDL_SHARED); lex->add_to_query_tables(table); return table; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 16b9342889a..08ec0c8a2ed 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -476,7 +476,7 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, To be able perform any operation on table we should own some kind of metadata lock on it. */ - DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_TABLE, table_list->db, + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, table_list->db, table_list->table_name)); /* Read table definition from cache */ @@ -2546,7 +2546,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, TABLES breaks metadata locking protocol (potentially can lead to deadlocks) it should be disallowed. */ - if (thd->mdl_context.is_lock_owner(MDL_TABLE, table_list->db, + if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, table_list->db, table_list->table_name)) { char path[FN_REFLEN + 1]; @@ -3781,13 +3781,13 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, switch (rt->mdl_request.key.mdl_namespace()) { - case MDL_FUNCTION: - case MDL_PROCEDURE: + case MDL_key::FUNCTION: + case MDL_key::PROCEDURE: { char qname_buff[NAME_LEN*2+1+1]; sp_name name(&rt->mdl_request.key, qname_buff); sp_head *sp; - int type= (rt->mdl_request.key.mdl_namespace() == MDL_FUNCTION) ? + int type= (rt->mdl_request.key.mdl_namespace() == MDL_key::FUNCTION) ? TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE; if (sp_cache_routine(thd, type, &name, &sp)) @@ -3800,7 +3800,7 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, } } break; - case MDL_TRIGGER: + case MDL_key::TRIGGER: break; default: /* Impossible type value. */ @@ -4305,7 +4305,7 @@ handle_routine(THD *thd, Query_tables_list *prelocking_ctx, */ if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first || - rt->mdl_request.key.mdl_namespace() != MDL_PROCEDURE) + rt->mdl_request.key.mdl_namespace() != MDL_key::PROCEDURE) { *need_prelocking= TRUE; sp_update_stmt_used_routines(thd, prelocking_ctx, &sp->m_sroutines, @@ -8234,7 +8234,8 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, safe_mutex_assert_owner(&LOCK_open); DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || - thd->mdl_context.is_exclusive_lock_owner(MDL_TABLE, db, table_name)); + thd->mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, + db, table_name)); key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 03be0382150..1f3621de502 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1177,7 +1177,7 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) goto trunc_by_del; - mdl_request.init(MDL_TABLE, table_list->db, table_list->table_name, MDL_EXCLUSIVE); + mdl_request.init(MDL_key::TABLE, table_list->db, table_list->table_name, MDL_EXCLUSIVE); if (thd->mdl_context.acquire_exclusive_lock(&mdl_request)) DBUG_RETURN(TRUE); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 94f6b248e45..a7fe5a03f10 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -264,7 +264,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) memcpy(hash_tables->db, tables->db, dblen); memcpy(hash_tables->table_name, tables->table_name, namelen); memcpy(hash_tables->alias, tables->alias, aliaslen); - hash_tables->mdl_request.init(MDL_TABLE, db, name, MDL_SHARED); + hash_tables->mdl_request.init(MDL_key::TABLE, db, name, MDL_SHARED); /* add to hash */ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2c5f72c8cf4..c49b22d23bd 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -6024,7 +6024,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); - ptr->mdl_request.init(MDL_TABLE, ptr->db, ptr->table_name, MDL_SHARED); + ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, MDL_SHARED); DBUG_RETURN(ptr); } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index fe941321dcd..ccb5875dee5 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3081,7 +3081,7 @@ try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table, bool can_deadlock) { bool error; - table->mdl_request.init(MDL_TABLE, table->db, table->table_name, + table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, MDL_SHARED_HIGH_PRIO); while (!(error= thd->mdl_context.try_acquire_shared_lock(&table->mdl_request)) && diff --git a/sql/sql_table.cc b/sql/sql_table.cc index ddb53dd3754..42d688542b5 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4140,7 +4140,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - target_mdl_request.init(MDL_TABLE, db, table_name, MDL_EXCLUSIVE); + target_mdl_request.init(MDL_key::TABLE, db, table_name, MDL_EXCLUSIVE); if (thd->mdl_context.try_acquire_exclusive_lock(&target_mdl_request)) { result= TRUE; @@ -4361,7 +4361,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, uint key_length; key_length= create_table_def_key(thd, key, table_list, 0); - table_list->mdl_request.init(MDL_TABLE, table_list->db, table_list->table_name, + table_list->mdl_request.init(MDL_key::TABLE, + table_list->db, table_list->table_name, MDL_EXCLUSIVE); if (thd->mdl_context.acquire_exclusive_lock(&table_list->mdl_request)) DBUG_RETURN(0); @@ -5271,7 +5272,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, } else { - table->mdl_request.init(MDL_TABLE, db, table_name, MDL_EXCLUSIVE); + table->mdl_request.init(MDL_key::TABLE, db, table_name, MDL_EXCLUSIVE); if (thd->mdl_context.try_acquire_exclusive_lock(&table->mdl_request)) DBUG_RETURN(TRUE); @@ -6637,7 +6638,8 @@ view_err: } else { - target_mdl_request.init(MDL_TABLE, new_db, new_name, MDL_EXCLUSIVE); + target_mdl_request.init(MDL_key::TABLE, new_db, new_name, + MDL_EXCLUSIVE); if (thd->mdl_context.try_acquire_exclusive_lock(&target_mdl_request)) DBUG_RETURN(TRUE); if (target_mdl_request.ticket == NULL) diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 676875ecc75..08d0dacd8e8 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -1888,7 +1888,7 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, In the future, only an exclusive metadata lock will be enough. */ #ifndef DBUG_OFF - if (thd->mdl_context.is_exclusive_lock_owner(MDL_TABLE, db, old_table)) + if (thd->mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, db, old_table)) safe_mutex_assert_owner(&LOCK_open); #endif @@ -2057,7 +2057,7 @@ add_tables_and_routines_for_triggers(THD *thd, if (trigger) { - MDL_key key(MDL_TRIGGER, trigger->m_db.str, trigger->m_name.str); + MDL_key key(MDL_key::TRIGGER, trigger->m_db.str, trigger->m_name.str); if (sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &key, table_list->belong_to_view)) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 577c60b4c10..826ec63a93e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -922,7 +922,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token PREV_SYM %token PRIMARY_SYM /* SQL-2003-R */ %token PRIVILEGES /* SQL-2003-N */ -%token PROCEDURE /* SQL-2003-R */ +%token PROCEDURE_SYM /* SQL-2003-R */ %token PROCESS %token PROCESSLIST_SYM %token PROFILE_SYM @@ -5807,7 +5807,7 @@ alter: lex->sql_command= SQLCOM_ALTER_DB_UPGRADE; lex->name= $3; } - | ALTER PROCEDURE sp_name + | ALTER PROCEDURE_SYM sp_name { LEX *lex= Lex; @@ -9439,7 +9439,7 @@ dec_num: procedure_clause: /* empty */ - | PROCEDURE ident /* Procedure name */ + | PROCEDURE_SYM ident /* Procedure name */ { LEX *lex=Lex; @@ -9695,7 +9695,7 @@ drop: spname->init_qname(thd); lex->spname= spname; } - | DROP PROCEDURE if_exists sp_name + | DROP PROCEDURE_SYM if_exists sp_name { LEX *lex=Lex; if (lex->sphead) @@ -10446,7 +10446,7 @@ show_param: { Lex->sql_command = SQLCOM_SHOW_SLAVE_STAT; } - | CREATE PROCEDURE sp_name + | CREATE PROCEDURE_SYM sp_name { LEX *lex= Lex; @@ -10466,7 +10466,7 @@ show_param: lex->sql_command= SQLCOM_SHOW_CREATE_TRIGGER; lex->spname= $3; } - | PROCEDURE STATUS_SYM wild_and_where + | PROCEDURE_SYM STATUS_SYM wild_and_where { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_STATUS_PROC; @@ -10480,7 +10480,7 @@ show_param: if (prepare_schema_table(YYTHD, lex, 0, SCH_PROCEDURES)) MYSQL_YYABORT; } - | PROCEDURE CODE_SYM sp_name + | PROCEDURE_SYM CODE_SYM sp_name { Lex->sql_command= SQLCOM_SHOW_PROC_CODE; Lex->spname= $3; @@ -12671,7 +12671,7 @@ revoke_command: lex->sql_command= SQLCOM_REVOKE; lex->type= TYPE_ENUM_FUNCTION; } - | grant_privileges ON PROCEDURE grant_ident FROM grant_list + | grant_privileges ON PROCEDURE_SYM grant_ident FROM grant_list { LEX *lex= Lex; if (lex->columns.elements) @@ -12713,7 +12713,7 @@ grant_command: lex->sql_command= SQLCOM_GRANT; lex->type= TYPE_ENUM_FUNCTION; } - | grant_privileges ON PROCEDURE grant_ident TO_SYM grant_list + | grant_privileges ON PROCEDURE_SYM grant_ident TO_SYM grant_list require_clause grant_options { LEX *lex= Lex; @@ -13721,7 +13721,7 @@ sf_tail: ; sp_tail: - PROCEDURE remember_name sp_name + PROCEDURE_SYM remember_name sp_name { LEX *lex= Lex; sp_head *sp; diff --git a/sql/table.cc b/sql/table.cc index c66610e5693..aef836c330e 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4822,7 +4822,8 @@ size_t max_row_length(TABLE *table, const uchar *data) void init_mdl_requests(TABLE_LIST *table_list) { for ( ; table_list ; table_list= table_list->next_global) - table_list->mdl_request.init(MDL_TABLE, table_list->db, table_list->table_name, + table_list->mdl_request.init(MDL_key::TABLE, + table_list->db, table_list->table_name, MDL_SHARED); } diff --git a/sql/table.h b/sql/table.h index 6a2bef4c610..eacf4f6085c 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1125,7 +1125,7 @@ struct TABLE_LIST table_name_length= table_name_length_arg; alias= (char*) alias_arg; lock_type= lock_type_arg; - mdl_request.init(MDL_TABLE, db, table_name, MDL_SHARED); + mdl_request.init(MDL_key::TABLE, db, table_name, MDL_SHARED); } /* From f3bc2406b0258d128b4f8f0ae21640e80a518f18 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Thu, 10 Dec 2009 10:32:23 +0100 Subject: [PATCH 105/212] Bug #46495 Crash in reload_acl_and_cache on SIGHUP An assert in reload_acl_and_cache didn't account for the case when the function is called with a NULL thd. A null thd is used whenever the function is called from the SIGHUP signal handler. Backported from 6.0-codebase (revid: 2617.69.35) --- sql/sql_parse.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 7ea5c81c52b..cafc70dc2ee 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -6568,7 +6568,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, } #endif /*HAVE_QUERY_CACHE*/ - DBUG_ASSERT(thd->locked_tables_mode || !thd->mdl_context.has_locks()); + DBUG_ASSERT(!thd || thd->locked_tables_mode || !thd->mdl_context.has_locks()); /* Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too From 5e1dfa4c066ec0e48a725b4976bdb5aa09c79685 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 11:53:20 +0100 Subject: [PATCH 106/212] Backport of revno: 2617.71.1 Bug#42546 Backup: RESTORE fails, thinking it finds an existing table The problem occured when a MDL locking conflict happened for a non-existent table between a CREATE and a INSERT statement. The code for CREATE interpreted this lock conflict to mean that the table existed, which meant that the statement failed when it should not have. The problem could occur for CREATE TABLE, CREATE TABLE LIKE and ALTER TABLE RENAME. This patch fixes the problem for CREATE TABLE and CREATE TABLE LIKE. It is based on code backported from the mysql-6.1-fk tree written by Dmitry Lenev. CREATE now uses normal open_and_lock_tables() code to acquire exclusive locks. This means that for the test case in the bug description, CREATE will wait until INSERT completes so that it can get the exclusive lock. This resolves the reported bug. The patch also prohibits CREATE TABLE and CREATE TABLE LIKE under LOCK TABLES. Note that this is an incompatible change and must be reflected in the documentation. Affected test cases have been updated. mdl_sync.test contains tests for CREATE TABLE and CREATE TABLE LIKE. Fixing the issue for ALTER TABLE RENAME is beyond the scope of this patch. ALTER TABLE cannot be prohibited from working under LOCK TABLES as this could seriously impact customers and a proper fix would require a significant rewrite. --- mysql-test/r/lock_multi.result | 3 +- mysql-test/r/mdl_sync.result | 60 ++++++ mysql-test/r/merge.result | 30 ++- mysql-test/r/ps_ddl.result | 17 +- mysql-test/t/lock_multi.test | 3 +- mysql-test/t/mdl_sync.test | 95 +++++++++ mysql-test/t/merge.test | 13 +- mysql-test/t/ps_ddl.test | 11 +- sql/mdl.cc | 3 + sql/mysql_priv.h | 6 +- sql/sql_acl.cc | 7 +- sql/sql_base.cc | 15 +- sql/sql_insert.cc | 75 ++++---- sql/sql_parse.cc | 79 +++++--- sql/sql_partition.cc | 49 ++--- sql/sql_prepare.cc | 36 ++-- sql/sql_table.cc | 338 +++++++++------------------------ sql/sql_trigger.cc | 2 +- sql/sql_view.cc | 2 +- sql/sql_yacc.yy | 20 +- sql/table.h | 16 +- 21 files changed, 465 insertions(+), 415 deletions(-) diff --git a/mysql-test/r/lock_multi.result b/mysql-test/r/lock_multi.result index ef9292ad8c0..5d12e0efd64 100644 --- a/mysql-test/r/lock_multi.result +++ b/mysql-test/r/lock_multi.result @@ -72,9 +72,10 @@ CREATE TABLE t1 (c1 int); LOCK TABLE t1 WRITE; FLUSH TABLES WITH READ LOCK; CREATE TABLE t2 (c1 int); +ERROR HY000: Table 't2' was not locked with LOCK TABLES UNLOCK TABLES; UNLOCK TABLES; -DROP TABLE t1, t2; +DROP TABLE t1; CREATE TABLE t1 (c1 int); LOCK TABLE t1 WRITE; FLUSH TABLES WITH READ LOCK; diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index d409157a70b..e5447c32b7d 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -63,6 +63,66 @@ unlock tables; # Clean-up. drop tables t1, t2, t3, t5; # +# Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table +# +DROP TABLE IF EXISTS t1; +set @save_log_output=@@global.log_output; +set global log_output=file; +# +# Test 1: CREATE TABLE +# +# Connection 2 +# Start insert on the not-yet existing table +# Wait after taking the MDL lock +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +INSERT INTO t1 VALUES(1,"def"); +# Connection 1 +SET DEBUG_SYNC= 'now WAIT_FOR locked'; +# Now INSERT has a MDL on the non-existent table t1. +# +# Continue the INSERT once CREATE waits for exclusive lock +SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish'; +# Try to create that table. +CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)); +# Connection 2 +# Insert fails +ERROR 42S02: Table 'test.t1' doesn't exist +# Connection 1 +SET DEBUG_SYNC= 'RESET'; +SHOW TABLES; +Tables_in_test +t1 +DROP TABLE IF EXISTS t1; +# +# Test 2: CREATE TABLE LIKE +# +CREATE TABLE t2 (c1 INT, c2 VARCHAR(100), KEY(c1)); +# Connection 2 +# Start insert on the not-yet existing table +# Wait after taking the MDL +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +INSERT INTO t1 VALUES(1,"def"); +# Connection 1 +SET DEBUG_SYNC= 'now WAIT_FOR locked'; +# Now INSERT has a MDL on the non-existent table t1. +# +# Continue the INSERT once CREATE waits for exclusive lock +SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish'; +# Try to create that table. +CREATE TABLE t1 LIKE t2; +# Connection 2 +# Insert fails +ERROR 42S02: Table 'test.t1' doesn't exist +# Connection 1 +SET DEBUG_SYNC= 'RESET'; +SHOW TABLES; +Tables_in_test +t1 +t2 +DROP TABLE t2; +DROP TABLE IF EXISTS t1; +set global log_output=@save_log_output; +# # Bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY # FOR UPDATE" # diff --git a/mysql-test/r/merge.result b/mysql-test/r/merge.result index 3a6cd4d3f5a..0417b91490e 100644 --- a/mysql-test/r/merge.result +++ b/mysql-test/r/merge.result @@ -1149,7 +1149,8 @@ SHOW CREATE TABLE t3; ERROR 42S02: Table 'test.t3' doesn't exist DROP TABLE t1, t2; # -# CREATE ... LIKE +# Bug#37371 "CREATE TABLE LIKE merge loses UNION parameter" +# Demonstrate that this is no longer the case. # # 1. Create like. CREATE TABLE t1 (c1 INT); @@ -1164,26 +1165,26 @@ SHOW CREATE TABLE t4; Table Create Table t4 CREATE TABLE `t4` ( `c1` int(11) DEFAULT NULL -) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 +) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=LAST UNION=(`t1`,`t2`) INSERT INTO t4 VALUES (4); -ERROR HY000: Table 't4' is read only DROP TABLE t4; # # 1. Create like with locked tables. LOCK TABLES t3 WRITE, t2 WRITE, t1 WRITE; CREATE TABLE t4 LIKE t3; +ERROR HY000: Table 't4' was not locked with LOCK TABLES SHOW CREATE TABLE t4; ERROR HY000: Table 't4' was not locked with LOCK TABLES INSERT INTO t4 VALUES (4); ERROR HY000: Table 't4' was not locked with LOCK TABLES +CREATE TEMPORARY TABLE t4 LIKE t3; +SHOW CREATE TABLE t4; +ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist +INSERT INTO t4 VALUES (4); +ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist UNLOCK TABLES; -SHOW CREATE TABLE t4; -Table Create Table -t4 CREATE TABLE `t4` ( - `c1` int(11) DEFAULT NULL -) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT INTO t4 VALUES (4); -ERROR HY000: Table 't4' is read only +ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist DROP TABLE t4; # # Rename child. @@ -1210,6 +1211,7 @@ c1 1 2 3 +4 RENAME TABLE t2 TO t5; SELECT * FROM t3 ORDER BY c1; ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist @@ -1219,6 +1221,7 @@ c1 1 2 3 +4 # # 3. Normal rename with locked tables. LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE; @@ -1227,6 +1230,7 @@ c1 1 2 3 +4 RENAME TABLE t2 TO t5; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3 ORDER BY c1; @@ -1234,6 +1238,7 @@ c1 1 2 3 +4 RENAME TABLE t5 TO t2; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3 ORDER BY c1; @@ -1241,6 +1246,7 @@ c1 1 2 3 +4 UNLOCK TABLES; # # 4. Alter table rename. @@ -1253,6 +1259,7 @@ c1 1 2 3 +4 # # 5. Alter table rename with locked tables. LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE; @@ -1268,6 +1275,7 @@ c1 1 2 3 +4 # # Rename parent. # @@ -1278,6 +1286,7 @@ c1 1 2 3 +4 RENAME TABLE t3 TO t5; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3 ORDER BY c1; @@ -1285,6 +1294,7 @@ c1 1 2 3 +4 RENAME TABLE t5 TO t3; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3 ORDER BY c1; @@ -1292,6 +1302,7 @@ c1 1 2 3 +4 # # 5. Alter table rename with locked tables. ALTER TABLE t3 RENAME TO t5; @@ -1306,6 +1317,7 @@ c1 1 2 3 +4 DROP TABLE t1, t2, t3; # # Drop locked tables. diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result index f411328ed7c..3d57c8f7332 100644 --- a/mysql-test/r/ps_ddl.result +++ b/mysql-test/r/ps_ddl.result @@ -1755,21 +1755,21 @@ SUCCESS drop table t1; deallocate prepare stmt; -# XXX: no validation of the first table in case of -# CREATE TEMPORARY TABLE. This is a shortcoming of the current code, -# but since validation is not strictly necessary, nothing is done -# about it. -# Will be fixed as part of work on Bug#21431 "Incomplete support of -# temporary tables" create table t1 (a int); insert into t1 (a) values (1); prepare stmt from "create temporary table if not exists t2 as select * from t1"; execute stmt; drop table t2; execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + execute stmt; Warnings: Note 1050 Table 't2' already exists +call p_verify_reprepare_count(1); +SUCCESS + select * from t2; a 1 @@ -1777,6 +1777,9 @@ a execute stmt; Warnings: Note 1050 Table 't2' already exists +call p_verify_reprepare_count(0); +SUCCESS + select * from t2; a 1 @@ -1790,7 +1793,7 @@ Note 1050 Table 't2' already exists select * from t2; a 1 -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); SUCCESS drop table t1; diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test index cbb99c04967..31a10f89796 100644 --- a/mysql-test/t/lock_multi.test +++ b/mysql-test/t/lock_multi.test @@ -199,6 +199,7 @@ let $wait_condition= where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK"; --source include/wait_condition.inc # This must not block. +--error ER_TABLE_NOT_LOCKED CREATE TABLE t2 (c1 int); UNLOCK TABLES; # @@ -208,7 +209,7 @@ reap; UNLOCK TABLES; # connection default; -DROP TABLE t1, t2; +DROP TABLE t1; # # Test if CREATE TABLE SELECT with LOCK TABLE deadlocks. # diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test index d50c056fda3..fd66f6d539d 100644 --- a/mysql-test/t/mdl_sync.test +++ b/mysql-test/t/mdl_sync.test @@ -141,6 +141,101 @@ disconnect con2root; drop tables t1, t2, t3, t5; +--echo # +--echo # Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings +set @save_log_output=@@global.log_output; +set global log_output=file; + +connect(con2, localhost, root,,); + +--echo # +--echo # Test 1: CREATE TABLE +--echo # + +--echo # Connection 2 +connection con2; +--echo # Start insert on the not-yet existing table +--echo # Wait after taking the MDL lock +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--send INSERT INTO t1 VALUES(1,"def") + +--echo # Connection 1 +connection default; +SET DEBUG_SYNC= 'now WAIT_FOR locked'; +--echo # Now INSERT has a MDL on the non-existent table t1. + +--echo # +--echo # Continue the INSERT once CREATE waits for exclusive lock +SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish'; +--echo # Try to create that table. +--send CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) + +--echo # Connection 2 +--echo # Insert fails +connection con2; +--error ER_NO_SUCH_TABLE +--reap + +--echo # Connection 1 +connection default; +--reap; +SET DEBUG_SYNC= 'RESET'; +SHOW TABLES; + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +--echo # +--echo # Test 2: CREATE TABLE LIKE +--echo # + +CREATE TABLE t2 (c1 INT, c2 VARCHAR(100), KEY(c1)); + +--echo # Connection 2 +connection con2; +--echo # Start insert on the not-yet existing table +--echo # Wait after taking the MDL +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--send INSERT INTO t1 VALUES(1,"def") + +--echo # Connection 1 +connection default; +SET DEBUG_SYNC= 'now WAIT_FOR locked'; +--echo # Now INSERT has a MDL on the non-existent table t1. + +--echo # +--echo # Continue the INSERT once CREATE waits for exclusive lock +SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish'; +--echo # Try to create that table. +--send CREATE TABLE t1 LIKE t2 + +--echo # Connection 2 +--echo # Insert fails +connection con2; +--error ER_NO_SUCH_TABLE +--reap + +--echo # Connection 1 +connection default; +--reap +SET DEBUG_SYNC= 'RESET'; +SHOW TABLES; + +DROP TABLE t2; +disconnect con2; +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +set global log_output=@save_log_output; + + --echo # --echo # Bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY --echo # FOR UPDATE" diff --git a/mysql-test/t/merge.test b/mysql-test/t/merge.test index 2738f79247f..b9e6813a4df 100644 --- a/mysql-test/t/merge.test +++ b/mysql-test/t/merge.test @@ -846,7 +846,8 @@ SHOW CREATE TABLE t3; DROP TABLE t1, t2; # --echo # ---echo # CREATE ... LIKE +--echo # Bug#37371 "CREATE TABLE LIKE merge loses UNION parameter" +--echo # Demonstrate that this is no longer the case. --echo # --echo # 1. Create like. CREATE TABLE t1 (c1 INT); @@ -858,20 +859,24 @@ INSERT INTO t2 VALUES (2); INSERT INTO t3 VALUES (3); CREATE TABLE t4 LIKE t3; SHOW CREATE TABLE t4; ---error ER_OPEN_AS_READONLY INSERT INTO t4 VALUES (4); DROP TABLE t4; --echo # --echo # 1. Create like with locked tables. LOCK TABLES t3 WRITE, t2 WRITE, t1 WRITE; +--error ER_TABLE_NOT_LOCKED CREATE TABLE t4 LIKE t3; --error ER_TABLE_NOT_LOCKED SHOW CREATE TABLE t4; --error ER_TABLE_NOT_LOCKED INSERT INTO t4 VALUES (4); -UNLOCK TABLES; +CREATE TEMPORARY TABLE t4 LIKE t3; +--error ER_WRONG_MRG_TABLE SHOW CREATE TABLE t4; ---error ER_OPEN_AS_READONLY +--error ER_WRONG_MRG_TABLE +INSERT INTO t4 VALUES (4); +UNLOCK TABLES; +--error ER_WRONG_MRG_TABLE INSERT INTO t4 VALUES (4); DROP TABLE t4; # diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test index 1ba193983b2..fe17bca1eba 100644 --- a/mysql-test/t/ps_ddl.test +++ b/mysql-test/t/ps_ddl.test @@ -1493,27 +1493,24 @@ execute stmt; call p_verify_reprepare_count(0); drop table t1; deallocate prepare stmt; ---echo # XXX: no validation of the first table in case of ---echo # CREATE TEMPORARY TABLE. This is a shortcoming of the current code, ---echo # but since validation is not strictly necessary, nothing is done ---echo # about it. ---echo # Will be fixed as part of work on Bug#21431 "Incomplete support of ---echo # temporary tables" create table t1 (a int); insert into t1 (a) values (1); prepare stmt from "create temporary table if not exists t2 as select * from t1"; execute stmt; drop table t2; execute stmt; +call p_verify_reprepare_count(0); execute stmt; +call p_verify_reprepare_count(1); select * from t2; execute stmt; +call p_verify_reprepare_count(0); select * from t2; drop table t2; create temporary table t2 (a varchar(10)); execute stmt; select * from t2; -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); drop table t1; create table t1 (x int); execute stmt; diff --git a/sql/mdl.cc b/sql/mdl.cc index b624b0658ed..a883b21423e 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -1051,6 +1051,9 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() signalled|= notify_shared_lock(thd, conflicting_ticket); } + /* There is a shared or exclusive lock on the object. */ + DEBUG_SYNC(thd, "mdl_upgrade_shared_lock_to_exclusive_wait"); + if (signalled) pthread_cond_wait(&COND_mdl, &LOCK_mdl); else diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 03a4462f199..b6ffcb238b2 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1033,6 +1033,7 @@ bool mysql_new_select(LEX *lex, bool move_down); void create_select_for_variable(const char *var_name); void mysql_init_multi_delete(LEX *lex); bool multi_delete_set_locks_and_link_aux_tables(LEX *lex); +void create_table_set_open_action_and_adjust_tables(LEX *lex); void init_max_user_conn(void); void init_update_queries(void); void free_max_user_conn(void); @@ -1170,10 +1171,9 @@ int prepare_create_field(Create_field *sql_field, longlong table_flags); CHARSET_INFO* get_sql_field_charset(Create_field *sql_field, HA_CREATE_INFO *create_info); -bool mysql_create_table(THD *thd,const char *db, const char *table_name, +bool mysql_create_table(THD *thd, TABLE_LIST *create_table, HA_CREATE_INFO *create_info, - Alter_info *alter_info, - bool tmp_table, uint select_field_count); + Alter_info *alter_info); bool mysql_create_table_no_lock(THD *thd, const char *db, const char *table_name, HA_CREATE_INFO *create_info, diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index fdcd68cc2ea..34680855337 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -689,8 +689,7 @@ my_bool acl_reload(THD *thd) tables[0].next_local= tables[0].next_global= tables+1; tables[1].next_local= tables[1].next_global= tables+2; tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ; - tables[0].skip_temporary= tables[1].skip_temporary= - tables[2].skip_temporary= TRUE; + tables[0].open_type= tables[1].open_type= tables[2].open_type= OT_BASE_ONLY; init_mdl_requests(tables); if (simple_open_n_lock_tables(thd, tables)) @@ -3797,7 +3796,7 @@ static my_bool grant_reload_procs_priv(THD *thd) table.init_one_table("mysql", 5, "procs_priv", strlen("procs_priv"), "procs_priv", TL_READ); - table.skip_temporary= 1; + table.open_type= OT_BASE_ONLY; if (simple_open_n_lock_tables(thd, &table)) { @@ -3863,7 +3862,7 @@ my_bool grant_reload(THD *thd) tables[0].db= tables[1].db= (char *) "mysql"; tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type= tables[1].lock_type= TL_READ; - tables[0].skip_temporary= tables[1].skip_temporary= TRUE; + tables[0].open_type= tables[1].open_type= OT_BASE_ONLY; init_mdl_requests(tables); /* diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 08ec0c8a2ed..7a3adc89ea9 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2437,7 +2437,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, same name. This block implements the behaviour. TODO: move this block into a separate function. */ - if (!table_list->skip_temporary && ! (flags & MYSQL_OPEN_SKIP_TEMPORARY)) + if (table_list->open_type != OT_BASE_ONLY && + ! (flags & MYSQL_OPEN_SKIP_TEMPORARY)) { for (table= thd->temporary_tables; table ; table=table->next) { @@ -2469,10 +2470,16 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } } - if (flags & MYSQL_OPEN_TEMPORARY_ONLY) + if (table_list->open_type == OT_TEMPORARY_ONLY || + (flags & MYSQL_OPEN_TEMPORARY_ONLY)) { - my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name); - DBUG_RETURN(TRUE); + if (table_list->open_strategy == TABLE_LIST::OPEN_NORMAL) + { + my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name); + DBUG_RETURN(TRUE); + } + else + DBUG_RETURN(FALSE); } /* diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 171c5e2cee0..8fd704c4f71 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3415,46 +3415,43 @@ void select_insert::abort() { CREATE TABLE (SELECT) ... ***************************************************************************/ -/* +/** Create table from lists of fields and items (or just return TABLE object for pre-opened existing table). - SYNOPSIS - create_table_from_items() - thd in Thread object - create_info in Create information (like MAX_ROWS, ENGINE or - temporary table flag) - create_table in Pointer to TABLE_LIST object providing database - and name for table to be created or to be open - alter_info in/out Initial list of columns and indexes for the table - to be created - items in List of items which should be used to produce rest - of fields for the table (corresponding fields will - be added to the end of alter_info->create_list) - lock out Pointer to the MYSQL_LOCK object for table created - (or open temporary table) will be returned in this - parameter. Since this table is not included in - THD::lock caller is responsible for explicitly - unlocking this table. - hooks + @param thd [in] Thread object + @param create_info [in] Create information (like MAX_ROWS, ENGINE or + temporary table flag) + @param create_table [in] Pointer to TABLE_LIST object providing database + and name for table to be created or to be open + @param alter_info [in/out] Initial list of columns and indexes for the + table to be created + @param items [in] List of items which should be used to produce + rest of fields for the table (corresponding + fields will be added to the end of + alter_info->create_list) + @param lock [out] Pointer to the MYSQL_LOCK object for table + created will be returned in this parameter. + Since this table is not included in THD::lock + caller is responsible for explicitly unlocking + this table. + @param hooks [in] Hooks to be invoked before and after obtaining + table lock on the table being created. - NOTES - This function behaves differently for base and temporary tables: - - For base table we assume that either table exists and was pre-opened - and locked at open_and_lock_tables() stage (and in this case we just - emit error or warning and return pre-opened TABLE object) or special - placeholder was put in table cache that guarantees that this table - won't be created or opened until the placeholder will be removed - (so there is an exclusive lock on this table). - - We don't pre-open existing temporary table, instead we either open - or create and then open table in this function. + @note + This function assumes that either table exists and was pre-opened and + locked at open_and_lock_tables() stage (and in this case we just emit + error or warning and return pre-opened TABLE object) or an exclusive + metadata lock was acquired on table so we can safely create, open and + lock table in it (we don't acquire metadata lock if this create is + for temporary table). + @note Since this function contains some logic specific to CREATE TABLE ... SELECT it should be changed before it can be used in other contexts. - RETURN VALUES - non-zero Pointer to TABLE object for table created or opened - 0 Error + @retval non-zero Pointer to TABLE object for table created or opened + @retval 0 Error */ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, @@ -3529,14 +3526,12 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, open_table(). */ { - tmp_disable_binlog(thd); if (!mysql_create_table_no_lock(thd, create_table->db, create_table->table_name, create_info, alter_info, 0, select_field_count)) { - if (create_info->table_existed && - !(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + if (create_info->table_existed) { /* This means that someone created table underneath server @@ -3572,8 +3567,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, { Open_table_context ot_ctx_unused(thd); if (open_table(thd, create_table, thd->mem_root, &ot_ctx_unused, - MYSQL_OPEN_TEMPORARY_ONLY) && - !create_info->table_existed) + MYSQL_OPEN_TEMPORARY_ONLY)) { /* This shouldn't happen as creation of temporary table should make @@ -3586,7 +3580,6 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, table= create_table->table; } } - reenable_binlog(thd); if (!table) // open failed DBUG_RETURN(0); } @@ -3610,9 +3603,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, mysql_unlock_tables(thd, *lock); *lock= 0; } - - if (!create_info->table_existed) - drop_open_table(thd, table, create_table->db, create_table->table_name); + drop_open_table(thd, table, create_table->db, create_table->table_name); DBUG_RETURN(0); } DBUG_RETURN(table); @@ -3704,7 +3695,7 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) DBUG_EXECUTE_IF("sleep_create_select_before_check_if_exists", my_sleep(6000000);); - if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) && create_table->table) + if (create_table->table) { /* Table already exists and was open at open_and_lock_tables() stage. */ if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index cafc70dc2ee..e462556c133 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2245,9 +2245,9 @@ case SQLCOM_PREPARE: { DBUG_ASSERT(first_table == all_tables && first_table != 0); bool link_to_local; - // Skip first table, which is the table we are creating - TABLE_LIST *create_table= lex->unlink_first_table(&link_to_local); - TABLE_LIST *select_tables= lex->query_tables; + TABLE_LIST *create_table= first_table; + TABLE_LIST *select_tables= lex->create_last_non_select_table->next_global; + /* Code below (especially in mysql_create_table() and select_create methods) may modify HA_CREATE_INFO structure in LEX, so we have to @@ -2327,6 +2327,10 @@ case SQLCOM_PREPARE: } #endif + /* Set strategies: reset default or 'prepared' values. */ + create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + create_table->lock_strategy= TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL; + /* Close any open handlers for the table */ @@ -2389,15 +2393,8 @@ case SQLCOM_PREPARE: goto end_with_restore_list; } - if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - lex->link_first_table_back(create_table, link_to_local); - /* Set strategies: reset default or 'prepared' values. */ - create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - create_table->lock_strategy= TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL; - } - - if (!(res= open_and_lock_tables(thd, lex->query_tables))) + if (!(res= open_and_lock_tables_derived(thd, lex->query_tables, TRUE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL))) { /* Is table which we are changing used somewhere in other parts @@ -2406,7 +2403,6 @@ case SQLCOM_PREPARE: if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) { TABLE_LIST *duplicate; - create_table= lex->unlink_first_table(&link_to_local); if ((duplicate= unique_table(thd, create_table, select_tables, 0))) { update_non_unique_table_error(create_table, "CREATE", duplicate); @@ -2432,6 +2428,13 @@ case SQLCOM_PREPARE: } } + /* + Remove target table from main select and name resolution + context. This can't be done earlier as it will break view merging in + statements like "CREATE TABLE IF NOT EXISTS existing_view SELECT". + */ + lex->unlink_first_table(&link_to_local); + /* select_create is currently not re-execution friendly and needs to be created for every execution of a PS/SP. @@ -2451,33 +2454,32 @@ case SQLCOM_PREPARE: res= handle_select(thd, lex, result, 0); delete result; } + + lex->link_first_table_back(create_table, link_to_local); } - else if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) - create_table= lex->unlink_first_table(&link_to_local); - } else { /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */ if (create_info.options & HA_LEX_CREATE_TMP_TABLE) thd->options|= OPTION_KEEP_LOG; - /* regular create */ if (create_info.options & HA_LEX_CREATE_TABLE_LIKE) + { + /* CREATE TABLE ... LIKE ... */ res= mysql_create_like_table(thd, create_table, select_tables, &create_info); + } else { - res= mysql_create_table(thd, create_table->db, - create_table->table_name, &create_info, - &alter_info, 0, 0); + /* Regular CREATE TABLE */ + res= mysql_create_table(thd, create_table, + &create_info, &alter_info); } if (!res) - my_ok(thd); + my_ok(thd); } - /* put tables back for PS rexecuting */ end_with_restore_list: - lex->link_first_table_back(create_table, link_to_local); break; } case SQLCOM_CREATE_INDEX: @@ -2705,7 +2707,8 @@ end_with_restore_list: } /* Ignore temporary tables if this is "SHOW CREATE VIEW" */ - first_table->skip_temporary= 1; + first_table->open_type= OT_BASE_ONLY; + } else { @@ -7194,6 +7197,34 @@ bool insert_precheck(THD *thd, TABLE_LIST *tables) } +/** + Set proper open mode and table type for element representing target table + of CREATE TABLE statement, also adjust statement table list if necessary. +*/ + +void create_table_set_open_action_and_adjust_tables(LEX *lex) +{ + TABLE_LIST *create_table= lex->query_tables; + + if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) + create_table->open_type= OT_TEMPORARY_ONLY; + else if (!lex->select_lex.item_list.elements) + create_table->open_type= OT_BASE_ONLY; + + if (!lex->select_lex.item_list.elements) + { + /* + Avoid opening and locking target table for ordinary CREATE TABLE + or CREATE TABLE LIKE for write (unlike in CREATE ... SELECT we + won't do any insertions in it anyway). Not doing this causes + problems when running CREATE TABLE IF NOT EXISTS for already + existing log table. + */ + create_table->lock_type= TL_READ; + } +} + + /** CREATE TABLE query pre-check. diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index e0461533dde..718471cc1b6 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -4175,39 +4175,22 @@ bool mysql_unpack_partition(THD *thd, ha_resolve_storage_engine_name(default_db_type))); if (is_create_table_ind && old_lex->sql_command == SQLCOM_CREATE_TABLE) { - if (old_lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE) - { - /* - This code is executed when we create table in CREATE TABLE t1 LIKE t2. - old_lex->query_tables contains table list element for t2 and the table - we are opening has name t1. - */ - if (partition_default_handling(table, part_info, FALSE, - old_lex->query_tables->table->s->path.str)) - { - result= TRUE; - goto end; - } - } - else - { - /* - When we come here we are doing a create table. In this case we - have already done some preparatory work on the old part_info - object. We don't really need this new partition_info object. - Thus we go back to the old partition info object. - We need to free any memory objects allocated on item_free_list - by the parser since we are keeping the old info from the first - parser call in CREATE TABLE. - We'll ensure that this object isn't put into table cache also - just to ensure we don't get into strange situations with the - item objects. - */ - thd->free_items(); - part_info= thd->work_part_info; - table->s->version= 0UL; - *work_part_info_used= true; - } + /* + When we come here we are doing a create table. In this case we + have already done some preparatory work on the old part_info + object. We don't really need this new partition_info object. + Thus we go back to the old partition info object. + We need to free any memory objects allocated on item_free_list + by the parser since we are keeping the old info from the first + parser call in CREATE TABLE. + We'll ensure that this object isn't put into table cache also + just to ensure we don't get into strange situations with the + item objects. + */ + thd->free_items(); + part_info= thd->work_part_info; + table->s->version= 0UL; + *work_part_info_used= true; } table->part_info= part_info; table->file->set_part_info(part_info); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 9c7949eaf1d..27fdd1e2a8d 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1660,39 +1660,35 @@ static bool mysql_test_create_table(Prepared_statement *stmt) LEX *lex= stmt->lex; SELECT_LEX *select_lex= &lex->select_lex; bool res= FALSE; - /* Skip first table, which is the table we are creating */ bool link_to_local; - TABLE_LIST *create_table= lex->unlink_first_table(&link_to_local); - TABLE_LIST *tables= lex->query_tables; + TABLE_LIST *create_table= lex->query_tables; + TABLE_LIST *tables= lex->create_last_non_select_table->next_global; if (create_table_precheck(thd, tables, create_table)) DBUG_RETURN(TRUE); + /* + The open and lock strategies will be set again once the + statement is executed. These values are only meaningful + for the prepare phase. + */ + create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + create_table->lock_strategy= TABLE_LIST::SHARED_MDL; + if (select_lex->item_list.elements) { - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - lex->link_first_table_back(create_table, link_to_local); - /* - The open and lock strategies will be set again once the - statement is executed. These values are only meaningful - for the prepare phase. - */ - create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - create_table->lock_strategy= TABLE_LIST::SHARED_MDL; - } - if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0)) DBUG_RETURN(TRUE); - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) - create_table= lex->unlink_first_table(&link_to_local); - select_lex->context.resolve_in_select_list= TRUE; + lex->unlink_first_table(&link_to_local); + res= select_like_stmt_test(stmt, 0, 0); + + lex->link_first_table_back(create_table, &link_to_local); } - else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE) + else { /* Check that the source table exist, and also record @@ -1704,8 +1700,6 @@ static bool mysql_test_create_table(Prepared_statement *stmt) DBUG_RETURN(TRUE); } - /* put tables back for PS rexecuting */ - lex->link_first_table_back(create_table, link_to_local); DBUG_RETURN(res); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 44a0d21580f..e9474d9add6 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4084,7 +4084,6 @@ bool mysql_create_table_no_lock(THD *thd, thd->thread_specific_used= TRUE; } - write_create_table_bin_log(thd, create_info, internal_tmp_table); error= FALSE; unlock_and_end: pthread_mutex_unlock(&LOCK_open); @@ -4109,21 +4108,18 @@ warn: Database and name-locking aware wrapper for mysql_create_table_no_lock(), */ -bool mysql_create_table(THD *thd, const char *db, const char *table_name, +bool mysql_create_table(THD *thd, TABLE_LIST *create_table, HA_CREATE_INFO *create_info, - Alter_info *alter_info, - bool internal_tmp_table, - uint select_field_count) + Alter_info *alter_info) { - MDL_request target_mdl_request; - bool has_target_mdl_lock= FALSE; bool result; DBUG_ENTER("mysql_create_table"); /* Wait for any database locks */ pthread_mutex_lock(&LOCK_lock_db); while (!thd->killed && - my_hash_search(&lock_db_cache,(uchar*) db, strlen(db))) + my_hash_search(&lock_db_cache, (uchar*)create_table->db, + create_table->db_length)) { wait_for_condition(thd, &LOCK_lock_db, &COND_refresh); pthread_mutex_lock(&LOCK_lock_db); @@ -4137,47 +4133,47 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, creating_table++; pthread_mutex_unlock(&LOCK_lock_db); - if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + /* + Open or obtain an exclusive metadata lock on table being created. + */ + if (open_and_lock_tables_derived(thd, thd->lex->query_tables, FALSE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL)) { - target_mdl_request.init(MDL_key::TABLE, db, table_name, MDL_EXCLUSIVE); - if (thd->mdl_context.try_acquire_exclusive_lock(&target_mdl_request)) - { - result= TRUE; - goto unlock; - } - if (target_mdl_request.ticket == NULL) - { - /* Table exists and is locked by some other thread. */ - if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), - table_name); - create_info->table_existed= 1; - result= FALSE; - write_create_table_bin_log(thd, create_info, internal_tmp_table); - } - else - { - my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); - result= TRUE; - } - goto unlock; - } - /* Got lock. */ - DEBUG_SYNC(thd, "locked_table_name"); - has_target_mdl_lock= TRUE; + result= TRUE; + goto unlock; } - result= mysql_create_table_no_lock(thd, db, table_name, create_info, - alter_info, - internal_tmp_table, - select_field_count); + /* Got lock. */ + DEBUG_SYNC(thd, "locked_table_name"); + + result= mysql_create_table_no_lock(thd, create_table->db, + create_table->table_name, create_info, + alter_info, FALSE, 0); + + /* + Don't write statement if: + - Table creation has failed + - Table has already existed + - Row-based logging is used and we are creating a temporary table + Otherwise, the statement shall be binlogged. + */ + if (!result && + !create_info->table_existed && + (!thd->current_stmt_binlog_row_based || + (thd->current_stmt_binlog_row_based && + !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + + if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + { + /* + close_thread_tables() takes care about both closing open tables (which + might be still around in case of error) and releasing metadata locks. + */ + close_thread_tables(thd); + } unlock: - if (has_target_mdl_lock) - thd->mdl_context.release_lock(target_mdl_request.ticket); - pthread_mutex_lock(&LOCK_lock_db); if (!--creating_table && creating_database) pthread_cond_signal(&COND_refresh); @@ -5158,55 +5154,6 @@ bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) } - -/** - @brief Create frm file based on I_S table - - @param[in] thd thread handler - @param[in] schema_table I_S table - @param[in] dst_path path where frm should be created - @param[in] create_info Create info - - @return Operation status - @retval 0 success - @retval 1 error -*/ - - -bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, - char *dst_path, HA_CREATE_INFO *create_info) -{ - HA_CREATE_INFO local_create_info; - Alter_info alter_info; - bool tmp_table= (create_info->options & HA_LEX_CREATE_TMP_TABLE); - uint keys= schema_table->table->s->keys; - uint db_options= 0; - DBUG_ENTER("mysql_create_like_schema_frm"); - - bzero((char*) &local_create_info, sizeof(local_create_info)); - local_create_info.db_type= schema_table->table->s->db_type(); - local_create_info.row_type= schema_table->table->s->row_type; - local_create_info.default_table_charset=default_charset_info; - alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE); - schema_table->table->use_all_columns(); - if (mysql_prepare_alter_table(thd, schema_table->table, - &local_create_info, &alter_info)) - DBUG_RETURN(1); - if (mysql_prepare_create_table(thd, &local_create_info, &alter_info, - tmp_table, &db_options, - schema_table->table->file, - &schema_table->table->s->key_info, &keys, 0)) - DBUG_RETURN(1); - local_create_info.max_rows= 0; - if (mysql_create_frm(thd, dst_path, NullS, NullS, - &local_create_info, alter_info.create_list, - keys, schema_table->table->s->key_info, - schema_table->table->file)) - DBUG_RETURN(1); - DBUG_RETURN(0); -} - - /* Create a table identical to the specified table @@ -5225,12 +5172,8 @@ bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO *create_info) { - char src_path[FN_REFLEN + 1], dst_path[FN_REFLEN + 1]; - uint dst_path_length; - bool has_mdl_lock= FALSE; - char *db= table->db; - char *table_name= table->table_name; - int err; + HA_CREATE_INFO local_create_info; + Alter_info local_alter_info; bool res= TRUE; uint not_used; #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -5242,161 +5185,63 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, /* - By opening source table and thus acquiring shared metadata lock on it - we guarantee that it exists and no concurrent DDL operation will mess - with it. Later we also take an exclusive metadata lock on target table - name, which makes copying of .frm file, call to ha_create_table() and - binlogging atomic against concurrent DML and DDL operations on target - table. Thus by holding both these "locks" we ensure that our statement - is properly isolated from all concurrent operations which matter. + We the open source table to get its description in HA_CREATE_INFO + and Alter_info objects. This also acquires a shared metadata lock + on this table which ensures that no concurrent DDL operation will + mess with it. + Also in case when we create non-temporary table open_tables() + call obtains an exclusive metadata lock on target table ensuring + that we can safely perform table creation. + Thus by holding both these locks we ensure that our statement is + properly isolated from all concurrent operations which matter. */ - if (open_tables(thd, &src_table, ¬_used, 0)) - DBUG_RETURN(TRUE); - - /* - For bug#25875, Newly created table through CREATE TABLE .. LIKE - has no ndb_dd attributes; - Add something to get possible tablespace info from src table, - it can get valid tablespace name only for disk-base ndb table - */ - if ((src_table->table->file->get_tablespace_name(thd, ts_name, FN_LEN))) - { - create_info->tablespace= ts_name; - create_info->storage_media= HA_SM_DISK; - } - - strxmov(src_path, src_table->table->s->path.str, reg_ext, NullS); - - DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000);); - - /* - Check that destination tables does not exist. Note that its name - was already checked when it was added to the table list. - */ - if (create_info->options & HA_LEX_CREATE_TMP_TABLE) - { - if (find_temporary_table(thd, db, table_name)) - goto table_exists; - dst_path_length= build_tmptable_filename(thd, dst_path, sizeof(dst_path)); - create_info->table_options|= HA_CREATE_DELAY_KEY_WRITE; - } - else - { - table->mdl_request.init(MDL_key::TABLE, db, table_name, MDL_EXCLUSIVE); - if (thd->mdl_context.try_acquire_exclusive_lock(&table->mdl_request)) - DBUG_RETURN(TRUE); - - if (table->mdl_request.ticket == NULL) - goto table_exists; - - DEBUG_SYNC(thd, "locked_table_name"); - has_mdl_lock= TRUE; - - dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1, - db, table_name, reg_ext, 0); - if (!access(dst_path, F_OK)) - goto table_exists; - } - - DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000);); - - if (opt_sync_frm && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)) - flags|= MY_SYNC; - - /* - Create a new table by copying from source table - and sync the new table if the flag MY_SYNC is set - - TODO: Obtaining LOCK_open mutex here is actually a legacy from the - times when some operations (e.g. I_S implementation) ignored - exclusive metadata lock on target table. Also some engines - (e.g. NDB cluster) require that LOCK_open should be held - during the call to ha_create_table() (See bug #28614 for more - info). So we should double check and probably fix this code - to not acquire this mutex. - */ - pthread_mutex_lock(&LOCK_open); - if (src_table->schema_table) - { - if (mysql_create_like_schema_frm(thd, src_table, dst_path, create_info)) - { - pthread_mutex_unlock(&LOCK_open); - goto err; - } - } - else if (my_copy(src_path, dst_path, flags)) - { - if (my_errno == ENOENT) - my_error(ER_BAD_DB_ERROR,MYF(0),db); - else - my_error(ER_CANT_CREATE_FILE,MYF(0),dst_path,my_errno); - pthread_mutex_unlock(&LOCK_open); + if (open_tables(thd, &thd->lex->query_tables, ¬_used, 0)) goto err; - } + src_table->table->use_all_columns(); - /* - As mysql_truncate don't work on a new table at this stage of - creation, instead create the table directly (for both normal - and temporary tables). - */ + /* Fill HA_CREATE_INFO and Alter_info with description of source table. */ + bzero((char*) &local_create_info, sizeof(local_create_info)); + local_create_info.db_type= src_table->table->s->db_type(); + local_create_info.row_type= src_table->table->s->row_type; + if (mysql_prepare_alter_table(thd, src_table->table, &local_create_info, + &local_alter_info)) + goto err; #ifdef WITH_PARTITION_STORAGE_ENGINE - /* - For partitioned tables we need to copy the .par file as well since - it is used in open_table_def to even be able to create a new handler. - There is no way to find out here if the original table is a - partitioned table so we copy the file and ignore any errors. - */ - fn_format(tmp_path, dst_path, reg_ext, ".par", MYF(MY_REPLACE_EXT)); - strmov(dst_path, tmp_path); - fn_format(tmp_path, src_path, reg_ext, ".par", MYF(MY_REPLACE_EXT)); - strmov(src_path, tmp_path); - my_copy(src_path, dst_path, MYF(MY_DONT_OVERWRITE_FILE)); + /* Partition info is not handled by mysql_prepare_alter_table() call. */ + if (src_table->table->part_info) + thd->work_part_info= src_table->table->part_info->get_clone(); #endif - DBUG_EXECUTE_IF("sleep_create_like_before_ha_create", my_sleep(6000000);); + /* + Adjust description of source table before using it for creation of + target table. - dst_path[dst_path_length - reg_ext_length]= '\0'; // Remove .frm - if (thd->variables.keep_files_on_create) - create_info->options|= HA_CREATE_KEEP_FILES; - err= ha_create_table(thd, dst_path, db, table_name, create_info, 1); - pthread_mutex_unlock(&LOCK_open); + Similarly to SHOW CREATE TABLE we ignore MAX_ROWS attribute of + temporary table which represents I_S table. + */ + if (src_table->schema_table) + local_create_info.max_rows= 0; + /* Set IF NOT EXISTS option as in the CREATE TABLE LIKE statement. */ + local_create_info.options|= create_info->options&HA_LEX_CREATE_IF_NOT_EXISTS; + /* Replace type of source table with one specified in the statement. */ + local_create_info.options&= ~HA_LEX_CREATE_TMP_TABLE; + local_create_info.options|= create_info->options & HA_LEX_CREATE_TMP_TABLE; + /* Reset auto-increment counter for the new table. */ + local_create_info.auto_increment_value= 0; - if (create_info->options & HA_LEX_CREATE_TMP_TABLE) - { - if (err || !open_temporary_table(thd, dst_path, db, table_name, 1)) - { - (void) rm_temporary_table(create_info->db_type, - dst_path); /* purecov: inspected */ - goto err; /* purecov: inspected */ - } - thd->thread_specific_used= TRUE; - } - else if (err) - { - (void) quick_rm_table(create_info->db_type, db, - table_name, 0); /* purecov: inspected */ - goto err; /* purecov: inspected */ - } - -goto binlog; - -table_exists: - if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) - { - char warn_buff[MYSQL_ERRMSG_SIZE]; - my_snprintf(warn_buff, sizeof(warn_buff), - ER(ER_TABLE_EXISTS_ERROR), table_name); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_TABLE_EXISTS_ERROR,warn_buff); - } - else - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); + if ((res= mysql_create_table_no_lock(thd, table->db, table->table_name, + &local_create_info, &local_alter_info, + FALSE, 0)) || + local_create_info.table_existed) goto err; - } -binlog: - DBUG_EXECUTE_IF("sleep_create_like_before_binlogging", my_sleep(6000000);); + /* + Ensure that we have an exclusive lock on target table if we are creating + non-temporary table. + */ + DBUG_ASSERT((create_info->options & HA_LEX_CREATE_TMP_TABLE) || + thd->mdl_context.is_exclusive_lock_owner(MDL_key::TABLE, table->db, + table->table_name)); /* We have to write the query before we unlock the tables. @@ -5463,12 +5308,7 @@ binlog: else write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - res= FALSE; - err: - if (has_mdl_lock) - thd->mdl_context.release_lock(table->mdl_request.ticket); - DBUG_RETURN(res); } diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 823dca5d93f..8719938d85d 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -445,7 +445,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) Also prevent DROP TRIGGER from opening temporary table which might shadow base table on which trigger to be dropped is defined. */ - tables->skip_temporary= TRUE; + tables->open_type= OT_BASE_ONLY; /* Keep consistent with respect to other DDL statements */ mysql_ha_rm_tables(thd, tables); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index b36ff6b6743..0f920bca101 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1259,7 +1259,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, tbl; tbl= (view_tables_tail= tbl)->next_global) { - tbl->skip_temporary= 1; + tbl->open_type= OT_BASE_ONLY; tbl->belong_to_view= top_view; tbl->referencing_view= table; tbl->prelocking_placeholder= table->prelocking_placeholder; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 826ec63a93e..1e6a7c4d50f 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1781,6 +1781,7 @@ create: ha_resolve_storage_engine_name(lex->create_info.db_type), $5->table.str); } + create_table_set_open_action_and_adjust_tables(lex); } | CREATE opt_unique_or_fulltext INDEX_SYM ident key_alg ON table_ident @@ -3921,7 +3922,7 @@ size_number: create2: '(' create2a {} | opt_create_table_options - opt_partitioning + opt_create_partitioning create3 {} | LIKE table_ident { @@ -3955,9 +3956,9 @@ create2: create2a: create_field_list ')' opt_create_table_options - opt_partitioning + opt_create_partitioning create3 {} - | opt_partitioning + | opt_create_partitioning create_select ')' { Select->set_braces(1);} union_opt {} @@ -3973,6 +3974,19 @@ create3: union_opt {} ; +opt_create_partitioning: + opt_partitioning + { + /* + Remove all tables used in PARTITION clause from the global table + list. Partitioning with subqueries is not allowed anyway. + */ + TABLE_LIST *last_non_sel_table= Lex->create_last_non_select_table; + last_non_sel_table->next_global= 0; + Lex->query_tables_last= &last_non_sel_table->next_global; + } + ; + /* This part of the parser is about handling of the partition information. diff --git a/sql/table.h b/sql/table.h index eacf4f6085c..b44c2e9f3be 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1069,6 +1069,16 @@ public: }; +/** + Type of table which can be open for an element of table list. +*/ + +enum enum_open_type +{ + OT_TEMPORARY_OR_BASE= 0, OT_TEMPORARY_ONLY, OT_BASE_ONLY +}; + + /* Table reference in the FROM clause. @@ -1330,7 +1340,11 @@ struct TABLE_LIST bool cacheable_table; /* stop PS caching */ /* used in multi-upd/views privilege check */ bool table_in_first_from_clause; - bool skip_temporary; /* this table shouldn't be temporary */ + /** + Specifies which kind of table should be open for this element + of table list. + */ + enum enum_open_type open_type; /* TRUE if this merged view contain auto_increment field */ bool contain_auto_increment; bool multitable_view; /* TRUE iff this is multitable view */ From 84e35f5e1c627a7129b3a4d79140219de81acb1e Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 12:46:16 +0100 Subject: [PATCH 107/212] Backport of revno: 2617.68.3 Followup to Bug#42546 Backup: RESTORE fails, thinking it finds an existing table This patch updates lowercase_table2.test with the changed error message CREATE TABLE produces if it fails because it finds an matching TABLE_SHARE in the TDC even if the .FRM/.MYD has been removed from disk. With the changes introduced in Bug#42546, CREATE TABLE uses open_tables() which will find the TDC entry and fail in open_table_from_share() with ER_FILE_NOT_FOUND. Before, CREATE TABLE would not use open_tables() and fail with ER_TABLE_EXISTS_ERROR upon finding the TDC entry in mysql_create_table_no_lock(). --- mysql-test/r/lowercase_table2.result | 16 +++++++++------- mysql-test/t/lowercase_table2.test | 14 +++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/lowercase_table2.result b/mysql-test/r/lowercase_table2.result index cf87fd1b5a4..b621a466a29 100644 --- a/mysql-test/r/lowercase_table2.result +++ b/mysql-test/r/lowercase_table2.result @@ -226,10 +226,9 @@ drop table t_bug44738_UPPERCASE; create table t_bug44738_UPPERCASE (i int); drop table t_bug44738_UPPERCASE; # Finally, let us check that another issue which was exposed by -# the original test case is solved. I.e. that fuse in CREATE TABLE -# which ensures that table is not created if there is an entry for -# it in TDC even though it was removed from disk uses normalized -# version of the table name. +# the original test case is solved. I.e. that the table is not +# created if there is an entry for it in TDC even though it was +# removed from disk. create table t_bug44738_UPPERCASE (i int) engine = myisam; # Load table definition in TDC. select table_schema, table_name, table_comment from information_schema.tables @@ -237,10 +236,13 @@ where table_schema = 'test' and table_name like 't_bug44738_%'; table_schema table_name table_comment test t_bug44738_UPPERCASE # Simulate manual removal of the table. -# After manual removal of table still there should be an entry for table -# in TDC so attempt to create table with the same name should fail. +# Check that still there is an entry for table in TDC. +show open tables like 't_bug44738_%'; +Database Table In_use Name_locked +test t_bug44738_uppercase 0 0 +# So attempt to create table with the same name should fail. create table t_bug44738_UPPERCASE (i int); -ERROR 42S01: Table 't_bug44738_uppercase' already exists +ERROR HY000: Can't find file: 't_bug44738_uppercase' (errno: 2) # And should succeed after FLUSH TABLES. flush tables; create table t_bug44738_UPPERCASE (i int); diff --git a/mysql-test/t/lowercase_table2.test b/mysql-test/t/lowercase_table2.test index 92add60616a..b8c7f532cde 100644 --- a/mysql-test/t/lowercase_table2.test +++ b/mysql-test/t/lowercase_table2.test @@ -201,10 +201,9 @@ create table t_bug44738_UPPERCASE (i int); drop table t_bug44738_UPPERCASE; --echo # Finally, let us check that another issue which was exposed by ---echo # the original test case is solved. I.e. that fuse in CREATE TABLE ---echo # which ensures that table is not created if there is an entry for ---echo # it in TDC even though it was removed from disk uses normalized ---echo # version of the table name. +--echo # the original test case is solved. I.e. that the table is not +--echo # created if there is an entry for it in TDC even though it was +--echo # removed from disk. create table t_bug44738_UPPERCASE (i int) engine = myisam; --echo # Load table definition in TDC. select table_schema, table_name, table_comment from information_schema.tables @@ -214,9 +213,10 @@ let $MYSQLD_DATADIR= `select @@datadir`; --remove_file $MYSQLD_DATADIR/test/t_bug44738_UPPERCASE.frm --remove_file $MYSQLD_DATADIR/test/t_bug44738_UPPERCASE.MYD --remove_file $MYSQLD_DATADIR/test/t_bug44738_UPPERCASE.MYI ---echo # After manual removal of table still there should be an entry for table ---echo # in TDC so attempt to create table with the same name should fail. ---error ER_TABLE_EXISTS_ERROR +--echo # Check that still there is an entry for table in TDC. +show open tables like 't_bug44738_%'; +--echo # So attempt to create table with the same name should fail. +--error ER_FILE_NOT_FOUND create table t_bug44738_UPPERCASE (i int); --echo # And should succeed after FLUSH TABLES. flush tables; From 0b874e3e09570250acd00e355bfb92c77dbd3ca5 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 13:02:37 +0100 Subject: [PATCH 108/212] Backport of revno: 2617.68.43 Bug #47335 assert in get_table_share The assert would happen if ALTER VIEW was used to alter a view (existing or non-existing) and a temporary table with the same name already existed. The assert is triggered if the current statement does not have a MDL lock on the view to be altered. This would happen because open_table() would open the temporary table instead and MDL locks are not taken for temporary tables (since they are local to one connection). The patch changes open_type for CREATE/ALTER VIEW to OT_BASE_ONLY. This prevents open_table() from trying to open a temporary table with the same name should one exist. Now the view will be altered if it exists or ER_NO_SUCH_TABLE will be reported if it does not. Test case added to view.test --- mysql-test/r/view.result | 17 +++++++++++++++++ mysql-test/t/view.test | 21 +++++++++++++++++++++ sql/sql_view.cc | 1 + 3 files changed, 39 insertions(+) diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index 7e9739173df..cb53bf462f5 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -3959,3 +3959,20 @@ DROP TABLE t1; # ----------------------------------------------------------------- # -- End of 5.1 tests. # ----------------------------------------------------------------- +# +# Bug #47335 assert in get_table_share +# +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +CREATE TEMPORARY TABLE t1 (id INT); +ALTER VIEW t1 AS SELECT 1 AS f1; +ERROR 42S02: Table 'test.t1' doesn't exist +DROP TABLE t1; +CREATE VIEW v1 AS SELECT 1 AS f1; +CREATE TEMPORARY TABLE v1 (id INT); +ALTER VIEW v1 AS SELECT 2 AS f1; +DROP TABLE v1; +SELECT * FROM v1; +f1 +2 +DROP VIEW v1; diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test index c3ff58880c9..13a557dda60 100644 --- a/mysql-test/t/view.test +++ b/mysql-test/t/view.test @@ -3906,3 +3906,24 @@ DROP TABLE t1; --echo # ----------------------------------------------------------------- --echo # -- End of 5.1 tests. --echo # ----------------------------------------------------------------- + +--echo # +--echo # Bug #47335 assert in get_table_share +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +--enable_warnings + +CREATE TEMPORARY TABLE t1 (id INT); +--error ER_NO_SUCH_TABLE +ALTER VIEW t1 AS SELECT 1 AS f1; +DROP TABLE t1; + +CREATE VIEW v1 AS SELECT 1 AS f1; +CREATE TEMPORARY TABLE v1 (id INT); +ALTER VIEW v1 AS SELECT 2 AS f1; +DROP TABLE v1; +SELECT * FROM v1; +DROP VIEW v1; diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 0f920bca101..6d2836afc0d 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -397,6 +397,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, lex->link_first_table_back(view, link_to_local); view->open_strategy= TABLE_LIST::OPEN_STUB; view->lock_strategy= TABLE_LIST::EXCLUSIVE_MDL; + view->open_type= OT_BASE_ONLY; if (open_and_lock_tables(thd, lex->query_tables)) { From 69f677b215dcc51b40c53d7ee9293a17116b5d93 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 13:15:20 +0100 Subject: [PATCH 109/212] Backport of revno: 2617.68.45 Bug #47635 assert in start_waiting_global_read_lock during CREATE VIEW The problem was that CREATE VIEW would trigger an assert if a temporary table with the same name already existed. This bug was fixed by the patch for Bug#47335. CREATE/ALTER VIEW will now ignore temporary tables. See Bug#47335 for more information. Test case added to view.test. --- mysql-test/r/view.result | 15 +++++++++++++++ mysql-test/t/view.test | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index cb53bf462f5..c097b70680f 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -3976,3 +3976,18 @@ SELECT * FROM v1; f1 2 DROP VIEW v1; +# +# Bug #47635 assert in start_waiting_global_read_lock +# during CREATE VIEW +# +DROP TABLE IF EXISTS t1, t2; +DROP VIEW IF EXISTS t2; +CREATE TABLE t1 (f1 integer); +CREATE TEMPORARY TABLE IF NOT EXISTS t1 (f1 integer); +CREATE TEMPORARY TABLE t2 (f1 integer); +DROP TABLE t1; +FLUSH TABLES WITH READ LOCK; +CREATE VIEW t2 AS SELECT * FROM t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +UNLOCK TABLES; +DROP TABLE t1, t2; diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test index 13a557dda60..880b4b6a645 100644 --- a/mysql-test/t/view.test +++ b/mysql-test/t/view.test @@ -1,3 +1,4 @@ + --disable_warnings drop table if exists t1,t2,t3,t4,t9,`t1a``b`,v1,v2,v3,v4,v5,v6; drop view if exists t1,t2,`t1a``b`,v1,v2,v3,v4,v5,v6; @@ -3927,3 +3928,25 @@ ALTER VIEW v1 AS SELECT 2 AS f1; DROP TABLE v1; SELECT * FROM v1; DROP VIEW v1; + + +--echo # +--echo # Bug #47635 assert in start_waiting_global_read_lock +--echo # during CREATE VIEW +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1, t2; +DROP VIEW IF EXISTS t2; +--enable_warnings + +CREATE TABLE t1 (f1 integer); +CREATE TEMPORARY TABLE IF NOT EXISTS t1 (f1 integer); +CREATE TEMPORARY TABLE t2 (f1 integer); +DROP TABLE t1; +FLUSH TABLES WITH READ LOCK; +--error ER_CANT_UPDATE_WITH_READLOCK +CREATE VIEW t2 AS SELECT * FROM t1; + +UNLOCK TABLES; +DROP TABLE t1, t2; From 58a9857bc5091bf08b3b3d7bd5221c5ae2a84b53 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 13:37:18 +0100 Subject: [PATCH 110/212] Backport of revno: 3673 Bug #47313 assert in check_key_in_view during CALL procedure View definitions are inlined in a stored procedure when the procedure is fist called. This means that if a temporary table is later added with the same name as the view, the stored procedure will still use the view. This happens even if temporary tables normally shadow base tables/views. The reason for the assert was that even if the stored procedure referenced the view, open_table() still tried to open the temporary table. This "half view/half temporary table" state caused the assert. The bug was not present in 5.1 as open_table() is not called for the view there. This code was changed with the introduction of MDL in order to properly lock the view and any objects it refers to. This patch fixes the problem by instructing open_table() to open base tables/views (using OT_BASE_ONLY) when reopening tables/views used by stored procedures. This also means that a prepared statement is no longer invalidated if a temporary table is created with the same name as a view used in the prepared statement. Test case added to sp.test. The test case also demonstrates the effect of sp cache invalidation between CALLs. mysql-test/t/ps_ddl.test: Extended the VIEW->TEMPORARY TABLE transition test to cover not only merged views, but now also materialized views and views containing a reference to an information schema table. Test also updated to reflect the change to prepared statement invalidatation. --- mysql-test/r/ps_ddl.result | 63 ++++++++++++++++++++++++++++- mysql-test/r/sp.result | 59 +++++++++++++++++++++++++++ mysql-test/t/ps_ddl.test | 57 ++++++++++++++++++++++++++- mysql-test/t/sp.test | 81 ++++++++++++++++++++++++++++++++++++++ sql/sql_view.cc | 14 +++++++ 5 files changed, 271 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result index 3d57c8f7332..c72d129c8e4 100644 --- a/mysql-test/r/ps_ddl.result +++ b/mysql-test/r/ps_ddl.result @@ -707,6 +707,9 @@ deallocate prepare stmt; ===================================================================== Part 16: VIEW -> TEMPORARY TABLE transitions ===================================================================== +# +# Test 1: Merged view +# create table t2 (a int); insert into t2 (a) values (1); create view t1 as select * from t2; @@ -720,16 +723,72 @@ SUCCESS create temporary table t1 (a int); execute stmt; a -call p_verify_reprepare_count(1); +1 +call p_verify_reprepare_count(0); SUCCESS drop view t1; execute stmt; -a +ERROR 42S02: Table 'test.t1' doesn't exist call p_verify_reprepare_count(0); SUCCESS drop table t2; +drop temporary table t1; +deallocate prepare stmt; +# +# Test 2: Materialized view +# +create table t2 (a int); +insert into t2 (a) values (1); +create algorithm = temptable view t1 as select * from t2; +prepare stmt from "select * from t1"; +execute stmt; +a +1 +call p_verify_reprepare_count(0); +SUCCESS + +create temporary table t1 (a int); +execute stmt; +a +1 +call p_verify_reprepare_count(0); +SUCCESS + +drop view t1; +execute stmt; +ERROR 42S02: Table 'test.t1' doesn't exist +call p_verify_reprepare_count(0); +SUCCESS + +drop table t2; +drop temporary table t1; +deallocate prepare stmt; +# +# Test 3: View referencing an Information schema table +# +create view t1 as select table_name from information_schema.views; +prepare stmt from "select * from t1"; +execute stmt; +table_name +t1 +call p_verify_reprepare_count(0); +SUCCESS + +create temporary table t1 (a int); +execute stmt; +table_name +t1 +call p_verify_reprepare_count(0); +SUCCESS + +drop view t1; +execute stmt; +table_name +call p_verify_reprepare_count(0); +SUCCESS + drop temporary table t1; deallocate prepare stmt; ===================================================================== diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index bcc738d695c..08c7831c955 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -7076,3 +7076,62 @@ SELECT routine_comment FROM information_schema.routines WHERE routine_name = "p1 routine_comment 12345678901234567890123456789012345678901234567890123456789012345678901234567890 DROP PROCEDURE p1; +# +# Bug #47313 assert in check_key_in_view during CALL procedure +# +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS t1, t2_unrelated; +DROP PROCEDURE IF EXISTS p1; +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE VIEW t1 AS SELECT 10 AS f1; +# t1 refers to the view +CALL p1(1); +ERROR HY000: The target table t1 of the INSERT is not insertable-into +CREATE TEMPORARY TABLE t1 (f1 INT); +# t1 still refers to the view since it was inlined +CALL p1(2); +ERROR HY000: The target table t1 of the INSERT is not insertable-into +DROP VIEW t1; +# t1 now refers to the temporary table +CALL p1(3); +# Check which values were inserted into the temp table. +SELECT * FROM t1; +f1 +3 +DROP TEMPORARY TABLE t1; +DROP PROCEDURE p1; +# Now test what happens if the sp cache is invalidated. +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE VIEW t1 AS SELECT 10 AS f1; +CREATE VIEW v2_unrelated AS SELECT 1 AS r1; +# Load the procedure into the sp cache +CALL p1(4); +ERROR HY000: The target table t1 of the INSERT is not insertable-into +CREATE TEMPORARY TABLE t1 (f1 int); +ALTER VIEW v2_unrelated AS SELECT 2 AS r1; +# Alter view causes the sp cache to be invalidated. +# Now t1 refers to the temporary table, not the view. +CALL p1(5); +# Check which values were inserted into the temp table. +SELECT * FROM t1; +f1 +5 +DROP TEMPORARY TABLE t1; +DROP VIEW t1, v2_unrelated; +DROP PROCEDURE p1; +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE TEMPORARY TABLE t1 (f1 INT); +# t1 refers to the temporary table +CALL p1(6); +CREATE VIEW t1 AS SELECT 10 AS f1; +# Create view causes the sp cache to be invalidated. +# t1 still refers to the temporary table since it shadows the view. +CALL p1(7); +DROP VIEW t1; +# Check which values were inserted into the temp table. +SELECT * FROM t1; +f1 +6 +7 +DROP TEMPORARY TABLE t1; +DROP PROCEDURE p1; diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test index fe17bca1eba..e00d63aaedc 100644 --- a/mysql-test/t/ps_ddl.test +++ b/mysql-test/t/ps_ddl.test @@ -642,6 +642,9 @@ deallocate prepare stmt; --echo Part 16: VIEW -> TEMPORARY TABLE transitions --echo ===================================================================== +--echo # +--echo # Test 1: Merged view +--echo # create table t2 (a int); insert into t2 (a) values (1); create view t1 as select * from t2; @@ -651,9 +654,13 @@ execute stmt; call p_verify_reprepare_count(0); create temporary table t1 (a int); +# t1 still refers to the view - no reprepare has been done. execute stmt; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); + drop view t1; +# t1 still refers to the, now deleted, view - no reprepare has been done. +--error ER_NO_SUCH_TABLE execute stmt; call p_verify_reprepare_count(0); @@ -661,6 +668,54 @@ drop table t2; drop temporary table t1; deallocate prepare stmt; +--echo # +--echo # Test 2: Materialized view +--echo # +create table t2 (a int); +insert into t2 (a) values (1); +create algorithm = temptable view t1 as select * from t2; + +prepare stmt from "select * from t1"; +execute stmt; +call p_verify_reprepare_count(0); + +create temporary table t1 (a int); +# t1 still refers to the view - no reprepare has been done. +execute stmt; +call p_verify_reprepare_count(0); + +drop view t1; +# t1 still refers to the, now deleted, view - no reprepare has been done. +--error ER_NO_SUCH_TABLE +execute stmt; +call p_verify_reprepare_count(0); + +drop table t2; +drop temporary table t1; +deallocate prepare stmt; + +--echo # +--echo # Test 3: View referencing an Information schema table +--echo # +create view t1 as select table_name from information_schema.views; + +prepare stmt from "select * from t1"; +execute stmt; +call p_verify_reprepare_count(0); + +create temporary table t1 (a int); +# t1 has been substituted with a reference to the IS table +execute stmt; +call p_verify_reprepare_count(0); + +drop view t1; +# Since the IS table has been substituted in, the statement still works +execute stmt; +call p_verify_reprepare_count(0); + +drop temporary table t1; +deallocate prepare stmt; + --echo ===================================================================== --echo Part 17: VIEW -> VIEW (DDL) transitions --echo ===================================================================== diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 7cf2fcf9bb7..a29275eeda4 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -8433,3 +8433,84 @@ SELECT routine_comment FROM information_schema.routines WHERE routine_name = "p1 DROP PROCEDURE p1; + +--echo # +--echo # Bug #47313 assert in check_key_in_view during CALL procedure +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS t1, t2_unrelated; +DROP PROCEDURE IF EXISTS p1; +--enable_warnings + +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE VIEW t1 AS SELECT 10 AS f1; + +--echo # t1 refers to the view +--error ER_NON_INSERTABLE_TABLE +CALL p1(1); + +CREATE TEMPORARY TABLE t1 (f1 INT); + +--echo # t1 still refers to the view since it was inlined +--error ER_NON_INSERTABLE_TABLE +CALL p1(2); + +DROP VIEW t1; + +--echo # t1 now refers to the temporary table +CALL p1(3); + +--echo # Check which values were inserted into the temp table. +SELECT * FROM t1; + +DROP TEMPORARY TABLE t1; +DROP PROCEDURE p1; + +--echo # Now test what happens if the sp cache is invalidated. + +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE VIEW t1 AS SELECT 10 AS f1; +CREATE VIEW v2_unrelated AS SELECT 1 AS r1; + +--echo # Load the procedure into the sp cache +--error ER_NON_INSERTABLE_TABLE +CALL p1(4); + +CREATE TEMPORARY TABLE t1 (f1 int); + +ALTER VIEW v2_unrelated AS SELECT 2 AS r1; + +--echo # Alter view causes the sp cache to be invalidated. +--echo # Now t1 refers to the temporary table, not the view. +CALL p1(5); + +--echo # Check which values were inserted into the temp table. +SELECT * FROM t1; + +DROP TEMPORARY TABLE t1; +DROP VIEW t1, v2_unrelated; +DROP PROCEDURE p1; + +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE TEMPORARY TABLE t1 (f1 INT); + +--echo # t1 refers to the temporary table +CALL p1(6); + +CREATE VIEW t1 AS SELECT 10 AS f1; + +--echo # Create view causes the sp cache to be invalidated. +--echo # t1 still refers to the temporary table since it shadows the view. +CALL p1(7); + +DROP VIEW t1; + +--echo # Check which values were inserted into the temp table. +SELECT * FROM t1; + +DROP TEMPORARY TABLE t1; +DROP PROCEDURE p1; + + diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 6d2836afc0d..17ac10ebfb9 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1137,6 +1137,20 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, table->view_db.length= table->db_length; table->view_name.str= table->table_name; table->view_name.length= table->table_name_length; + /* + We don't invalidate a prepared statement when a view changes, + or when someone creates a temporary table. + Instead, the view is inlined into the body of the statement + upon the first execution. Below, make sure that on + re-execution of a prepared statement we don't prefer + a temporary table to the view, if the view name was shadowed + with a temporary table with the same name. + This assignment ensures that on re-execution open_table() will + not try to call find_temporary_table() for this TABLE_LIST, + but will invoke open_table_from_share(), which will + eventually call this function. + */ + table->open_type= OT_BASE_ONLY; /*TODO: md5 test here and warning if it is differ */ From 7d71d7153564d1027b591ecedbc7ee051ca5decb Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 14:15:50 +0100 Subject: [PATCH 111/212] Backport of revno: 3514 Bug#40181 Made use of tdc_remove_table instead of just setting share->version to 0 to make sure all unused table instances go away as part of CREATE/ALTER TABLE. --- mysql-test/r/partition.result | 9 +++++++++ mysql-test/t/partition.test | 13 +++++++++++++ sql/sql_partition.cc | 21 +++++++++++++++------ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/mysql-test/r/partition.result b/mysql-test/r/partition.result index 543a70f9a2a..e9bbc011f7b 100644 --- a/mysql-test/r/partition.result +++ b/mysql-test/r/partition.result @@ -65,6 +65,15 @@ show indexes from t1; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment t1 1 a 1 a A 1 NULL NULL YES BTREE drop table t1; +create table t1 (a int) +partition by hash (a); +create index i on t1 (a); +insert into t1 values (1); +insert into t1 select * from t1; +create index i on t1 (a); +ERROR 42000: Duplicate key name 'i' +create index i2 on t1 (a); +drop table t1; CREATE TABLE t1 (a INT, FOREIGN KEY (a) REFERENCES t0 (a)) ENGINE=MyISAM PARTITION BY HASH (a); diff --git a/mysql-test/t/partition.test b/mysql-test/t/partition.test index 8ab91f23522..01e885a527c 100644 --- a/mysql-test/t/partition.test +++ b/mysql-test/t/partition.test @@ -74,6 +74,19 @@ analyze table t1; show indexes from t1; drop table t1; +# +# Bug#40181: hang if create index +# +create table t1 (a int) +partition by hash (a); +create index i on t1 (a); +insert into t1 values (1); +insert into t1 select * from t1; +--error ER_DUP_KEYNAME +create index i on t1 (a); +create index i2 on t1 (a); +drop table t1; + # # Bug#36001: Partitions: spelling and using some error messages # diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 718471cc1b6..52657deed83 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -4189,7 +4189,9 @@ bool mysql_unpack_partition(THD *thd, */ thd->free_items(); part_info= thd->work_part_info; - table->s->version= 0UL; + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table->s->db.str, + table->s->table_name.str); *work_part_info_used= true; } table->part_info= part_info; @@ -4482,12 +4484,17 @@ uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, /* We are going to manipulate the partition info on the table object - so we need to ensure that the data structure of the table object - is freed by setting version to 0. table->s->version= 0 forces a - flush of the table object in close_thread_tables(). + so we need to ensure that the table instances cached and all other + instances are properly closed. */ if (table->part_info) - table->s->version= 0L; + { + pthread_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table->s->db.str, + table->s->table_name.str); + pthread_mutex_unlock(&LOCK_open); + } thd->work_part_info= thd->lex->part_info; if (thd->work_part_info && @@ -6242,7 +6249,9 @@ static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) alter_partition_lock_handling() and the table is closed by close_thread_tables() instead. */ - table->s->version= 0; + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table->s->db.str, + table->s->table_name.str); } } pthread_mutex_unlock(&LOCK_open); From fb6b5ee4622c5dc02a86d1fe9a0de1f6f8aac799 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 14:26:00 +0100 Subject: [PATCH 112/212] Backport of revno: 2617.68.37 Bug #46654 False deadlock on concurrent DML/DDL with partitions, inconsistent behavior The problem was that if one connection is running a multi-statement transaction which involves a single partitioned table, and another connection attempts to alter the table, the first connection gets ER_LOCK_DEADLOCK and cannot proceed anymore, even when the ALTER TABLE statement in another connection has timed out or failed. The reason for this was that the prepare phase for ALTER TABLE for partitioned tables removed all instances of the table from the table definition cache before it started waiting on the lock. The transaction running in the first connection would notice this and report ER_LOCK_DEADLOCK. This patch changes the prep_alter_part_table() ALTER TABLE code so that tdc_remove_table() is no longer called. Instead, only the TABLE instance changed by prep_alter_part_table() is marked as needing reopen. The patch also removes an unnecessary call to tdc_remove_table() from mysql_unpack_partition() as the changed TABLE object is destroyed by the caller at a later point. Test case added in partition_sync.test. --- mysql-test/r/partition_sync.result | 32 +++++++++++++++++++ mysql-test/t/partition_sync.test | 51 ++++++++++++++++++++++++++++++ sql/sql_base.cc | 8 ++--- sql/sql_handler.cc | 2 +- sql/sql_insert.cc | 2 +- sql/sql_partition.cc | 22 ++++--------- sql/sql_table.cc | 4 +++ sql/table.h | 15 +++++++-- 8 files changed, 112 insertions(+), 24 deletions(-) diff --git a/mysql-test/r/partition_sync.result b/mysql-test/r/partition_sync.result index 41ca19426fe..fbea2e0273a 100644 --- a/mysql-test/r/partition_sync.result +++ b/mysql-test/r/partition_sync.result @@ -1,2 +1,34 @@ # Disabled until Bug#46654 False deadlock on concurrent DML/DDL # with partitions, inconsistent behavior is backported +# +# Bug #46654 False deadlock on concurrent DML/DDL +# with partitions, inconsistent behavior +# +DROP TABLE IF EXISTS tbl_with_partitions; +CREATE TABLE tbl_with_partitions ( i INT ) +PARTITION BY HASH(i); +INSERT INTO tbl_with_partitions VALUES (1); +# Connection 3 +LOCK TABLE tbl_with_partitions READ; +# Connection 1 +# Access table with disabled autocommit +SET AUTOCOMMIT = 0; +SELECT * FROM tbl_with_partitions; +i +1 +# Connection 2 +# Alter table, abort after prepare +set session debug="+d,abort_copy_table"; +ALTER TABLE tbl_with_partitions ADD COLUMN f INT; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +# Connection 1 +# Try accessing the table after Alter aborted. +# This used to give ER_LOCK_DEADLOCK. +SELECT * FROM tbl_with_partitions; +i +1 +# Connection 3 +UNLOCK TABLES; +# Connection 1 +# Cleanup +DROP TABLE tbl_with_partitions; diff --git a/mysql-test/t/partition_sync.test b/mysql-test/t/partition_sync.test index 5d2b25e87f3..672e4cc0562 100644 --- a/mysql-test/t/partition_sync.test +++ b/mysql-test/t/partition_sync.test @@ -39,6 +39,57 @@ #DROP TABLE t1; +--echo # +--echo # Bug #46654 False deadlock on concurrent DML/DDL +--echo # with partitions, inconsistent behavior +--echo # + +--disable_warnings +DROP TABLE IF EXISTS tbl_with_partitions; +--enable_warnings + +CREATE TABLE tbl_with_partitions ( i INT ) + PARTITION BY HASH(i); +INSERT INTO tbl_with_partitions VALUES (1); + +connect(con2,localhost,root); +connect(con3,localhost,root); + +--echo # Connection 3 +connection con3; +LOCK TABLE tbl_with_partitions READ; + +--echo # Connection 1 +--echo # Access table with disabled autocommit +connection default; +SET AUTOCOMMIT = 0; +SELECT * FROM tbl_with_partitions; + +--echo # Connection 2 +--echo # Alter table, abort after prepare +connection con2; +set session debug="+d,abort_copy_table"; +--error ER_LOCK_WAIT_TIMEOUT +ALTER TABLE tbl_with_partitions ADD COLUMN f INT; + +--echo # Connection 1 +--echo # Try accessing the table after Alter aborted. +--echo # This used to give ER_LOCK_DEADLOCK. +connection default; +SELECT * FROM tbl_with_partitions; + +--echo # Connection 3 +connection con3; +UNLOCK TABLES; + +--echo # Connection 1 +--echo # Cleanup +connection default; +disconnect con2; +disconnect con3; +DROP TABLE tbl_with_partitions; + + # Check that all connections opened by test cases in this file are really # gone so execution of other tests won't be affected by their presence. --source include/wait_until_count_sessions.inc diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 7a3adc89ea9..9672647f30b 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1530,8 +1530,8 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) *table_ptr=table->next; table->mdl_ticket= NULL; - if (table->needs_reopen() || - thd->version != refresh_version || !table->db_stat || + if (table->s->needs_reopen() || + thd->version != refresh_version || table->needs_reopen() || table_def_shutdown_in_progress) { free_cache_entry(table); @@ -8186,13 +8186,13 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use) thd_table= thd_table->next) { /* - Check for TABLE::db_stat is needed since in some places we call + Check for TABLE::needs_reopen() is needed since in some places we call handler::close() for table instance (and set TABLE::db_stat to 0) and do not remove such instances from the THD::open_tables for some time, during which other thread can see those instances (e.g. see partitioning code). */ - if (thd_table->db_stat) + if (!thd_table->needs_reopen()) signalled|= mysql_lock_abort_for_thread(thd, thd_table); } pthread_mutex_unlock(&LOCK_open); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 1966c7d8b39..94f5e84fb10 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -820,7 +820,7 @@ void mysql_ha_flush(THD *thd) if (hash_tables->table && (hash_tables->table->mdl_ticket && hash_tables->table->mdl_ticket->has_pending_conflicting_lock() || - hash_tables->table->needs_reopen())) + hash_tables->table->s->needs_reopen())) mysql_ha_close_table(thd, hash_tables); } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 8fd704c4f71..9a351085b3a 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2657,7 +2657,7 @@ bool Delayed_insert::handle_inserts(void) thd_proc_info(&thd, "insert"); max_rows= delayed_insert_limit; - if (thd.killed || table->needs_reopen()) + if (thd.killed || table->s->needs_reopen()) { thd.killed= THD::KILL_CONNECTION; max_rows= ULONG_MAX; // Do as much as possible diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 52657deed83..bcf81a49f56 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -4183,15 +4183,13 @@ bool mysql_unpack_partition(THD *thd, We need to free any memory objects allocated on item_free_list by the parser since we are keeping the old info from the first parser call in CREATE TABLE. - We'll ensure that this object isn't put into table cache also - just to ensure we don't get into strange situations with the - item objects. + + This table object can not be used any more. However, since + this is CREATE TABLE, we know that it will be destroyed by the + caller, and rely on that. */ thd->free_items(); part_info= thd->work_part_info; - tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, - table->s->db.str, - table->s->table_name.str); *work_part_info_used= true; } table->part_info= part_info; @@ -4484,17 +4482,11 @@ uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, /* We are going to manipulate the partition info on the table object - so we need to ensure that the table instances cached and all other - instances are properly closed. + so we need to ensure that the table instance is removed from the + table cache. */ if (table->part_info) - { - pthread_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, - table->s->db.str, - table->s->table_name.str); - pthread_mutex_unlock(&LOCK_open); - } + table->m_needs_reopen= TRUE; thd->work_part_info= thd->lex->part_info; if (thd->work_part_info && diff --git a/sql/sql_table.cc b/sql/sql_table.cc index e9474d9add6..556c0e65eee 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -7049,6 +7049,10 @@ view_err: new_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; new_table->next_number_field=new_table->found_next_number_field; thd_proc_info(thd, "copy to tmp table"); + DBUG_EXECUTE_IF("abort_copy_table", { + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + goto err_new_table_cleanup; + }); error= copy_data_between_tables(table, new_table, alter_info->create_list, ignore, order_num, order, &copied, &deleted, diff --git a/sql/table.h b/sql/table.h index b44c2e9f3be..4b85d8a64f4 100644 --- a/sql/table.h +++ b/sql/table.h @@ -294,6 +294,8 @@ TABLE_CATEGORY get_table_category(const LEX_STRING *db, struct TABLE_share; +extern ulong refresh_version; + /* This structure is shared between different table objects. There is one instance of table share per one table in the database. @@ -503,6 +505,14 @@ struct TABLE_SHARE return table_map_id; } + + /* + Must all TABLEs be reopened? + */ + inline bool needs_reopen() + { + return version != refresh_version; + } /** Convert unrelated members of TABLE_SHARE to one enum representing its type. @@ -605,8 +615,6 @@ struct TABLE_SHARE }; -extern ulong refresh_version; - /* Information for one open table */ enum index_hint_type { @@ -804,6 +812,7 @@ public: my_bool insert_or_update; /* Can be used by the handler */ my_bool alias_name_used; /* true if table_name is alias */ my_bool get_fields_in_item_tree; /* Signal to fix_field */ + my_bool m_needs_reopen; REGINFO reginfo; /* field connections */ MEM_ROOT mem_root; @@ -853,7 +862,7 @@ public: Is this instance of the table should be reopen? */ inline bool needs_reopen() - { return s->version != refresh_version; } + { return !db_stat || m_needs_reopen; } }; From f80551752226492812553d9c23ffe4277a6375d0 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 10 Dec 2009 16:38:03 +0300 Subject: [PATCH 113/212] Backport a part of Monty's fix for Bug#39396, rev. 2736.2.11 "ha_maria.cc:2415: assertion in ha_maria::store_lock()". sql/lock.cc: Fixed wrong cleanup of mysql_lock_tables() - We must call read_lock_data() BEFORE we set lock_count to 0. Added DBUG statements. --- sql/lock.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sql/lock.cc b/sql/lock.cc index b64f7b0b44c..bd91494f174 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -168,9 +168,8 @@ int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags) DBUG_RETURN(0); } - /** - Reset lock type in lock data and free. + Reset lock type in lock data @param mysql_lock Lock structures to reset. @@ -328,6 +327,13 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, } else if (rc == 1) /* aborted or killed */ { + /* + reset_lock_data is required here. If thr_multi_lock fails it + resets lock type for tables, which were locked before (and + including) one that caused error. Lock type for other tables + preserved. + */ + reset_lock_data(sql_lock); thd->some_tables_deleted=1; // Try again sql_lock->lock_count= 0; // Locks are already freed // Fall through: unlock, reset lock data, free and retry From 6c13f657dbcbf1d3d0b2d391c158aedcd0437735 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 14:41:41 +0100 Subject: [PATCH 114/212] Backport of revno: 2617.80.1 Also re-enables the test for Bug #43867 Followup to Bug#46654 False deadlock on concurrent DML/DDL with partitions, inconsistent behavior Partition_sync.test uses features only available in debug builds. Disabling the test for non-debug builds. --- mysql-test/r/partition_sync.result | 27 +++++++++++- mysql-test/t/partition_sync.test | 66 +++++++++++++++--------------- 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/mysql-test/r/partition_sync.result b/mysql-test/r/partition_sync.result index fbea2e0273a..d48362cb57c 100644 --- a/mysql-test/r/partition_sync.result +++ b/mysql-test/r/partition_sync.result @@ -1,5 +1,28 @@ -# Disabled until Bug#46654 False deadlock on concurrent DML/DDL -# with partitions, inconsistent behavior is backported +# +# Bug #43867 ALTER TABLE on a partitioned table +# causes unnecessary deadlocks +# +CREATE TABLE t1 (a int) PARTITION BY RANGE (a) +(PARTITION p0 VALUES LESS THAN (1), +PARTITION p1 VALUES LESS THAN (2)); +INSERT INTO t1 VALUES (0),(1); +# Connection 2 +BEGIN; +SELECT * FROM t1; +a +0 +1 +# Connection 1 +ALTER TABLE t1 DROP PARTITION p3; +ERROR HY000: Error in list of partitions to DROP +# Connection 2 +# This failed with deadlock and should not do so. +SELECT * FROM t1; +a +0 +1 +# Connection 1 +DROP TABLE t1; # # Bug #46654 False deadlock on concurrent DML/DDL # with partitions, inconsistent behavior diff --git a/mysql-test/t/partition_sync.test b/mysql-test/t/partition_sync.test index 672e4cc0562..85eb33ebb6b 100644 --- a/mysql-test/t/partition_sync.test +++ b/mysql-test/t/partition_sync.test @@ -1,42 +1,40 @@ --source include/have_partition.inc +--source include/have_debug.inc # Save the initial number of concurrent sessions. --source include/count_sessions.inc ---echo # Disabled until Bug#46654 False deadlock on concurrent DML/DDL ---echo # with partitions, inconsistent behavior is backported +--echo # +--echo # Bug #43867 ALTER TABLE on a partitioned table +--echo # causes unnecessary deadlocks +--echo # -#--echo # -#--echo # Bug #43867 ALTER TABLE on a partitioned table -#--echo # causes unnecessary deadlocks -#--echo # -# -#CREATE TABLE t1 (a int) PARTITION BY RANGE (a) -#(PARTITION p0 VALUES LESS THAN (1), -# PARTITION p1 VALUES LESS THAN (2)); -# -#INSERT INTO t1 VALUES (0),(1); -# -#connect(con1,localhost,root); -# -#--echo # Connection 2 -#connection con1; -#BEGIN; -#SELECT * FROM t1; -# -#--echo # Connection 1 -#connection default; -#--error ER_DROP_PARTITION_NON_EXISTENT -#ALTER TABLE t1 DROP PARTITION p3; -# -#--echo # Connection 2 -#connection con1; -#--echo # This failed with deadlock and should not do so. -#SELECT * FROM t1; -# -#--echo # Connection 1 -#connection default; -#disconnect con1; -#DROP TABLE t1; +CREATE TABLE t1 (a int) PARTITION BY RANGE (a) +(PARTITION p0 VALUES LESS THAN (1), + PARTITION p1 VALUES LESS THAN (2)); + +INSERT INTO t1 VALUES (0),(1); + +connect(con1,localhost,root); + +--echo # Connection 2 +connection con1; +BEGIN; +SELECT * FROM t1; + +--echo # Connection 1 +connection default; +--error ER_DROP_PARTITION_NON_EXISTENT +ALTER TABLE t1 DROP PARTITION p3; + +--echo # Connection 2 +connection con1; +--echo # This failed with deadlock and should not do so. +SELECT * FROM t1; + +--echo # Connection 1 +connection default; +disconnect con1; +DROP TABLE t1; --echo # From 2f7a8770a91bd4feac00b08251966cd27b5c75ae Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 10 Dec 2009 16:43:04 +0300 Subject: [PATCH 115/212] Backport of: 2630.16.14 Sergei Golubchik 2008-08-25 fixed a crash in partition tests introduced by HA_EXTRA_PREPARE_FOR_DROP patch sql/sql_base.cc: Don't call ::extra() for closed tables. --- sql/sql_base.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 7a3adc89ea9..fad58900002 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1318,6 +1318,10 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, if (table->s->table_cache_key.length == key_length && !memcmp(table->s->table_cache_key.str, key, key_length)) { + /* Inform handler that table will be dropped after close */ + if (table->db_stat) + table->file->extra(HA_EXTRA_PREPARE_FOR_DROP); + /* Does nothing if the table is not locked. This allows one to use this function after a table From 3d062adf2526fa7f92ed15dfb9525e4d9ba5e9f2 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 15:09:00 +0100 Subject: [PATCH 116/212] Backport of revno: 3685 Bug #48210 FLUSH TABLES WITH READ LOCK deadlocks against concurrent CREATE PROCEDURE This deadlock occured between a) CREATE PROCEDURE (or other commands listed below) b) FLUSH TABLES WITH READ LOCK If the execution of them happened in the following order: - a) opens a table (e.g. mysql.proc) - b) locks the global read lock (or GRL) - a) sleeps inside wait_if_global_read_lock() - b) increases refresh_version and sleeps waiting for old tables to go away Note that a) must start waiting on the GRL before FLUSH increases refresh_version. Otherwise a) won't wait on the GRL and instead close its tables for reopen, allowing FLUSH to complete and thus avoid the deadlock. With this patch the deadlock is avoided by making CREATE PROCEDURE acquire a protection against global read locks before it starts executing. This means that FLUSH TABLES WITH READ LOCK will have to wait until CREATE PROCEDURE completes before acquiring the global read lock, thereby avoiding the deadlock. This is implemented by introducing a new SQL command flag called CF_PROTECT_AGAINST_GRL. Commands marked with this flag will acquire a GRL protection in the beginning of mysql_execute_command(). This patch adds the flag to CREATE, ALTER and DROP for PROCEDURE and FUNCTION, as well as CREATE USER, DROP USER, RENAME USER and REVOKE ALL. All these commands either call open_grant_tables() or open_system_table_for_updated() which make them susceptible for this deadlock. The patch also adds the CF_PROTECT_AGAINST_GRL flag to a number of commands that previously acquired GRL protection in their respective SQLCOM case in mysql_execute_command(). Test case that checks for GRL protection for CREATE PROCEDURE and CREATE USER added to mdl_sync.test. --- mysql-test/r/mdl_sync.result | 37 ++++++++++++++ mysql-test/t/mdl_sync.test | 69 +++++++++++++++++++++++++ sql/lock.cc | 27 +++++++++- sql/sql_class.h | 10 ++++ sql/sql_parse.cc | 97 ++++++++++++------------------------ 5 files changed, 175 insertions(+), 65 deletions(-) diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index e5447c32b7d..130a8f22710 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -254,3 +254,40 @@ commit; # Switching to connection 'default'. # Clean-up drop table t1; +# +# Bug#48210 FLUSH TABLES WITH READ LOCK deadlocks +# against concurrent CREATE PROCEDURE +# +# Test 1: CREATE PROCEDURE +# Connection 1 +# Start CREATE PROCEDURE and open mysql.proc +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; +CREATE PROCEDURE p1() SELECT 1; +# Connection 2 +SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; +# Check that FLUSH must wait to get the GRL +# and let CREATE PROCEDURE continue +SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; +FLUSH TABLES WITH READ LOCK; +# Connection 1 +# Connection 2 +UNLOCK TABLES; +# Connection 1 +DROP PROCEDURE p1; +SET DEBUG_SYNC= 'RESET'; +# Test 2: CREATE USER +# Start CREATE USER and open the grant tables +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; +CREATE USER 'user_1@localhost'; +# Connection 2 +SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; +# Check that FLUSH must wait to get the GRL +# and let CREATE USER continue +SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; +FLUSH TABLES WITH READ LOCK; +# Connection 1 +# Connection 2 +UNLOCK TABLES; +# Connection 1 +DROP USER 'user_1@localhost'; +SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test index fd66f6d539d..9422c67cb6f 100644 --- a/mysql-test/t/mdl_sync.test +++ b/mysql-test/t/mdl_sync.test @@ -475,6 +475,75 @@ disconnect con46673; drop table t1; +--echo # +--echo # Bug#48210 FLUSH TABLES WITH READ LOCK deadlocks +--echo # against concurrent CREATE PROCEDURE +--echo # + +connect (con2, localhost, root); + +--echo # Test 1: CREATE PROCEDURE + +--echo # Connection 1 +connection default; +--echo # Start CREATE PROCEDURE and open mysql.proc +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; +--send CREATE PROCEDURE p1() SELECT 1 + +--echo # Connection 2 +connection con2; +SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; +--echo # Check that FLUSH must wait to get the GRL +--echo # and let CREATE PROCEDURE continue +SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; +--send FLUSH TABLES WITH READ LOCK + +--echo # Connection 1 +connection default; +--reap + +--echo # Connection 2 +connection con2; +--reap +UNLOCK TABLES; + +--echo # Connection 1 +connection default; +DROP PROCEDURE p1; +SET DEBUG_SYNC= 'RESET'; + +--echo # Test 2: CREATE USER + +connection default; +--echo # Start CREATE USER and open the grant tables +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; +--send CREATE USER 'user_1@localhost' + +--echo # Connection 2 +connection con2; +SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; +--echo # Check that FLUSH must wait to get the GRL +--echo # and let CREATE USER continue +SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; +--send FLUSH TABLES WITH READ LOCK + +--echo # Connection 1 +connection default; +--reap + +--echo # Connection 2 +connection con2; +--reap +UNLOCK TABLES; + +--echo # Connection 1 +connection default; +DROP USER 'user_1@localhost'; +SET DEBUG_SYNC= 'RESET'; + +disconnect con2; + + # Check that all connections opened by test cases in this file are really # gone so execution of other tests won't be affected by their presence. --source include/wait_until_count_sessions.inc diff --git a/sql/lock.cc b/sql/lock.cc index b64f7b0b44c..98217254240 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -74,6 +74,7 @@ */ #include "mysql_priv.h" +#include "debug_sync.h" #include #include @@ -1119,9 +1120,33 @@ bool lock_global_read_lock(THD *thd) if (!thd->global_read_lock) { const char *old_message; + const char *new_message= "Waiting to get readlock"; (void) pthread_mutex_lock(&LOCK_global_read_lock); + +#if defined(ENABLED_DEBUG_SYNC) + /* + The below sync point fires if we have to wait for + protect_against_global_read_lock. + + WARNING: Beware to use WAIT_FOR with this sync point. We hold + LOCK_global_read_lock here. + + Call the sync point before calling enter_cond() as it does use + enter_cond() and exit_cond() itself if a WAIT_FOR action is + executed in spite of the above warning. + + Pre-set proc_info so that it is available immediately after the + sync point sends a SIGNAL. This makes tests more reliable. + */ + if (protect_against_global_read_lock) + { + thd_proc_info(thd, new_message); + DEBUG_SYNC(thd, "wait_lock_global_read_lock"); + } +#endif /* defined(ENABLED_DEBUG_SYNC) */ + old_message=thd->enter_cond(&COND_global_read_lock, &LOCK_global_read_lock, - "Waiting to get readlock"); + new_message); DBUG_PRINT("info", ("waiting_for: %d protect_against: %d", waiting_for_read_lock, protect_against_global_read_lock)); diff --git a/sql/sql_class.h b/sql/sql_class.h index 889d7c5472b..4bfd3e66438 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3341,6 +3341,16 @@ public: */ #define CF_DIAGNOSTIC_STMT (1U << 8) +/** + SQL statements that must be protected against impending global read lock + to prevent deadlock. This deadlock could otherwise happen if the statement + starts waiting for the GRL to go away inside mysql_lock_tables while at the + same time having "old" opened tables. The thread holding the GRL can be + waiting for these "old" opened tables to be closed, causing a deadlock + (FLUSH TABLES WITH READ LOCK). + */ +#define CF_PROTECT_AGAINST_GRL (1U << 10) + /* Bits in server_command_flags */ /** diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index e462556c133..289b6ce0da8 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -182,14 +182,15 @@ void init_update_queries(void) memset(sql_command_flags, 0, sizeof(sql_command_flags)); sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_AUTO_COMMIT_TRANS; + CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS; + CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; @@ -207,17 +208,17 @@ void init_update_queries(void) sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | @@ -276,20 +277,21 @@ void init_update_queries(void) CF_REEXECUTION_FRAGILE); - sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_REVOKE_ALL]= CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA; @@ -1969,6 +1971,17 @@ mysql_execute_command(THD *thd) thd->mdl_context.release_all_locks(); } + /* + Check if this command needs protection against the global read lock + to avoid deadlock. See CF_PROTECT_AGAINST_GRL. + start_waiting_global_read_lock() is called at the end of + mysql_execute_command(). + */ + if (((sql_command_flags[lex->sql_command] & CF_PROTECT_AGAINST_GRL) != 0) && + !thd->locked_tables_mode) + if (wait_if_global_read_lock(thd, FALSE, TRUE)) + goto error; + switch (lex->sql_command) { case SQLCOM_SHOW_EVENTS: @@ -2309,12 +2322,9 @@ case SQLCOM_PREPARE: start_waiting_global_read_lock(). We protect the normal CREATE TABLE in the same way. That way we avoid that a new table is created during a global read lock. + Protection against grl is covered by the CF_PROTECT_AGAINST_GRL flag. */ - if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) - { - res= 1; - goto end_with_restore_list; - } + #ifdef WITH_PARTITION_STORAGE_ENGINE { partition_info *part_info= thd->lex->part_info; @@ -2617,12 +2627,6 @@ end_with_restore_list: "INDEX DIRECTORY"); create_info.data_file_name= create_info.index_file_name= NULL; - if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) - { - res= 1; - break; - } - thd->enable_slow_log= opt_log_slow_admin_statements; res= mysql_alter_table(thd, select_lex->db, lex->name.str, &create_info, @@ -2852,8 +2856,6 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (update_precheck(thd, all_tables)) break; - if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) - goto error; DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); MYSQL_UPDATE_START(thd->query()); @@ -2884,15 +2886,6 @@ end_with_restore_list: else res= 0; - /* - Protection might have already been risen if its a fall through - from the SQLCOM_UPDATE case above. - */ - if (!thd->locked_tables_mode && - lex->sql_command == SQLCOM_UPDATE_MULTI && - wait_if_global_read_lock(thd, 0, 1)) - goto error; - res= mysql_multi_update_prepare(thd); #ifdef HAVE_REPLICATION @@ -2992,11 +2985,6 @@ end_with_restore_list: if ((res= insert_precheck(thd, all_tables))) break; - if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) - { - res= 1; - break; - } MYSQL_INSERT_START(thd->query()); res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values, lex->update_list, lex->value_list, @@ -3031,11 +3019,6 @@ end_with_restore_list: unit->set_limit(select_lex); - if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) - { - res= 1; - break; - } if (!(res= open_and_lock_tables(thd, all_tables))) { MYSQL_INSERT_SELECT_START(thd->query()); @@ -3113,11 +3096,6 @@ end_with_restore_list: DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); - if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) - { - res= 1; - break; - } MYSQL_DELETE_START(thd->query()); res = mysql_delete(thd, all_tables, select_lex->where, &select_lex->order_list, @@ -3133,12 +3111,6 @@ end_with_restore_list: (TABLE_LIST *)thd->lex->auxiliary_table_list.first; multi_delete *del_result; - if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) - { - res= 1; - break; - } - if ((res= multi_delete_precheck(thd, all_tables))) break; @@ -3277,9 +3249,6 @@ end_with_restore_list: if (check_one_table_access(thd, privilege, all_tables)) goto error; - if (!thd->locked_tables_mode && wait_if_global_read_lock(thd, 0, 1)) - goto error; - res= mysql_load(thd, lex->exchange, first_table, lex->field_list, lex->update_list, lex->value_list, lex->duplicates, lex->ignore, (bool) lex->local_file); From 94154c9592e8a4b1eb9ee19529ab22f1f07a3e26 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 10 Dec 2009 15:09:56 +0100 Subject: [PATCH 117/212] Backport of revno: 3690 Postfix for Bug#48210 FLUSH TABLES WITH READ LOCK deadlocks against concurrent CREATE PROCEDURE Rewrote the second test to use DROP PROCEDURE instead of CREATE USER as CREATE USER does not work with embedded server. --- mysql-test/r/mdl_sync.result | 10 ++++------ mysql-test/t/mdl_sync.test | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index 130a8f22710..ec02f29b008 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -273,21 +273,19 @@ FLUSH TABLES WITH READ LOCK; # Connection 2 UNLOCK TABLES; # Connection 1 -DROP PROCEDURE p1; SET DEBUG_SYNC= 'RESET'; -# Test 2: CREATE USER -# Start CREATE USER and open the grant tables +# Test 2: DROP PROCEDURE +# Start DROP PROCEDURE and open tables SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; -CREATE USER 'user_1@localhost'; +DROP PROCEDURE p1; # Connection 2 SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; # Check that FLUSH must wait to get the GRL -# and let CREATE USER continue +# and let DROP PROCEDURE continue SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; FLUSH TABLES WITH READ LOCK; # Connection 1 # Connection 2 UNLOCK TABLES; # Connection 1 -DROP USER 'user_1@localhost'; SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test index 9422c67cb6f..e3aceaa05fa 100644 --- a/mysql-test/t/mdl_sync.test +++ b/mysql-test/t/mdl_sync.test @@ -509,21 +509,20 @@ UNLOCK TABLES; --echo # Connection 1 connection default; -DROP PROCEDURE p1; SET DEBUG_SYNC= 'RESET'; ---echo # Test 2: CREATE USER +--echo # Test 2: DROP PROCEDURE connection default; ---echo # Start CREATE USER and open the grant tables +--echo # Start DROP PROCEDURE and open tables SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; ---send CREATE USER 'user_1@localhost' +--send DROP PROCEDURE p1 --echo # Connection 2 connection con2; SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; --echo # Check that FLUSH must wait to get the GRL ---echo # and let CREATE USER continue +--echo # and let DROP PROCEDURE continue SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; --send FLUSH TABLES WITH READ LOCK @@ -538,7 +537,6 @@ UNLOCK TABLES; --echo # Connection 1 connection default; -DROP USER 'user_1@localhost'; SET DEBUG_SYNC= 'RESET'; disconnect con2; From 16f315157056230117cddf2f28ddb6603657786f Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Thu, 10 Dec 2009 15:49:15 +0100 Subject: [PATCH 118/212] Bug#41425 Assertion in Protocol::end_statement() (pushbuild2) (diagnostics_area) Execution of CREATE TABLE ... SELECT statement was not atomic in the sense that concurrent statements trying to affect its target table might have sneaked in between the moment when the table was created and moment when it was filled according to SELECT clause. This resulted in inconsistent binary log, unexpected target table contents. In cases when concurrent statement was a DDL statement CREATE TABLE ... SELECT might have failed with ER_CANT_LOCK error. In more detail: Due to premature metadata lock downgrade which occured after CREATE TABLE SELECT statement created table but before it managed to obtain table-level lock on it other statements were allowed to open, lock and change target table in the middle of CREATE TABLE SELECT execution. This also meant that it was possible that CREATE TABLE SELECT would wait in mysql_lock_tables() when it was called for newly created table and that this wait could have been aborted by concurrent DDL. The latter led to execution of unexpected branch of code and CREATE TABLE SELECT ending with ER_CANT_LOCK error. The premature downgrade occured because open_table(), which was called for newly created table, decided that it is OK to downgrade metadata lock from exclusive to shared since table exists, even although it was not acquired within this call. This fix ensures that open_table() does not downgrade metadata lock if it is not acquired during its current invocation. Testing: The bug is exposed in a race condition, and is thus difficult to expose in a standard mysql-test-run test case. Instead, a stress test using the Random Query Generator (https://launchpad.net/randgen) will trip the problem occasionally. % perl runall.pl \ --basedir= \ --mysqld=--table-lock-wait-timeout=5 \ --mysqld=--skip-safemalloc \ --grammar=conf/maria_bulk_insert.yy \ --reporters=ErrorLog,Backtrace,WinPackage \ --mysqld=--log-output=file \ --queries=100000 \ --threads=10 \ --engine=myisam Note: You will need a debug build to expose the bug When the bug is tripped, the server will abort and dump core. Backport from 6.0-codebase (revid: 2617.53.4) --- sql/sql_base.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 9672647f30b..eae69c415bd 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2840,7 +2840,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table exists now we should downgrade our exclusive metadata lock on this table to shared metadata lock. */ - if (table_list->lock_strategy == TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL) + if (table_list->lock_strategy == TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL && + !(flags & MYSQL_OPEN_HAS_MDL_LOCK)) mdl_ticket->downgrade_exclusive_lock(); table->mdl_ticket= mdl_ticket; From cc7239b2fc2a16a3c5d9c07108f85a91468f79fe Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Thu, 10 Dec 2009 16:22:41 +0100 Subject: [PATCH 119/212] Bug#46374 crash, INSERT INTO t1 uses function, function modifies t1 An error occuring in the execution of a stored procedure, called from do_select is masked, since the error condition is not propagated back to the caller (join->conds->val_int() returns a result value, and not an error code) An explicit check was added to see if the thd error code has been set, and if so, the loop status is set to the error state. Backport from 6.0-codebase (revid: 2617.68.31) --- mysql-test/r/sp-error.result | 11 +++++++++++ mysql-test/t/sp-error.test | 30 ++++++++++++++++++++++++++++++ sql/sql_select.cc | 7 +++++++ 3 files changed, 48 insertions(+) diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index 00ae7dd4eca..034923bbd4f 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -1687,6 +1687,17 @@ NULL SELECT non_existent (a) FROM t1 WHERE b = 999999; ERROR 42000: FUNCTION test.non_existent does not exist DROP TABLE t1; +CREATE TABLE t1 ( f2 INTEGER, f3 INTEGER ); +INSERT INTO t1 VALUES ( 1, 1 ); +CREATE FUNCTION func_1 () RETURNS INTEGER +BEGIN +INSERT INTO t1 SELECT * FROM t1 ; +RETURN 1 ; +END| +INSERT INTO t1 SELECT * FROM (SELECT 2 AS f1, 2 AS f2) AS A WHERE func_1() = 5; +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +DROP FUNCTION func_1; +DROP TABLE t1; # # Bug #47788: Crash in TABLE_LIST::hide_view_error on UPDATE + VIEW + # SP + MERGE + ALTER diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index e33adf56284..4df1118cd56 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -2490,6 +2490,35 @@ SELECT AVG (a) FROM t1 WHERE b = 999999; SELECT non_existent (a) FROM t1 WHERE b = 999999; DROP TABLE t1; + +# +# Bug #46374 crash, INSERT INTO t1 uses function, function modifies t1 +# +CREATE TABLE t1 ( f2 INTEGER, f3 INTEGER ); +INSERT INTO t1 VALUES ( 1, 1 ); + +delimiter |; + +CREATE FUNCTION func_1 () RETURNS INTEGER +BEGIN + INSERT INTO t1 SELECT * FROM t1 ; + RETURN 1 ; +END| + +delimiter ;| + +# The bug caused the following INSERT statement to trigger +# an assertion. Error 1442 is the correct response +# +--error 1442 +INSERT INTO t1 SELECT * FROM (SELECT 2 AS f1, 2 AS f2) AS A WHERE func_1() = 5; + +# Cleanup +DROP FUNCTION func_1; +DROP TABLE t1; + + + --echo # --echo # Bug #47788: Crash in TABLE_LIST::hide_view_error on UPDATE + VIEW + --echo # SP + MERGE + ALTER @@ -2513,3 +2542,4 @@ DROP VIEW v1; DROP TABLE t1; --echo End of 5.1 tests + diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 5320da45322..2195719a3e9 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11096,6 +11096,13 @@ do_select(JOIN *join,List *fields,TABLE *table,Procedure *procedure) fields); rc= join->result->send_data(*columns_list); } + /* + An error can happen when evaluating the conds + (the join condition and piece of where clause + relevant to this join table). + */ + if (join->thd->is_error()) + error= NESTED_LOOP_ERROR; } else { From 1a122dc2201feeec9e691f606075d10d85950e88 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 11 Dec 2009 14:07:38 +0300 Subject: [PATCH 120/212] Partial backport of: ---------------------------------------------------- 2736.2.10 Michael Widenius 2008-10-22 Fix for bug#39395 Maria: ma_extra.c:286: maria_extra: Assertion `share->reopen == 1' failed sql/sql_base.cc: Race condition in wait_while_table_is_used() where a table used by another connection could be forced closed, but there was no protection against the other thread re-opening the table and trying to lock it again before the table was name locked by original thread. --- sql/sql_base.cc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 9f88393706a..a19f0bb96a7 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2118,13 +2118,15 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, enum ha_extra_function function) { enum thr_lock_type old_lock_type; - DBUG_ENTER("wait_while_table_is_used"); DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", table->s->table_name.str, (ulong) table->s, table->db_stat, table->s->version)); - (void) table->file->extra(function); + /* Ensure no one can reopen table before it's removed */ + pthread_mutex_lock(&LOCK_open); + table->s->version= 0; + pthread_mutex_unlock(&LOCK_open); old_lock_type= table->reginfo.lock_type; mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ @@ -2139,6 +2141,8 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN, table->s->db.str, table->s->table_name.str); pthread_mutex_unlock(&LOCK_open); + /* extra() call must come only after all instances above are closed */ + (void) table->file->extra(function); DBUG_RETURN(FALSE); } @@ -2165,6 +2169,7 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, void drop_open_table(THD *thd, TABLE *table, const char *db_name, const char *table_name) { + DBUG_ENTER("drop_open_table"); if (table->s->tmp_table) close_temporary_table(thd, table, 1, 1); else @@ -2176,10 +2181,12 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name, table->s->version= 0; pthread_mutex_lock(&LOCK_open); + table->file->extra(HA_EXTRA_PREPARE_FOR_DROP); close_thread_table(thd, &thd->open_tables); quick_rm_table(table_type, db_name, table_name, 0); pthread_mutex_unlock(&LOCK_open); } + DBUG_VOID_RETURN; } @@ -3141,7 +3148,7 @@ Locked_tables_list::unlock_locked_tables(THD *thd) the locked tables list. Passing a TABLE_LIST instance that is not part of locked tables list will lead to a crash. - @parma remove_from_locked_tables + @param remove_from_locked_tables TRUE if the table is removed from the list permanently. @@ -3443,7 +3450,6 @@ check_and_update_table_version(THD *thd, } DBUG_EXECUTE_IF("reprepare_each_statement", return inject_reprepare(thd);); - return FALSE; } @@ -4119,6 +4125,7 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, MEM_ROOT new_frm_mem; bool has_prelocking_list= thd->lex->requires_prelocking(); DBUG_ENTER("open_tables"); + /* Close HANDLER tables which are marked for flush or against which there are pending exclusive metadata locks. Note that we do this not to avoid From 42ed7740734937373e1da3c0fac6d1c49e835d1a Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 11 Dec 2009 14:12:47 +0300 Subject: [PATCH 121/212] Partial backport of: ----------------------------------------------------------- 2497.392.1 Michael Widenius 2008-08-19 Fixes for Bug #38016 Maria: trying to access freed memory when committing a transaction. Don't write out states if they haven't changed. sql/sql_table.cc: Call extra(HA_EXTRA_PREPARE_FOR_RENAME) before renaming a table. --- sql/sql_table.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 556c0e65eee..759afe79be9 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -7645,7 +7645,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, if (to->file->ha_end_bulk_insert() && error <= 0) { to->file->print_error(my_errno,MYF(0)); - error=1; + error= 1; } to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); @@ -7673,6 +7673,8 @@ copy_data_between_tables(TABLE *from,TABLE *to, to->file->ha_release_auto_increment(); if (to->file->ha_external_lock(thd,F_UNLCK)) error=1; + if (error < 0 && to->file->extra(HA_EXTRA_PREPARE_FOR_RENAME)) + error= 1; DBUG_RETURN(error > 0 ? -1 : 0); } From 3097c22afdcd3dce204c7587bdadd2a2872eb3c0 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 11 Dec 2009 14:18:59 +0300 Subject: [PATCH 122/212] Backport of: ----------------------------------------------------------- 2630.28.28 Magne Mahre 2008-12-05 Bug #38661 'all threads hang in "opening tables" or "waiting for table" and cpu is at 100%' Concurrent execution of FLUSH TABLES statement and at least two statements using the same table might have led to live-lock which caused all three connections to stall and hog 100% of CPU. tdc_wait_for_old_versions() wrongly assumed that there cannot be a share with an old version and no used TABLE instances and thus was failing to perform wait in situation when such old share was cached in MDL subsystem thanks to a still active metadata lock on the table. So it might have happened that two or more connections simultaneously executing statements which involve table being flushed managed to prevent each other from waiting in this function by keeping shared metadata lock on the table constantly active (i.e. one of the statements managed to take/hold this lock while other statements were calling tdc_wait_for_old_versions()). Thus they were forcing each other to loop infinitely in open_tables() - close_thread_tables_for_reopen() - tdc_wait_for_old_versions() cycle causing CPU hogging. This patch fixes this problem by removing this false assumption from tdc_wait_for_old_versions(). Note that the problem is specific only for server versions >= 6.0. No test case is submitted for this test, as the test infrastructure hasn't got the necessary primitives to test the behaviour. The manifestation is that throughput will decrease to a low level (possibly 0) after some time, and stay at that level. Several transactions will not complete. Manual testing can be done by running the code submitted by Shane Bester attached to the bug report. If the bug persists, the transaction thruput will almost immediately drop to near zero (shown as the transaction count output from the test program staying on a close to constant value, instead of increasing rapidly). --- sql/sql_base.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index a19f0bb96a7..cce376d86d1 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -8324,8 +8324,7 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests) { if ((share= get_cached_table_share(mdl_request->key.db_name(), mdl_request->key.name())) && - share->version != refresh_version && - !share->used_tables.is_empty()) + share->version != refresh_version) break; } if (!mdl_request) From 5a4f8e214c9d1ce67ea50ec360932e932d909e98 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 11 Dec 2009 15:24:23 +0300 Subject: [PATCH 123/212] Backport of: ------------------------------------------------------------ 2599.161.3 Ingo Struewing 2009-07-21 Bug#20667 - Truncate table fails for a write locked table TRUNCATE TABLE was not allowed under LOCK TABLES. The patch removes this restriction. mysql_truncate() does now handle that case. mysql-test/r/merge.result: Bug#20667 - Truncate table fails for a write locked table Updated test result. mysql-test/r/truncate.result: Bug#20667 - Truncate table fails for a write locked table Updated test result. mysql-test/r/truncate_coverage.result: Bug#20667 - Truncate table fails for a write locked table New test result. mysql-test/t/merge.test: Bug#20667 - Truncate table fails for a write locked table Updated test case due to now working TRUNCATE under LOCK TABLES. Added some SELECTs to show that child tables are truncated. mysql-test/t/truncate.test: Bug#20667 - Truncate table fails for a write locked table Added test cases for TRUNCATE under LOCK TABLE. mysql-test/t/truncate_coverage.test: Bug#20667 - Truncate table fails for a write locked table New test file. Coverage tests for TRUNCATE. sql/sql_delete.cc: Bug#20667 - Truncate table fails for a write locked table Added branches for thd->locked_tables_mode. sql/sql_parse.cc: Bug#20667 - Truncate table fails for a write locked table Deleted rejection of TRUNCATE in case of LOCK TABLES. --- mysql-test/r/merge.result | 29 +++-- mysql-test/r/truncate.result | 87 ++++++++++++++- mysql-test/r/truncate_coverage.result | 70 ++++++++++++ mysql-test/t/merge.test | 16 ++- mysql-test/t/truncate.test | 73 +++++++++++- mysql-test/t/truncate_coverage.test | 155 ++++++++++++++++++++++++++ sql/sql_delete.cc | 73 +++++++++--- sql/sql_parse.cc | 2 +- 8 files changed, 470 insertions(+), 35 deletions(-) create mode 100644 mysql-test/r/truncate_coverage.result create mode 100644 mysql-test/t/truncate_coverage.test diff --git a/mysql-test/r/merge.result b/mysql-test/r/merge.result index 0417b91490e..a215c818b0f 100644 --- a/mysql-test/r/merge.result +++ b/mysql-test/r/merge.result @@ -1046,18 +1046,21 @@ c1 LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE; INSERT INTO t1 VALUES (1); TRUNCATE TABLE t3; -ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3; c1 -1 -2 +UNLOCK TABLES; +SELECT * FROM t1; +c1 +SELECT * FROM t2; +c1 # # Truncate child table under locked tables. +LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); TRUNCATE TABLE t1; -ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3; c1 -1 2 UNLOCK TABLES; DROP TABLE t1, t2, t3; @@ -1089,18 +1092,24 @@ INSERT INTO t1 VALUES (1); CREATE TABLE t4 (c1 INT, INDEX(c1)); LOCK TABLE t4 WRITE; TRUNCATE TABLE t3; -ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3; c1 -1 -2 +SELECT * FROM t1; +c1 +SELECT * FROM t2; +c1 # # Truncate temporary child table under locked tables. +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); TRUNCATE TABLE t1; -ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3; c1 -1 +2 +SELECT * FROM t1; +c1 +SELECT * FROM t2; +c1 2 UNLOCK TABLES; DROP TABLE t1, t2, t3, t4; diff --git a/mysql-test/r/truncate.result b/mysql-test/r/truncate.result index 8ce2ad9be21..8f237c81e75 100644 --- a/mysql-test/r/truncate.result +++ b/mysql-test/r/truncate.result @@ -1,4 +1,4 @@ -drop table if exists t1; +drop table if exists t1, t2; create table t1 (a integer, b integer,c1 CHAR(10)); insert into t1 (a) values (1),(2); truncate table t1; @@ -61,6 +61,91 @@ ERROR 42S02: Table 'test.v1' doesn't exist drop view v1; drop table t1; # +# Bug#20667 - Truncate table fails for a write locked table +# +CREATE TABLE t1 (c1 INT); +LOCK TABLE t1 WRITE; +INSERT INTO t1 VALUES (1); +SELECT * FROM t1; +c1 +1 +TRUNCATE TABLE t1; +SELECT * FROM t1; +c1 +UNLOCK TABLES; +LOCK TABLE t1 READ; +TRUNCATE TABLE t1; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +UNLOCK TABLES; +CREATE TABLE t2 (c1 INT); +LOCK TABLE t2 WRITE; +TRUNCATE TABLE t1; +ERROR HY000: Table 't1' was not locked with LOCK TABLES +UNLOCK TABLES; +CREATE VIEW v1 AS SELECT t1.c1 FROM t1,t2 WHERE t1.c1 = t2.c1; +INSERT INTO t1 VALUES (1), (2), (3); +INSERT INTO t2 VALUES (1), (3), (4); +SELECT * FROM v1; +c1 +1 +3 +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +c1 +1 +3 +LOCK TABLE t1 WRITE; +SELECT * FROM v1; +ERROR HY000: Table 'v1' was not locked with LOCK TABLES +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +ERROR HY000: Table 'v1' was not locked with LOCK TABLES +UNLOCK TABLES; +LOCK TABLE t1 WRITE, t2 WRITE; +SELECT * FROM v1; +ERROR HY000: Table 'v1' was not locked with LOCK TABLES +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +ERROR HY000: Table 'v1' was not locked with LOCK TABLES +UNLOCK TABLES; +LOCK TABLE v1 WRITE; +SELECT * FROM v1; +c1 +1 +3 +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +c1 +1 +3 +UNLOCK TABLES; +LOCK TABLE t1 WRITE, t2 WRITE, v1 WRITE; +SELECT * FROM v1; +c1 +1 +3 +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +c1 +1 +3 +UNLOCK TABLES; +DROP VIEW v1; +DROP TABLE t1, t2; +CREATE PROCEDURE p1() SET @a = 5; +TRUNCATE p1; +ERROR 42S02: Table 'test.p1' doesn't exist +SHOW CREATE PROCEDURE p1; +Procedure sql_mode Create Procedure character_set_client collation_connection Database Collation +p1 CREATE DEFINER=`root`@`localhost` PROCEDURE `p1`() +SET @a = 5 latin1 latin1_swedish_ci latin1_swedish_ci +DROP PROCEDURE p1; +# # Bug#46452 Crash in MDL, HANDLER OPEN + TRUNCATE TABLE # DROP TABLE IF EXISTS t1; diff --git a/mysql-test/r/truncate_coverage.result b/mysql-test/r/truncate_coverage.result new file mode 100644 index 00000000000..bb036329f6f --- /dev/null +++ b/mysql-test/r/truncate_coverage.result @@ -0,0 +1,70 @@ +SET DEBUG_SYNC='RESET'; +DROP TABLE IF EXISTS t1; +# +# Bug#20667 - Truncate table fails for a write locked table +# +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# connection con1 +START TRANSACTION; +INSERT INTO t1 VALUES (2); +# +# connection default +LOCK TABLE t1 WRITE; +SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; +TRUNCATE TABLE t1; +# +# connection con1 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +KILL QUERY @id; +COMMIT; +# +# connection default +ERROR 70100: Query execution was interrupted +UNLOCK TABLES; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# connection con1 +START TRANSACTION; +INSERT INTO t1 VALUES (2); +# +# connection default +LOCK TABLE t1 WRITE; +SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; +TRUNCATE TABLE t1; +# +# connection con1 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +COMMIT; +# +# connection default +ERROR 42S02: Table 'test.t1' doesn't exist +UNLOCK TABLES; +DROP TABLE t1; +ERROR 42S02: Unknown table 't1' +SET DEBUG_SYNC='RESET'; +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# connection con1 +START TRANSACTION; +INSERT INTO t1 VALUES (2); +# +# connection default +SET DEBUG_SYNC='mdl_acquire_exclusive_locks_wait SIGNAL waiting'; +TRUNCATE TABLE t1; +# +# connection con1 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +KILL QUERY @id; +COMMIT; +# +# connection default +ERROR 70100: Query execution was interrupted +UNLOCK TABLES; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; diff --git a/mysql-test/t/merge.test b/mysql-test/t/merge.test index b9e6813a4df..c3499037e97 100644 --- a/mysql-test/t/merge.test +++ b/mysql-test/t/merge.test @@ -675,12 +675,16 @@ SELECT * FROM t3; --echo # Truncate MERGE table under locked tables. LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE; INSERT INTO t1 VALUES (1); ---error ER_LOCK_OR_ACTIVE_TRANSACTION TRUNCATE TABLE t3; SELECT * FROM t3; +UNLOCK TABLES; +SELECT * FROM t1; +SELECT * FROM t2; --echo # --echo # Truncate child table under locked tables. ---error ER_LOCK_OR_ACTIVE_TRANSACTION +LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); TRUNCATE TABLE t1; SELECT * FROM t3; UNLOCK TABLES; @@ -706,14 +710,18 @@ SELECT * FROM t3; INSERT INTO t1 VALUES (1); CREATE TABLE t4 (c1 INT, INDEX(c1)); LOCK TABLE t4 WRITE; ---error ER_LOCK_OR_ACTIVE_TRANSACTION TRUNCATE TABLE t3; SELECT * FROM t3; +SELECT * FROM t1; +SELECT * FROM t2; --echo # --echo # Truncate temporary child table under locked tables. ---error ER_LOCK_OR_ACTIVE_TRANSACTION +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); TRUNCATE TABLE t1; SELECT * FROM t3; +SELECT * FROM t1; +SELECT * FROM t2; UNLOCK TABLES; DROP TABLE t1, t2, t3, t4; diff --git a/mysql-test/t/truncate.test b/mysql-test/t/truncate.test index feec4051e35..cdfa448f78a 100644 --- a/mysql-test/t/truncate.test +++ b/mysql-test/t/truncate.test @@ -2,7 +2,7 @@ # Test of truncate # --disable_warnings -drop table if exists t1; +drop table if exists t1, t2; --enable_warnings create table t1 (a integer, b integer,c1 CHAR(10)); @@ -69,6 +69,77 @@ drop table t1; # End of 5.0 tests +--echo # +--echo # Bug#20667 - Truncate table fails for a write locked table +--echo # +CREATE TABLE t1 (c1 INT); +LOCK TABLE t1 WRITE; +INSERT INTO t1 VALUES (1); +SELECT * FROM t1; +TRUNCATE TABLE t1; +SELECT * FROM t1; +UNLOCK TABLES; +# +LOCK TABLE t1 READ; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +TRUNCATE TABLE t1; +UNLOCK TABLES; +# +CREATE TABLE t2 (c1 INT); +LOCK TABLE t2 WRITE; +--error ER_TABLE_NOT_LOCKED +TRUNCATE TABLE t1; +UNLOCK TABLES; +# +CREATE VIEW v1 AS SELECT t1.c1 FROM t1,t2 WHERE t1.c1 = t2.c1; +INSERT INTO t1 VALUES (1), (2), (3); +INSERT INTO t2 VALUES (1), (3), (4); +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +SELECT * FROM v1; +# +LOCK TABLE t1 WRITE; +--error ER_TABLE_NOT_LOCKED +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +--error ER_TABLE_NOT_LOCKED +SELECT * FROM v1; +UNLOCK TABLES; +# +LOCK TABLE t1 WRITE, t2 WRITE; +--error ER_TABLE_NOT_LOCKED +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +--error ER_TABLE_NOT_LOCKED +SELECT * FROM v1; +UNLOCK TABLES; +# +LOCK TABLE v1 WRITE; +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +SELECT * FROM v1; +UNLOCK TABLES; +# +LOCK TABLE t1 WRITE, t2 WRITE, v1 WRITE; +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +SELECT * FROM v1; +UNLOCK TABLES; +# +DROP VIEW v1; +DROP TABLE t1, t2; +# +CREATE PROCEDURE p1() SET @a = 5; +--error ER_NO_SUCH_TABLE +TRUNCATE p1; +SHOW CREATE PROCEDURE p1; +DROP PROCEDURE p1; + --echo # --echo # Bug#46452 Crash in MDL, HANDLER OPEN + TRUNCATE TABLE --echo # diff --git a/mysql-test/t/truncate_coverage.test b/mysql-test/t/truncate_coverage.test new file mode 100644 index 00000000000..9870fbb5ebf --- /dev/null +++ b/mysql-test/t/truncate_coverage.test @@ -0,0 +1,155 @@ +# +# Code coverage testing of TRUNCATE TABLE. +# +# Ingo Struewing, 2009-07-20 +# + +--source include/have_debug_sync.inc +SET DEBUG_SYNC='RESET'; + +--let $MYSQLD_DATADIR= `SELECT @@datadir` + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +--echo # +--echo # Bug#20667 - Truncate table fails for a write locked table +--echo # +######## +# Attack wait_while_table_is_used(). Kill query while trying to +# upgrade MDL. +# +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# Start a transaction and execute a DML in it. Since 5.4.4 this leaves +# a shared meta data lock (MDL) behind. TRUNCATE shall block on it. +# +--echo # +--echo # connection con1 +--connect (con1, localhost, root,,) +START TRANSACTION; +INSERT INTO t1 VALUES (2); +# +# Get connection id of default connection. +# Lock the table and start TRUNCATE, which will block on MDL upgrade. +# +--echo # +--echo # connection default +--connection default +let $ID= `SELECT @id := CONNECTION_ID()`; +LOCK TABLE t1 WRITE; +SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; +send TRUNCATE TABLE t1; +# +# Get the default connection ID into a variable in an invisible statement. +# Kill the TRUNCATE query. This shall result in an error return +# from wait_while_table_is_used(). +# +--echo # +--echo # connection con1 +--connection con1 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +let $invisible_assignment_in_select = `SELECT @id := $ID`; +KILL QUERY @id; +COMMIT; +--disconnect con1 +--echo # +--echo # connection default +--connection default +--error ER_QUERY_INTERRUPTED +reap; +UNLOCK TABLES; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; +######## +# Attack reopen_tables(). Remove form file. +# +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# Start a transaction and execute a DML in it. Since 5.4.4 this leaves +# a shared meta data lock (MDL) behind. TRUNCATE shall block on it. +# +--echo # +--echo # connection con1 +--connect (con1, localhost, root,,) +START TRANSACTION; +INSERT INTO t1 VALUES (2); +# +# Lock the table and start TRUNCATE, which will block on MDL upgrade. +# +--echo # +--echo # connection default +--connection default +LOCK TABLE t1 WRITE; +SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; +send TRUNCATE TABLE t1; +# +# Remove datafile. +# Commit to let TRUNCATE continue. +# +--echo # +--echo # connection con1 +--connection con1 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +--remove_file $MYSQLD_DATADIR/test/t1.frm +COMMIT; +--disconnect con1 +--echo # +--echo # connection default +--connection default +--error ER_NO_SUCH_TABLE +reap; +UNLOCK TABLES; +--error ER_BAD_TABLE_ERROR +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; +######## +# Attack acquire_exclusive_locks(). Hold a global read lock. +# Non-LOCK TABLE case. +# +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# Start a transaction and execute a DML in it. Since 5.4.4 this leaves +# a shared meta data lock (MDL) behind. TRUNCATE shall block on it. +# +--echo # +--echo # connection con1 +--connect (con1, localhost, root,,) +START TRANSACTION; +INSERT INTO t1 VALUES (2); +# +# Get connection id of default connection. +# Start TRUNCATE, which will block on acquire_exclusive_locks(). +# +--echo # +--echo # connection default +--connection default +let $ID= `SELECT @id := CONNECTION_ID()`; +SET DEBUG_SYNC='mdl_acquire_exclusive_locks_wait SIGNAL waiting'; +send TRUNCATE TABLE t1; +# +# Get the default connection ID into a variable in an invisible statement. +# Kill the TRUNCATE query. This shall result in an error return +# from wait_while_table_is_used(). +# +--echo # +--echo # connection con1 +--connection con1 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +let $invisible_assignment_in_select = `SELECT @id := $ID`; +KILL QUERY @id; +COMMIT; +--disconnect con1 +--echo # +--echo # connection default +--connection default +--error ER_QUERY_INTERRUPTED +reap; +UNLOCK TABLES; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; + diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 1f3621de502..26478b31290 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1098,9 +1098,15 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) HA_CREATE_INFO create_info; char path[FN_REFLEN + 1]; TABLE *table; - bool error; + bool error= TRUE; uint path_length; MDL_request mdl_request; + /* + Is set if we're under LOCK TABLES, and used + to downgrade the exclusive lock after the + table was truncated. + */ + MDL_ticket *mdl_ticket= NULL; bool has_mdl_lock= FALSE; DBUG_ENTER("mysql_truncate"); @@ -1119,7 +1125,7 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) goto trunc_by_del; table->file->info(HA_STATUS_AUTO | HA_STATUS_NO_LOCK); - + close_temporary_table(thd, table, 0, 0); // Don't free share ha_create_table(thd, share->normalized_path.str, share->db.str, share->table_name.str, &create_info, 1); @@ -1177,21 +1183,47 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) goto trunc_by_del; - mdl_request.init(MDL_key::TABLE, table_list->db, table_list->table_name, MDL_EXCLUSIVE); - if (thd->mdl_context.acquire_exclusive_lock(&mdl_request)) - DBUG_RETURN(TRUE); - has_mdl_lock= TRUE; + if (thd->locked_tables_mode) + { + if (!(table= find_write_locked_table(thd->open_tables, table_list->db, + table_list->table_name))) + DBUG_RETURN(TRUE); + mdl_ticket= table->mdl_ticket; + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto end; + close_all_tables_for_name(thd, table->s, FALSE); + } + else + { + /* + Even though we could use the previous execution branch + here just as well, we must not try to open the table: + MySQL manual documents that TRUNCATE can be used to + repair a damaged table, i.e. a table that can not be + fully "opened". In particular MySQL manual says: - pthread_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, - table_list->table_name); - pthread_mutex_unlock(&LOCK_open); + As long as the table format file tbl_name.frm is valid, + the table can be re-created as an empty table with TRUNCATE + TABLE, even if the data or index files have become corrupted. + */ + mdl_request.init(MDL_key::TABLE, table_list->db, table_list->table_name, + MDL_EXCLUSIVE); + if (thd->mdl_context.acquire_exclusive_lock(&mdl_request)) + DBUG_RETURN(TRUE); + has_mdl_lock= TRUE; + pthread_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, + table_list->table_name); + pthread_mutex_unlock(&LOCK_open); + } } - // Remove the .frm extension AIX 5.2 64-bit compiler bug (BUG#16155): this - // crashes, replacement works. *(path + path_length - reg_ext_length)= - // '\0'; + /* + Remove the .frm extension AIX 5.2 64-bit compiler bug (BUG#16155): this + crashes, replacement works. *(path + path_length - reg_ext_length)= + '\0'; + */ path[path_length - reg_ext_length] = 0; pthread_mutex_lock(&LOCK_open); error= ha_create_table(thd, path, table_list->db, table_list->table_name, @@ -1202,6 +1234,12 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) end: if (!dont_send_ok) { + if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd)) + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + /* + Even if we failed to reopen some tables, + the operation itself succeeded, write the binlog. + */ if (!error) { /* @@ -1213,12 +1251,11 @@ end: } if (has_mdl_lock) thd->mdl_context.release_lock(mdl_request.ticket); + if (mdl_ticket) + mdl_ticket->downgrade_exclusive_lock(); } - else if (error) - { - if (has_mdl_lock) - thd->mdl_context.release_lock(mdl_request.ticket); - } + + DBUG_PRINT("exit", ("error: %d", error)); DBUG_RETURN(error); trunc_by_del: diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 289b6ce0da8..0d21eb82b87 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3078,7 +3078,7 @@ end_with_restore_list: Don't allow this within a transaction because we want to use re-generate table */ - if (thd->locked_tables_mode || thd->active_transaction()) + if (thd->active_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); From 2fc4fbfd6b5cf11fe37fcec80085841b88d81145 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Tue, 15 Dec 2009 10:12:24 +0100 Subject: [PATCH 124/212] Bug #49672 mtr should extract end of result log if mysqltest fails without output Extracts last 20 lines if no output after failure --- mysql-test/mysql-test-run.pl | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 7d2426459d0..f0898e1cab7 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -3500,7 +3500,7 @@ sub run_testcase ($) { { # mysqltest failed, probably crashed $tinfo->{comment}= - "mysqltest failed with unexpected return code $res"; + "mysqltest failed with unexpected return code $res\n"; report_failure_and_restart($tinfo); } @@ -4091,6 +4091,19 @@ sub report_failure_and_restart ($) { # about what failed has been saved to file. Save the report # in tinfo $tinfo->{logfile}= mtr_fromfile($logfile); + # If no newlines in the test log: + if ($tinfo->{logfile} !~ /\n/) + { + # Show how far it got before suddenly failing + $tinfo->{comment}.= "mysqltest failed but provided no output\n"; + my $log_file_name= $opt_vardir."/log/".$tinfo->{shortname}.".log"; + if (-e $log_file_name) { + $tinfo->{comment}.= + "The result from queries just before the failure was:". + "\n< snip >\n". + mtr_lastlinesfromfile($log_file_name, 20)."\n"; + } + } } else { From 6331ef3efd56d995fd7a82b7325a2615750766ad Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Tue, 15 Dec 2009 14:18:10 +0100 Subject: [PATCH 125/212] Bug #48940 MDL deadlocks against mysql_rm_db This deadlock would occur between two connections A and B if statements where executed in the following way: 1) Connection A executes a DML statement against table s1.t1 with autocommit off. This causes a shared metadata lock on s1.t1 to be acquired. (With autocommit on, the metadata lock will be dropped once the statment completes and the deadlock will not occour.) 2) Connection B tries to DROP DATABASE s1. This will block against the metadata lock connection A holds on s1.t1. While blocking, connection B will hold the LOCK_mysql_create_db mutex. 3) Connection A tries to ALTER DATABASE s1. This will block when trying to get LOCK_mysql_create_db mutex held by connection B. 4) Deadlock between DROP DATABASE and ALTER DATABASE (which has autocommit off). If Connection A used an explicitly started transaction rather than having autocommit off, this deadlock did not happen as ALTER DATABASE is disallowed inside transactions. This patch fixes the problem by changing ALTER DATABASE to cause an implicit commit before executing. This will cause the metadata lock on s1.t1 to be dropped, allowing DROP DATABASE to proceed. This will in turn cause the LOCK_mysql_create_db mutex to be unlocked, allowing ALTER DATABASE to proceed. Note that SQL commands other than ALTER DATABASE that also use LOCK_mysql_create_db, already cause an implicit commit. Incompatible change: ALTER DATABASE (and its synonym ALTER SCHEMA) now cause an implicit commit. This must be reflected in the documentation. Test case added to schema.test. sql/sql_parse.cc: Added CF_AUTO_COMMIT_TRANS to SQLCOM_ALTER_DB. Removed thd->active_transaction() checks from SQLCOM_DROP_DB, SQLCOM_ALTER_DB_UPGRADE and SQLCOM_ALTER_DB as these statements cause an implicit commit. --- mysql-test/r/schema.result | 17 +++++++++++++ mysql-test/t/schema.test | 50 ++++++++++++++++++++++++++++++++++++++ sql/sql_parse.cc | 10 ++++---- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/mysql-test/r/schema.result b/mysql-test/r/schema.result index 564fb3626df..33a2d4d9448 100644 --- a/mysql-test/r/schema.result +++ b/mysql-test/r/schema.result @@ -11,3 +11,20 @@ mtr mysql test drop schema foo; +# +# Bug #48940 MDL deadlocks against mysql_rm_db +# +DROP SCHEMA IF EXISTS schema1; +# Connection default +CREATE SCHEMA schema1; +CREATE TABLE schema1.t1 (a INT); +SET autocommit= FALSE; +INSERT INTO schema1.t1 VALUES (1); +# Connection 2 +DROP SCHEMA schema1; +# Connection default +ALTER SCHEMA schema1 DEFAULT CHARACTER SET utf8; +ERROR HY000: Can't create/write to file './schema1/db.opt' (Errcode: 2) +SET autocommit= TRUE; +# Connection 2 +# Connection default diff --git a/mysql-test/t/schema.test b/mysql-test/t/schema.test index a08d9b38935..a380a6241dd 100644 --- a/mysql-test/t/schema.test +++ b/mysql-test/t/schema.test @@ -4,6 +4,9 @@ # Drop mysqltest1 database, as it can left from the previous tests. # +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + --disable_warnings drop database if exists mysqltest1; --enable_warnings @@ -12,3 +15,50 @@ create schema foo; show create schema foo; show schemas; drop schema foo; + + +--echo # +--echo # Bug #48940 MDL deadlocks against mysql_rm_db +--echo # + +--disable_warnings +DROP SCHEMA IF EXISTS schema1; +--enable_warnings + +connect(con2, localhost, root); + +--echo # Connection default +connection default; + +CREATE SCHEMA schema1; +CREATE TABLE schema1.t1 (a INT); + +SET autocommit= FALSE; +INSERT INTO schema1.t1 VALUES (1); + +--echo # Connection 2 +connection con2; +--send DROP SCHEMA schema1 + +--echo # Connection default +connection default; +let $wait_condition= SELECT COUNT(*)= 1 FROM information_schema.processlist + WHERE state= 'Waiting for table' + AND info='DROP SCHEMA schema1'; +--source include/wait_condition.inc +--error 1 +ALTER SCHEMA schema1 DEFAULT CHARACTER SET utf8; +SET autocommit= TRUE; + +--echo # Connection 2 +connection con2; +--reap + +--echo # Connection default +connection default; +disconnect con2; + + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 0d21eb82b87..6ea6c3e850b 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -193,6 +193,8 @@ void init_update_queries(void) CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | @@ -203,7 +205,6 @@ void init_update_queries(void) sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_AUTO_COMMIT_TRANS; @@ -283,7 +284,6 @@ void init_update_queries(void) sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_REVOKE_ALL]= CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA; @@ -3430,7 +3430,7 @@ end_with_restore_list: if (check_access(thd,DROP_ACL,lex->name.str,0,1,0, is_schema_db(lex->name.str))) break; - if (thd->locked_tables_mode || thd->active_transaction()) + if (thd->locked_tables_mode) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); @@ -3464,7 +3464,7 @@ end_with_restore_list: res= 1; break; } - if (thd->locked_tables_mode || thd->active_transaction()) + if (thd->locked_tables_mode) { res= 1; my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, @@ -3504,7 +3504,7 @@ end_with_restore_list: #endif if (check_access(thd, ALTER_ACL, db->str, 0, 1, 0, is_schema_db(db->str))) break; - if (thd->locked_tables_mode || thd->active_transaction()) + if (thd->locked_tables_mode) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); From a88898e692dd1fcbeb4fc21ef2afee199769afb1 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Wed, 16 Dec 2009 10:06:37 +0100 Subject: [PATCH 126/212] backport mysqltest send_eval from 42520 --- client/mysqltest.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 3cd87bd3236..7f758e7c306 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -280,7 +280,7 @@ enum enum_commands { Q_SEND_QUIT, Q_CHANGE_USER, Q_MKDIR, Q_RMDIR, Q_LIST_FILES, Q_LIST_FILES_WRITE_FILE, Q_LIST_FILES_APPEND_FILE, Q_SEND_SHUTDOWN, Q_SHUTDOWN_SERVER, - Q_MOVE_FILE, + Q_MOVE_FILE, Q_SEND_EVAL, Q_UNKNOWN, /* Unknown command. */ Q_COMMENT, /* Comments, ignored. */ @@ -378,6 +378,7 @@ const char *command_names[]= "send_shutdown", "shutdown_server", "move_file", + "send_eval", 0 }; @@ -7047,7 +7048,7 @@ void run_query(struct st_connection *cn, struct st_command *command, int flags) /* Evaluate query if this is an eval command */ - if (command->type == Q_EVAL) + if (command->type == Q_EVAL || command->type == Q_SEND_EVAL) { init_dynamic_string(&eval_query, "", command->query_len+256, 1024); do_eval(&eval_query, command->query, command->end, FALSE); @@ -7866,6 +7867,7 @@ int main(int argc, char **argv) break; } case Q_SEND: + case Q_SEND_EVAL: if (!*command->first_argument) { /* From 6d4e09d68e1ea104e84e1f9bafffe43b00a7aec9 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 16 Dec 2009 12:32:11 +0100 Subject: [PATCH 127/212] Bug #48541 Deadlock between LOCK_open and LOCK_mdl The reason for the deadlock was an improper exit from MDL_context::wait_for_locks() which caused mysys_var->current_mutex to remain LOCK_mdl even though LOCK_mdl was no longer held by that connection. This could for example lead to a deadlock in the following way: 1) INSERT DELAYED tries to open a table but fails, and trying to recover it calls wait_for_locks(). 2) Due to a pending exclusive request, wait_for_locks() fails and exits without resetting mysys_var->current_mutex for the delayed insert handler thread. So it continues to point to LOCK_mdl. 3) The handler thread manages to open a table. 4) A different connection takes LOCK_open and tries to take LOCK_mdl. 5) FLUSH TABLES from a third connection notices that the handler thread has a table open, and tries to kill it. This involves locking mysys_var->current_mutex while having LOCK_open locked. Since current_mutex mistakenly points to LOCK_mdl, we have a deadlock. This patch makes sure MDL_EXIT_COND() is called before exiting wait_for_locks(). This clears mysys->current_mutex which resolves the issue. An assert is added to recover_from_failed_open_table_attempt() after wait_for_locks() is called, to check that current_mutex is indeed reset. With this assert in place, existing tests in (e.g.) mdl_sync.test will fail without this patch. --- sql/mdl.cc | 3 ++- sql/sql_base.cc | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sql/mdl.cc b/sql/mdl.cc index a883b21423e..6187d4515a3 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -1254,7 +1254,8 @@ MDL_context::wait_for_locks(MDL_request_list *mdl_requests) } if (!mdl_request) { - pthread_mutex_unlock(&LOCK_mdl); + /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ + MDL_EXIT_COND(m_thd, mysys_var, old_msg); break; } pthread_cond_wait(&COND_mdl, &LOCK_mdl); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 99a0b33f213..61028f692b3 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3693,6 +3693,7 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table) case OT_WAIT: result= (thd->mdl_context.wait_for_locks(&m_mdl_requests) || tdc_wait_for_old_versions(thd, &m_mdl_requests)); + DBUG_ASSERT(thd->mysys_var->current_mutex == NULL); break; case OT_DISCOVER: { From 4315dc033ea5d7462cff454f6260498d47c88768 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 17 Dec 2009 13:43:07 +0100 Subject: [PATCH 128/212] Bug #48724 Deadlock between INSERT DELAYED and FLUSH TABLES If the handler (or delayed insert) thread failed to lock a table due to being killed, the "dead" flag was used to notify the connection thread of this failure. However, with the changes introduced by Bug#45949, the handler thread will no longer try to lock the table if it was killed. This meant that the "dead" flag would not be set, and the connection thread would not notice that the handler thread had failed. This could happen with concurrent INSERT DELAYED and FLUSH TABLES. FLUSH TABLES would kill any active INSERT DELAYED that had opened any table(s) to be flushed. This could cause the INSERT DELAYED connection thread to be stuck waiting for the handler thread to lock its table, while the handler thread would be looping, trying to get the connection thread to notice the error. The root of the problem was that the handler thread had both the "dead" flag and "thd->killed" to indicate that it had been killed. Most places both were set, but some only set "thd->killed". And Delayed_insert::get_local_table() only checked "dead" while waiting for the table to be locked. This patch removes the "dead" variable and replaces its usage with "thd->killed", thereby resolving the issue. --- sql/sql_insert.cc | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index b108ff75f44..9f8af87f1e2 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1761,7 +1761,7 @@ public: pthread_mutex_t mutex; pthread_cond_t cond,cond_client; volatile uint tables_in_use,stacked_inserts; - volatile bool status,dead; + volatile bool status; COPY_INFO info; I_List rows; ulong group_count; @@ -1769,8 +1769,7 @@ public: Delayed_insert() :locks_in_memory(0), - table(0),tables_in_use(0),stacked_inserts(0), status(0), dead(0), - group_count(0) + table(0),tables_in_use(0),stacked_inserts(0), status(0), group_count(0) { thd.security_ctx->user=thd.security_ctx->priv_user=(char*) delayed_user; thd.security_ctx->host=(char*) my_localhost; @@ -1918,9 +1917,8 @@ Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list) a given consumer (delayed insert thread), only at different stages of producer-consumer relationship. - 'dead' and 'status' variables in Delayed_insert are redundant - too, since there is already 'di->thd.killed' and - di->stacked_inserts. + The 'status' variable in Delayed_insert is redundant + too, since there is already di->stacked_inserts. */ static @@ -2073,17 +2071,29 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) { thd_proc_info(client_thd, "waiting for handler lock"); pthread_cond_signal(&cond); // Tell handler to lock table - while (!dead && !thd.lock && ! client_thd->killed) + while (!thd.killed && !thd.lock && ! client_thd->killed) { pthread_cond_wait(&cond_client,&mutex); } thd_proc_info(client_thd, "got handler lock"); if (client_thd->killed) goto error; - if (dead) + if (thd.killed) { - /* Don't copy over "Server shutdown in progress". */ - if (thd.stmt_da->sql_errno() == ER_SERVER_SHUTDOWN) + /* + Copy the error message. Note that we don't treat fatal + errors in the delayed thread as fatal errors in the + main thread. If delayed thread was killed, we don't + want to send "Server shutdown in progress" in the + INSERT THREAD. + + The thread could be killed with an error message if + di->handle_inserts() or di->open_and_lock_table() fails. + The thread could be killed without an error message if + killed using mysql_notify_thread_having_shared_lock() or + kill_delayed_threads_for_table(). + */ + if (!thd.is_error() || thd.stmt_da->sql_errno() == ER_SERVER_SHUTDOWN) my_message(ER_QUERY_INTERRUPTED, ER(ER_QUERY_INTERRUPTED), MYF(0)); else my_message(thd.stmt_da->sql_errno(), thd.stmt_da->message(), MYF(0)); @@ -2454,7 +2464,8 @@ pthread_handler_t handle_delayed_insert(void *arg) break; // Time to die } - if (!di->status && !di->stacked_inserts) + /* Shouldn't wait if killed or an insert is waiting. */ + if (!thd->killed && !di->status && !di->stacked_inserts) { struct timespec abstime; set_timespec(abstime, delayed_insert_timeout); @@ -2465,7 +2476,7 @@ pthread_handler_t handle_delayed_insert(void *arg) thd_proc_info(&(di->thd), "Waiting for INSERT"); DBUG_PRINT("info",("Waiting for someone to insert rows")); - while (!thd->killed) + while (!thd->killed && !di->status) { int error; #if defined(HAVE_BROKEN_COND_TIMEDWAIT) @@ -2481,13 +2492,8 @@ pthread_handler_t handle_delayed_insert(void *arg) } #endif #endif - if (thd->killed || di->status) - break; if (error == ETIMEDOUT || error == ETIME) - { thd->killed= THD::KILL_CONNECTION; - break; - } } /* We can't lock di->mutex and mysys_var->mutex at the same time */ pthread_mutex_unlock(&di->mutex); @@ -2528,14 +2534,12 @@ pthread_handler_t handle_delayed_insert(void *arg) di->table= 0; if (di->open_and_lock_table()) { - di->dead= 1; thd->killed= THD::KILL_CONNECTION; } } else { /* Fatal error */ - di->dead= 1; thd->killed= THD::KILL_CONNECTION; } } @@ -2546,7 +2550,6 @@ pthread_handler_t handle_delayed_insert(void *arg) if (di->handle_inserts()) { /* Some fatal error */ - di->dead= 1; thd->killed= THD::KILL_CONNECTION; } } @@ -2598,7 +2601,6 @@ pthread_handler_t handle_delayed_insert(void *arg) close_thread_tables(thd); // Free the table di->table=0; - di->dead= 1; // If error thd->killed= THD::KILL_CONNECTION; // If error pthread_cond_broadcast(&di->cond_client); // Safety pthread_mutex_unlock(&di->mutex); From a5beaf5cbfe2daf2a137e03922a8bf24889153a0 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 17 Dec 2009 16:40:02 +0100 Subject: [PATCH 129/212] Partial backport of: ------------------------------------------------------------ revno: 2617.14.26 committer: Vladislav Vaintroub branch nick: mysql-6.0-wtf timestamp: Wed 2008-11-05 11:19:19 +0100 message: CMakeLists.txt files cleanup. - remove SAFEMALLOC and SAFE_MUTEX definitions that were present in *each* CMakeLists.txt. Instead, put them into top level MakeLists.txt, but disable on Windows, because a) SAFEMALLOC does not add any functionality that is not already present in Debug C runtime ( and 2 safe malloc one on top of the other only unnecessarily slows down the server) b)SAFE_MUTEX does not work on Windows and have been explicitely disabled on Windows with #undef previously. Fortunately, ntdll does pretty good job identifying l problems with CRITICAL_SECTIONs. (DebugBreak()s on using uninited critical section, unlocking unowned critical section) -Remove occationally used -D_DEBUG (added by compiler anyway) -Remove MAP file generation, it became obsolete . There are many ways to get callstack of a crash now, with stacktrace in error log , minidump etc --- storage/innobase/CMakeLists.txt | 6 ------ zlib/CMakeLists.txt | 2 -- 2 files changed, 8 deletions(-) diff --git a/storage/innobase/CMakeLists.txt b/storage/innobase/CMakeLists.txt index d67b518642c..d8213216383 100644 --- a/storage/innobase/CMakeLists.txt +++ b/storage/innobase/CMakeLists.txt @@ -16,12 +16,6 @@ # This is the CMakeLists for InnoDB Plugin -# TODO: remove the two FLAGS_DEBUG settings when merging into -# 6.0-based trees, like is already the case for other engines in -# those trees. -SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") -SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") - # Starting at 5.1.38, MySQL CMake files are simplified. But the plugin # CMakeLists.txt still needs to work with previous versions of MySQL. IF (MYSQL_VERSION_ID GREATER "50137") diff --git a/zlib/CMakeLists.txt b/zlib/CMakeLists.txt index 43235b631f6..20ba4bc51e0 100755 --- a/zlib/CMakeLists.txt +++ b/zlib/CMakeLists.txt @@ -20,8 +20,6 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/zlib) -SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG") - SET(ZLIB_SOURCES adler32.c compress.c crc32.c crc32.h deflate.c deflate.h gzio.c infback.c inffast.c inffast.h inffixed.h inflate.c inflate.h inftrees.c inftrees.h trees.c trees.h uncompr.c zconf.h zlib.h zutil.c zutil.h) From dfdbc84585a154d056fdef3878172e383117354b Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 22 Dec 2009 19:09:15 +0300 Subject: [PATCH 130/212] A prerequisite patch for the fix for Bug#46224 "HANDLER statements within a transaction might lead to deadlocks". Introduce a notion of a sentinel to MDL_context. A sentinel is a ticket that separates all tickets in the context into two groups: before and after it. Currently we can have (and need) only one designated sentinel -- it separates all locks taken by LOCK TABLE or HANDLER statement, which must survive COMMIT and ROLLBACK and all other locks, which must be released at COMMIT or ROLLBACK. The tricky part is maintaining the sentinel up to date when someone release its corresponding ticket. This can happen, e.g. if someone issues DROP TABLE under LOCK TABLES (generally, see all calls to release_all_locks_for_name()). MDL_context::release_ticket() is modified to take care of it. ****** A fix and a test case for Bug#46224 "HANDLER statements within a transaction might lead to deadlocks". An attempt to mix HANDLER SQL statements, which are transaction- agnostic, an open multi-statement transaction, and DDL against the involved tables (in a concurrent connection) could lead to a deadlock. The deadlock would occur when HANDLER OPEN or HANDLER READ would have to wait on a conflicting metadata lock. If the connection that issued HANDLER statement also had other metadata locks (say, acquired in scope of a transaction), a classical deadlock situation of mutual wait could occur. Incompatible change: entering LOCK TABLES mode automatically closes all open HANDLERs in the current connection. Incompatible change: previously an attempt to wait on a lock in a connection that has an open HANDLER statement could wait indefinitely/deadlock. After this patch, an error ER_LOCK_DEADLOCK is produced. The idea of the fix is to merge thd->handler_mdl_context with the main mdl_context of the connection, used for transactional locks. This makes deadlock detection possible, since all waits with locks are "visible" and available to analysis in a single MDL context of the connection. Since HANDLER locks and transactional locks have a different life cycle -- HANDLERs are explicitly open and closed, and so are HANDLER locks, explicitly acquired and released, whereas transactional locks "accumulate" till the end of a transaction and are released only with COMMIT, ROLLBACK and ROLLBACK TO SAVEPOINT, a concept of "sentinel" was introduced to MDL_context. All locks, HANDLER and others, reside in the same linked list. However, a selected element of the list separates locks with different life cycle. HANDLER locks always reside at the end of the list, after the sentinel. Transactional locks are prepended to the beginning of the list, before the sentinel. Thus, ROLLBACK, COMMIT or ROLLBACK TO SAVEPOINT, only release those locks that reside before the sentinel. HANDLER locks must be released explicitly as part of HANDLER CLOSE statement, or an implicit close. The same approach with sentinel is also employed for LOCK TABLES locks. Since HANDLER and LOCK TABLES statement has never worked together, the implementation is made simple and only maintains one sentinel, which is used either for HANDLER locks, or for LOCK TABLES locks. mysql-test/include/handler.inc: Add test coverage for Bug#46224 "HANDLER statements within a transaction might lead to deadlocks". Extended HANDLER coverage to cover a mix of HANDLER, transactions and DDL statements. mysql-test/r/handler_innodb.result: Update results (Bug#46224). mysql-test/r/handler_myisam.result: Update results (Bug#46224). sql/lock.cc: Remove thd->some_tables_deleted, it's never used. sql/log_event.cc: No need to check for thd->locked_tables_mode, it's done inside release_transactional_locks(). sql/mdl.cc: Implement the concept of HANDLER and LOCK TABLES "sentinel". Implement a method to clone an acquired ticket. Do not return tickets beyond the sentinel when acquiring locks, create a copy. Remove methods to merge and backup MDL_context, they are now not used (Hurra!). This opens a path to a proper constructor and destructor of class MDL_context (to be done in a separate patch). Modify find_ticket() to provide information about where the ticket position is with regard to the sentinel. sql/mdl.h: Add declarations necessary for the implementation of the concept of "sentinel", a dedicated ticket separating transactional and non-transactional locks. sql/mysql_priv.h: Add mark_tmp_table_for_reuse() declaration, a function to "close" a single session (temporary) table. sql/sql_base.cc: Remove thd->some_tables_deleted. Modify deadlock-prevention asserts and deadlock detection heuristics to take into account that from now on HANDLER locks reside in the same locking context. Add broadcast_refresh() to mysql_notify_thread_having_shared_lock(): this is necessary for the case when a thread having a shared lock is asleep in tdc_wait_for_old_versions(). This situation is only possible with HANDLER t1 OPEN; FLUSH TABLE (since all over code paths that lead to tdc_wait_for_old_versions() always have an empty MDL_context). Previously the server would simply deadlock in this situation. sql/sql_class.cc: Remove now unused member "THD::some_tables_deleted". Move mysql_ha_cleanup() a few lines above in THD::cleanup() to make sure that all handlers are closed when it's time to destroy the MDL_context of this connection. Remove handler_mdl_context and handler_tables. sql/sql_class.h: Remove THD::handler_tables, THD::handler_mdl_context, THD::some_tables_deleted. sql/sql_handler.cc: Remove thd->handler_tables. Remove thd->handler_mdl_context. Rewrite mysql_ha_open() to have no special provision for MERGE tables, now that we don't have to manipulate with thd->handler_tables it's easy to do. Remove dead code. Fix a bug in mysql_ha_flush() when we would always flush a temporary HANDLER when mysql_ha_flush() is called (actually mysql_ha_flush() never needs to flush temporary tables). sql/sql_insert.cc: Update a comment, no more thd->some_tables_deleted. sql/sql_parse.cc: Implement an incompatible change: entering LOCK TABLES closes active HANDLERs, if any. Now that we have a sentinel, we don't need to check for thd->locked_tables_mode when releasing metadata locks in COMMIT/ROLLBACK. sql/sql_plist.h: Add new (now necessary) methods to the list class. sql/sql_prepare.cc: Make sure we don't release HANDLER locks when rollback to a savepoint, set to not keep locks taken at PREPARE. sql/sql_servers.cc: Update to a new signature of MDL_context::release_all_locks(). sql/sql_table.cc: Remove thd->some_tables_deleted. sql/transaction.cc: Add comments. Make sure rollback to (MDL) savepoint works under LOCK TABLES and with HANDLER tables. --- mysql-test/include/handler.inc | 617 ++++++++++++++++++++++++++++- mysql-test/r/handler_innodb.result | 588 ++++++++++++++++++++++++++- mysql-test/r/handler_myisam.result | 587 ++++++++++++++++++++++++++- sql/lock.cc | 17 +- sql/log_event.cc | 3 +- sql/mdl.cc | 308 +++++++++----- sql/mdl.h | 46 ++- sql/mysql_priv.h | 1 + sql/rpl_injector.cc | 3 +- sql/rpl_rli.cc | 4 +- sql/set_var.cc | 3 +- sql/slave.cc | 3 +- sql/sql_acl.cc | 6 +- sql/sql_base.cc | 164 +++++--- sql/sql_class.cc | 17 +- sql/sql_class.h | 21 +- sql/sql_handler.cc | 207 ++++------ sql/sql_insert.cc | 3 +- sql/sql_parse.cc | 47 +-- sql/sql_plist.h | 40 +- sql/sql_prepare.cc | 13 +- sql/sql_servers.cc | 3 +- sql/sql_table.cc | 13 +- sql/transaction.cc | 12 +- 24 files changed, 2335 insertions(+), 391 deletions(-) diff --git a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc index 8ff38c7e7a1..a9965f97926 100644 --- a/mysql-test/include/handler.inc +++ b/mysql-test/include/handler.inc @@ -561,14 +561,29 @@ let $wait_condition= --source include/wait_condition.inc connection default; --echo connection: default +--echo # +--echo # RENAME placed two pending locks and waits. +--echo # When HANDLER t2 OPEN does open_tables(), it calls +--echo # mysql_ha_flush(), which in turn closes the open HANDLER for t1. +--echo # RENAME TABLE gets unblocked. If it gets scheduled quickly +--echo # and manages to complete before open_tables() +--echo # of HANDLER t2 OPEN, open_tables() and therefore the whole +--echo # HANDLER t2 OPEN succeeds. Otherwise open_tables() +--echo # notices a pending or active exclusive metadata lock on t2 +--echo # and the whole HANDLER t2 OPEN fails with ER_LOCK_DEADLOCK +--echo # error. +--echo # +--error 0, ER_LOCK_DEADLOCK handler t2 open; -handler t2 read first; ---error ER_NO_SUCH_TABLE -handler t1 read next; -handler t1 close; +--error 0, ER_UNKNOWN_TABLE handler t2 close; +--echo connection: flush connection flush; reap; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--error ER_UNKNOWN_TABLE +handler t1 close; connection default; drop table t2; connection flush; @@ -748,3 +763,597 @@ USE information_schema; --error ER_WRONG_USAGE HANDLER COLUMNS OPEN; USE test; + +--echo # +--echo # Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL. +--echo # +--disable_warnings +drop table if exists t1, t2, t3; +--enable_warnings +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 (a int, key a (a)) select * from t1; +create temporary table t3 (a int, key a (a)) select * from t2; +handler t1 open; +handler t2 open; +handler t3 open; +--echo # +--echo # LOCK TABLES implicitly closes all handlers. +--echo # +lock table t3 read; +--echo # +--echo # No HANDLER sql is available under lock tables anyway. +--echo # +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t1 open; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t1 read next; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t2 close; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t3 open; +--echo # After UNLOCK TABLES no handlers are around, they were +--echo # implicitly closed. +unlock tables; +drop temporary table t3; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--error ER_UNKNOWN_TABLE +handler t2 close; +--error ER_UNKNOWN_TABLE +handler t3 read next; +--echo # +--echo # Other operations also implicitly close handler: +--echo # +--echo # TRUNCATE +--echo # +handler t1 open; +truncate table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +handler t1 open; +--echo # +--echo # CREATE TRIGGER +--echo # +create trigger t1_ai after insert on t1 for each row set @a=1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # DROP TRIGGER +--echo # +handler t1 open; +drop trigger t1_ai; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # ALTER TABLE +--echo # +handler t1 open; +alter table t1 add column b int; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # ANALYZE TABLE +--echo # +handler t1 open; +analyze table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # OPTIMIZE TABLE +--echo # +handler t1 open; +optimize table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # REPAIR TABLE +--echo # +handler t1 open; +repair table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # DROP TABLE, naturally. +--echo # +handler t1 open; +drop table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +create table t1 (a int, b int, key a (a)) select a from t2; +--echo # +--echo # RENAME TABLE, naturally +--echo # +handler t1 open; +rename table t1 to t3; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # CREATE TABLE (even with IF NOT EXISTS clause, +--echo # and the table exists). +--echo # +handler t2 open; +create table if not exists t2 (a int); +--error ER_UNKNOWN_TABLE +handler t2 read next; +rename table t3 to t1; +drop table t2; +--echo # +--echo # FLUSH TABLE doesn't close the table but loses the position +--echo # +handler t1 open; +handler t1 read a prev; +flush table t1; +handler t1 read a prev; +handler t1 close; +--echo # +--echo # FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE. +--echo # +handler t1 open; +handler t1 read a prev; +flush tables with read lock; +handler t1 read a prev; +handler t1 close; +unlock tables; +--echo # +--echo # Explore the effect of HANDLER locks on concurrent DDL +--echo # +handler t1 open; +--echo # Establishing auxiliary connections con1, con2, con3 +connect(con1, localhost, root,,); +connect(con2, localhost, root,,); +connect(con3, localhost, root,,); +--echo # --> connection con1; +connection con1; +--echo # Sending: +--send drop table t1 +--echo # We can't use connection 'default' as wait_condition will +--echo # autoclose handlers. +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop table t1' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1'; +--source include/wait_condition.inc +--echo # --> connection default +connection default; +handler t1 read a prev; +handler t1 read a prev; +handler t1 close; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop table t1'... +--reap +--echo # --> connection default +connection default; +--echo # +--echo # Explore the effect of HANDLER locks in parallel with SELECT +--echo # +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +handler t1 open; +handler t1 read a prev; +handler t1 read a prev; +handler t1 close; +--echo # --> connection con1; +connection con1; +--echo # Sending: +--send drop table t1 +--echo # --> connection con2 +connection con2; +--echo # Waiting for 'drop table t1' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1'; +--source include/wait_condition.inc +--echo # --> connection default +connection default; +--echo # We can still use the table, it's part of the transaction +select * from t1; +--echo # Such are the circumstances that t1 is a part of transaction, +--echo # thus we can reopen it in the handler +handler t1 open; +--echo # We can commit the transaction, it doesn't close the handler +--echo # and doesn't let DROP to proceed. +commit; +handler t1 read a prev; +handler t1 read a prev; +handler t1 read a prev; +handler t1 close; +--echo # --> connection con1 +connection con1; +--echo # Now drop can proceed +--echo # Reaping 'drop table t1'... +--reap +--echo # --> connection default +connection default; +--echo # +--echo # Demonstrate that HANDLER locks and transaction locks +--echo # reside in the same context, and we don't back-off +--echo # when have transaction or handler locks. +--echo # +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 (a int, key a (a)); +insert into t2 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +--echo # --> connection con1 +connection con1; +lock table t2 read; +--echo # --> connection con2 +connection con2; +--echo # Sending: +--send drop table t2 +--echo # --> connection con1 +connection con1; +--echo # Waiting for 'drop table t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--source include/wait_condition.inc +--echo # --> connection default +connection default; +--error ER_LOCK_DEADLOCK +handler t2 open; +--error ER_LOCK_DEADLOCK +select * from t2; +handler t1 open; +commit; +--error ER_LOCK_DEADLOCK +handler t2 open; +handler t1 close; +--echo # --> connection con1 +connection con1; +unlock tables; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'drop table t2'... +--reap +--echo # --> connection default +connection default; +handler t1 open; +handler t1 read a prev; +handler t1 close; +--echo # +--echo # Likewise, this doesn't require a multi-statement transaction. +--echo # ER_LOCK_DEADLOCK is also produced when we have an open +--echo # HANDLER and try to acquire locks for a single statement. +--echo # +create table t2 (a int, key a (a)); +handler t1 open; +--echo # --> connection con1 +connection con1; +lock tables t2 read; +--echo # --> connection con2 +connection con2; +--echo # Sending 'drop table t2'... +--send drop table t2 +--echo # --> connection con1 +connection con1; +--echo # Waiting for 'drop table t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--source include/wait_condition.inc +--echo # --> connection default +connection default; +--error ER_LOCK_DEADLOCK +select * from t2; +--echo # --> connection con1 +connection con1; +unlock tables; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'drop table t2'... +--reap +--echo # --> connection default +connection default; +handler t1 close; + +--echo # +--echo # ROLLBACK TO SAVEPOINT releases transactional locks, +--echo # but has no effect on open HANDLERs +--echo # +create table t2 like t1; +create table t3 like t1; +begin; +--echo # Have something before the savepoint +select * from t3; +savepoint sv; +handler t1 open; +handler t1 read a first; +handler t1 read a next; +select * from t2; +--echo # --> connection con1 +connection con1; +--echo # Sending: +--send drop table t1 +--echo # --> connection con2 +connection con2; +--echo # Sending: +--send drop table t2 +--echo # --> connection default +connection default; +--echo # Let DROP TABLE statements sync in. We must use +--echo # a separate connection for that, because otherwise SELECT +--echo # will auto-close the HANDLERs, becaues there are pending +--echo # exclusive locks against them. +--echo # --> connection con3 +connection con3; +--echo # Waiting for 'drop table t1' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1'; +--source include/wait_condition.inc +--echo # Waiting for 'drop table t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--source include/wait_condition.inc +--echo # Demonstrate that t2 lock was released and t2 was dropped +--echo # after ROLLBACK TO SAVEPOINT +--echo # --> connection default +connection default; +rollback to savepoint sv; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'drop table t2'... +--reap +--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +--echo # lock. +--echo # --> connection default +connection default; +handler t1 read a next; +handler t1 read a next; +--echo # Demonstrate that the drop will go through as soon as we close the +--echo # HANDLER +handler t1 close; +--echo # connection con1 +connection con1; +--echo # Reaping 'drop table t1'... +--reap +--echo # --> connection default +connection default; +commit; +drop table t3; +--echo # +--echo # A few special cases when using SAVEPOINT/ROLLBACK TO +--echo # SAVEPOINT and HANDLER. +--echo # +--echo # Show that rollback to the savepoint taken in the beginning +--echo # of the transaction doesn't release mdl lock on +--echo # the HANDLER that was opened later. +--echo # +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +begin; +savepoint sv; +handler t1 open; +handler t1 read a first; +handler t1 read a next; +select * from t2; +--echo # --> connection con1 +connection con1; +--echo # Sending: +--send drop table t1 +--echo # --> connection con2 +connection con2; +--echo # Sending: +--send drop table t2 +--echo # --> connection default +connection default; +--echo # Let DROP TABLE statements sync in. We must use +--echo # a separate connection for that, because otherwise SELECT +--echo # will auto-close the HANDLERs, becaues there are pending +--echo # exclusive locks against them. +--echo # --> connection con3 +connection con3; +--echo # Waiting for 'drop table t1' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1'; +--source include/wait_condition.inc +--echo # Waiting for 'drop table t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--source include/wait_condition.inc +--echo # Demonstrate that t2 lock was released and t2 was dropped +--echo # after ROLLBACK TO SAVEPOINT +--echo # --> connection default +connection default; +rollback to savepoint sv; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'drop table t2'... +--reap +--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +--echo # lock. +--echo # --> connection default +connection default; +handler t1 read a next; +handler t1 read a next; +--echo # Demonstrate that the drop will go through as soon as we close the +--echo # HANDLER +handler t1 close; +--echo # connection con1 +connection con1; +--echo # Reaping 'drop table t1'... +--reap +--echo # --> connection default +connection default; +commit; +--echo # +--echo # Show that rollback to the savepoint taken in the beginning +--echo # of the transaction works properly (no valgrind warnins, etc), +--echo # even though it's done after the HANDLER mdl lock that was there +--echo # at the beginning is released and added again. +--echo # +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +create table t3 like t1; +insert into t3 (a) select a from t1; +begin; +handler t1 open; +savepoint sv; +handler t1 read a first; +select * from t2; +handler t1 close; +handler t3 open; +handler t3 read a first; +rollback to savepoint sv; +--echo # --> connection con1 +connection con1; +drop table t1, t2; +--echo # Sending: +--send drop table t3 +--echo # Let DROP TABLE statement sync in. +--echo # --> connection con2 +connection con2; +--echo # Waiting for 'drop table t3' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t3'; +--source include/wait_condition.inc +--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +--echo # lock. +--echo # --> connection default +connection default; +handler t3 read a next; +--echo # Demonstrate that the drop will go through as soon as we close the +--echo # HANDLER +handler t3 close; +--echo # connection con1 +connection con1; +--echo # Reaping 'drop table t3'... +--reap +--echo # --> connection default +connection default; +commit; + +--echo # +--echo # If we have to wait on an exclusive locks while having +--echo # an open HANDLER, ER_LOCK_DEADLOCK is reported. +--echo # +create table t1 (a int, key a(a)); +create table t2 like t1; +handler t1 open; +--echo # --> connection con1 +connection con1; +lock table t2 read; +--echo # --> connection default +connection default; +--error ER_LOCK_DEADLOCK +drop table t2; +--error ER_LOCK_DEADLOCK +rename table t2 to t3; +--echo # Demonstrate that there is no deadlock with FLUSH TABLE, +--echo # even though it is waiting for the other table to go away +--echo # Sending: +--send flush table t2 +--echo # --> connection con2 +connection con2; +drop table t1; +--echo # --> connection con1 +connection con1; +unlock tables; +--echo # --> connection default +connection default; +--echo # Reaping 'flush table t2'... +--reap +drop table t2; + +--echo # +--echo # Bug #46224 HANDLER statements within a transaction might +--echo # lead to deadlocks +--echo # +create table t1 (a int, key a(a)); + +--echo # --> connection default +connection default; +begin; +select * from t1; +handler t1 open; + +--echo # --> connection con1 +connection con1; +lock tables t1 write; + +--echo # --> connection default +connection default; +--echo # Sending: +--send handler t1 read a next + +--echo # --> connection con1 +connection con1; +--echo # Waiting for 'handler t1 read a next' to get blocked... +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "handler t1 read a next"; +--source include/wait_condition.inc +--echo # Sending: +--send drop table t1 + +--echo # --> connection con2 +connection con2; +--echo # Waiting for 'drop table t1' to get blocked... +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "drop table t1"; +--source include/wait_condition.inc + +--echo # --> connection default +connection default; +--echo # Reaping 'handler t1 read a next'... +--error ER_LOCK_DEADLOCK +--reap +handler t1 close; +commit; + +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop table t1'... +--reap + +--echo # --> connection con1 +connection con1; +disconnect con1; +--source include/wait_until_disconnected.inc +--echo # --> connection con2 +connection con2; +disconnect con2; +--source include/wait_until_disconnected.inc +--echo # --> connection con3 +connection con3; +disconnect con3; +--source include/wait_until_disconnected.inc +connection default; + +--echo # +--echo # A temporary table test. +--echo # Check that we don't loose positions of HANDLER opened +--echo # against a temporary table. +--echo # +create table t1 (a int, b int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create temporary table t2 (a int, b int, key a (a)); +insert into t2 (a) select a from t1; +handler t1 open; +handler t1 read a next; +handler t2 open; +handler t2 read a next; +flush table t1; +handler t2 read a next; +--echo # Sic: the position is lost +handler t1 read a next; +select * from t1; +--echo # Sic: the position is not lost +handler t2 read a next; +--error ER_CANT_REOPEN_TABLE +select * from t2; +handler t2 read a next; +drop table t1; +drop temporary table t2; + +--echo # +--echo # A test for lock_table_names()/unlock_table_names() function. +--echo # It should work properly in presence of open HANDLER. +--echo # +create table t1 (a int, b int, key a (a)); +create table t2 like t1; +create table t3 like t1; +create table t4 like t1; +handler t1 open; +handler t2 open; +rename table t4 to t5, t3 to t4, t5 to t3; +handler t1 read first; +handler t2 read first; +drop table t1, t2, t3, t4; diff --git a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result index 5990b19062b..df948f3d0b6 100644 --- a/mysql-test/r/handler_innodb.result +++ b/mysql-test/r/handler_innodb.result @@ -570,13 +570,25 @@ connection: flush rename table t1 to t2;; connection: waiter connection: default +# +# RENAME placed two pending locks and waits. +# When HANDLER t2 OPEN does open_tables(), it calls +# mysql_ha_flush(), which in turn closes the open HANDLER for t1. +# RENAME TABLE gets unblocked. If it gets scheduled quickly +# and manages to complete before open_tables() +# of HANDLER t2 OPEN, open_tables() and therefore the whole +# HANDLER t2 OPEN succeeds. Otherwise open_tables() +# notices a pending or active exclusive metadata lock on t2 +# and the whole HANDLER t2 OPEN fails with ER_LOCK_DEADLOCK +# error. +# handler t2 open; -handler t2 read first; -c1 -handler t1 read next; -ERROR 42S02: Table 'test.t1' doesn't exist -handler t1 close; handler t2 close; +connection: flush +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t1 close; +ERROR 42S02: Unknown table 't1' in HANDLER drop table t2; drop table if exists t1; create temporary table t1 (a int, b char(1), key a(a), key b(a,b)); @@ -745,3 +757,569 @@ USE information_schema; HANDLER COLUMNS OPEN; ERROR HY000: Incorrect usage of HANDLER OPEN and information_schema USE test; +# +# Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL. +# +drop table if exists t1, t2, t3; +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 (a int, key a (a)) select * from t1; +create temporary table t3 (a int, key a (a)) select * from t2; +handler t1 open; +handler t2 open; +handler t3 open; +# +# LOCK TABLES implicitly closes all handlers. +# +lock table t3 read; +# +# No HANDLER sql is available under lock tables anyway. +# +handler t1 open; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t1 read next; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t2 close; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t3 open; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# After UNLOCK TABLES no handlers are around, they were +# implicitly closed. +unlock tables; +drop temporary table t3; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t2 close; +ERROR 42S02: Unknown table 't2' in HANDLER +handler t3 read next; +ERROR 42S02: Unknown table 't3' in HANDLER +# +# Other operations also implicitly close handler: +# +# TRUNCATE +# +handler t1 open; +truncate table t1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t1 open; +# +# CREATE TRIGGER +# +create trigger t1_ai after insert on t1 for each row set @a=1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# DROP TRIGGER +# +handler t1 open; +drop trigger t1_ai; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# ALTER TABLE +# +handler t1 open; +alter table t1 add column b int; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# ANALYZE TABLE +# +handler t1 open; +analyze table t1; +Table Op Msg_type Msg_text +test.t1 analyze status OK +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# OPTIMIZE TABLE +# +handler t1 open; +optimize table t1; +Table Op Msg_type Msg_text +test.t1 optimize note Table does not support optimize, doing recreate + analyze instead +test.t1 optimize status OK +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# REPAIR TABLE +# +handler t1 open; +repair table t1; +Table Op Msg_type Msg_text +test.t1 repair note The storage engine for the table doesn't support repair +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# DROP TABLE, naturally. +# +handler t1 open; +drop table t1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +create table t1 (a int, b int, key a (a)) select a from t2; +# +# RENAME TABLE, naturally +# +handler t1 open; +rename table t1 to t3; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# CREATE TABLE (even with IF NOT EXISTS clause, +# and the table exists). +# +handler t2 open; +create table if not exists t2 (a int); +Warnings: +Note 1050 Table 't2' already exists +handler t2 read next; +ERROR 42S02: Unknown table 't2' in HANDLER +rename table t3 to t1; +drop table t2; +# +# FLUSH TABLE doesn't close the table but loses the position +# +handler t1 open; +handler t1 read a prev; +b a +NULL 5 +flush table t1; +handler t1 read a prev; +b a +NULL 5 +handler t1 close; +# +# FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE. +# +handler t1 open; +handler t1 read a prev; +b a +NULL 5 +flush tables with read lock; +handler t1 read a prev; +b a +NULL 5 +handler t1 close; +unlock tables; +# +# Explore the effect of HANDLER locks on concurrent DDL +# +handler t1 open; +# Establishing auxiliary connections con1, con2, con3 +# --> connection con1; +# Sending: +drop table t1 ; +# We can't use connection 'default' as wait_condition will +# autoclose handlers. +# --> connection con2 +# Waitng for 'drop table t1' to get blocked... +# --> connection default +handler t1 read a prev; +b a +NULL 5 +handler t1 read a prev; +b a +NULL 4 +handler t1 close; +# --> connection con1 +# Reaping 'drop table t1'... +# --> connection default +# +# Explore the effect of HANDLER locks in parallel with SELECT +# +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +a +1 +2 +3 +4 +5 +handler t1 open; +handler t1 read a prev; +a +5 +handler t1 read a prev; +a +4 +handler t1 close; +# --> connection con1; +# Sending: +drop table t1 ; +# --> connection con2 +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# We can still use the table, it's part of the transaction +select * from t1; +a +1 +2 +3 +4 +5 +# Such are the circumstances that t1 is a part of transaction, +# thus we can reopen it in the handler +handler t1 open; +# We can commit the transaction, it doesn't close the handler +# and doesn't let DROP to proceed. +commit; +handler t1 read a prev; +a +5 +handler t1 read a prev; +a +4 +handler t1 read a prev; +a +3 +handler t1 close; +# --> connection con1 +# Now drop can proceed +# Reaping 'drop table t1'... +# --> connection default +# +# Demonstrate that HANDLER locks and transaction locks +# reside in the same context, and we don't back-off +# when have transaction or handler locks. +# +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 (a int, key a (a)); +insert into t2 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +a +1 +2 +3 +4 +5 +# --> connection con1 +lock table t2 read; +# --> connection con2 +# Sending: +drop table t2; +# --> connection con1 +# Waiting for 'drop table t2' to get blocked... +# --> connection default +handler t2 open; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +select * from t2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +handler t1 open; +commit; +handler t2 open; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +handler t1 close; +# --> connection con1 +unlock tables; +# --> connection con2 +# Reaping 'drop table t2'... +# --> connection default +handler t1 open; +handler t1 read a prev; +a +5 +handler t1 close; +# +# Likewise, this doesn't require a multi-statement transaction. +# ER_LOCK_DEADLOCK is also produced when we have an open +# HANDLER and try to acquire locks for a single statement. +# +create table t2 (a int, key a (a)); +handler t1 open; +# --> connection con1 +lock tables t2 read; +# --> connection con2 +# Sending 'drop table t2'... +drop table t2; +# --> connection con1 +# Waiting for 'drop table t2' to get blocked... +# --> connection default +select * from t2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# --> connection con1 +unlock tables; +# --> connection con2 +# Reaping 'drop table t2'... +# --> connection default +handler t1 close; +# +# ROLLBACK TO SAVEPOINT releases transactional locks, +# but has no effect on open HANDLERs +# +create table t2 like t1; +create table t3 like t1; +begin; +# Have something before the savepoint +select * from t3; +a +savepoint sv; +handler t1 open; +handler t1 read a first; +a +1 +handler t1 read a next; +a +2 +select * from t2; +a +# --> connection con1 +# Sending: +drop table t1; +# --> connection con2 +# Sending: +drop table t2; +# --> connection default +# Let DROP TABLE statements sync in. We must use +# a separate connection for that, because otherwise SELECT +# will auto-close the HANDLERs, becaues there are pending +# exclusive locks against them. +# --> connection con3 +# Waiting for 'drop table t1' to get blocked... +# Waiting for 'drop table t2' to get blocked... +# Demonstrate that t2 lock was released and t2 was dropped +# after ROLLBACK TO SAVEPOINT +# --> connection default +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop table t2'... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t1 read a next; +a +3 +handler t1 read a next; +a +4 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t1 close; +# connection con1 +# Reaping 'drop table t1'... +# --> connection default +commit; +drop table t3; +# +# A few special cases when using SAVEPOINT/ROLLBACK TO +# SAVEPOINT and HANDLER. +# +# Show that rollback to the savepoint taken in the beginning +# of the transaction doesn't release mdl lock on +# the HANDLER that was opened later. +# +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +begin; +savepoint sv; +handler t1 open; +handler t1 read a first; +a +1 +handler t1 read a next; +a +2 +select * from t2; +a +# --> connection con1 +# Sending: +drop table t1; +# --> connection con2 +# Sending: +drop table t2; +# --> connection default +# Let DROP TABLE statements sync in. We must use +# a separate connection for that, because otherwise SELECT +# will auto-close the HANDLERs, becaues there are pending +# exclusive locks against them. +# --> connection con3 +# Waiting for 'drop table t1' to get blocked... +# Waiting for 'drop table t2' to get blocked... +# Demonstrate that t2 lock was released and t2 was dropped +# after ROLLBACK TO SAVEPOINT +# --> connection default +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop table t2'... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t1 read a next; +a +3 +handler t1 read a next; +a +4 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t1 close; +# connection con1 +# Reaping 'drop table t1'... +# --> connection default +commit; +# +# Show that rollback to the savepoint taken in the beginning +# of the transaction works properly (no valgrind warnins, etc), +# even though it's done after the HANDLER mdl lock that was there +# at the beginning is released and added again. +# +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +create table t3 like t1; +insert into t3 (a) select a from t1; +begin; +handler t1 open; +savepoint sv; +handler t1 read a first; +a +1 +select * from t2; +a +handler t1 close; +handler t3 open; +handler t3 read a first; +a +1 +rollback to savepoint sv; +# --> connection con1 +drop table t1, t2; +# Sending: +drop table t3; +# Let DROP TABLE statement sync in. +# --> connection con2 +# Waiting for 'drop table t3' to get blocked... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t3 read a next; +a +2 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t3 close; +# connection con1 +# Reaping 'drop table t3'... +# --> connection default +commit; +# +# If we have to wait on an exclusive locks while having +# an open HANDLER, ER_LOCK_DEADLOCK is reported. +# +create table t1 (a int, key a(a)); +create table t2 like t1; +handler t1 open; +# --> connection con1 +lock table t2 read; +# --> connection default +drop table t2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +rename table t2 to t3; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Demonstrate that there is no deadlock with FLUSH TABLE, +# even though it is waiting for the other table to go away +# Sending: +flush table t2; +# --> connection con2 +drop table t1; +# --> connection con1 +unlock tables; +# --> connection default +# Reaping 'flush table t2'... +drop table t2; +# +# Bug #46224 HANDLER statements within a transaction might +# lead to deadlocks +# +create table t1 (a int, key a(a)); +# --> connection default +begin; +select * from t1; +a +handler t1 open; +# --> connection con1 +lock tables t1 write; +# --> connection default +# Sending: +handler t1 read a next; +# --> connection con1 +# Waiting for 'handler t1 read a next' to get blocked... +# Sending: +drop table t1; +# --> connection con2 +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# Reaping 'handler t1 read a next'... +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +handler t1 close; +commit; +# --> connection con1 +# Reaping 'drop table t1'... +# --> connection con1 +# --> connection con2 +# --> connection con3 +# +# A temporary table test. +# Check that we don't loose positions of HANDLER opened +# against a temporary table. +# +create table t1 (a int, b int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create temporary table t2 (a int, b int, key a (a)); +insert into t2 (a) select a from t1; +handler t1 open; +handler t1 read a next; +a b +1 NULL +handler t2 open; +handler t2 read a next; +a b +1 NULL +flush table t1; +handler t2 read a next; +a b +2 NULL +# Sic: the position is lost +handler t1 read a next; +a b +1 NULL +select * from t1; +a b +1 NULL +2 NULL +3 NULL +4 NULL +5 NULL +# Sic: the position is not lost +handler t2 read a next; +a b +3 NULL +select * from t2; +ERROR HY000: Can't reopen table: 't2' +handler t2 read a next; +a b +4 NULL +drop table t1; +drop temporary table t2; +# +# A test for lock_table_names()/unlock_table_names() function. +# It should work properly in presence of open HANDLER. +# +create table t1 (a int, b int, key a (a)); +create table t2 like t1; +create table t3 like t1; +create table t4 like t1; +handler t1 open; +handler t2 open; +rename table t4 to t5, t3 to t4, t5 to t3; +handler t1 read first; +a b +handler t2 read first; +a b +drop table t1, t2, t3, t4; diff --git a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result index f7b0ff2e04e..4b287e6560b 100644 --- a/mysql-test/r/handler_myisam.result +++ b/mysql-test/r/handler_myisam.result @@ -569,13 +569,25 @@ connection: flush rename table t1 to t2;; connection: waiter connection: default +# +# RENAME placed two pending locks and waits. +# When HANDLER t2 OPEN does open_tables(), it calls +# mysql_ha_flush(), which in turn closes the open HANDLER for t1. +# RENAME TABLE gets unblocked. If it gets scheduled quickly +# and manages to complete before open_tables() +# of HANDLER t2 OPEN, open_tables() and therefore the whole +# HANDLER t2 OPEN succeeds. Otherwise open_tables() +# notices a pending or active exclusive metadata lock on t2 +# and the whole HANDLER t2 OPEN fails with ER_LOCK_DEADLOCK +# error. +# handler t2 open; -handler t2 read first; -c1 -handler t1 read next; -ERROR 42S02: Table 'test.t1' doesn't exist -handler t1 close; handler t2 close; +connection: flush +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t1 close; +ERROR 42S02: Unknown table 't1' in HANDLER drop table t2; drop table if exists t1; create temporary table t1 (a int, b char(1), key a(a), key b(a,b)); @@ -744,6 +756,571 @@ HANDLER COLUMNS OPEN; ERROR HY000: Incorrect usage of HANDLER OPEN and information_schema USE test; # +# Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL. +# +drop table if exists t1, t2, t3; +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 (a int, key a (a)) select * from t1; +create temporary table t3 (a int, key a (a)) select * from t2; +handler t1 open; +handler t2 open; +handler t3 open; +# +# LOCK TABLES implicitly closes all handlers. +# +lock table t3 read; +# +# No HANDLER sql is available under lock tables anyway. +# +handler t1 open; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t1 read next; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t2 close; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t3 open; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# After UNLOCK TABLES no handlers are around, they were +# implicitly closed. +unlock tables; +drop temporary table t3; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t2 close; +ERROR 42S02: Unknown table 't2' in HANDLER +handler t3 read next; +ERROR 42S02: Unknown table 't3' in HANDLER +# +# Other operations also implicitly close handler: +# +# TRUNCATE +# +handler t1 open; +truncate table t1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t1 open; +# +# CREATE TRIGGER +# +create trigger t1_ai after insert on t1 for each row set @a=1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# DROP TRIGGER +# +handler t1 open; +drop trigger t1_ai; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# ALTER TABLE +# +handler t1 open; +alter table t1 add column b int; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# ANALYZE TABLE +# +handler t1 open; +analyze table t1; +Table Op Msg_type Msg_text +test.t1 analyze status Table is already up to date +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# OPTIMIZE TABLE +# +handler t1 open; +optimize table t1; +Table Op Msg_type Msg_text +test.t1 optimize status OK +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# REPAIR TABLE +# +handler t1 open; +repair table t1; +Table Op Msg_type Msg_text +test.t1 repair status OK +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# DROP TABLE, naturally. +# +handler t1 open; +drop table t1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +create table t1 (a int, b int, key a (a)) select a from t2; +# +# RENAME TABLE, naturally +# +handler t1 open; +rename table t1 to t3; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# CREATE TABLE (even with IF NOT EXISTS clause, +# and the table exists). +# +handler t2 open; +create table if not exists t2 (a int); +Warnings: +Note 1050 Table 't2' already exists +handler t2 read next; +ERROR 42S02: Unknown table 't2' in HANDLER +rename table t3 to t1; +drop table t2; +# +# FLUSH TABLE doesn't close the table but loses the position +# +handler t1 open; +handler t1 read a prev; +b a +NULL 5 +flush table t1; +handler t1 read a prev; +b a +NULL 5 +handler t1 close; +# +# FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE. +# +handler t1 open; +handler t1 read a prev; +b a +NULL 5 +flush tables with read lock; +handler t1 read a prev; +b a +NULL 5 +handler t1 close; +unlock tables; +# +# Explore the effect of HANDLER locks on concurrent DDL +# +handler t1 open; +# Establishing auxiliary connections con1, con2, con3 +# --> connection con1; +# Sending: +drop table t1 ; +# We can't use connection 'default' as wait_condition will +# autoclose handlers. +# --> connection con2 +# Waitng for 'drop table t1' to get blocked... +# --> connection default +handler t1 read a prev; +b a +NULL 5 +handler t1 read a prev; +b a +NULL 4 +handler t1 close; +# --> connection con1 +# Reaping 'drop table t1'... +# --> connection default +# +# Explore the effect of HANDLER locks in parallel with SELECT +# +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +a +1 +2 +3 +4 +5 +handler t1 open; +handler t1 read a prev; +a +5 +handler t1 read a prev; +a +4 +handler t1 close; +# --> connection con1; +# Sending: +drop table t1 ; +# --> connection con2 +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# We can still use the table, it's part of the transaction +select * from t1; +a +1 +2 +3 +4 +5 +# Such are the circumstances that t1 is a part of transaction, +# thus we can reopen it in the handler +handler t1 open; +# We can commit the transaction, it doesn't close the handler +# and doesn't let DROP to proceed. +commit; +handler t1 read a prev; +a +5 +handler t1 read a prev; +a +4 +handler t1 read a prev; +a +3 +handler t1 close; +# --> connection con1 +# Now drop can proceed +# Reaping 'drop table t1'... +# --> connection default +# +# Demonstrate that HANDLER locks and transaction locks +# reside in the same context, and we don't back-off +# when have transaction or handler locks. +# +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 (a int, key a (a)); +insert into t2 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +a +1 +2 +3 +4 +5 +# --> connection con1 +lock table t2 read; +# --> connection con2 +# Sending: +drop table t2; +# --> connection con1 +# Waiting for 'drop table t2' to get blocked... +# --> connection default +handler t2 open; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +select * from t2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +handler t1 open; +commit; +handler t2 open; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +handler t1 close; +# --> connection con1 +unlock tables; +# --> connection con2 +# Reaping 'drop table t2'... +# --> connection default +handler t1 open; +handler t1 read a prev; +a +5 +handler t1 close; +# +# Likewise, this doesn't require a multi-statement transaction. +# ER_LOCK_DEADLOCK is also produced when we have an open +# HANDLER and try to acquire locks for a single statement. +# +create table t2 (a int, key a (a)); +handler t1 open; +# --> connection con1 +lock tables t2 read; +# --> connection con2 +# Sending 'drop table t2'... +drop table t2; +# --> connection con1 +# Waiting for 'drop table t2' to get blocked... +# --> connection default +select * from t2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# --> connection con1 +unlock tables; +# --> connection con2 +# Reaping 'drop table t2'... +# --> connection default +handler t1 close; +# +# ROLLBACK TO SAVEPOINT releases transactional locks, +# but has no effect on open HANDLERs +# +create table t2 like t1; +create table t3 like t1; +begin; +# Have something before the savepoint +select * from t3; +a +savepoint sv; +handler t1 open; +handler t1 read a first; +a +1 +handler t1 read a next; +a +2 +select * from t2; +a +# --> connection con1 +# Sending: +drop table t1; +# --> connection con2 +# Sending: +drop table t2; +# --> connection default +# Let DROP TABLE statements sync in. We must use +# a separate connection for that, because otherwise SELECT +# will auto-close the HANDLERs, becaues there are pending +# exclusive locks against them. +# --> connection con3 +# Waiting for 'drop table t1' to get blocked... +# Waiting for 'drop table t2' to get blocked... +# Demonstrate that t2 lock was released and t2 was dropped +# after ROLLBACK TO SAVEPOINT +# --> connection default +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop table t2'... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t1 read a next; +a +3 +handler t1 read a next; +a +4 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t1 close; +# connection con1 +# Reaping 'drop table t1'... +# --> connection default +commit; +drop table t3; +# +# A few special cases when using SAVEPOINT/ROLLBACK TO +# SAVEPOINT and HANDLER. +# +# Show that rollback to the savepoint taken in the beginning +# of the transaction doesn't release mdl lock on +# the HANDLER that was opened later. +# +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +begin; +savepoint sv; +handler t1 open; +handler t1 read a first; +a +1 +handler t1 read a next; +a +2 +select * from t2; +a +# --> connection con1 +# Sending: +drop table t1; +# --> connection con2 +# Sending: +drop table t2; +# --> connection default +# Let DROP TABLE statements sync in. We must use +# a separate connection for that, because otherwise SELECT +# will auto-close the HANDLERs, becaues there are pending +# exclusive locks against them. +# --> connection con3 +# Waiting for 'drop table t1' to get blocked... +# Waiting for 'drop table t2' to get blocked... +# Demonstrate that t2 lock was released and t2 was dropped +# after ROLLBACK TO SAVEPOINT +# --> connection default +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop table t2'... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t1 read a next; +a +3 +handler t1 read a next; +a +4 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t1 close; +# connection con1 +# Reaping 'drop table t1'... +# --> connection default +commit; +# +# Show that rollback to the savepoint taken in the beginning +# of the transaction works properly (no valgrind warnins, etc), +# even though it's done after the HANDLER mdl lock that was there +# at the beginning is released and added again. +# +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +create table t3 like t1; +insert into t3 (a) select a from t1; +begin; +handler t1 open; +savepoint sv; +handler t1 read a first; +a +1 +select * from t2; +a +handler t1 close; +handler t3 open; +handler t3 read a first; +a +1 +rollback to savepoint sv; +# --> connection con1 +drop table t1, t2; +# Sending: +drop table t3; +# Let DROP TABLE statement sync in. +# --> connection con2 +# Waiting for 'drop table t3' to get blocked... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t3 read a next; +a +2 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t3 close; +# connection con1 +# Reaping 'drop table t3'... +# --> connection default +commit; +# +# If we have to wait on an exclusive locks while having +# an open HANDLER, ER_LOCK_DEADLOCK is reported. +# +create table t1 (a int, key a(a)); +create table t2 like t1; +handler t1 open; +# --> connection con1 +lock table t2 read; +# --> connection default +drop table t2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +rename table t2 to t3; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Demonstrate that there is no deadlock with FLUSH TABLE, +# even though it is waiting for the other table to go away +# Sending: +flush table t2; +# --> connection con2 +drop table t1; +# --> connection con1 +unlock tables; +# --> connection default +# Reaping 'flush table t2'... +drop table t2; +# +# Bug #46224 HANDLER statements within a transaction might +# lead to deadlocks +# +create table t1 (a int, key a(a)); +# --> connection default +begin; +select * from t1; +a +handler t1 open; +# --> connection con1 +lock tables t1 write; +# --> connection default +# Sending: +handler t1 read a next; +# --> connection con1 +# Waiting for 'handler t1 read a next' to get blocked... +# Sending: +drop table t1; +# --> connection con2 +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# Reaping 'handler t1 read a next'... +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +handler t1 close; +commit; +# --> connection con1 +# Reaping 'drop table t1'... +# --> connection con1 +# --> connection con2 +# --> connection con3 +# +# A temporary table test. +# Check that we don't loose positions of HANDLER opened +# against a temporary table. +# +create table t1 (a int, b int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create temporary table t2 (a int, b int, key a (a)); +insert into t2 (a) select a from t1; +handler t1 open; +handler t1 read a next; +a b +1 NULL +handler t2 open; +handler t2 read a next; +a b +1 NULL +flush table t1; +handler t2 read a next; +a b +2 NULL +# Sic: the position is lost +handler t1 read a next; +a b +1 NULL +select * from t1; +a b +1 NULL +2 NULL +3 NULL +4 NULL +5 NULL +# Sic: the position is not lost +handler t2 read a next; +a b +3 NULL +select * from t2; +ERROR HY000: Can't reopen table: 't2' +handler t2 read a next; +a b +4 NULL +drop table t1; +drop temporary table t2; +# +# A test for lock_table_names()/unlock_table_names() function. +# It should work properly in presence of open HANDLER. +# +create table t1 (a int, b int, key a (a)); +create table t2 like t1; +create table t3 like t1; +create table t4 like t1; +handler t1 open; +handler t2 open; +rename table t4 to t5, t3 to t4, t5 to t3; +handler t1 read first; +a b +handler t2 read first; +a b +drop table t1, t2, t3, t4; +# # BUG #46456: HANDLER OPEN + TRUNCATE + DROP (temporary) TABLE, crash # CREATE TABLE t1 AS SELECT 1 AS f1; diff --git a/sql/lock.cc b/sql/lock.cc index d414d7d6ae2..31773585bff 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -335,23 +335,12 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, preserved. */ reset_lock_data(sql_lock); - thd->some_tables_deleted=1; // Try again sql_lock->lock_count= 0; // Locks are already freed // Fall through: unlock, reset lock data, free and retry } - else if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH)) + else { - /* - Success and nobody set thd->some_tables_deleted to force reopen - or we were called with MYSQL_LOCK_IGNORE_FLUSH so such attempts - should be ignored. - */ - break; - } - else if (!thd->open_tables) - { - // Only using temporary tables, no need to unlock - thd->some_tables_deleted=0; + /* Success */ break; } thd_proc_info(thd, 0); @@ -986,7 +975,7 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list) void unlock_table_names(THD *thd) { DBUG_ENTER("unlock_table_names"); - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); DBUG_VOID_RETURN; } diff --git a/sql/log_event.cc b/sql/log_event.cc index 46d016b2c15..8afd243da63 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -5321,8 +5321,7 @@ int Xid_log_event::do_apply_event(Relay_log_info const *rli) if (!(res= trans_commit(thd))) { close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); } return res; } diff --git a/sql/mdl.cc b/sql/mdl.cc index 6187d4515a3..40074879e21 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -187,6 +187,7 @@ void MDL_context::init(THD *thd_arg) { m_has_global_shared_lock= FALSE; m_thd= thd_arg; + m_lt_or_ha_sentinel= NULL; /* FIXME: In reset_n_backup_open_tables_state, we abuse "init" as a reset, i.e. call it on an already @@ -217,76 +218,6 @@ void MDL_context::destroy() } -/** - Backup and reset state of meta-data locking context. - - mdl_context_backup_and_reset(), mdl_context_restore() and - mdl_context_merge() are used by HANDLER implementation which - needs to open table for new HANDLER independently of already - open HANDLERs and add this table/metadata lock to the set of - tables open/metadata locks for HANDLERs afterwards. -*/ - -void MDL_context::backup_and_reset(MDL_context *backup) -{ - DBUG_ASSERT(backup->m_tickets.is_empty()); - - m_tickets.swap(backup->m_tickets); - - backup->m_has_global_shared_lock= m_has_global_shared_lock; - /* - When the main context is swapped out, one can not take - the global shared lock, and one can not rely on it: - the functionality in this mode is reduced, since it exists as - a temporary hack to support ad-hoc opening of system tables. - */ - m_has_global_shared_lock= FALSE; -} - - -/** - Restore state of meta-data locking context from backup. -*/ - -void MDL_context::restore_from_backup(MDL_context *backup) -{ - DBUG_ASSERT(m_tickets.is_empty()); - DBUG_ASSERT(m_has_global_shared_lock == FALSE); - - m_tickets.swap(backup->m_tickets); - m_has_global_shared_lock= backup->m_has_global_shared_lock; -} - - -/** - Merge meta-data locks from one context into another. -*/ - -void MDL_context::merge(MDL_context *src) -{ - MDL_ticket *ticket; - - DBUG_ASSERT(m_thd == src->m_thd); - - if (!src->m_tickets.is_empty()) - { - Ticket_iterator it(src->m_tickets); - while ((ticket= it++)) - { - DBUG_ASSERT(ticket->m_ctx); - ticket->m_ctx= this; - m_tickets.push_front(ticket); - } - src->m_tickets.empty(); - } - /* - MDL_context::merge() is a hack used in one place only: to open - an SQL handler. We never acquire the global shared lock there. - */ - DBUG_ASSERT(! src->m_has_global_shared_lock); -} - - /** Initialize a lock request. @@ -606,7 +537,7 @@ MDL_lock::can_grant_lock(const MDL_context *requestor_ctx, enum_mdl_type type_ar if (waiting.is_empty() || type_arg == MDL_SHARED_HIGH_PRIO) can_grant= TRUE; } - else if (granted.head()->get_ctx() == requestor_ctx) + else if (granted.front()->get_ctx() == requestor_ctx) { /* When exclusive lock comes from the same context we can satisfy our @@ -659,20 +590,31 @@ MDL_lock::can_grant_lock(const MDL_context *requestor_ctx, enum_mdl_type type_ar /** Check whether the context already holds a compatible lock ticket on an object. + Start searching the transactional locks. If not + found in the list of transactional locks, look at LOCK TABLES + and HANDLER locks. @param mdl_request Lock request object for lock to be acquired + @param[out] is_lt_or_ha Did we pass beyond m_lt_or_ha_sentinel while + searching for ticket? @return A pointer to the lock ticket for the object or NULL otherwise. */ MDL_ticket * -MDL_context::find_ticket(MDL_request *mdl_request) +MDL_context::find_ticket(MDL_request *mdl_request, + bool *is_lt_or_ha) { MDL_ticket *ticket; Ticket_iterator it(m_tickets); + *is_lt_or_ha= FALSE; + while ((ticket= it++)) { + if (ticket == m_lt_or_ha_sentinel) + *is_lt_or_ha= TRUE; + if (mdl_request->type == ticket->m_type && mdl_request->key.is_equal(&ticket->m_lock->key)) break; @@ -709,6 +651,7 @@ MDL_context::try_acquire_shared_lock(MDL_request *mdl_request) MDL_lock *lock; MDL_key *key= &mdl_request->key; MDL_ticket *ticket; + bool is_lt_or_ha; DBUG_ASSERT(mdl_request->is_shared() && mdl_request->ticket == NULL); @@ -727,12 +670,35 @@ MDL_context::try_acquire_shared_lock(MDL_request *mdl_request) Check whether the context already holds a shared lock on the object, and if so, grant the request. */ - if ((ticket= find_ticket(mdl_request))) + if ((ticket= find_ticket(mdl_request, &is_lt_or_ha))) { DBUG_ASSERT(ticket->m_state == MDL_ACQUIRED); /* Only shared locks can be recursive. */ DBUG_ASSERT(ticket->is_shared()); + /* + If the request is for a transactional lock, and we found + a transactional lock, just reuse the found ticket. + + It's possible that we found a transactional lock, + but the request is for a HANDLER lock. In that case HANDLER + code will clone the ticket (see below why it's needed). + + If the request is for a transactional lock, and we found + a HANDLER lock, create a copy, to make sure that when user + does HANDLER CLOSE, the transactional lock is not released. + + If the request is for a handler lock, and we found a + HANDLER lock, also do the clone. HANDLER CLOSE for one alias + should not release the lock on the table HANDLER opened through + a different alias. + */ mdl_request->ticket= ticket; + if (is_lt_or_ha && clone_ticket(mdl_request)) + { + /* Clone failed. */ + mdl_request->ticket= NULL; + return TRUE; + } return FALSE; } @@ -786,6 +752,46 @@ MDL_context::try_acquire_shared_lock(MDL_request *mdl_request) } +/** + Create a copy of a granted ticket. + This is used to make sure that HANDLER ticket + is never shared with a ticket that belongs to + a transaction, so that when we HANDLER CLOSE, + we don't release a transactional ticket, and + vice versa -- when we COMMIT, we don't mistakenly + release a ticket for an open HANDLER. + + @retval TRUE Out of memory. + @retval FALSE Success. +*/ + +bool +MDL_context::clone_ticket(MDL_request *mdl_request) +{ + MDL_ticket *ticket; + + safe_mutex_assert_not_owner(&LOCK_open); + /* Only used for HANDLER. */ + DBUG_ASSERT(mdl_request->ticket && mdl_request->ticket->is_shared()); + + if (!(ticket= MDL_ticket::create(this, mdl_request->type))) + return TRUE; + + ticket->m_state= MDL_ACQUIRED; + ticket->m_lock= mdl_request->ticket->m_lock; + mdl_request->ticket= ticket; + + pthread_mutex_lock(&LOCK_mdl); + ticket->m_lock->granted.push_front(ticket); + if (mdl_request->type == MDL_SHARED_UPGRADABLE) + global_lock.active_intention_exclusive++; + pthread_mutex_unlock(&LOCK_mdl); + + m_tickets.push_front(ticket); + + return FALSE; +} + /** Notify a thread holding a shared metadata lock which conflicts with a pending exclusive lock. @@ -850,7 +856,9 @@ bool MDL_context::acquire_exclusive_locks(MDL_request_list *mdl_requests) safe_mutex_assert_not_owner(&LOCK_open); /* Exclusive locks must always be acquired first, all at once. */ - DBUG_ASSERT(! has_locks()); + DBUG_ASSERT(! has_locks() || + (m_lt_or_ha_sentinel && + m_tickets.front() == m_lt_or_ha_sentinel)); if (m_has_global_shared_lock) { @@ -924,6 +932,17 @@ bool MDL_context::acquire_exclusive_locks(MDL_request_list *mdl_requests) if (!mdl_request) break; + if (m_lt_or_ha_sentinel) + { + /* + We're about to start waiting. Don't do it if we have + HANDLER locks (we can't have any other locks here). + Waiting with locks may lead to a deadlock. + */ + my_error(ER_LOCK_DEADLOCK, MYF(0)); + goto err; + } + /* There is a shared or exclusive lock on the object. */ DEBUG_SYNC(m_thd, "mdl_acquire_exclusive_locks_wait"); @@ -1041,6 +1060,30 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() if (m_lock->can_grant_lock(m_ctx, MDL_EXCLUSIVE, TRUE)) break; + /* + If m_ctx->lt_or_ha_sentinel(), and this sentinel is for HANDLER, + we can deadlock. However, HANDLER is not allowed under + LOCK TABLES, and apart from LOCK TABLES there are only + two cases of lock upgrade: ALTER TABLE and CREATE/DROP + TRIGGER (*). This leaves us with the following scenario + for deadlock: + + connection 1 connection 2 + handler t1 open; handler t2 open; + alter table t2 ... alter table t1 ... + + This scenario is quite remote, since ALTER + (and CREATE/DROP TRIGGER) performs mysql_ha_flush() in + the beginning, and thus closes open HANDLERS against which + there is a pending lock upgrade. Still, two ALTER statements + can interleave and not notice each other's pending lock + (e.g. if both upgrade their locks at the same time). + This, however, is quite unlikely, so we do nothing to + address it. + + (*) There is no requirement to upgrade lock in + CREATE/DROP TRIGGER, it's used there just for convenience. + */ bool signalled= FALSE; MDL_ticket *conflicting_ticket; MDL_lock::Ticket_iterator it(m_lock->granted); @@ -1280,6 +1323,9 @@ void MDL_context::release_ticket(MDL_ticket *ticket) safe_mutex_assert_owner(&LOCK_mdl); + if (ticket == m_lt_or_ha_sentinel) + m_lt_or_ha_sentinel= ++Ticket_list::Iterator(m_tickets, ticket); + m_tickets.remove(ticket); switch (ticket->m_type) @@ -1317,18 +1363,27 @@ void MDL_context::release_ticket(MDL_ticket *ticket) /** - Release all locks associated with the context. + Release all locks associated with the context. If the sentinel + is not NULL, do not release locks stored in the list after and + including the sentinel. - This function is used to back off in case of a lock conflict. - It is also used to release shared locks in the end of an SQL - statement. + Transactional locks are added to the beginning of the list, i.e. + stored in reverse temporal order. This allows to employ this + function to: + - back off in case of a lock conflict. + - release all locks in the end of a transaction + - rollback to a savepoint. + + The sentinel semantics is used to support LOCK TABLES + mode and HANDLER statements: locks taken by these statements + survive COMMIT, ROLLBACK, ROLLBACK TO SAVEPOINT. */ -void MDL_context::release_all_locks() +void MDL_context::release_locks_stored_before(MDL_ticket *sentinel) { MDL_ticket *ticket; Ticket_iterator it(m_tickets); - DBUG_ENTER("MDL_context::release_all_locks"); + DBUG_ENTER("MDL_context::release_locks_stored_before"); safe_mutex_assert_not_owner(&LOCK_open); @@ -1336,7 +1391,7 @@ void MDL_context::release_all_locks() DBUG_VOID_RETURN; pthread_mutex_lock(&LOCK_mdl); - while ((ticket= it++)) + while ((ticket= it++) && ticket != sentinel) { DBUG_PRINT("info", ("found lock to release ticket=%p", ticket)); release_ticket(ticket); @@ -1345,8 +1400,6 @@ void MDL_context::release_all_locks() pthread_cond_broadcast(&COND_mdl); pthread_mutex_unlock(&LOCK_mdl); - m_tickets.empty(); - DBUG_VOID_RETURN; } @@ -1452,8 +1505,9 @@ MDL_context::is_exclusive_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, const char *name) { MDL_request mdl_request; + bool is_lt_or_ha_unused; mdl_request.init(mdl_namespace, db, name, MDL_EXCLUSIVE); - MDL_ticket *ticket= find_ticket(&mdl_request); + MDL_ticket *ticket= find_ticket(&mdl_request, &is_lt_or_ha_unused); DBUG_ASSERT(ticket == NULL || ticket->m_state == MDL_ACQUIRED); @@ -1593,19 +1647,87 @@ void *MDL_ticket::get_cached_object() void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint) { - MDL_ticket *ticket; - Ticket_iterator it(m_tickets); DBUG_ENTER("MDL_context::rollback_to_savepoint"); - while ((ticket= it++)) - { - /* Stop when lock was acquired before this savepoint. */ - if (ticket == mdl_savepoint) - break; - release_lock(ticket); - } + /* If savepoint is NULL, it is from the start of the transaction. */ + release_locks_stored_before(mdl_savepoint ? + mdl_savepoint : m_lt_or_ha_sentinel); DBUG_VOID_RETURN; } +/** + Release locks acquired by normal statements (SELECT, UPDATE, + DELETE, etc) in the course of a transaction. Do not release + HANDLER locks, if there are any. + + This method is used at the end of a transaction, in + implementation of COMMIT (implicit or explicit) and ROLLBACK. +*/ + +void MDL_context::release_transactional_locks() +{ + DBUG_ENTER("MDL_context::release_transactional_locks"); + release_locks_stored_before(m_lt_or_ha_sentinel); + DBUG_VOID_RETURN; +} + + +/** + Does this savepoint have this lock? + + @retval TRUE The ticket is older than the savepoint and + is not LT or HA ticket. Thus it belongs to + the savepoint. + @retval FALSE The ticket is newer than the savepoint + or is an LT or HA ticket. +*/ + +bool MDL_context::has_lock(MDL_ticket *mdl_savepoint, + MDL_ticket *mdl_ticket) +{ + MDL_ticket *ticket; + MDL_context::Ticket_iterator it(m_tickets); + bool found_savepoint= FALSE; + + while ((ticket= it++) && ticket != m_lt_or_ha_sentinel) + { + /* + First met the savepoint. The ticket must be + somewhere after it. + */ + if (ticket == mdl_savepoint) + found_savepoint= TRUE; + /* + Met the ticket. If we haven't yet met the savepoint, + the ticket is newer than the savepoint. + */ + if (ticket == mdl_ticket) + return found_savepoint; + } + /* Reached m_lt_or_ha_sentinel. The ticket must be an LT or HA ticket. */ + return FALSE; +} + + +/** + Rearrange the ticket to reside in the part of the list that's + beyond m_lt_or_ha_sentinel. This effectively changes the ticket + life cycle, from automatic to manual: i.e. the ticket is no + longer released by MDL_context::release_transactional_locks() or + MDL_context::rollback_to_savepoint(), it must be released manually. +*/ + +void MDL_context::move_ticket_after_lt_or_ha_sentinel(MDL_ticket *mdl_ticket) +{ + m_tickets.remove(mdl_ticket); + if (m_lt_or_ha_sentinel == NULL) + { + m_lt_or_ha_sentinel= mdl_ticket; + /* sic: linear from the number of transactional tickets acquired so-far! */ + m_tickets.push_back(mdl_ticket); + } + else + m_tickets.insert_after(m_lt_or_ha_sentinel, mdl_ticket); +} diff --git a/sql/mdl.h b/sql/mdl.h index 2758bd3a8e6..e85f1232ff9 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -327,19 +327,15 @@ public: void init(THD *thd); void destroy(); - void backup_and_reset(MDL_context *backup); - void restore_from_backup(MDL_context *backup); - void merge(MDL_context *source); - bool try_acquire_shared_lock(MDL_request *mdl_request); bool acquire_exclusive_lock(MDL_request *mdl_request); bool acquire_exclusive_locks(MDL_request_list *requests); bool try_acquire_exclusive_lock(MDL_request *mdl_request); bool acquire_global_shared_lock(); + bool clone_ticket(MDL_request *mdl_request); bool wait_for_locks(MDL_request_list *requests); - void release_all_locks(); void release_all_locks_for_name(MDL_ticket *ticket); void release_lock(MDL_ticket *ticket); void release_global_shared_lock(); @@ -350,26 +346,60 @@ public: bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, const char *name); + + bool has_lock(MDL_ticket *mdl_savepoint, MDL_ticket *mdl_ticket); + inline bool has_locks() const { return !m_tickets.is_empty(); } - inline MDL_ticket *mdl_savepoint() + MDL_ticket *mdl_savepoint() { - return m_tickets.head(); + /* + NULL savepoint represents the start of the transaction. + Checking for m_lt_or_ha_sentinel also makes sure we never + return a pointer to HANDLER ticket as a savepoint. + */ + return m_tickets.front() == m_lt_or_ha_sentinel ? NULL : m_tickets.front(); } + void set_lt_or_ha_sentinel() + { + DBUG_ASSERT(m_lt_or_ha_sentinel == NULL); + m_lt_or_ha_sentinel= mdl_savepoint(); + } + MDL_ticket *lt_or_ha_sentinel() const { return m_lt_or_ha_sentinel; } + + void clear_lt_or_ha_sentinel() + { + m_lt_or_ha_sentinel= NULL; + } + void move_ticket_after_lt_or_ha_sentinel(MDL_ticket *mdl_ticket); + + void release_transactional_locks(); void rollback_to_savepoint(MDL_ticket *mdl_savepoint); inline THD *get_thd() const { return m_thd; } private: Ticket_list m_tickets; bool m_has_global_shared_lock; + /** + This member has two uses: + 1) When entering LOCK TABLES mode, remember the last taken + metadata lock. COMMIT/ROLLBACK must preserve these metadata + locks. + 2) When we have an open HANDLER tables, store the position + in the list beyond which we keep locks for HANDLER tables. + COMMIT/ROLLBACK must, again, preserve HANDLER metadata locks. + */ + MDL_ticket *m_lt_or_ha_sentinel; THD *m_thd; private: void release_ticket(MDL_ticket *ticket); - MDL_ticket *find_ticket(MDL_request *mdl_req); + MDL_ticket *find_ticket(MDL_request *mdl_req, + bool *is_lt_or_ha); + void release_locks_stored_before(MDL_ticket *sentinel); }; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 1a171705dae..caf3130c517 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1534,6 +1534,7 @@ TABLE *find_temporary_table(THD *thd, TABLE_LIST *table_list); int drop_temporary_table(THD *thd, TABLE_LIST *table_list); void close_temporary_table(THD *thd, TABLE *table, bool free_share, bool delete_table); +void mark_tmp_table_for_reuse(TABLE *table); void close_temporary(TABLE *table, bool free_share, bool delete_table); bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db, const char *table_name); diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index 9d82307d2e7..d47c49ed515 100644 --- a/sql/rpl_injector.cc +++ b/sql/rpl_injector.cc @@ -86,8 +86,7 @@ int injector::transaction::commit() if (!trans_commit(m_thd)) { close_thread_tables(m_thd); - if (!m_thd->locked_tables_mode) - m_thd->mdl_context.release_all_locks(); + m_thd->mdl_context.release_transactional_locks(); } DBUG_RETURN(0); } diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index b4554bb4b6c..bd03afb8dd8 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -1189,8 +1189,8 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) } m_table_map.clear_tables(); slave_close_thread_tables(thd); - if (error && !thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + if (error) + thd->mdl_context.release_transactional_locks(); clear_flag(IN_STMT); /* Cleanup for the flags that have been set at do_apply_event. diff --git a/sql/set_var.cc b/sql/set_var.cc index dd009541274..ce7cfcc81a8 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -3196,8 +3196,7 @@ static bool set_option_autocommit(THD *thd, set_var *var) return TRUE; close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); } if (var->save_result.ulong_value != 0) diff --git a/sql/slave.cc b/sql/slave.cc index 62ddc8aaf21..ca72aaea69a 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -2432,8 +2432,7 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) exec_res= 0; trans_rollback(thd); close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); /* chance for concurrent connection to get more locks */ safe_sleep(thd, min(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE), (CHECK_KILLED_FUNC)sql_slave_killed, (void*)rli); diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 4b9cee98211..451b2293109 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -730,8 +730,7 @@ my_bool acl_reload(THD *thd) end: trans_commit_implicit(thd); close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); DBUG_RETURN(return_val); } @@ -3895,8 +3894,7 @@ my_bool grant_reload(THD *thd) rw_unlock(&LOCK_grant); trans_commit_implicit(thd); close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); /* It is OK failing to load procs_priv table because we may be diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 61028f692b3..459ca646d8c 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1169,40 +1169,54 @@ static void mark_temp_tables_as_free_for_reuse(THD *thd) for (TABLE *table= thd->temporary_tables ; table ; table= table->next) { if ((table->query_id == thd->query_id) && ! table->open_by_handler) - { - table->query_id= 0; - table->file->ha_reset(); - - /* Detach temporary MERGE children from temporary parent. */ - DBUG_ASSERT(table->file); - table->file->extra(HA_EXTRA_DETACH_CHILDREN); - - /* - Reset temporary table lock type to it's default value (TL_WRITE). - - Statements such as INSERT INTO .. SELECT FROM tmp, CREATE TABLE - .. SELECT FROM tmp and UPDATE may under some circumstances modify - the lock type of the tables participating in the statement. This - isn't a problem for non-temporary tables since their lock type is - reset at every open, but the same does not occur for temporary - tables for historical reasons. - - Furthermore, the lock type of temporary tables is not really that - important because they can only be used by one query at a time and - not even twice in a query -- a temporary table is represented by - only one TABLE object. Nonetheless, it's safer from a maintenance - point of view to reset the lock type of this singleton TABLE object - as to not cause problems when the table is reused. - - Even under LOCK TABLES mode its okay to reset the lock type as - LOCK TABLES is allowed (but ignored) for a temporary table. - */ - table->reginfo.lock_type= TL_WRITE; - } + mark_tmp_table_for_reuse(table); } } +/** + Reset a single temporary table. + Effectively this "closes" one temporary table, + in a session. + + @param table Temporary table. +*/ + +void mark_tmp_table_for_reuse(TABLE *table) +{ + DBUG_ASSERT(table->s->tmp_table); + + table->query_id= 0; + table->file->ha_reset(); + + /* Detach temporary MERGE children from temporary parent. */ + DBUG_ASSERT(table->file); + table->file->extra(HA_EXTRA_DETACH_CHILDREN); + + /* + Reset temporary table lock type to it's default value (TL_WRITE). + + Statements such as INSERT INTO .. SELECT FROM tmp, CREATE TABLE + .. SELECT FROM tmp and UPDATE may under some circumstances modify + the lock type of the tables participating in the statement. This + isn't a problem for non-temporary tables since their lock type is + reset at every open, but the same does not occur for temporary + tables for historical reasons. + + Furthermore, the lock type of temporary tables is not really that + important because they can only be used by one query at a time and + not even twice in a query -- a temporary table is represented by + only one TABLE object. Nonetheless, it's safer from a maintenance + point of view to reset the lock type of this singleton TABLE object + as to not cause problems when the table is reused. + + Even under LOCK TABLES mode its okay to reset the lock type as + LOCK TABLES is allowed (but ignored) for a temporary table. + */ + table->reginfo.lock_type= TL_WRITE; +} + + /* Mark all tables in the list which were used by current substatement as free for reuse. @@ -1261,7 +1275,6 @@ static void close_open_tables(THD *thd) while (thd->open_tables) found_old_table|= close_thread_table(thd, &thd->open_tables); - thd->some_tables_deleted= 0; /* Free tables to hold down open files */ while (table_cache_count > table_cache_size && unused_tables) @@ -1475,7 +1488,7 @@ void close_thread_tables(THD *thd) if (thd->locked_tables_mode == LTM_LOCK_TABLES) DBUG_VOID_RETURN; - thd->locked_tables_mode= LTM_NONE; + thd->leave_locked_tables_mode(); /* Fallthrough */ } @@ -1505,16 +1518,27 @@ void close_thread_tables(THD *thd) if (thd->open_tables) close_open_tables(thd); - /* - Defer the release of metadata locks until the current transaction - is either committed or rolled back. This prevents other statements - from modifying the table for the entire duration of this transaction. - This provides commitment ordering for guaranteeing serializability - across multiple transactions. - */ - if (!thd->in_multi_stmt_transaction() || - (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) - thd->mdl_context.release_all_locks(); + if (thd->state_flags & Open_tables_state::BACKUPS_AVAIL) + { + /* We can't have an open HANDLER in the backup open tables state. */ + DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); + /* + Due to the above assert, this is guaranteed to release *all* locks + in the context. + */ + thd->mdl_context.release_transactional_locks(); + } + else if (! thd->in_multi_stmt_transaction()) + { + /* + Defer the release of metadata locks until the current transaction + is either committed or rolled back. This prevents other statements + from modifying the table for the entire duration of this transaction. + This provides commitment ordering for guaranteeing serializability + across multiple transactions. + */ + thd->mdl_context.release_transactional_locks(); + } DBUG_VOID_RETURN; } @@ -2337,7 +2361,8 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, enforced by asserts in metadata locking subsystem. */ mdl_request->set_type(MDL_EXCLUSIVE); - DBUG_ASSERT(! thd->mdl_context.has_locks()); + DBUG_ASSERT(! thd->mdl_context.has_locks() || + thd->handler_tables_hash.records); if (thd->mdl_context.acquire_exclusive_lock(mdl_request)) return 1; @@ -2791,7 +2816,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (!share->free_tables.is_empty()) { - table= share->free_tables.head(); + table= share->free_tables.front(); table_def_use_table(thd, table); /* We need to release share as we have EXTRA reference to it in our hands. */ release_table_share(share); @@ -3082,7 +3107,7 @@ Locked_tables_list::init_locked_tables(THD *thd) return TRUE; } } - thd->locked_tables_mode= LTM_LOCK_TABLES; + thd->enter_locked_tables_mode(LTM_LOCK_TABLES); return FALSE; } @@ -3121,7 +3146,7 @@ Locked_tables_list::unlock_locked_tables(THD *thd) */ table_list->table->pos_in_locked_tables= NULL; } - thd->locked_tables_mode= LTM_NONE; + thd->leave_locked_tables_mode(); close_thread_tables(thd); } @@ -3636,7 +3661,8 @@ end_with_lock_open: Open_table_context::Open_table_context(THD *thd) :m_action(OT_NO_ACTION), - m_can_deadlock(thd->in_multi_stmt_transaction() && + m_can_deadlock((thd->in_multi_stmt_transaction() || + thd->mdl_context.lt_or_ha_sentinel())&& thd->mdl_context.has_locks()) {} @@ -4136,7 +4162,7 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, even if they don't create problems for current thread (i.e. to avoid having DDL blocked by HANDLERs opened for long time). */ - if (thd->handler_tables) + if (thd->handler_tables_hash.records) mysql_ha_flush(thd); /* @@ -4642,13 +4668,14 @@ retry: while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, 0)) && ot_ctx.can_recover_from_failed_open_table()) { + /* We can't back off with an open HANDLER, we don't wait with locks. */ + DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); /* Even though we have failed to open table we still need to - call release_all_locks() to release metadata locks which + call release_transactional_locks() to release metadata locks which might have been acquired successfully. */ - if (! thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); table_list->mdl_request.ticket= 0; if (ot_ctx.recover_from_failed_open_table_attempt(thd, table_list)) break; @@ -4699,8 +4726,12 @@ retry: close_thread_tables(thd); table_list->table= NULL; table_list->mdl_request.ticket= NULL; - if (! thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + /* + We can't back off with an open HANDLER, + we don't wait with locks. + */ + DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); + thd->mdl_context.release_transactional_locks(); goto retry; } } @@ -4769,7 +4800,8 @@ bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, break; if (!need_reopen) DBUG_RETURN(TRUE); - if (thd->in_multi_stmt_transaction() && has_locks) + if ((thd->in_multi_stmt_transaction() || + thd->mdl_context.lt_or_ha_sentinel()) && has_locks) { my_error(ER_LOCK_DEADLOCK, MYF(0)); DBUG_RETURN(TRUE); @@ -5124,7 +5156,7 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, */ mark_real_tables_as_free_for_reuse(first_not_own); DBUG_PRINT("info",("locked_tables_mode= LTM_PRELOCKED")); - thd->locked_tables_mode= LTM_PRELOCKED; + thd->enter_locked_tables_mode(LTM_PRELOCKED); } } else @@ -5226,8 +5258,13 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) for (tmp= first_not_own_table; tmp; tmp= tmp->next_global) tmp->mdl_request.ticket= NULL; close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + /* We can't back off with an open HANDLERs, we must not wait with locks. */ + DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); + /* + Due to the above assert, this effectively releases *all* locks + of this session, so that we can safely wait on tables. + */ + thd->mdl_context.release_transactional_locks(); } @@ -8202,6 +8239,15 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use) if (!thd_table->needs_reopen()) signalled|= mysql_lock_abort_for_thread(thd, thd_table); } + /* + Wake up threads waiting in tdc_wait_for_old_versions(). + Normally such threads would already get blocked + in MDL subsystem, when trying to acquire a shared lock. + But in case a thread has an open HANDLER statement, + (and thus already grabbed a metadata lock), it gets + blocked only too late -- at the table cache level. + */ + broadcast_refresh(); pthread_mutex_unlock(&LOCK_open); return signalled; } @@ -8721,7 +8767,9 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup) pthread_mutex_unlock(&LOCK_open); - thd->mdl_context.release_all_locks(); + /* We can't have an open HANDLER in the backup context. */ + DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); + thd->mdl_context.release_transactional_locks(); thd->restore_backup_open_tables_state(backup); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index aa647aada60..95c985b2c10 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -482,7 +482,7 @@ THD::THD() catalog= (char*)"std"; // the only catalog we have for now main_security_ctx.init(); security_ctx= &main_security_ctx; - some_tables_deleted=no_errors=password= 0; + no_errors=password= 0; query_start_used= 0; count_cuted_fields= CHECK_FIELD_IGNORE; killed= NOT_KILLED; @@ -997,6 +997,7 @@ void THD::cleanup(void) } locked_tables_list.unlock_locked_tables(this); + mysql_ha_cleanup(this); /* If the thread was in the middle of an ongoing transaction (rolled @@ -1005,14 +1006,19 @@ void THD::cleanup(void) metadata locks. Release them. */ DBUG_ASSERT(open_tables == NULL); - mdl_context.release_all_locks(); + /* All HANDLERs must have been closed by now. */ + DBUG_ASSERT(mdl_context.lt_or_ha_sentinel() == NULL); + /* + Due to the above assert, this is guaranteed to release *all* in + this session. + */ + mdl_context.release_transactional_locks(); #if defined(ENABLED_DEBUG_SYNC) /* End the Debug Sync Facility. See debug_sync.cc. */ debug_sync_end_thread(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ - mysql_ha_cleanup(this); delete_dynamic(&user_var_events); my_hash_free(&user_vars); close_temporary_tables(this); @@ -1061,8 +1067,6 @@ THD::~THD() cleanup(); mdl_context.destroy(); - handler_mdl_context.destroy(); - ha_close_connection(this); plugin_thdvar_cleanup(this); @@ -3038,12 +3042,11 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup) to be sure that it was properly cleaned up. */ DBUG_ASSERT(open_tables == 0 && temporary_tables == 0 && - handler_tables == 0 && derived_tables == 0 && + derived_tables == 0 && lock == 0 && locked_tables_mode == LTM_NONE && m_reprepare_observer == NULL); mdl_context.destroy(); - handler_mdl_context.destroy(); set_open_tables_state(backup); DBUG_VOID_RETURN; diff --git a/sql/sql_class.h b/sql/sql_class.h index ebea2041715..11e0010d85b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -914,11 +914,6 @@ public: XXX Why are internal temporary tables added to this list? */ TABLE *temporary_tables; - /** - List of tables that were opened with HANDLER OPEN and are - still in use by this thread. - */ - TABLE *handler_tables; TABLE *derived_tables; /* During a MySQL session, one can lock tables in two modes: automatic @@ -985,7 +980,6 @@ public: uint state_flags; MDL_context mdl_context; - MDL_context handler_mdl_context; /** This constructor initializes Open_tables_state instance which can only @@ -1011,13 +1005,23 @@ public: void reset_open_tables_state(THD *thd) { - open_tables= temporary_tables= handler_tables= derived_tables= 0; + open_tables= temporary_tables= derived_tables= 0; extra_lock= lock= 0; locked_tables_mode= LTM_NONE; state_flags= 0U; m_reprepare_observer= NULL; mdl_context.init(thd); - handler_mdl_context.init(thd); + } + void enter_locked_tables_mode(enum_locked_tables_mode mode_arg) + { + DBUG_ASSERT(locked_tables_mode == LTM_NONE); + mdl_context.set_lt_or_ha_sentinel(); + locked_tables_mode= mode_arg; + } + void leave_locked_tables_mode() + { + locked_tables_mode= LTM_NONE; + mdl_context.clear_lt_or_ha_sentinel(); } }; @@ -1902,7 +1906,6 @@ public: bool slave_thread, one_shot_set; /* tells if current statement should binlog row-based(1) or stmt-based(0) */ bool current_stmt_binlog_row_based; - bool some_tables_deleted; bool last_cuted_field; bool no_errors, password; /** diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 94f5e84fb10..ccfe21d1af5 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -33,27 +33,21 @@ */ /* - There are two containers holding information about open handler tables. - The first is 'thd->handler_tables'. It is a linked list of TABLE objects. - It is used like 'thd->open_tables' in the table cache. The trick is to - exchange these two lists during open and lock of tables. Thus the normal - table cache code can be used. - The second container is a HASH. It holds objects of the type TABLE_LIST. - Despite its name, no lists of tables but only single structs are hashed - (the 'next' pointer is always NULL). The reason for theis second container - is, that we want handler tables to survive FLUSH TABLE commands. A table - affected by FLUSH TABLE must be closed so that other threads are not - blocked by handler tables still in use. Since we use the normal table cache - functions with 'thd->handler_tables', the closed tables are removed from - this list. Hence we need the original open information for the handler - table in the case that it is used again. This information is handed over - to mysql_ha_open() as a TABLE_LIST. So we store this information in the - second container, where it is not affected by FLUSH TABLE. The second - container is implemented as a hash for performance reasons. Consequently, - we use it not only for re-opening a handler table, but also for the - HANDLER ... READ commands. For this purpose, we store a pointer to the - TABLE structure (in the first container) in the TBALE_LIST object in the - second container. When the table is flushed, the pointer is cleared. + The information about open HANDLER objects is stored in a HASH. + It holds objects of type TABLE_LIST, which are indexed by table + name/alias, and allows us to quickly find a HANDLER table for any + operation at hand - be it HANDLER READ or HANDLER CLOSE. + + It also allows us to maintain an "open" HANDLER even in cases + when there is no physically open cursor. E.g. a FLUSH TABLE + statement in this or some other connection demands that all open + HANDLERs against the flushed table are closed. In order to + preserve the information about an open HANDLER, we don't perform + a complete HANDLER CLOSE, but only close the TABLE object. The + corresponding TABLE_LIST is kept in the cache with 'table' + pointer set to NULL. The table will be reopened on next access + (this, however, leads to loss of cursor position, unless the + cursor points at the first record). */ #include "mysql_priv.h" @@ -124,32 +118,19 @@ static void mysql_ha_hash_free(TABLE_LIST *tables) static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) { - TABLE **table_ptr; - MDL_ticket *mdl_ticket; - /* - Though we could take the table pointer from hash_tables->table, - we must follow the thd->handler_tables chain anyway, as we need the - address of the 'next' pointer referencing this table - for close_thread_table(). - */ - for (table_ptr= &(thd->handler_tables); - *table_ptr && (*table_ptr != tables->table); - table_ptr= &(*table_ptr)->next) - ; - - if (*table_ptr) + if (tables->table && !tables->table->s->tmp_table) { - (*table_ptr)->file->ha_index_or_rnd_end(); - mdl_ticket= (*table_ptr)->mdl_ticket; + /* Non temporary table. */ + tables->table->file->ha_index_or_rnd_end(); pthread_mutex_lock(&LOCK_open); - if (close_thread_table(thd, table_ptr)) + if (close_thread_table(thd, &tables->table)) { /* Tell threads waiting for refresh that something has happened */ broadcast_refresh(); } pthread_mutex_unlock(&LOCK_open); - thd->handler_mdl_context.release_lock(mdl_ticket); + thd->mdl_context.release_lock(tables->mdl_request.ticket); } else if (tables->table) { @@ -158,6 +139,7 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) table->file->ha_index_or_rnd_end(); table->query_id= thd->query_id; table->open_by_handler= 0; + mark_tmp_table_for_reuse(table); } /* Mark table as closed, ready for re-open if necessary. */ @@ -195,7 +177,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) uint dblen, namelen, aliaslen, counter; bool error; TABLE *backup_open_tables; - MDL_context backup_mdl_context; + MDL_ticket *mdl_savepoint; DBUG_ENTER("mysql_ha_open"); DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", tables->db, tables->table_name, tables->alias, @@ -265,6 +247,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) memcpy(hash_tables->table_name, tables->table_name, namelen); memcpy(hash_tables->alias, tables->alias, aliaslen); hash_tables->mdl_request.init(MDL_key::TABLE, db, name, MDL_SHARED); + /* for now HANDLER can be used only for real TABLES */ + hash_tables->required_type= FRMTYPE_TABLE; /* add to hash */ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) @@ -283,16 +267,11 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) from open_tables(), thd->open_tables will contain only the opened table. - The thd->handler_tables list is kept as-is to avoid deadlocks if - open_table(), called by open_tables(), needs to back-off because - of a pending exclusive metadata lock or flush for the table being - opened. - See open_table() back-off comments for more details. */ backup_open_tables= thd->open_tables; thd->open_tables= NULL; - thd->mdl_context.backup_and_reset(&backup_mdl_context); + mdl_savepoint= thd->mdl_context.mdl_savepoint(); /* open_tables() will set 'hash_tables->table' if successful. @@ -300,53 +279,47 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) */ DBUG_ASSERT(! hash_tables->table); - /* for now HANDLER can be used only for real TABLES */ - hash_tables->required_type= FRMTYPE_TABLE; /* We use open_tables() here, rather than, say, open_ltable() or open_table() because we would like to be able to open a temporary table. */ error= open_tables(thd, &hash_tables, &counter, 0); - if (thd->open_tables) - { - if (thd->open_tables->next) - { - /* - We opened something that is more than a single table. - This happens with MERGE engine. Don't try to link - this mess into thd->handler_tables list, close it - and report an error. We must do it right away - because mysql_ha_close_table(), called down the road, - can close a single table only. - */ - close_thread_tables(thd); - thd->mdl_context.release_all_locks(); - my_error(ER_ILLEGAL_HA, MYF(0), hash_tables->alias); - error= TRUE; - } - else - { - /* Merge the opened table into handler_tables list. */ - thd->open_tables->next= thd->handler_tables; - thd->handler_tables= thd->open_tables; - } - } - thd->handler_mdl_context.merge(&thd->mdl_context); - thd->open_tables= backup_open_tables; - thd->mdl_context.restore_from_backup(&backup_mdl_context); - - if (error) - goto err; - - /* There can be only one table in '*tables'. */ - if (! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) + if (! error && + ! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) { my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); - goto err; + error= TRUE; } + if (!error && + hash_tables->mdl_request.ticket && + thd->mdl_context.has_lock(mdl_savepoint, + hash_tables->mdl_request.ticket)) + { + /* The ticket returned is within a savepoint. Make a copy. */ + error= thd->mdl_context.clone_ticket(&hash_tables->mdl_request); + hash_tables->table->mdl_ticket= hash_tables->mdl_request.ticket; + } + if (error) + { + close_thread_tables(thd); + thd->open_tables= backup_open_tables; + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + if (!reopen) + my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); + else + hash_tables->table= NULL; + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } + thd->open_tables= backup_open_tables; + if (hash_tables->mdl_request.ticket) + thd->mdl_context. + move_ticket_after_lt_or_ha_sentinel(hash_tables->mdl_request.ticket); + /* Assert that the above check prevent opening of views and merge tables. */ + DBUG_ASSERT(hash_tables->table->next == NULL); /* If it's a temp table, don't reset table->query_id as the table is being used by this handler. Otherwise, no meaning at all. @@ -357,14 +330,6 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) my_ok(thd); DBUG_PRINT("exit",("OK")); DBUG_RETURN(FALSE); - -err: - if (hash_tables->table) - mysql_ha_close_table(thd, hash_tables); - if (!reopen) - my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); - DBUG_PRINT("exit",("ERROR")); - DBUG_RETURN(TRUE); } @@ -496,59 +461,41 @@ retry: hash_tables->db, hash_tables->table_name, hash_tables->alias, table)); } - table->pos_in_table_list= tables; -#if MYSQL_VERSION_ID < 40100 - if (*tables->db && strcmp(table->table_cache_key, tables->db)) - { - DBUG_PRINT("info",("wrong db")); - table= NULL; - } -#endif } else table= NULL; if (!table) { -#if MYSQL_VERSION_ID < 40100 - char buff[MAX_DBKEY_LENGTH]; - if (*tables->db) - strxnmov(buff, sizeof(buff)-1, tables->db, ".", tables->table_name, - NullS); - else - strncpy(buff, tables->alias, sizeof(buff)); - my_error(ER_UNKNOWN_TABLE, MYF(0), buff, "HANDLER"); -#else my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias, "HANDLER"); -#endif goto err0; } - tables->table=table; /* save open_tables state */ backup_open_tables= thd->open_tables; + /* Always a one-element list, see mysql_ha_open(). */ + DBUG_ASSERT(hash_tables->table->next == NULL); /* mysql_lock_tables() needs thd->open_tables to be set correctly to - be able to handle aborts properly. When the abort happens, it's - safe to not protect thd->handler_tables because it won't close any - tables. + be able to handle aborts properly. */ - thd->open_tables= thd->handler_tables; + thd->open_tables= hash_tables->table; - lock= mysql_lock_tables(thd, &tables->table, 1, 0, &need_reopen); - /* restore previous context */ + lock= mysql_lock_tables(thd, &thd->open_tables, 1, 0, &need_reopen); + + /* + In 5.1 and earlier, mysql_lock_tables() could replace the TABLE + object with another one (reopen it). This is no longer the case + with new MDL. + */ + DBUG_ASSERT(hash_tables->table == thd->open_tables); + /* Restore previous context. */ thd->open_tables= backup_open_tables; if (need_reopen) { mysql_ha_close_table(thd, hash_tables); - /* - The lock might have been aborted, we need to manually reset - thd->some_tables_deleted because handler's tables are closed - in a non-standard way. Otherwise we might loop indefinitely. - */ - thd->some_tables_deleted= 0; goto retry; } @@ -556,7 +503,8 @@ retry: goto err0; // mysql_lock_tables() printed error message already // Always read all columns - tables->table->read_set= &tables->table->s->all_set; + hash_tables->table->read_set= &hash_tables->table->s->all_set; + tables->table= hash_tables->table; if (cond) { @@ -811,6 +759,14 @@ void mysql_ha_flush(THD *thd) safe_mutex_assert_not_owner(&LOCK_open); + /* + Don't try to flush open HANDLERs when we're working with + system tables. The main MDL context is backed up and we can't + properly release HANDLER locks stored there. + */ + if (thd->state_flags & Open_tables_state::BACKUPS_AVAIL) + DBUG_VOID_RETURN; + for (uint i= 0; i < thd->handler_tables_hash.records; i++) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); @@ -818,9 +774,10 @@ void mysql_ha_flush(THD *thd) TABLE::mdl_ticket is 0 for temporary tables so we need extra check. */ if (hash_tables->table && - (hash_tables->table->mdl_ticket && - hash_tables->table->mdl_ticket->has_pending_conflicting_lock() || - hash_tables->table->s->needs_reopen())) + ((hash_tables->table->mdl_ticket && + hash_tables->table->mdl_ticket->has_pending_conflicting_lock()) || + (!hash_tables->table->s->tmp_table && + hash_tables->table->s->needs_reopen()))) mysql_ha_close_table(thd, hash_tables); } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 9f8af87f1e2..40ef55423a9 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3610,8 +3610,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, /* mysql_lock_tables() below should never fail with request to reopen table since it won't wait for the table lock (we have exclusive metadata lock on - the table) and thus can't get aborted and since it ignores other threads - setting THD::some_tables_deleted thanks to MYSQL_LOCK_IGNORE_FLUSH. + the table) and thus can't get aborted. */ if (! ((*lock)= mysql_lock_tables(thd, &table, 1, MYSQL_LOCK_IGNORE_FLUSH, ¬_used)) || diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 7fbf04c751f..79f10120268 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1180,8 +1180,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, if (trans_commit_implicit(thd)) break; close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); if (check_global_access(thd,RELOAD_ACL)) break; general_log_print(thd, command, NullS); @@ -1211,8 +1210,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, if (trans_commit_implicit(thd)) break; close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); my_ok(thd); break; } @@ -1967,8 +1965,7 @@ mysql_execute_command(THD *thd) goto error; /* Close tables and release metadata locks. */ close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); } /* @@ -3302,7 +3299,7 @@ end_with_restore_list: if (thd->options & OPTION_TABLE_LOCK) { trans_commit_implicit(thd); - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); thd->options&= ~(OPTION_TABLE_LOCK); } if (thd->global_read_lock) @@ -3311,11 +3308,19 @@ end_with_restore_list: break; case SQLCOM_LOCK_TABLES: thd->locked_tables_list.unlock_locked_tables(thd); + /* + As of 5.5, entering LOCK TABLES mode implicitly closes all + open HANDLERs. Both HANDLER code and LOCK TABLES mode use + the sentinel mechanism in MDL subsystem and thus could not be + used at the same time. All HANDLER operations are prohibited + under LOCK TABLES anyway. + */ + mysql_ha_cleanup(thd); /* we must end the trasaction first, regardless of anything */ if (trans_commit_implicit(thd)) goto error; /* release transactional metadata locks. */ - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; @@ -3354,7 +3359,7 @@ end_with_restore_list: */ close_thread_tables(thd); DBUG_ASSERT(!thd->locked_tables_mode); - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); thd->options&= ~(OPTION_TABLE_LOCK); } else @@ -3845,8 +3850,7 @@ end_with_restore_list: thd->locked_tables_mode == LTM_LOCK_TABLES); if (trans_commit(thd)) goto error; - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); /* Begin transaction with the same isolation level. */ if (lex->tx_chain && trans_begin(thd)) goto error; @@ -3860,8 +3864,7 @@ end_with_restore_list: thd->locked_tables_mode == LTM_LOCK_TABLES); if (trans_rollback(thd)) goto error; - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); /* Begin transaction with the same isolation level. */ if (lex->tx_chain && trans_begin(thd)) goto error; @@ -4212,8 +4215,7 @@ create_sp_error: close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); #ifndef NO_EMBEDDED_ACCESS_CHECKS if (sp_automatic_privileges && !opt_noacl && @@ -4394,15 +4396,13 @@ create_sp_error: case SQLCOM_XA_COMMIT: if (trans_xa_commit(thd)) goto error; - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); my_ok(thd); break; case SQLCOM_XA_ROLLBACK: if (trans_xa_rollback(thd)) goto error; - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); my_ok(thd); break; case SQLCOM_XA_RECOVER: @@ -4555,11 +4555,10 @@ finish: thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); /* Commit the normal transaction if one is active. */ trans_commit_implicit(thd); + thd->stmt_da->can_overwrite_status= FALSE; /* Close tables and release metadata locks. */ close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); - thd->stmt_da->can_overwrite_status= FALSE; + thd->mdl_context.release_transactional_locks(); } DBUG_RETURN(res || thd->is_error()); @@ -6539,7 +6538,9 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, } #endif /*HAVE_QUERY_CACHE*/ - DBUG_ASSERT(!thd || thd->locked_tables_mode || !thd->mdl_context.has_locks()); + DBUG_ASSERT(!thd || thd->locked_tables_mode || + !thd->mdl_context.has_locks() || + thd->handler_tables_hash.records); /* Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too diff --git a/sql/sql_plist.h b/sql/sql_plist.h index b0a0bb016d0..94e437362a9 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -71,6 +71,36 @@ public: first= a; *B::prev_ptr(a)= &first; } + inline void push_back(T *a) + { + insert_after(back(), a); + } + inline T *back() + { + T *t= front(); + if (t) + { + while (*B::next_ptr(t)) + t= *B::next_ptr(t); + } + return t; + } + inline void insert_after(T *pos, T *a) + { + if (pos == NULL) + push_front(a); + else + { + *B::next_ptr(a)= *B::next_ptr(pos); + *B::prev_ptr(a)= B::next_ptr(pos); + *B::next_ptr(pos)= a; + if (*B::next_ptr(a)) + { + T *old_next= *B::next_ptr(a); + *B::prev_ptr(old_next)= B::next_ptr(a); + } + } + } inline void remove(T *a) { T *next= *B::next_ptr(a); @@ -78,8 +108,8 @@ public: *B::prev_ptr(next)= *B::prev_ptr(a); **B::prev_ptr(a)= next; } - inline T* head() { return first; } - inline const T *head() const { return first; } + inline T* front() { return first; } + inline const T *front() const { return first; } void swap(I_P_List &rhs) { swap_variables(T *, first, rhs.first); @@ -106,6 +136,7 @@ class I_P_List_iterator T *current; public: I_P_List_iterator(I_P_List &a) : list(&a), current(a.first) {} + I_P_List_iterator(I_P_List &a, T* current_arg) : list(&a), current(current_arg) {} inline void init(I_P_List &a) { list= &a; @@ -118,6 +149,11 @@ public: current= *B::next_ptr(current); return result; } + inline T* operator++() + { + current= *B::next_ptr(current); + return current; + } inline void rewind() { current= list->first; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 27fdd1e2a8d..700017f2b3e 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -3193,8 +3193,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) Marker used to release metadata locks acquired while the prepared statement is being checked. */ - if (thd->in_multi_stmt_transaction()) - mdl_savepoint= thd->mdl_context.mdl_savepoint(); + mdl_savepoint= thd->mdl_context.mdl_savepoint(); /* The only case where we should have items in the thd->free_list is @@ -3220,13 +3219,11 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) lex_end(lex); cleanup_stmt(); /* - If not inside a multi-statement transaction, the metadata locks have - already been released and the rollback_to_savepoint is a nop. - Otherwise, release acquired locks -- a NULL mdl_savepoint means that - all locks are going to be released or that the transaction didn't - own any locks. + If not inside a multi-statement transaction, the metadata + locks have already been released and our savepoint points + to ticket which has been released as well. */ - if (!thd->locked_tables_mode) + if (thd->in_multi_stmt_transaction()) thd->mdl_context.rollback_to_savepoint(mdl_savepoint); thd->restore_backup_statement(this, &stmt_backup); thd->stmt_arena= old_stmt_arena; diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index 464a70e4175..4d5a4f41849 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -247,8 +247,7 @@ bool servers_reload(THD *thd) end: trans_commit_implicit(thd); close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); DBUG_PRINT("info", ("unlocking servers_cache")); rw_unlock(&THR_LOCK_servers); DBUG_RETURN(return_val); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 941352cb963..3b87a4dd6e8 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4714,8 +4714,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, trans_commit_stmt(thd); trans_commit(thd); close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); lex->reset_query_tables_list(FALSE); table->table=0; // For query cache if (protocol->write()) @@ -4766,8 +4765,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, trans_rollback_stmt(thd); trans_rollback(thd); close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); tmp_disable_binlog(thd); // binlogging is done by caller if wanted result_code= mysql_recreate_table(thd, table); reenable_binlog(thd); @@ -4883,8 +4881,7 @@ send_result_message: trans_commit_stmt(thd); trans_commit(thd); close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); DEBUG_SYNC(thd, "ha_admin_try_alter"); protocol->store(STRING_WITH_LEN("note"), system_charset_info); protocol->store(STRING_WITH_LEN( @@ -4910,8 +4907,7 @@ send_result_message: trans_commit_stmt(thd); trans_commit(thd); close_thread_tables(thd); - if (!thd->locked_tables_mode) - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); if (!result_code) // recreation went ok { /* Clear the ticket released in close_thread_tables(). */ @@ -7429,7 +7425,6 @@ end_temporary: (ulong) (copied + deleted), (ulong) deleted, (ulong) thd->warning_info->statement_warn_count()); my_ok(thd, copied + deleted, 0L, tmp_name); - thd->some_tables_deleted=0; DBUG_RETURN(FALSE); err_new_table_cleanup: diff --git a/sql/transaction.cc b/sql/transaction.cc index d1c7244ba83..1ca455028f0 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -103,7 +103,7 @@ bool trans_begin(THD *thd, uint flags) Release transactional metadata locks only after the transaction has been committed. */ - thd->mdl_context.release_all_locks(); + thd->mdl_context.release_transactional_locks(); thd->options|= OPTION_BEGIN; thd->server_status|= SERVER_STATUS_IN_TRANS; @@ -341,6 +341,10 @@ bool trans_savepoint(THD *thd, LEX_STRING name) Remember the last acquired lock before the savepoint was set. This is used as a marker to only release locks acquired after the setting of this savepoint. + Note: this works just fine if we're under LOCK TABLES, + since mdl_savepoint() is guaranteed to be beyond + the last locked table. This allows to release some + locks acquired during LOCK TABLES. */ newsv->mdl_savepoint = thd->mdl_context.mdl_savepoint(); @@ -388,8 +392,10 @@ bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name) thd->transaction.savepoints= sv; - /* Release metadata locks that were acquired during this savepoint unit. */ - if (!res && !thd->locked_tables_mode) + /* + Release metadata locks that were acquired during this savepoint unit. + */ + if (!res) thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint); DBUG_RETURN(test(res)); From bf9c1b7353d59f4c875c50dabd62dfd9765caf05 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Tue, 29 Dec 2009 15:19:05 +0300 Subject: [PATCH 131/212] Apply and review: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3655 Jon Olav Hauglid 2009-10-19 Bug #30977 Concurrent statement using stored function and DROP FUNCTION breaks SBR Bug #48246 assert in close_thread_table Implement a fix for: Bug #41804 purge stored procedure cache causes mysterious hang for many minutes Bug #49972 Crash in prepared statements The problem was that concurrent execution of DML statements that use stored functions and DDL statements that drop/modify the same function might result in incorrect binary log in statement (and mixed) mode and therefore break replication. This patch fixes the problem by introducing metadata locking for stored procedures and functions. This is similar to what is done in Bug#25144 for views. Procedures and functions now are locked using metadata locks until the transaction is either committed or rolled back. This prevents other statements from modifying the procedure/function while it is being executed. This provides commit ordering - guaranteeing serializability across multiple transactions and thus fixes the reported binlog problem. Note that we do not take locks for top-level CALLs. This means that procedures called directly are not protected from changes by simultaneous DDL operations so they are executed at the state they had at the time of the CALL. By not taking locks for top-level CALLs, we still allow transactions to be started inside procedures. This patch also changes stored procedure cache invalidation. Upon a change of cache version, we no longer invalidate the entire cache, but only those routines which we use, only when a statement is executed that uses them. This patch also changes the logic of prepared statement validation. A stored procedure used by a prepared statement is now validated only once a metadata lock has been acquired. A version mismatch causes a flush of the obsolete routine from the cache and statement reprepare. Incompatible changes: 1) ER_LOCK_DEADLOCK is reported for a transaction trying to access a procedure/function that is locked by a DDL operation in another connection. 2) Procedure/function DDL operations are now prohibited in LOCK TABLES mode as exclusive locks must be taken all at once and LOCK TABLES provides no way to specifiy procedures/functions to be locked. Test cases have been added to sp-lock.test and rpl_sp.test. Work on this bug has very much been a team effort and this patch includes and is based on contributions from Davi Arnaut, Dmitry Lenev, Magne Mæhre and Konstantin Osipov. mysql-test/r/ps_ddl.result: Update results (Bug#30977). mysql-test/r/ps_ddl1.result: Update results (Bug#30977). mysql-test/r/sp-error.result: Update results (Bug#30977). mysql-test/r/sp-lock.result: Update results (Bug#30977). mysql-test/suite/rpl/r/rpl_sp.result: Update results (Bug#30977). mysql-test/suite/rpl/t/rpl_sp.test: Add a test case for Bug#30977. mysql-test/t/ps_ddl.test: Update comments. We no longer re-prepare a prepared statement when a stored procedure used in top-level CALL is changed. mysql-test/t/ps_ddl1.test: Modifying stored procedure p1 no longer invalidates prepared statement "call p1" -- we can re-use the prepared statement without invalidation. mysql-test/t/sp-error.test: Use a constant for an error value. mysql-test/t/sp-lock.test: Add test coverage for Bug#30977. sql/lock.cc: Implement lock_routine_name() - a way to acquire an exclusive metadata lock (ex- name-lock) on stored procedure/function. sql/sp.cc: Change semantics of sp_cache_routine() -- now it has an option to make sure that the routine that is cached is up to date (has the latest sp cache version). Add sp_cache_invalidate() to sp_drop_routine(), where it was missing (a bug!). Acquire metadata locks for SP DDL (ALTER/CREATE/DROP). This is the core of the fix for Bug#30977. Since caching and cache invalidation scheme was changed, make sure we don't invalidate the SP cache in the middle of a stored routine execution. At the same time, make sure we don't access stale data due to lack of invalidation. For that, change ALTER FUNCTION/PROCEDURE to not use the cache, and SHOW PROCEDURE CODE/SHOW CREATE PROCEDURE/FUNCTION to always read an up to date version of the routine from the cache. sql/sp.h: Add a helper wrapper around sp_cache_routine(). sql/sp_cache.cc: Implement new sp_cache_version() and sp_cache_flush_obsolete(). Now we flush stale routines individually, rather than all at once. sql/sp_cache.h: Update signatures of sp_cache_version() and sp_cache_flush_obsolete(). sql/sp_head.cc: Add a default initialization of sp_head::m_sp_cache_version. Remove a redundant sp_head::create(). sql/sp_head.h: Add m_sp_cache_version to sp_head class - we now keep track of every routine in the stored procedure cache, rather than of the entire cache. sql/sql_base.cc: Implement prelocking for stored routines. Validate stored routines after they were locked. Flush obsolete routines upon next access, one by one, not all at once (Bug#41804). Style fixes. sql/sql_class.h: Rename a Open_table_context method. sql/sql_parse.cc: Make sure stored procedures DDL commits the active transaction (issues an implicit commit before and after). Remove sp_head::create(), a pure redundancy. Move the semantical check during alter routine inside sp_update_routine() code in order to: - avoid using SP cache during update, it may be obsolete. - speed up and simplify the update procedure. Remove sp_cache_flush_obsolete() calls, we no longer flush the entire cache, ever, stale routines are flushed before next use, one at a time. sql/sql_prepare.cc: Move routine metadata validation to open_and_process_routine(). Fix Bug#49972 (don't swap flags at reprepare). Reset Sroutine_hash_entries in reinit_stmt_before_use(). Remove SP cache invalidation, it's now done by open_tables(). sql/sql_show.cc: Fix a warning: remove an unused label. sql/sql_table.cc: Reset mdl_request.ticket for tickets acquired for routines inlined through a view, in CHECK TABLE statement, to satisfy an MDL assert. sql/sql_update.cc: Move the cleanup of "translation items" to close_tables_for_reopen(), since it's needed in all cases when we back off, not just the back-off in multi-update. This fixes a bug when the server would crash on attempt to back off when opening tables for a statement that uses information_schema tables. --- mysql-test/r/ps_ddl.result | 13 +- mysql-test/r/ps_ddl1.result | 2 +- mysql-test/r/sp-error.result | 2 +- mysql-test/r/sp-lock.result | 697 +++++++++++++++++++++ mysql-test/suite/rpl/r/rpl_sp.result | 45 +- mysql-test/suite/rpl/t/rpl_sp.test | 59 +- mysql-test/t/ps_ddl.test | 11 +- mysql-test/t/ps_ddl1.test | 2 +- mysql-test/t/sp-error.test | 2 +- mysql-test/t/sp-lock.test | 876 +++++++++++++++++++++++++++ sql/lock.cc | 51 ++ sql/mysql_priv.h | 3 + sql/sp.cc | 168 +++-- sql/sp.h | 18 +- sql/sp_cache.cc | 67 +- sql/sp_cache.h | 4 +- sql/sp_head.cc | 15 +- sql/sp_head.h | 30 +- sql/sql_base.cc | 239 ++++++-- sql/sql_class.h | 7 +- sql/sql_parse.cc | 166 ++--- sql/sql_prepare.cc | 50 +- sql/sql_show.cc | 1 - sql/sql_table.cc | 18 + sql/sql_update.cc | 4 - 25 files changed, 2243 insertions(+), 307 deletions(-) create mode 100644 mysql-test/r/sp-lock.result create mode 100644 mysql-test/t/sp-lock.test diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result index c72d129c8e4..2ce43d363c3 100644 --- a/mysql-test/r/ps_ddl.result +++ b/mysql-test/r/ps_ddl.result @@ -269,8 +269,6 @@ Part 7: TABLE -> TABLE (TRIGGER dependencies) transitions ===================================================================== # Test 7-a: dependent PROCEDURE has changed # -# Note, this scenario is not supported, subject of Bug#12093 -# create table t1 (a int); create trigger t1_ai after insert on t1 for each row call p1(new.a); @@ -282,10 +280,9 @@ drop procedure p1; create procedure p1 (a int) begin end; set @var= 2; execute stmt using @var; -ERROR 42000: PROCEDURE test.p1 does not exist # Cleanup drop procedure p1; -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); SUCCESS # Test 7-b: dependent FUNCTION has changed @@ -361,11 +358,13 @@ set @var=8; # XXX: bug, the SQL statement in the trigger is still # pointing at table 't3', since the view was expanded # at first statement execution. +# Since the view definition is inlined in the statement +# at prepare, changing the view definition does not cause +# repreparation. # Repreparation of the main statement doesn't cause repreparation # of trigger statements. execute stmt using @var; -ERROR 42S02: Table 'test.t3' doesn't exist -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); SUCCESS # @@ -382,6 +381,7 @@ select * from t3; a 6 7 +8 flush table t1; set @var=9; execute stmt using @var; @@ -396,6 +396,7 @@ select * from t3; a 6 7 +8 drop view v1; drop table t1,t2,t3; # Test 7-d: dependent TABLE has changed diff --git a/mysql-test/r/ps_ddl1.result b/mysql-test/r/ps_ddl1.result index e41a72ceb96..87abcd90590 100644 --- a/mysql-test/r/ps_ddl1.result +++ b/mysql-test/r/ps_ddl1.result @@ -460,7 +460,7 @@ create schema mysqltest; end| execute stmt; ERROR 42000: PROCEDURE test.p1 does not exist -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); SUCCESS execute stmt; diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index 034923bbd4f..4cefee95903 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -512,7 +512,7 @@ select * from t1; end| lock table t1 read| alter procedure bug9566 comment 'Some comment'| -ERROR HY000: Table 'proc' was not locked with LOCK TABLES +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction unlock tables| drop procedure bug9566| drop procedure if exists bug7299| diff --git a/mysql-test/r/sp-lock.result b/mysql-test/r/sp-lock.result new file mode 100644 index 00000000000..65524d02d08 --- /dev/null +++ b/mysql-test/r/sp-lock.result @@ -0,0 +1,697 @@ +# +# Test coverage for changes performed by the fix +# for Bug#30977 "Concurrent statement using stored function +# and DROP FUNCTION breaks SBR. +# +# +# 1) Verify that the preceding transaction is +# (implicitly) committed before CREATE/ALTER/DROP +# PROCEDURE. Note, that this is already tested +# in implicit_commit.test, but here we use an alternative +# approach. +# +# Start a transaction, create a savepoint, +# then call a DDL operation on a procedure, and then check +# that the savepoint is no longer present. +drop table if exists t1; +drop procedure if exists p1; +drop procedure if exists p2; +drop procedure if exists p3; +drop procedure if exists p4; +drop function if exists f1; +create table t1 (a int); +# +# Test 'CREATE PROCEDURE'. +# +begin; +savepoint sv; +create procedure p1() begin end; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'ALTER PROCEDURE'. +# +begin; +savepoint sv; +alter procedure p1 comment 'changed comment'; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'DROP PROCEDURE'. +# +begin; +savepoint sv; +drop procedure p1; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'CREATE FUNCTION'. +# +begin; +savepoint sv; +create function f1() returns int return 1; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'ALTER FUNCTION'. +# +begin; +savepoint sv; +alter function f1 comment 'new comment'; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'DROP FUNCTION'. +# +begin; +savepoint sv; +drop function f1; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# 2) Verify that procedure DDL operations fail +# under lock tables. +# +# Auxiliary routines to test ALTER. +create procedure p1() begin end; +create function f1() returns int return 1; +lock table t1 write; +create procedure p2() begin end; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter procedure p1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +drop procedure p1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +create function f2() returns int return 1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter function f1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +lock table t1 read; +create procedure p2() begin end; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter procedure p1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +drop procedure p1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +create function f2() returns int return 1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter function f1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +unlock tables; +# +# Even if we locked a temporary table. +# Todo: this is a restriction we could possibly lift. +# +drop table t1; +create temporary table t1 (a int); +lock table t1 read; +create procedure p2() begin end; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter procedure p1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +drop procedure p1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +create function f2() returns int return 1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter function f1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +unlock tables; +drop function f1; +drop procedure p1; +drop temporary table t1; +# +# 3) Verify that CREATE/ALTER/DROP routine grab an +# exclusive lock. +# +# For that, start a transaction, use a routine. In a concurrent +# connection, try to drop or alter the routine. It should place +# a pending or exlusive lock and block. In a concurrnet +# connection, try to use the routine under LOCK TABLES. +# That should yield ER_LOCK_DEADLOCK. +# +# Establish helper connections. +# +# Test DROP PROCEDURE. +# +# --> connection default +create table t1 (a int); +create procedure p1() begin end; +create function f1() returns int +begin +call p1(); +return 1; +end| +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'drop procedure p1'... +drop procedure p1; +# --> connection con2 +# Waitng for 'drop procedure t1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +lock table t1 read; +select f1(); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default +commit; +# --> connection con1 +# Reaping 'drop procedure p1'... +# --> connection default +# +# Test CREATE PROCEDURE. +# +create procedure p1() begin end; +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'create procedure p1'... +create procedure p1() begin end; +# --> connection con2 +# Waitng for 'create procedure t1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +lock table t1 read; +select f1(); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default +commit; +# --> connection con1 +# Reaping 'create procedure p1'... +ERROR 42000: PROCEDURE p1 already exists +# +# Test ALTER PROCEDURE. +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'alter procedure p1'... +alter procedure p1 contains sql; +# --> connection con2 +# Waitng for 'alter procedure t1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +lock table t1 read; +select f1(); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default +commit; +# --> connection con1 +# Reaping 'alter procedure p1'... +# --> connection default +# +# Test DROP FUNCTION. +# +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'drop function f1'... +drop function f1; +# --> connection con2 +# Waitng for 'drop function f1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +lock table t1 read; +select f1(); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default +commit; +# --> connection con1 +# Reaping 'drop function f1'... +# --> connection default +# +# Test CREATE FUNCTION. +# +create function f1() returns int return 1; +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'create function f1'... +create function f1() returns int return 2; +# --> connection con2 +# Waitng for 'create function f1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +lock table t1 read; +select f1(); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default +commit; +# --> connection con1 +# Reaping 'create function f1'... +ERROR 42000: FUNCTION f1 already exists +# --> connection default +# +# Test ALTER FUNCTION. +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'alter function f1'... +alter function f1 contains sql; +# --> connection con2 +# Waitng for 'alter function f1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +lock table t1 read; +select f1(); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default +commit; +# --> connection con1 +# Reaping 'alter function f1'... +# --> connection default +drop function f1; +drop procedure p1; +# +# 4) MDL lock should not be taken for +# unrolled CALL statements. +# The primary goal of metadata locks is a consistent binary log. +# When a call statement is unrolled, it doesn't get to the +# binary log, instead the statements that are contained +# in the procedure body do. This can nest to any level. +# +create procedure p1() begin end; +create procedure p2() begin end; +create procedure p3() +begin +call p1(); +call p1(); +call p2(); +end| +create procedure p4() +begin +call p1(); +call p1(); +call p2(); +call p2(); +call p3(); +end| +begin; +select * from t1; +a +savepoint sv; +call p4(); +# Prepared statement should not add any locks either. +prepare stmt from "call p4()"; +execute stmt; +execute stmt; +# --> connection con1 +drop procedure p1; +drop procedure p2; +drop procedure p3; +drop procedure p4; +# --> connection default +# This is to verify there was no implicit commit. +rollback to savepoint sv; +call p4(); +ERROR 42000: PROCEDURE test.p4 does not exist +commit; +drop table t1; +# +# 5) Locks should be taken on routines +# used indirectly by views or triggers. +# +# +# A function is used from a trigger. +# +create function f1() returns int return 1; +create table t1 (a int); +create table t2 (a int, b int); +create trigger t1_ai after insert on t1 for each row +insert into t2 (a, b) values (new.a, f1()); +begin; +insert into t1 (a) values (1); +# --> connection con1 +# Sending 'drop function f1' +drop function f1; +# --> connection con2 +# Waitng for 'drop function f1' to get blocked on MDL lock... +# --> connnection default +commit; +# --> connection con1 +# Reaping 'drop function f1'... +# --> connection default +# +# A function is used from a view. +# +create function f1() returns int return 1; +create view v1 as select f1() as a; +begin; +select * from v1; +a +1 +# --> connection con1 +# Sending 'drop function f1' +drop function f1; +# --> connection con2 +# Waitng for 'drop function f1' to get blocked on MDL lock... +# --> connnection default +commit; +# --> connection con1 +# Reaping 'drop function f1'... +# --> connection default +# +# A procedure is used from a function. +# +create function f1() returns int +begin +declare v_out int; +call p1(v_out); +return v_out; +end| +create procedure p1(out v_out int) set v_out=3; +begin; +select * from v1; +a +3 +# --> connection con1 +# Sending 'drop procedure p1' +drop procedure p1; +# --> connection con2 +# Waitng for 'drop procedure p1' to get blocked on MDL lock... +# --> connnection default +commit; +# --> connection con1 +# Reaping 'drop procedure p1'... +# --> connection default +# +# Deep nesting: a function is used from a procedure used +# from a function used from a view used in a trigger. +# +create function f2() returns int return 4; +create procedure p1(out v_out int) set v_out=f2(); +drop trigger t1_ai; +create trigger t1_ai after insert on t1 for each row +insert into t2 (a, b) values (new.a, (select max(a) from v1)); +begin; +insert into t1 (a) values (3); +# --> connection con1 +# Sending 'drop function f2' +drop function f2; +# --> connection con2 +# Waitng for 'drop function f2' to get blocked on MDL lock... +# --> connnection default +commit; +# --> connection con1 +# Reaping 'drop function f2'... +# --> connection default +drop view v1; +drop function f1; +drop procedure p1; +drop table t1, t2; +# +# 6) Check that ER_LOCK_DEADLOCK is reported if +# acquisition of a shared lock fails during a transaction or +# we need to back off to flush the sp cache. +# +# a) A back off due to a lock conflict. +# +create table t1 (a int); +create function f1() returns int return 6; +begin; +select f1(); +f1() +6 +# --> connection con1 +# Sending 'drop function f1'... +drop function f1; +# --> connection con2 +# Waitng for 'drop function f1' to get blocked on MDL lock... +begin; +select * from t1; +a +select f1(); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +commit; +# --> connection default +commit; +# --> connection con1 +# Reaping 'drop function f1'... +# --> connection default +# +# b) A back off to flush the cache. +# Sic: now this situation does not require a back off since we +# flush the cache on the fly. +# +create function f1() returns int return 7; +begin; +select * from t1; +a +select f1(); +f1() +7 +commit; +drop table t1; +drop function f1; +# +# 7) Demonstrate that under LOCK TABLES we accumulate locks +# on stored routines, and release metadata locks in +# ROLLBACK TO SAVEPOINT. That is done only for those stored +# routines that are not part of LOCK TABLES prelocking list. +# Those stored routines that are part of LOCK TABLES +# prelocking list are implicitly locked when entering +# LOCK TABLES, and ROLLBACK TO SAVEPOINT has no effect on +# them. +# +create function f1() returns varchar(20) return "f1()"; +create function f2() returns varchar(20) return "f2()"; +create view v1 as select f1() as a; +set @@session.autocommit=0; +lock table v1 read; +select * from v1; +a +f1() +savepoint sv; +select f2(); +f2() +f2() +# --> connection con1 +# Sending 'drop function f1'... +drop function f1; +# --> connection con2 +# Waitng for 'drop function f1' to get blocked on MDL lock... +# Sending 'drop function f2'... +drop function f2; +# --> connection default +# Waitng for 'drop function f2' to get blocked on MDL lock... +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop function f2'... +# --> connection default +unlock tables; +# --> connection con1 +# Reaping 'drop function f1'... +# --> connection default +drop function f1; +ERROR 42000: FUNCTION test.f1 does not exist +drop function f2; +ERROR 42000: FUNCTION test.f2 does not exist +drop view v1; +set @@session.autocommit=default; +# +# 8) Check the situation when we're preparing or executing a +# prepared statement, and as part of that try to flush the +# session sp cache. However, one of the procedures that +# needs a flush is in use. Verify that there is no infinite +# reprepare loop and no crash. +# +create function f1() returns int return 1; +# +# We just mention p1() in the body of f2() to make +# sure that p1() metadata is validated when validating +# 'select f2()'. +# Recursion is not allowed in stored functions, so +# an attempt to just invoke p1() from f2() which is in turn +# called from p1() would have given a run-time error. +# +create function f2() returns int +begin +if @var is null then +call p1(); +end if; +return 1; +end| +create procedure p1() +begin +select f1() into @var; +execute stmt; +end| +# --> connection con2 +prepare stmt from "select f2()"; +# --> connection default +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'alter function f1 ...'... +alter function f1 comment "comment"; +# --> connection con2 +# Waitng for 'alter function f1 ...' to get blocked on MDL lock... +# Sending 'call p1()'... +call p1(); +# Waitng for 'call p1()' to get blocked on MDL lock on f1... +# Let 'alter function f1 ...' go through... +commit; +# --> connection con1 +# Reaping 'alter function f1 ...' +# --> connection con2 +# Reaping 'call p1()'... +f2() +1 +deallocate prepare stmt; +# --> connection default +drop function f1; +drop function f2; +drop procedure p1; +# +# 9) Check the situation when a stored function is invoked +# from a stored procedure, and recursively invokes the +# stored procedure that is in use. But for the second +# invocation, a cache flush is requested. We can't +# flush the procedure that's in use, and are forced +# to use an old version. It is not a violation of +# consistency, since we unroll top-level calls. +# Just verify the code works. +# +create function f1() returns int return 1; +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'alter function f1 ...'... +alter function f1 comment "comment"; +# --> connection con2 +# Waitng for 'alter function f1 ...' to get blocked on MDL lock... +# +# We just mention p1() in the body of f2() to make +# sure that p1() is prelocked for f2(). +# Recursion is not allowed in stored functions, so +# an attempt to just invoke p1() from f2() which is in turn +# called from p1() would have given a run-time error. +# +create function f2() returns int +begin +if @var is null then +call p1(); +end if; +return 1; +end| +create procedure p1() +begin +select f1() into @var; +select f2() into @var; +end| +# Sending 'call p1()'... +call p1(); +# Waitng for 'call p1()' to get blocked on MDL lock on f1... +# Let 'alter function f1 ...' go through... +commit; +# --> connection con1 +# Reaping 'alter function f1 ...' +# --> connection con2 +# Reaping 'call p1()'... +# --> connection default +drop function f1; +drop function f2; +drop procedure p1; +# +# 10) A select from information_schema.routines now +# flushes the stored routines caches. Test that this +# does not remove from the cache a stored routine +# that is already prelocked. +# +create function f1() returns int return get_lock("30977", 100000); +create function f2() returns int return 2; +create function f3() returns varchar(255) +begin +declare res varchar(255); +declare c cursor for select routine_name from +information_schema.routines where routine_name='f1'; +select f1() into @var; +open c; +fetch c into res; +close c; +select f2() into @var; +return res; +end| +# --> connection con1 +select get_lock("30977", 0); +get_lock("30977", 0) +1 +# --> connection default +# Sending 'select f3()'... +select f3(); +# --> connection con1 +# Waitng for 'select f3()' to get blocked on the user level lock... +# Do something to change the cache version. +create function f4() returns int return 4; +drop function f4; +select release_lock("30977"); +release_lock("30977") +1 +# --> connection default +# Reaping 'select f3()'... +# Routine 'f2()' should exist and get executed successfully. +f3() +f1 +select @var; +@var +2 +drop function f1; +drop function f2; +drop function f3; +# 11) Check the situation when the connection is flushing the +# SP cache which contains a procedure that is being executed. +# +# Function f1() calls p1(). Procedure p1() has a DROP +# VIEW statement, which, we know, invalidates the routines cache. +# During cache flush p1() must not be flushed since it's in +# use. +# +create function f1() returns int +begin +call p1(); +return 1; +end| +create procedure p1() +begin +create view v1 as select 1; +drop view v1; +select f1() into @var; +set @exec_count=@exec_count+1; +end| +set @exec_count=0; +call p1(); +ERROR HY000: Recursive limit 0 (as set by the max_sp_recursion_depth variable) was exceeded for routine p1 +select @exec_count; +@exec_count +0 +set @@session.max_sp_recursion_depth=5; +set @exec_count=0; +call p1(); +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +select @exec_count; +@exec_count +0 +drop procedure p1; +drop function f1; +set @@session.max_sp_recursion_depth=default; +# --> connection con1 +# --> connection con2 +# --> connection default +# +# End of 5.5 tests +# diff --git a/mysql-test/suite/rpl/r/rpl_sp.result b/mysql-test/suite/rpl/r/rpl_sp.result index 33d8267ad91..ba748049ac1 100644 --- a/mysql-test/suite/rpl/r/rpl_sp.result +++ b/mysql-test/suite/rpl/r/rpl_sp.result @@ -977,4 +977,47 @@ drop procedure mysqltestbug36570_p1; drop procedure ` mysqltestbug36570_p2`; drop function mysqltestbug36570_f1; End of 5.0 tests -End of 5.1 tests +# End of 5.1 tests +# +# Test Bug#30977 Concurrent statement using stored +# function and DROP FUNCTION breaks SBR. +# +# Demonstrate that stored function DDL can not go through, +# or, worse yet, make its way into the binary log, while +# the stored function is in use. +# For that, try to insert a result of a stored function +# into a table. Block the insert in the beginning, waiting +# on a table lock. While insert is blocked, attempt to +# drop the routine. Verify that this attempt +# blocks and waits for INSERT to complete. Commit and +# reap the chain of events. Master and slave must contain +# identical data. Statements in the binrary log must be +# consistent with data in the table. +# +# --> connection default +drop table if exists t1, t2; +drop function if exists t1; +create table t1 (a int); +create table t2 (a int) as select 1 as a; +create function f1() returns int deterministic return (select max(a) from t2); +lock table t2 write; +# --> connection master +# Sending 'insert into t1 (a) values (f1())'... +insert into t1 (a) values (f1()); +# Waitng for 'insert into t1 ...' to get blocked on table lock... +# Sending 'drop function f1'. It will abort the table lock wait. +drop function f1; +# --> connection default +# Now let's let 'insert' go through... +unlock tables; +# --> connection con1 +# Reaping 'insert into t1 (a) values (f1())'... +ERROR 42000: FUNCTION test.f1 does not exist +select * from t1; +a +select * from t1; +a +drop table t1, t2; +drop function f1; +ERROR 42000: FUNCTION test.f1 does not exist +# End of 5.5 tests. diff --git a/mysql-test/suite/rpl/t/rpl_sp.test b/mysql-test/suite/rpl/t/rpl_sp.test index 96a41d9a9ad..231f0c6bcc0 100644 --- a/mysql-test/suite/rpl/t/rpl_sp.test +++ b/mysql-test/suite/rpl/t/rpl_sp.test @@ -621,7 +621,64 @@ drop procedure mysqltestbug36570_p1; drop procedure ` mysqltestbug36570_p2`; drop function mysqltestbug36570_f1; --echo End of 5.0 tests ---echo End of 5.1 tests +--echo # End of 5.1 tests +--echo # +--echo # Test Bug#30977 Concurrent statement using stored +--echo # function and DROP FUNCTION breaks SBR. +--echo # +--echo # Demonstrate that stored function DDL can not go through, +--echo # or, worse yet, make its way into the binary log, while +--echo # the stored function is in use. +--echo # For that, try to insert a result of a stored function +--echo # into a table. Block the insert in the beginning, waiting +--echo # on a table lock. While insert is blocked, attempt to +--echo # drop the routine. Verify that this attempt +--echo # blocks and waits for INSERT to complete. Commit and +--echo # reap the chain of events. Master and slave must contain +--echo # identical data. Statements in the binrary log must be +--echo # consistent with data in the table. +--echo # +--echo # --> connection default +connection default; +--disable_warnings +drop table if exists t1, t2; +drop function if exists t1; +--enable_warnings +create table t1 (a int); +create table t2 (a int) as select 1 as a; +create function f1() returns int deterministic return (select max(a) from t2); +lock table t2 write; +--echo # --> connection master +connection master; +--echo # Sending 'insert into t1 (a) values (f1())'... +--send insert into t1 (a) values (f1()) +connection master1; +--echo # Waitng for 'insert into t1 ...' to get blocked on table lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Table lock' and info='insert into t1 (a) values (f1())'; +--source include/wait_condition.inc +--echo # Sending 'drop function f1'. It will abort the table lock wait. +drop function f1; +--echo # --> connection default +connection default; +--echo # Now let's let 'insert' go through... +unlock tables; +--echo # --> connection con1 +connection master; +--echo # Reaping 'insert into t1 (a) values (f1())'... +--error ER_SP_DOES_NOT_EXIST +--reap +connection master1; +select * from t1; +connection slave; +select * from t1; +connection master; +drop table t1, t2; +--error ER_SP_DOES_NOT_EXIST +drop function f1; + +--echo # End of 5.5 tests. + # Cleanup sync_slave_with_master; diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test index e00d63aaedc..6a2b49ac9b7 100644 --- a/mysql-test/t/ps_ddl.test +++ b/mysql-test/t/ps_ddl.test @@ -278,8 +278,6 @@ deallocate prepare stmt; --echo # Test 7-a: dependent PROCEDURE has changed --echo # ---echo # Note, this scenario is not supported, subject of Bug#12093 ---echo # create table t1 (a int); create trigger t1_ai after insert on t1 for each row @@ -291,11 +289,10 @@ execute stmt using @var; drop procedure p1; create procedure p1 (a int) begin end; set @var= 2; ---error ER_SP_DOES_NOT_EXIST execute stmt using @var; --echo # Cleanup drop procedure p1; -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); --echo # Test 7-b: dependent FUNCTION has changed --echo # @@ -355,11 +352,13 @@ set @var=8; --echo # XXX: bug, the SQL statement in the trigger is still --echo # pointing at table 't3', since the view was expanded --echo # at first statement execution. +--echo # Since the view definition is inlined in the statement +--echo # at prepare, changing the view definition does not cause +--echo # repreparation. --echo # Repreparation of the main statement doesn't cause repreparation --echo # of trigger statements. ---error ER_NO_SUCH_TABLE execute stmt using @var; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); --echo # --echo # Sic: the insert went into t3, even though the view now --echo # points at t2. This is because neither the merged view diff --git a/mysql-test/t/ps_ddl1.test b/mysql-test/t/ps_ddl1.test index 379ed576b5f..0145d445a14 100644 --- a/mysql-test/t/ps_ddl1.test +++ b/mysql-test/t/ps_ddl1.test @@ -363,7 +363,7 @@ end| delimiter ;| --error ER_SP_DOES_NOT_EXIST execute stmt; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); --error ER_SP_DOES_NOT_EXIST execute stmt; call p_verify_reprepare_count(0); diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index 4df1118cd56..c8b2595e23d 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -723,7 +723,7 @@ lock table t1 read| # This should fail since we forgot to lock mysql.proc for writing # explicitly, and we can't open mysql.proc for _writing_ if there # are locked tables. ---error 1100 +--error ER_LOCK_OR_ACTIVE_TRANSACTION alter procedure bug9566 comment 'Some comment'| unlock tables| # This should succeed diff --git a/mysql-test/t/sp-lock.test b/mysql-test/t/sp-lock.test new file mode 100644 index 00000000000..a90b85a59be --- /dev/null +++ b/mysql-test/t/sp-lock.test @@ -0,0 +1,876 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# +# 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 Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# +# Metadata lock handling for stored procedures and +# functions. +# +--echo # +--echo # Test coverage for changes performed by the fix +--echo # for Bug#30977 "Concurrent statement using stored function +--echo # and DROP FUNCTION breaks SBR. +--echo # + +--echo # +--echo # 1) Verify that the preceding transaction is +--echo # (implicitly) committed before CREATE/ALTER/DROP +--echo # PROCEDURE. Note, that this is already tested +--echo # in implicit_commit.test, but here we use an alternative +--echo # approach. +--echo # + +--echo # Start a transaction, create a savepoint, +--echo # then call a DDL operation on a procedure, and then check +--echo # that the savepoint is no longer present. + +--disable_warnings +drop table if exists t1; +drop procedure if exists p1; +drop procedure if exists p2; +drop procedure if exists p3; +drop procedure if exists p4; +drop function if exists f1; +--enable_warnings +create table t1 (a int); +--echo # +--echo # Test 'CREATE PROCEDURE'. +--echo # +begin; +savepoint sv; +create procedure p1() begin end; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'ALTER PROCEDURE'. +--echo # +begin; +savepoint sv; +alter procedure p1 comment 'changed comment'; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'DROP PROCEDURE'. +--echo # +begin; +savepoint sv; +drop procedure p1; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'CREATE FUNCTION'. +--echo # +begin; +savepoint sv; +create function f1() returns int return 1; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'ALTER FUNCTION'. +--echo # +begin; +savepoint sv; +alter function f1 comment 'new comment'; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'DROP FUNCTION'. +--echo # +begin; +savepoint sv; +drop function f1; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; + +--echo # +--echo # 2) Verify that procedure DDL operations fail +--echo # under lock tables. +--echo # +--echo # Auxiliary routines to test ALTER. +create procedure p1() begin end; +create function f1() returns int return 1; + +lock table t1 write; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create procedure p2() begin end; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter procedure p1 comment 'changed comment'; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +drop procedure p1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create function f2() returns int return 1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter function f1 comment 'changed comment'; +lock table t1 read; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create procedure p2() begin end; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter procedure p1 comment 'changed comment'; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +drop procedure p1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create function f2() returns int return 1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter function f1 comment 'changed comment'; +unlock tables; +--echo # +--echo # Even if we locked a temporary table. +--echo # Todo: this is a restriction we could possibly lift. +--echo # +drop table t1; +create temporary table t1 (a int); +lock table t1 read; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create procedure p2() begin end; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter procedure p1 comment 'changed comment'; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +drop procedure p1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create function f2() returns int return 1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter function f1 comment 'changed comment'; +unlock tables; + +drop function f1; +drop procedure p1; +drop temporary table t1; + +--echo # +--echo # 3) Verify that CREATE/ALTER/DROP routine grab an +--echo # exclusive lock. +--echo # +--echo # For that, start a transaction, use a routine. In a concurrent +--echo # connection, try to drop or alter the routine. It should place +--echo # a pending or exlusive lock and block. In a concurrnet +--echo # connection, try to use the routine under LOCK TABLES. +--echo # That should yield ER_LOCK_DEADLOCK. +--echo # +--echo # Establish helper connections. +connect(con1, localhost, root,,); +connect(con2, localhost, root,,); + +--echo # +--echo # Test DROP PROCEDURE. +--echo # +--echo # --> connection default +connection default; +create table t1 (a int); +create procedure p1() begin end; +delimiter |; +create function f1() returns int +begin + call p1(); + return 1; +end| +delimiter ;| +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop procedure p1'... +--send drop procedure p1 +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop procedure t1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop procedure p1'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +lock table t1 read; +--error ER_LOCK_DEADLOCK +select f1(); +unlock tables; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop procedure p1'... +--reap +--echo # --> connection default +connection default; + +--echo # +--echo # Test CREATE PROCEDURE. +--echo # +create procedure p1() begin end; +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'create procedure p1'... +--send create procedure p1() begin end +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'create procedure t1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='create procedure p1() begin end'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +lock table t1 read; +--error ER_LOCK_DEADLOCK +select f1(); +unlock tables; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'create procedure p1'... +--error ER_SP_ALREADY_EXISTS +--reap +connection default; +--echo # +--echo # Test ALTER PROCEDURE. +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'alter procedure p1'... +--send alter procedure p1 contains sql +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'alter procedure t1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='alter procedure p1 contains sql'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +lock table t1 read; +--error ER_LOCK_DEADLOCK +select f1(); +unlock tables; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'alter procedure p1'... +--reap +--echo # --> connection default +connection default; + +--echo # +--echo # Test DROP FUNCTION. +--echo # +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f1'... +--send drop function f1 +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +lock table t1 read; +--error ER_LOCK_DEADLOCK +select f1(); +unlock tables; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f1'... +--reap +--echo # --> connection default +connection default; + +--echo # +--echo # Test CREATE FUNCTION. +--echo # +create function f1() returns int return 1; +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'create function f1'... +--send create function f1() returns int return 2 +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'create function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='create function f1() returns int return 2'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +lock table t1 read; +--error ER_LOCK_DEADLOCK +select f1(); +unlock tables; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'create function f1'... +--error ER_SP_ALREADY_EXISTS +--reap +--echo # --> connection default +connection default; +--echo # +--echo # Test ALTER FUNCTION. +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'alter function f1'... +--send alter function f1 contains sql +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'alter function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='alter function f1 contains sql'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +lock table t1 read; +--error ER_LOCK_DEADLOCK +select f1(); +unlock tables; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'alter function f1'... +--reap +--echo # --> connection default +connection default; +drop function f1; +drop procedure p1; + +--echo # +--echo # 4) MDL lock should not be taken for +--echo # unrolled CALL statements. +--echo # The primary goal of metadata locks is a consistent binary log. +--echo # When a call statement is unrolled, it doesn't get to the +--echo # binary log, instead the statements that are contained +--echo # in the procedure body do. This can nest to any level. +--echo # +create procedure p1() begin end; +create procedure p2() begin end; +delimiter |; +create procedure p3() +begin + call p1(); + call p1(); + call p2(); +end| +create procedure p4() +begin + call p1(); + call p1(); + call p2(); + call p2(); + call p3(); +end| +delimiter ;| +begin; +select * from t1; +savepoint sv; +call p4(); +--echo # Prepared statement should not add any locks either. +prepare stmt from "call p4()"; +execute stmt; +execute stmt; +--echo # --> connection con1 +connection con1; +drop procedure p1; +drop procedure p2; +drop procedure p3; +drop procedure p4; +--echo # --> connection default +connection default; +--echo # This is to verify there was no implicit commit. +rollback to savepoint sv; +--error ER_SP_DOES_NOT_EXIST +call p4(); +commit; +drop table t1; + +--echo # +--echo # 5) Locks should be taken on routines +--echo # used indirectly by views or triggers. +--echo # +--echo # +--echo # A function is used from a trigger. +--echo # +create function f1() returns int return 1; +create table t1 (a int); +create table t2 (a int, b int); +create trigger t1_ai after insert on t1 for each row + insert into t2 (a, b) values (new.a, f1()); +begin; +insert into t1 (a) values (1); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f1' +--send drop function f1 +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc +--echo # --> connnection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f1'... +--reap +--echo # --> connection default +connection default; +--echo # +--echo # A function is used from a view. +--echo # +create function f1() returns int return 1; +create view v1 as select f1() as a; +begin; +select * from v1; +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f1' +--send drop function f1 +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc +--echo # --> connnection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f1'... +--reap +--echo # --> connection default +connection default; +--echo # +--echo # A procedure is used from a function. +--echo # +delimiter |; +create function f1() returns int +begin + declare v_out int; + call p1(v_out); + return v_out; +end| +delimiter ;| +create procedure p1(out v_out int) set v_out=3; +begin; +select * from v1; +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop procedure p1' +--send drop procedure p1 +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop procedure p1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop procedure p1'; +--source include/wait_condition.inc +--echo # --> connnection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop procedure p1'... +--reap +--echo # --> connection default +connection default; + +--echo # +--echo # Deep nesting: a function is used from a procedure used +--echo # from a function used from a view used in a trigger. +--echo # +create function f2() returns int return 4; +create procedure p1(out v_out int) set v_out=f2(); +drop trigger t1_ai; +create trigger t1_ai after insert on t1 for each row + insert into t2 (a, b) values (new.a, (select max(a) from v1)); +begin; +insert into t1 (a) values (3); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f2' +--send drop function f2 +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f2' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f2'; +--source include/wait_condition.inc +--echo # --> connnection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f2'... +--reap +--echo # --> connection default +connection default; + +drop view v1; +drop function f1; +drop procedure p1; +drop table t1, t2; + +--echo # +--echo # 6) Check that ER_LOCK_DEADLOCK is reported if +--echo # acquisition of a shared lock fails during a transaction or +--echo # we need to back off to flush the sp cache. +--echo # +--echo # a) A back off due to a lock conflict. +--echo # +create table t1 (a int); +create function f1() returns int return 6; +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f1'... +--send drop function f1 +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc +begin; +select * from t1; +--error ER_LOCK_DEADLOCK +select f1(); +commit; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f1'... +--reap +--echo # --> connection default +connection default; +--echo # +--echo # b) A back off to flush the cache. +--echo # Sic: now this situation does not require a back off since we +--echo # flush the cache on the fly. +--echo # +create function f1() returns int return 7; +begin; +select * from t1; +# Used to have a back-off here, with optional ER_LOCK_DEADLOCK +#--error ER_LOCK_DEADLOCK +select f1(); +commit; +drop table t1; +drop function f1; + +--echo # +--echo # 7) Demonstrate that under LOCK TABLES we accumulate locks +--echo # on stored routines, and release metadata locks in +--echo # ROLLBACK TO SAVEPOINT. That is done only for those stored +--echo # routines that are not part of LOCK TABLES prelocking list. +--echo # Those stored routines that are part of LOCK TABLES +--echo # prelocking list are implicitly locked when entering +--echo # LOCK TABLES, and ROLLBACK TO SAVEPOINT has no effect on +--echo # them. +--echo # +create function f1() returns varchar(20) return "f1()"; +create function f2() returns varchar(20) return "f2()"; +create view v1 as select f1() as a; +set @@session.autocommit=0; +lock table v1 read; +select * from v1; +savepoint sv; +select f2(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f1'... +--send drop function f1 +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc +--echo # Sending 'drop function f2'... +--send drop function f2 +--echo # --> connection default +connection default; +--echo # Waitng for 'drop function f2' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f2'; +--source include/wait_condition.inc +rollback to savepoint sv; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'drop function f2'... +--reap +--echo # --> connection default +connection default; +unlock tables; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f1'... +--reap +--echo # --> connection default +connection default; +--error ER_SP_DOES_NOT_EXIST +drop function f1; +--error ER_SP_DOES_NOT_EXIST +drop function f2; +drop view v1; +set @@session.autocommit=default; + +--echo # +--echo # 8) Check the situation when we're preparing or executing a +--echo # prepared statement, and as part of that try to flush the +--echo # session sp cache. However, one of the procedures that +--echo # needs a flush is in use. Verify that there is no infinite +--echo # reprepare loop and no crash. +--echo # +create function f1() returns int return 1; +delimiter |; +--echo # +--echo # We just mention p1() in the body of f2() to make +--echo # sure that p1() metadata is validated when validating +--echo # 'select f2()'. +--echo # Recursion is not allowed in stored functions, so +--echo # an attempt to just invoke p1() from f2() which is in turn +--echo # called from p1() would have given a run-time error. +--echo # +create function f2() returns int +begin + if @var is null then + call p1(); + end if; + return 1; +end| +create procedure p1() +begin + select f1() into @var; + execute stmt; +end| +delimiter ;| +--echo # --> connection con2 +connection con2; +prepare stmt from "select f2()"; +--echo # --> connection default +connection default; +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'alter function f1 ...'... +--send alter function f1 comment "comment" +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'alter function f1 ...' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info like 'alter function f1 comment%'; +--source include/wait_condition.inc +--echo # Sending 'call p1()'... +--send call p1() +connection default; +--echo # Waitng for 'call p1()' to get blocked on MDL lock on f1... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1() into @var'; +--source include/wait_condition.inc +--echo # Let 'alter function f1 ...' go through... +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'alter function f1 ...' +--reap +--echo # --> connection con2 +connection con2; +--echo # Reaping 'call p1()'... +--reap +deallocate prepare stmt; +--echo # --> connection default +connection default; +drop function f1; +drop function f2; +drop procedure p1; + +--echo # +--echo # 9) Check the situation when a stored function is invoked +--echo # from a stored procedure, and recursively invokes the +--echo # stored procedure that is in use. But for the second +--echo # invocation, a cache flush is requested. We can't +--echo # flush the procedure that's in use, and are forced +--echo # to use an old version. It is not a violation of +--echo # consistency, since we unroll top-level calls. +--echo # Just verify the code works. +--echo # +create function f1() returns int return 1; +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'alter function f1 ...'... +--send alter function f1 comment "comment" +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'alter function f1 ...' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info like 'alter function f1 comment%'; +--source include/wait_condition.inc +delimiter |; +--echo # +--echo # We just mention p1() in the body of f2() to make +--echo # sure that p1() is prelocked for f2(). +--echo # Recursion is not allowed in stored functions, so +--echo # an attempt to just invoke p1() from f2() which is in turn +--echo # called from p1() would have given a run-time error. +--echo # +create function f2() returns int +begin + if @var is null then + call p1(); + end if; + return 1; +end| +create procedure p1() +begin + select f1() into @var; + select f2() into @var; +end| +delimiter ;| +--echo # Sending 'call p1()'... +--send call p1() +connection default; +--echo # Waitng for 'call p1()' to get blocked on MDL lock on f1... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1() into @var'; +--source include/wait_condition.inc +--echo # Let 'alter function f1 ...' go through... +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'alter function f1 ...' +--reap +--echo # --> connection con2 +connection con2; +--echo # Reaping 'call p1()'... +--reap +--echo # --> connection default +connection default; +drop function f1; +drop function f2; +drop procedure p1; + +--echo # +--echo # 10) A select from information_schema.routines now +--echo # flushes the stored routines caches. Test that this +--echo # does not remove from the cache a stored routine +--echo # that is already prelocked. +--echo # +create function f1() returns int return get_lock("30977", 100000); +create function f2() returns int return 2; +delimiter |; +create function f3() returns varchar(255) +begin + declare res varchar(255); + declare c cursor for select routine_name from + information_schema.routines where routine_name='f1'; + select f1() into @var; + open c; + fetch c into res; + close c; + select f2() into @var; + return res; +end| +delimiter ;| +--echo # --> connection con1 +connection con1; +select get_lock("30977", 0); +--echo # --> connection default +connection default; +--echo # Sending 'select f3()'... +--send select f3() +--echo # --> connection con1 +connection con1; +--echo # Waitng for 'select f3()' to get blocked on the user level lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='User lock' and info='select f1() into @var'; +--source include/wait_condition.inc +--echo # Do something to change the cache version. +create function f4() returns int return 4; +drop function f4; +select release_lock("30977"); +--echo # --> connection default +connection default; +--echo # Reaping 'select f3()'... +--echo # Routine 'f2()' should exist and get executed successfully. +--reap +select @var; +drop function f1; +drop function f2; +drop function f3; + + +--echo # 11) Check the situation when the connection is flushing the +--echo # SP cache which contains a procedure that is being executed. +--echo # +--echo # Function f1() calls p1(). Procedure p1() has a DROP +--echo # VIEW statement, which, we know, invalidates the routines cache. +--echo # During cache flush p1() must not be flushed since it's in +--echo # use. +--echo # +delimiter |; +create function f1() returns int +begin + call p1(); + return 1; +end| +create procedure p1() +begin + create view v1 as select 1; + drop view v1; + select f1() into @var; + set @exec_count=@exec_count+1; +end| +delimiter ;| +set @exec_count=0; +--error ER_SP_RECURSION_LIMIT +call p1(); +select @exec_count; +set @@session.max_sp_recursion_depth=5; +set @exec_count=0; +--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +call p1(); +select @exec_count; +drop procedure p1; +drop function f1; +set @@session.max_sp_recursion_depth=default; + +--echo # --> connection con1 +connection con1; +disconnect con1; +--source include/wait_until_disconnected.inc +--echo # --> connection con2 +connection con2; +disconnect con2; +--source include/wait_until_disconnected.inc +--echo # --> connection default +connection default; +--echo # +--echo # End of 5.5 tests +--echo # diff --git a/sql/lock.cc b/sql/lock.cc index 31773585bff..6cdf2e4a202 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -980,6 +980,57 @@ void unlock_table_names(THD *thd) } +/** + Obtain an exclusive metadata lock on the stored routine name. + + @param thd Thread handle. + @param is_function Stored routine type (only functions or procedures + are name-locked. + @param db The schema the routine belongs to. + @param name Routine name. + + This function assumes that no metadata locks were acquired + before calling it. Additionally, it cannot be called while + holding LOCK_open mutex. Both these invariants are enforced by + asserts in MDL_context::acquire_exclusive_locks(). + To avoid deadlocks, we do not try to obtain exclusive metadata + locks in LOCK TABLES mode, since in this mode there may be + other metadata locks already taken by the current connection, + and we must not wait for MDL locks while holding locks. + + @retval FALSE Success. + @retval TRUE Failure: we're in LOCK TABLES mode, or out of memory, + or this connection was killed. +*/ + +bool lock_routine_name(THD *thd, bool is_function, + const char *db, const char *name) +{ + MDL_key::enum_mdl_namespace mdl_type= (is_function ? + MDL_key::FUNCTION : + MDL_key::PROCEDURE); + MDL_request mdl_request; + + if (thd->locked_tables_mode) + { + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + return TRUE; + } + + DBUG_ASSERT(name); + DEBUG_SYNC(thd, "before_wait_locked_pname"); + + mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE); + + if (thd->mdl_context.acquire_exclusive_lock(&mdl_request)) + return TRUE; + + DEBUG_SYNC(thd, "after_wait_locked_pname"); + return FALSE; +} + + static void print_lock_error(int error, const char *table) { int textno; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index caf3130c517..124392f4c63 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -2111,6 +2111,9 @@ void broadcast_refresh(void); bool lock_table_names(THD *thd, TABLE_LIST *table_list); void unlock_table_names(THD *thd); +/* Lock based on stored routine name */ +bool lock_routine_name(THD *thd, bool is_function, const char *db, + const char *name); /* old unireg functions */ diff --git a/sql/sp.cc b/sql/sp.cc index 279f0d44890..1375d44cb9b 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -753,6 +753,11 @@ sp_create_routine(THD *thd, int type, sp_head *sp) */ thd->clear_current_stmt_binlog_row_based(); + /* Grab an exclusive MDL lock. */ + if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, + sp->m_db.str, sp->m_name.str)) + DBUG_RETURN(SP_OPEN_TABLE_FAILED); + saved_count_cuted_fields= thd->count_cuted_fields; thd->count_cuted_fields= CHECK_FIELD_WARN; @@ -919,7 +924,10 @@ sp_create_routine(THD *thd, int type, sp_head *sp) ret= SP_OK; if (table->file->ha_write_row(table->record[0])) ret= SP_WRITE_ROW_FAILED; - else if (mysql_bin_log.is_open()) + if (ret == SP_OK) + sp_cache_invalidate(); + + if (ret == SP_OK && mysql_bin_log.is_open()) { thd->clear_error(); @@ -948,7 +956,6 @@ sp_create_routine(THD *thd, int type, sp_head *sp) FALSE, FALSE, 0); thd->variables.sql_mode= 0; } - } done: @@ -994,6 +1001,11 @@ sp_drop_routine(THD *thd, int type, sp_name *name) */ thd->clear_current_stmt_binlog_row_based(); + /* Grab an exclusive MDL lock. */ + if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, + name->m_db.str, name->m_name.str)) + DBUG_RETURN(SP_DELETE_ROW_FAILED); + if (!(table= open_proc_table_for_update(thd))) DBUG_RETURN(SP_OPEN_TABLE_FAILED); if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK) @@ -1006,6 +1018,20 @@ sp_drop_routine(THD *thd, int type, sp_name *name) { write_bin_log(thd, TRUE, thd->query(), thd->query_length()); sp_cache_invalidate(); + + /* + A lame workaround for lack of cache flush: + make sure the routine is at least gone from the + local cache. + */ + { + sp_head *sp; + sp_cache **spc= (type == TYPE_ENUM_FUNCTION ? + &thd->sp_func_cache : &thd->sp_proc_cache); + sp= sp_cache_lookup(spc, name); + if (sp) + sp_cache_flush_obsolete(spc, &sp); + } } close_thread_tables(thd); @@ -1041,6 +1067,12 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || type == TYPE_ENUM_FUNCTION); + + /* Grab an exclusive MDL lock. */ + if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, + name->m_db.str, name->m_name.str)) + DBUG_RETURN(SP_OPEN_TABLE_FAILED); + /* This statement will be replicated as a statement, even when using row-based replication. The flag will be reset at the end of the @@ -1052,6 +1084,30 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) DBUG_RETURN(SP_OPEN_TABLE_FAILED); if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK) { + if (type == TYPE_ENUM_FUNCTION && ! trust_function_creators && + mysql_bin_log.is_open() && + (chistics->daccess == SP_CONTAINS_SQL || + chistics->daccess == SP_MODIFIES_SQL_DATA)) + { + char *ptr; + bool is_deterministic; + ptr= get_field(thd->mem_root, + table->field[MYSQL_PROC_FIELD_DETERMINISTIC]); + if (ptr == NULL) + { + ret= SP_INTERNAL_ERROR; + goto err; + } + is_deterministic= ptr[0] == 'N' ? FALSE : TRUE; + if (!is_deterministic) + { + my_message(ER_BINLOG_UNSAFE_ROUTINE, + ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0)); + ret= SP_INTERNAL_ERROR; + goto err; + } + } + store_record(table,record[1]); table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_MODIFIED])->set_time(); @@ -1077,7 +1133,7 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) write_bin_log(thd, TRUE, thd->query(), thd->query_length()); sp_cache_invalidate(); } - +err: close_thread_tables(thd); DBUG_RETURN(ret); } @@ -1161,10 +1217,7 @@ err: bool sp_show_create_routine(THD *thd, int type, sp_name *name) { - bool err_status= TRUE; sp_head *sp; - sp_cache **cache = type == TYPE_ENUM_PROCEDURE ? - &thd->sp_proc_cache : &thd->sp_func_cache; DBUG_ENTER("sp_show_create_routine"); DBUG_PRINT("enter", ("name: %.*s", @@ -1174,28 +1227,29 @@ sp_show_create_routine(THD *thd, int type, sp_name *name) DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || type == TYPE_ENUM_FUNCTION); - if (type == TYPE_ENUM_PROCEDURE) + /* + @todo: Consider using prelocking for this code as well. Currently + SHOW CREATE PROCEDURE/FUNCTION is a dirty read of the data + dictionary, i.e. takes no metadata locks. + It is "safe" to do as long as it doesn't affect the results + of the binary log or the query cache, which currently it does not. + */ + if (sp_cache_routine(thd, type, name, FALSE, &sp)) + DBUG_RETURN(TRUE); + + if (sp == NULL || sp->show_create_routine(thd, type)) { /* - SHOW CREATE PROCEDURE may require two instances of one sp_head - object when SHOW CREATE PROCEDURE is called for the procedure that - is being executed. Basically, there is no actual recursion, so we - increase the recursion limit for this statement (kind of hack). - - SHOW CREATE FUNCTION does not require this because SHOW CREATE - statements are prohibitted within stored functions. - */ - - thd->variables.max_sp_recursion_depth++; + If we have insufficient privileges, pretend the routine + does not exist. + */ + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), + type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE", + name->m_name.str); + DBUG_RETURN(TRUE); } - if ((sp= sp_find_routine(thd, type, name, cache, FALSE))) - err_status= sp->show_create_routine(thd, type); - - if (type == TYPE_ENUM_PROCEDURE) - thd->variables.max_sp_recursion_depth--; - - DBUG_RETURN(err_status); + DBUG_RETURN(FALSE); } @@ -1451,6 +1505,7 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn); prelocking_ctx->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next); rn->belong_to_view= belong_to_view; + rn->m_sp_cache_version= 0; return TRUE; } return FALSE; @@ -1596,41 +1651,81 @@ void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, } +/** + A helper wrapper around sp_cache_routine() to use from + prelocking until 'sp_name' is eradicated as a class. +*/ + +int sp_cache_routine(THD *thd, Sroutine_hash_entry *rt, + bool lookup_only, sp_head **sp) +{ + char qname_buff[NAME_LEN*2+1+1]; + sp_name name(&rt->mdl_request.key, qname_buff); + MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace(); + int type= ((mdl_type == MDL_key::FUNCTION) ? + TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE); + + /* + Check that we have an MDL lock on this routine, unless it's a top-level + CALL. The assert below should be unambiguous: the first element + in sroutines_list has an MDL lock unless it's a top-level call, or a + trigger, but triggers can't occur here (see the preceding assert). + */ + DBUG_ASSERT(rt->mdl_request.ticket || + rt == (Sroutine_hash_entry*) thd->lex->sroutines_list.first); + + return sp_cache_routine(thd, type, &name, lookup_only, sp); +} + + /** Ensure that routine is present in cache by loading it from the mysql.proc - table if needed. Emit an appropriate error if there was a problem during + table if needed. If the routine is present but old, reload it. + Emit an appropriate error if there was a problem during loading. @param[in] thd Thread context. @param[in] type Type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE). @param[in] name Name of routine. + @param[in] lookup_only Only check that the routine is in the cache. + If it's not, don't try to load. If it is present, + but old, don't try to reload. @param[out] sp Pointer to sp_head object for routine, NULL if routine was - not found, + not found. @retval 0 Either routine is found and was succesfully loaded into cache or it does not exist. @retval non-0 Error while loading routine from mysql,proc table. */ -int sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp) +int sp_cache_routine(THD *thd, int type, sp_name *name, + bool lookup_only, sp_head **sp) { int ret= 0; + sp_cache **spc= (type == TYPE_ENUM_FUNCTION ? + &thd->sp_func_cache : &thd->sp_proc_cache); DBUG_ENTER("sp_cache_routine"); DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE); - if (!(*sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ? - &thd->sp_func_cache : &thd->sp_proc_cache), - name))) + + *sp= sp_cache_lookup(spc, name); + + if (lookup_only) + DBUG_RETURN(SP_OK); + + if (*sp) + { + sp_cache_flush_obsolete(spc, sp); + if (*sp) + DBUG_RETURN(SP_OK); + } + + switch ((ret= db_find_routine(thd, type, name, sp))) { - switch ((ret= db_find_routine(thd, type, name, sp))) - { case SP_OK: - if (type == TYPE_ENUM_FUNCTION) - sp_cache_insert(&thd->sp_func_cache, *sp); - else - sp_cache_insert(&thd->sp_proc_cache, *sp); + sp_cache_insert(spc, *sp); break; case SP_KEY_NOT_FOUND: ret= SP_OK; @@ -1669,7 +1764,6 @@ int sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp) my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret); } break; - } } DBUG_RETURN(ret); } diff --git a/sql/sp.h b/sql/sp.h index a66d72d0e9e..cab4d7dbeb5 100644 --- a/sql/sp.h +++ b/sql/sp.h @@ -43,7 +43,13 @@ sp_find_routine(THD *thd, int type, sp_name *name, sp_cache **cp, bool cache_only); int -sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp); +sp_cache_routine(THD *thd, Sroutine_hash_entry *rt, + bool lookup_only, sp_head **sp); + + +int +sp_cache_routine(THD *thd, int type, sp_name *name, + bool lookup_only, sp_head **sp); bool sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any); @@ -88,6 +94,16 @@ public: statement uses routine both via view and directly. */ TABLE_LIST *belong_to_view; + /** + This is for prepared statement validation purposes. + A statement looks up and pre-loads all its stored functions + at prepare. Later on, if a function is gone from the cache, + execute may fail. + Remember the version of sp_head at prepare to be able to + invalidate the prepared statement at execute if it + changes. + */ + ulong m_sp_cache_version; }; diff --git a/sql/sp_cache.cc b/sql/sp_cache.cc index d9a23d2be4e..bb962ad0300 100644 --- a/sql/sp_cache.cc +++ b/sql/sp_cache.cc @@ -31,8 +31,6 @@ static ulong volatile Cversion= 0; class sp_cache { public: - ulong version; - sp_cache(); ~sp_cache(); @@ -48,25 +46,10 @@ public: namelen); } -#ifdef NOT_USED - inline bool remove(char *name, uint namelen) + inline void remove(sp_head *sp) { - sp_head *sp= lookup(name, namelen); - if (sp) - { - hash_delete(&m_hashtable, (uchar *)sp); - return TRUE; - } - return FALSE; + my_hash_delete(&m_hashtable, (uchar *)sp); } -#endif - - inline void remove_all() - { - cleanup(); - init(); - } - private: void init(); void cleanup(); @@ -129,8 +112,9 @@ void sp_cache_insert(sp_cache **cp, sp_head *sp) { if (!(c= new sp_cache())) return; // End of memory error - c->version= Cversion; // No need to lock when reading long variable } + /* Reading a ulong variable with no lock. */ + sp->set_sp_cache_version(Cversion); DBUG_PRINT("info",("sp_cache: inserting: %.*s", (int) sp->m_qname.length, sp->m_qname.str)); c->insert(sp); @@ -181,46 +165,34 @@ void sp_cache_invalidate() } -/* - Remove out-of-date SPs from the cache. - - SYNOPSIS - sp_cache_flush_obsolete() - cp Cache to flush +/** + Remove an out-of-date SP from the cache. - NOTE - This invalidates pointers to sp_head objects this thread uses. - In practice that means 'dont call this function when inside SP'. + @param[in] cp Cache to flush + @param[in] sp SP to remove. + + @note This invalidates pointers to sp_head objects this thread + uses. In practice that means 'dont call this function when + inside SP'. */ -void sp_cache_flush_obsolete(sp_cache **cp) +void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp) { - sp_cache *c= *cp; - if (c) + if ((*sp)->sp_cache_version() < Cversion && !(*sp)->is_invoked()) { - ulong v; - v= Cversion; // No need to lock when reading long variable - if (c->version < v) - { - DBUG_PRINT("info",("sp_cache: deleting all functions")); - /* We need to delete all elements. */ - c->remove_all(); - c->version= v; - } + (*cp)->remove(*sp); + *sp= NULL; } } /** - Return the current version of the cache. + Return the current global version of the cache. */ -ulong sp_cache_version(sp_cache **cp) +ulong sp_cache_version() { - sp_cache *c= *cp; - if (c) - return c->version; - return 0; + return Cversion; } @@ -265,7 +237,6 @@ sp_cache::init() { my_hash_init(&m_hashtable, system_charset_info, 0, 0, 0, hash_get_key_for_sp_head, hash_free_sp_head, 0); - version= 0; } diff --git a/sql/sp_cache.h b/sql/sp_cache.h index f4d44a1f29f..7dbb0d5429c 100644 --- a/sql/sp_cache.h +++ b/sql/sp_cache.h @@ -57,7 +57,7 @@ void sp_cache_clear(sp_cache **cp); void sp_cache_insert(sp_cache **cp, sp_head *sp); sp_head *sp_cache_lookup(sp_cache **cp, sp_name *name); void sp_cache_invalidate(); -void sp_cache_flush_obsolete(sp_cache **cp); -ulong sp_cache_version(sp_cache **cp); +void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp); +ulong sp_cache_version(); #endif /* _SP_CACHE_H_ */ diff --git a/sql/sp_head.cc b/sql/sp_head.cc index da99d9e0f6a..4f5ca1fff04 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -511,7 +511,10 @@ sp_head::operator delete(void *ptr, size_t size) throw() sp_head::sp_head() :Query_arena(&main_mem_root, INITIALIZED_FOR_SP), - m_flags(0), m_recursion_level(0), m_next_cached_sp(0), + m_flags(0), + m_sp_cache_version(0), + m_recursion_level(0), + m_next_cached_sp(0), m_cont_level(0) { const LEX_STRING str_reset= { NULL, 0 }; @@ -727,16 +730,6 @@ create_typelib(MEM_ROOT *mem_root, Create_field *field_def, List *src) } -int -sp_head::create(THD *thd) -{ - DBUG_ENTER("sp_head::create"); - DBUG_PRINT("info", ("type: %d name: %s params: %s body: %s", - m_type, m_name.str, m_params.str, m_body.str)); - - DBUG_RETURN(sp_create_routine(thd, m_type, this)); -} - sp_head::~sp_head() { DBUG_ENTER("sp_head::~sp_head"); diff --git a/sql/sp_head.h b/sql/sp_head.h index 74fcd03180e..bac97a1fd77 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -175,7 +175,34 @@ public: LEX_STRING m_definer_user; LEX_STRING m_definer_host; + /** + Is this routine being executed? + */ + bool is_invoked() const { return m_flags & IS_INVOKED; } + + /** + Get the value of the SP cache version, as remembered + when the routine was inserted into the cache. + */ + ulong sp_cache_version() const { return m_sp_cache_version; } + + /** Set the value of the SP cache version. */ + void set_sp_cache_version(ulong version_arg) + { + m_sp_cache_version= version_arg; + } private: + /** + Version of the stored routine cache at the moment when the + routine was added to it. Is used only for functions and + procedures, not used for triggers or events. When sp_head is + created, its version is 0. When it's added to the cache, the + version is assigned the global value 'Cversion'. + If later on Cversion is incremented, we know that the routine + is obsolete and should not be used -- + sp_cache_flush_obsolete() will purge it. + */ + ulong m_sp_cache_version; Stored_program_creation_ctx *m_creation_ctx; public: @@ -263,9 +290,6 @@ public: void set_stmt_end(THD *thd); - int - create(THD *thd); - virtual ~sp_head(); /// Free memory diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 459ca646d8c..78bb9f9bad7 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -21,6 +21,7 @@ #include "sql_select.h" #include "sp_head.h" #include "sp.h" +#include "sp_cache.h" #include "sql_trigger.h" #include "transaction.h" #include "sql_prepare.h" @@ -3479,6 +3480,66 @@ check_and_update_table_version(THD *thd, } +/** + Compares versions of a stored routine obtained from the sp cache + and the version used at prepare. + + @details If the new and the old values mismatch, invoke + Metadata_version_observer. + At prepared statement prepare, all Sroutine_hash_entry version values + are NULL and we always have a mismatch. But there is no observer set + in THD, and therefore no error is reported. Instead, we update + the value in Sroutine_hash_entry, effectively recording the original + version. + At prepared statement execute, an observer may be installed. If + there is a version mismatch, we push an error and return TRUE. + + For conventional execution (no prepared statements), the + observer is never installed. + + @param[in] thd used to report errors + @param[in/out] rt pointer to stored routine entry in the + parse tree + @param[in] sp pointer to stored routine cache entry. + Can be NULL if there is no such routine. + @retval TRUE an error, which has been reported + @retval FALSE success, version in Sroutine_hash_entry has been updated +*/ + +static bool +check_and_update_routine_version(THD *thd, Sroutine_hash_entry *rt, + sp_head *sp) +{ + ulong spc_version= sp_cache_version(); + /* sp is NULL if there is no such routine. */ + ulong version= sp ? sp->sp_cache_version() : spc_version; + /* + If the version in the parse tree is stale, + or the version in the cache is stale and sp is not used, + we need to reprepare. + Sic: version != spc_version <--> sp is not NULL. + */ + if (rt->m_sp_cache_version != version || + (version != spc_version && !sp->is_invoked())) + { + if (thd->m_reprepare_observer && + thd->m_reprepare_observer->report_error(thd)) + { + /* + Version of the sp cache is different from the + previous execution of the prepared statement, and it is + unacceptable for this SQLCOM. Error has been reported. + */ + DBUG_ASSERT(thd->is_error()); + return TRUE; + } + /* Always maintain the latest cache version. */ + rt->m_sp_cache_version= version; + } + return FALSE; +} + + /** Open view by getting its definition from disk (and table cache in future). @@ -3696,13 +3757,16 @@ request_backoff_action(enum_open_table_action action_arg) /** - Recover from failed attempt ot open table by performing requested action. + Recover from failed attempt of open table by performing requested action. @param thd Thread context - @param table Table list element for table that caused problem - @param action Type of action requested by failed open_table() call + @param mdl_request MDL_request of the object that caused the problem. + @param table Optional (can be NULL). Used only if action is OT_REPAIR. + In that case a TABLE_LIST for the table to be repaired. + @todo: It's unnecessary and should be removed. - @pre This function should be called only with "action" != OT_NO_ACTION. + @pre This function should be called only with "action" != OT_NO_ACTION + and after having called @sa close_tables_for_reopen(). @retval FALSE - Success. One should try to open tables once again. @retval TRUE - Error @@ -3710,7 +3774,8 @@ request_backoff_action(enum_open_table_action action_arg) bool Open_table_context:: -recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table) +recover_from_failed_open(THD *thd, MDL_request *mdl_request, + TABLE_LIST *table) { bool result= FALSE; /* Execute the action. */ @@ -3723,14 +3788,20 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table) break; case OT_DISCOVER: { - MDL_request mdl_xlock_request(&table->mdl_request); + MDL_request mdl_xlock_request(mdl_request); mdl_xlock_request.set_type(MDL_EXCLUSIVE); if ((result= thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request))) break; + + DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); pthread_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); - ha_create_table_from_engine(thd, table->db, table->table_name); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, + mdl_request->key.db_name(), + mdl_request->key.name()); + ha_create_table_from_engine(thd, + mdl_request->key.db_name(), + mdl_request->key.name()); pthread_mutex_unlock(&LOCK_open); thd->warning_info->clear_warning_info(thd->query_id); @@ -3740,14 +3811,17 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table) } case OT_REPAIR: { - MDL_request mdl_xlock_request(&table->mdl_request); + MDL_request mdl_xlock_request(mdl_request); mdl_xlock_request.set_type(MDL_EXCLUSIVE); if ((result= thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request))) break; + DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); pthread_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, + mdl_request->key.db_name(), + mdl_request->key.name()); pthread_mutex_unlock(&LOCK_open); result= auto_repair_table(thd, table); @@ -3808,7 +3882,11 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) @param[in] prelocking_strategy Strategy which specifies how the prelocking set should be extended when one of its elements is processed. - @param[out] need_prelocking Set to TRUE if it was detected that this + @param[in] has_prelocking_list Indicates that prelocking set/list for + this statement has already been built. + @param[in] ot_ctx Context of open_table used to recover from + locking failures. + @param[out] need_prelocking Set to TRUE if it was detected that this statement will require prelocked mode for its execution, not touched otherwise. @@ -3820,32 +3898,99 @@ static bool open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, Sroutine_hash_entry *rt, Prelocking_strategy *prelocking_strategy, + bool has_prelocking_list, + Open_table_context *ot_ctx, bool *need_prelocking) { + MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace(); DBUG_ENTER("open_and_process_routine"); - switch (rt->mdl_request.key.mdl_namespace()) + switch (mdl_type) { case MDL_key::FUNCTION: case MDL_key::PROCEDURE: { - char qname_buff[NAME_LEN*2+1+1]; - sp_name name(&rt->mdl_request.key, qname_buff); sp_head *sp; - int type= (rt->mdl_request.key.mdl_namespace() == MDL_key::FUNCTION) ? - TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE; - - if (sp_cache_routine(thd, type, &name, &sp)) - DBUG_RETURN(TRUE); - - if (sp) + /* + Try to get MDL lock on the routine. + Note that we do not take locks on top-level CALLs as this can + lead to a deadlock. Not locking top-level CALLs does not break + the binlog as only the statements in the called procedure show + up there, not the CALL itself. + */ + if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first || + mdl_type != MDL_key::PROCEDURE) { - prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp, - need_prelocking); + ot_ctx->add_request(&rt->mdl_request); + if (thd->mdl_context.try_acquire_shared_lock(&rt->mdl_request)) + DBUG_RETURN(TRUE); + + if (rt->mdl_request.ticket == NULL) + { + /* A lock conflict. Someone's trying to modify SP metadata. */ + ot_ctx->request_backoff_action(Open_table_context::OT_WAIT); + DBUG_RETURN(TRUE); + } + DEBUG_SYNC(thd, "after_shared_lock_pname"); + + /* Ensures the routine is up-to-date and cached, if exists. */ + if (sp_cache_routine(thd, rt, has_prelocking_list, &sp)) + DBUG_RETURN(TRUE); + + /* Remember the version of the routine in the parse tree. */ + if (check_and_update_routine_version(thd, rt, sp)) + DBUG_RETURN(TRUE); + + /* 'sp' is NULL when there is no such routine. */ + if (sp && !has_prelocking_list) + { + prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp, + need_prelocking); + } + } + else + { + /* + If it's a top level call, just make sure we have a recent + version of the routine, if it exists. + Validating routine version is unnecessary, since CALL + does not affect the prepared statement prelocked list. + */ + sp_cache_routine(thd, rt, FALSE, &sp); } } break; case MDL_key::TRIGGER: + /** + We add trigger entries to lex->sroutines_list, but we don't + load them here. The trigger entry is only used when building + a transitive closure of objects used in a statement, to avoid + adding to this closure objects that are used in the trigger more + than once. + E.g. if a trigger trg refers to table t2, and the trigger table t1 + is used multiple times in the statement (say, because it's used in + function f1() twice), we will only add t2 once to the list of + tables to prelock. + + We don't take metadata locks on triggers either: they are protected + by a respective lock on the table, on which the trigger is defined. + + The only two cases which give "trouble" are SHOW CREATE TRIGGER + and DROP TRIGGER statements. For these, statement syntax doesn't + specify the table on which this trigger is defined, so we have + to make a "dirty" read in the data dictionary to find out the + table name. Once we discover the table name, we take a metadata + lock on it, and this protects all trigger operations. + Of course the table, in theory, may disappear between the dirty + read and metadata lock acquisition, but in that case we just return + a run-time error. + + Grammar of other trigger DDL statements (CREATE, DROP) requires + the table to be specified explicitly, so we use the table metadata + lock to protect trigger metadata in these statements. Similarly, in + DML we always use triggers together with their tables, and thus don't + need to take separate metadata locks on them. + */ break; default: /* Impossible type value. */ @@ -3965,7 +4110,7 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, if (error) { - if (! ot_ctx->can_recover_from_failed_open_table() && safe_to_ignore_table) + if (! ot_ctx->can_recover_from_failed_open() && safe_to_ignore_table) { DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'", tables->db, tables->alias)); @@ -4150,7 +4295,7 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, Open_table_context ot_ctx(thd); bool error= FALSE; MEM_ROOT new_frm_mem; - bool has_prelocking_list= thd->lex->requires_prelocking(); + bool has_prelocking_list; DBUG_ENTER("open_tables"); /* @@ -4172,7 +4317,8 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, init_sql_alloc(&new_frm_mem, 8024, 8024); thd->current_tablenr= 0; - restart: +restart: + has_prelocking_list= thd->lex->requires_prelocking(); table_to_open= start; sroutine_to_open= (Sroutine_hash_entry**) &thd->lex->sroutines_list.first; *counter= 0; @@ -4184,7 +4330,6 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, */ while (*table_to_open || (thd->locked_tables_mode <= LTM_LOCK_TABLES && - ! has_prelocking_list && *sroutine_to_open)) { /* @@ -4201,7 +4346,7 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, if (error) { - if (ot_ctx.can_recover_from_failed_open_table()) + if (ot_ctx.can_recover_from_failed_open()) { /* We have met exclusive metadata lock or old version of table. @@ -4220,12 +4365,14 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, */ TABLE_LIST *failed_table= *table_to_open; close_tables_for_reopen(thd, start); + /* Here we rely on the fact that 'tables' still points to the valid TABLE_LIST element. Altough currently this assumption is valid it may change in future. */ - if (ot_ctx.recover_from_failed_open_table_attempt(thd, failed_table)) + if (ot_ctx.recover_from_failed_open(thd, &failed_table->mdl_request, + failed_table)) goto err; error= FALSE; @@ -4239,8 +4386,11 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, If we are not already in prelocked mode and extended table list is not yet built for our statement we need to cache routines it uses and build the prelocking list for it. + If we are not in prelocked mode but have built the extended table + list, we still need to call open_and_process_routine() to take + MDL locks on the routines. */ - if (thd->locked_tables_mode <= LTM_LOCK_TABLES && ! has_prelocking_list) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) { bool need_prelocking= FALSE; TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; @@ -4256,12 +4406,21 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, for (Sroutine_hash_entry *rt= *sroutine_to_open; rt; sroutine_to_open= &rt->next, rt= rt->next) { - error= open_and_process_routine(thd, thd->lex, rt, - prelocking_strategy, + error= open_and_process_routine(thd, thd->lex, rt, prelocking_strategy, + has_prelocking_list, &ot_ctx, &need_prelocking); if (error) { + if (ot_ctx.can_recover_from_failed_open()) + { + close_tables_for_reopen(thd, start); + if (ot_ctx.recover_from_failed_open(thd, &rt->mdl_request, NULL)) + goto err; + + error= FALSE; + goto restart; + } /* Serious error during reading stored routines from mysql.proc table. Something is wrong with the table or its contents, and an error has @@ -4666,7 +4825,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, retry: while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, 0)) && - ot_ctx.can_recover_from_failed_open_table()) + ot_ctx.can_recover_from_failed_open()) { /* We can't back off with an open HANDLER, we don't wait with locks. */ DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); @@ -4677,7 +4836,8 @@ retry: */ thd->mdl_context.release_transactional_locks(); table_list->mdl_request.ticket= 0; - if (ot_ctx.recover_from_failed_open_table_attempt(thd, table_list)) + if (ot_ctx.recover_from_failed_open(thd, &table_list->mdl_request, + table_list)) break; } @@ -5242,11 +5402,18 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) if (first_not_own_table == *tables) *tables= 0; thd->lex->chop_off_not_own_tables(); + /* Reset MDL tickets for procedures/functions */ + for (Sroutine_hash_entry *rt= + (Sroutine_hash_entry*)thd->lex->sroutines_list.first; + rt; rt= rt->next) + rt->mdl_request.ticket= NULL; sp_remove_not_own_routines(thd->lex); for (tmp= *tables; tmp; tmp= tmp->next_global) { tmp->table= 0; tmp->mdl_request.ticket= NULL; + /* We have to cleanup translation tables of views. */ + tmp->cleanup_items(); } /* Metadata lock requests for tables from extended part of prelocking set @@ -8363,6 +8530,10 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests) MDL_request_list::Iterator it(*mdl_requests); while ((mdl_request= it++)) { + /* Skip requests on non-TDC objects. */ + if (mdl_request->key.mdl_namespace() != MDL_key::TABLE) + continue; + if ((share= get_cached_table_share(mdl_request->key.db_name(), mdl_request->key.name())) && share->version != refresh_version) diff --git a/sql/sql_class.h b/sql/sql_class.h index 11e0010d85b..ff1b51e7e87 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1271,7 +1271,7 @@ private: /** A context of open_tables() function, used to recover - from a failed open_table() attempt. + from a failed open_table() or open_routine() attempt. Implemented in sql_base.cc. */ @@ -1288,13 +1288,14 @@ public: }; Open_table_context(THD *thd); - bool recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *tables); + bool recover_from_failed_open(THD *thd, MDL_request *mdl_request, + TABLE_LIST *table); bool request_backoff_action(enum_open_table_action action_arg); void add_request(MDL_request *request) { m_mdl_requests.push_front(request); } - bool can_recover_from_failed_open_table() const + bool can_recover_from_failed_open() const { return m_action != OT_NO_ACTION; } bool can_deadlock() const { return m_can_deadlock; } private: diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 79f10120268..689b2cec270 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -284,14 +284,14 @@ void init_update_queries(void) sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_REVOKE_ALL]= CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA; @@ -319,10 +319,6 @@ void init_update_queries(void) sql_command_flags[SQLCOM_REVOKE]|= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_GRANT]|= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_CREATE_PROCEDURE]|= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_CREATE_SPFUNCTION]|= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_ALTER_PROCEDURE]|= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_ALTER_FUNCTION]|= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_PRELOAD_KEYS]= CF_AUTO_COMMIT_TRANS; @@ -1988,9 +1984,10 @@ mysql_execute_command(THD *thd) #endif case SQLCOM_SHOW_STATUS_PROC: case SQLCOM_SHOW_STATUS_FUNC: - if (!(res= check_table_access(thd, SELECT_ACL, all_tables, FALSE, + if ((res= check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE))) - res= execute_sqlcom_select(thd, all_tables); + goto error; + res= execute_sqlcom_select(thd, all_tables); break; case SQLCOM_SHOW_STATUS: { @@ -3939,7 +3936,7 @@ end_with_restore_list: if (sp_process_definer(thd)) goto create_sp_error; - res= (sp_result= lex->sphead->create(thd)); + res= (sp_result= sp_create_routine(thd, lex->sphead->m_type, lex->sphead)); switch (sp_result) { case SP_OK: { #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -3949,6 +3946,16 @@ end_with_restore_list: bool restore_backup_context= false; Security_context *backup= NULL; LEX_USER *definer= thd->lex->definer; + /* + We're going to issue an implicit GRANT statement. + It takes metadata locks and updates system tables. + Make sure that sp_create_routine() did not leave any + locks in the MDL context, so there is no risk to + deadlock. + */ + trans_commit_implicit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); /* Check if the definer exists on slave, then use definer privilege to insert routine privileges to mysql.procs_priv. @@ -4021,7 +4028,6 @@ create_sp_error: case SQLCOM_CALL: { sp_head *sp; - /* This will cache all SP and SF and open and lock all tables required for execution. @@ -4117,65 +4123,22 @@ create_sp_error: case SQLCOM_ALTER_FUNCTION: { int sp_result; - sp_head *sp; - st_sp_chistics chistics; + int type= (lex->sql_command == SQLCOM_ALTER_PROCEDURE ? + TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); - memcpy(&chistics, &lex->sp_chistics, sizeof(chistics)); - if (lex->sql_command == SQLCOM_ALTER_PROCEDURE) - sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname, - &thd->sp_proc_cache, FALSE); - else - sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname, - &thd->sp_func_cache, FALSE); - thd->warning_info->opt_clear_warning_info(thd->query_id); - if (! sp) - { - if (lex->spname->m_db.str) - sp_result= SP_KEY_NOT_FOUND; - else - { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - goto error; - } - } - else - { - if (check_routine_access(thd, ALTER_PROC_ACL, sp->m_db.str, - sp->m_name.str, - lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0)) - goto error; + if (check_routine_access(thd, ALTER_PROC_ACL, lex->spname->m_db.str, + lex->spname->m_name.str, + lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0)) + goto error; - memcpy(&lex->sp_chistics, &chistics, sizeof(lex->sp_chistics)); - if ((sp->m_type == TYPE_ENUM_FUNCTION) && - !trust_function_creators && mysql_bin_log.is_open() && - !sp->m_chistics->detistic && - (chistics.daccess == SP_CONTAINS_SQL || - chistics.daccess == SP_MODIFIES_SQL_DATA)) - { - my_message(ER_BINLOG_UNSAFE_ROUTINE, - ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0)); - sp_result= SP_INTERNAL_ERROR; - } - else - { - /* - Note that if you implement the capability of ALTER FUNCTION to - alter the body of the function, this command should be made to - follow the restrictions that log-bin-trust-function-creators=0 - already puts on CREATE FUNCTION. - */ - /* Conditionally writes to binlog */ - - int type= lex->sql_command == SQLCOM_ALTER_PROCEDURE ? - TYPE_ENUM_PROCEDURE : - TYPE_ENUM_FUNCTION; - - sp_result= sp_update_routine(thd, - type, - lex->spname, - &lex->sp_chistics); - } - } + /* + Note that if you implement the capability of ALTER FUNCTION to + alter the body of the function, this command should be made to + follow the restrictions that log-bin-trust-function-creators=0 + already puts on CREATE FUNCTION. + */ + /* Conditionally writes to binlog */ + sp_result= sp_update_routine(thd, type, lex->spname, &lex->sp_chistics); switch (sp_result) { case SP_OK: @@ -4199,6 +4162,12 @@ create_sp_error: int type= (lex->sql_command == SQLCOM_DROP_PROCEDURE ? TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); + /* + @todo: here we break the metadata locking protocol by + looking up the information about the routine without + a metadata lock. Rewrite this piece to make sp_drop_routine + return whether the routine existed or not. + */ sp_result= sp_routine_exists_in_table(thd, type, lex->spname); thd->warning_info->opt_clear_warning_info(thd->query_id); if (sp_result == SP_OK) @@ -4210,30 +4179,30 @@ create_sp_error: lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) goto error; - if (trans_commit_implicit(thd)) - goto error; - - close_thread_tables(thd); - - thd->mdl_context.release_transactional_locks(); + /* Conditionally writes to binlog */ + sp_result= sp_drop_routine(thd, type, lex->spname); #ifndef NO_EMBEDDED_ACCESS_CHECKS + /* + We're going to issue an implicit REVOKE statement. + It takes metadata locks and updates system tables. + Make sure that sp_create_routine() did not leave any + locks in the MDL context, so there is no risk to + deadlock. + */ + trans_commit_implicit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + if (sp_automatic_privileges && !opt_noacl && - sp_revoke_privileges(thd, db, name, + sp_revoke_privileges(thd, db, name, lex->sql_command == SQLCOM_DROP_PROCEDURE)) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_PROC_AUTO_REVOKE_FAIL, ER(ER_PROC_AUTO_REVOKE_FAIL)); } #endif - /* Conditionally writes to binlog */ - - int type= lex->sql_command == SQLCOM_DROP_PROCEDURE ? - TYPE_ENUM_PROCEDURE : - TYPE_ENUM_FUNCTION; - - sp_result= sp_drop_routine(thd, type, lex->spname); } else { @@ -4292,21 +4261,13 @@ create_sp_error: case SQLCOM_SHOW_CREATE_PROC: { if (sp_show_create_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname)) - { - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - SP_COM_STRING(lex), lex->spname->m_name.str); - goto error; - } + goto error; break; } case SQLCOM_SHOW_CREATE_FUNC: { if (sp_show_create_routine(thd, TYPE_ENUM_FUNCTION, lex->spname)) - { - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - SP_COM_STRING(lex), lex->spname->m_name.str); goto error; - } break; } case SQLCOM_SHOW_PROC_CODE: @@ -4314,13 +4275,11 @@ create_sp_error: { #ifndef DBUG_OFF sp_head *sp; + int type= (lex->sql_command == SQLCOM_SHOW_PROC_CODE ? + TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); - if (lex->sql_command == SQLCOM_SHOW_PROC_CODE) - sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname, - &thd->sp_proc_cache, FALSE); - else - sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname, - &thd->sp_func_cache, FALSE); + if (sp_cache_routine(thd, type, lex->spname, FALSE, &sp)) + goto error; if (!sp || sp->show_routine_code(thd)) { /* We don't distinguish between errors for now */ @@ -5579,9 +5538,6 @@ void mysql_parse(THD *thd, const char *inBuf, uint length, { LEX *lex= thd->lex; - sp_cache_flush_obsolete(&thd->sp_proc_cache); - sp_cache_flush_obsolete(&thd->sp_func_cache); - Parser_state parser_state(thd, inBuf, length); bool err= parse_sql(thd, & parser_state, NULL); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 700017f2b3e..838f25320ec 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -173,8 +173,6 @@ private: SELECT_LEX and other classes). */ MEM_ROOT main_mem_root; - /* Version of the stored functions cache at the time of prepare. */ - ulong m_sp_cache_version; private: bool set_db(const char *db, uint db_length); bool set_parameters(String *expanded_query, @@ -2138,9 +2136,6 @@ void mysqld_stmt_prepare(THD *thd, const char *packet, uint packet_length) DBUG_VOID_RETURN; } - sp_cache_flush_obsolete(&thd->sp_proc_cache); - sp_cache_flush_obsolete(&thd->sp_func_cache); - thd->protocol= &thd->protocol_binary; if (stmt->prepare(packet, packet_length)) @@ -2419,6 +2414,13 @@ void reinit_stmt_before_use(THD *thd, LEX *lex) { tables->reinit_before_use(thd); } + + /* Reset MDL tickets for procedures/functions */ + for (Sroutine_hash_entry *rt= + (Sroutine_hash_entry*)thd->lex->sroutines_list.first; + rt; rt= rt->next) + rt->mdl_request.ticket= NULL; + /* Cleanup of the special case of DELETE t1, t2 FROM t1, t2, t3 ... (multi-delete). We do a full clean up, although at the moment all we @@ -2512,9 +2514,6 @@ void mysqld_stmt_execute(THD *thd, char *packet_arg, uint packet_length) DBUG_PRINT("exec_query", ("%s", stmt->query())); DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt)); - sp_cache_flush_obsolete(&thd->sp_proc_cache); - sp_cache_flush_obsolete(&thd->sp_func_cache); - open_cursor= test(flags & (ulong) CURSOR_TYPE_READ_ONLY); thd->protocol= &thd->protocol_binary; @@ -2964,8 +2963,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg) param_array(0), param_count(0), last_errno(0), - flags((uint) IS_IN_USE), - m_sp_cache_version(0) + flags((uint) IS_IN_USE) { init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size, thd_arg->variables.query_prealloc_size); @@ -3234,20 +3232,6 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) init_stmt_after_parse(lex); state= Query_arena::PREPARED; flags&= ~ (uint) IS_IN_USE; - /* - This is for prepared statement validation purposes. - A statement looks up and pre-loads all its stored functions - at prepare. Later on, if a function is gone from the cache, - execute may fail. - Remember the cache version to be able to invalidate the prepared - statement at execute if it changes. - We only need to care about version of the stored functions cache: - if a prepared statement uses a stored procedure, it's indirect, - via a stored function. The only exception is SQLCOM_CALL, - but the latter one looks up the stored procedure each time - it's invoked, rather than once at prepare. - */ - m_sp_cache_version= sp_cache_version(&thd->sp_func_cache); /* Log COM_EXECUTE to the general log. Note, that in case of SQL @@ -3588,13 +3572,12 @@ Prepared_statement::swap_prepared_statement(Prepared_statement *copy) is allocated in the old arena. */ swap_variables(Item_param **, param_array, copy->param_array); - /* Swap flags: this is perhaps unnecessary */ - swap_variables(uint, flags, copy->flags); + /* Don't swap flags: the copy has IS_SQL_PREPARE always set. */ + /* swap_variables(uint, flags, copy->flags); */ /* Swap names, the old name is allocated in the wrong memory root */ swap_variables(LEX_STRING, name, copy->name); /* Ditto */ swap_variables(char *, db, copy->db); - swap_variables(ulong, m_sp_cache_version, copy->m_sp_cache_version); DBUG_ASSERT(db_length == copy->db_length); DBUG_ASSERT(param_count == copy->param_count); @@ -3653,19 +3636,6 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) return TRUE; } - /* - Reprepare the statement if we're using stored functions - and the version of the stored routines cache has changed. - */ - if (lex->uses_stored_routines() && - m_sp_cache_version != sp_cache_version(&thd->sp_func_cache) && - thd->m_reprepare_observer && - thd->m_reprepare_observer->report_error(thd)) - { - return TRUE; - } - - /* For SHOW VARIABLES lex->result is NULL, as it's a non-SELECT command. For such queries we don't return an error and don't diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 492bb46ebad..72fb49cf38c 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3237,7 +3237,6 @@ end_share: end_unlock: pthread_mutex_unlock(&LOCK_open); -end: thd->mdl_context.release_lock(table_list.mdl_request.ticket); thd->clear_error(); return res; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 3b87a4dd6e8..30d6efff7ec 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -21,6 +21,7 @@ #include #include #include "sp_head.h" +#include "sp.h" #include "sql_trigger.h" #include "sql_show.h" #include "transaction.h" @@ -5010,6 +5011,23 @@ send_result_message: trans_commit_implicit(thd); close_thread_tables(thd); table->table=0; // For query cache + + /* + If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run + separate open_tables() for each CHECK TABLE argument. + Right now we do not have a separate method to reset the prelocking + state in the lex to the state after parsing, so each open will pollute + this state: add elements to lex->srotuines_list, TABLE_LISTs to + lex->query_tables. Below is a lame attempt to recover from this + pollution. + @todo: have a method to reset a prelocking context, or use separate + contexts for each open. + */ + for (Sroutine_hash_entry *rt= + (Sroutine_hash_entry*)thd->lex->sroutines_list.first; + rt; rt= rt->next) + rt->mdl_request.ticket= NULL; + if (protocol->write()) goto err; } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index b81fb30ec27..603ab1b9682 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1117,10 +1117,6 @@ reopen_tables: while ((item= it++)) item->cleanup(); - /* We have to cleanup translation tables of views. */ - for (TABLE_LIST *tbl= table_list; tbl; tbl= tbl->next_global) - tbl->cleanup_items(); - /* To not to hog memory (as a result of the unit->reinit_exec_mechanism() call below): From 5ba347944be4589cf75a1d2362f4e8a4573356e3 Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Tue, 29 Dec 2009 18:50:01 +0300 Subject: [PATCH 132/212] A test case for Bug#49972 (Crash in prepared statements). --- tests/mysql_client_test.c | 110 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 866c63ae1d3..cce5910311f 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -18935,6 +18935,115 @@ static void test_bug44495() DBUG_VOID_RETURN; } +/* + Bug#49972: Crash in prepared statements. + + The following case lead to a server crash: + - Use binary protocol; + - Prepare a statement with OUT-parameter; + - Execute the statement; + - Cause re-prepare of the statement (change dependencies); + - Execute the statement again -- crash here. +*/ + +static void test_bug49972() +{ + int rc; + MYSQL_STMT *stmt; + + MYSQL_BIND in_param_bind; + MYSQL_BIND out_param_bind; + int int_data; + my_bool is_null; + + DBUG_ENTER("test_bug49972"); + myheader("test_49972"); + + rc= mysql_query(mysql, "DROP FUNCTION IF EXISTS f1"); + myquery(rc); + + rc= mysql_query(mysql, "DROP PROCEDURE IF EXISTS p1"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE FUNCTION f1() RETURNS INT RETURN 1"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE PROCEDURE p1(IN a INT, OUT b INT) SET b = a"); + myquery(rc); + + stmt= mysql_simple_prepare(mysql, "CALL p1((SELECT f1()), ?)"); + check_stmt(stmt); + + bzero((char *) &in_param_bind, sizeof (in_param_bind)); + + in_param_bind.buffer_type= MYSQL_TYPE_LONG; + in_param_bind.buffer= (char *) &int_data; + in_param_bind.length= 0; + in_param_bind.is_null= 0; + + rc= mysql_stmt_bind_param(stmt, &in_param_bind); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + { + bzero(&out_param_bind, sizeof (out_param_bind)); + + out_param_bind.buffer_type= MYSQL_TYPE_LONG; + out_param_bind.is_null= &is_null; + out_param_bind.buffer= &int_data; + out_param_bind.buffer_length= sizeof (int_data); + + rc= mysql_stmt_bind_result(stmt, &out_param_bind); + check_execute(stmt, rc); + + rc= mysql_stmt_fetch(stmt); + rc= mysql_stmt_fetch(stmt); + DBUG_ASSERT(rc == MYSQL_NO_DATA); + + mysql_stmt_next_result(stmt); + mysql_stmt_fetch(stmt); + } + + rc= mysql_query(mysql, "DROP FUNCTION f1"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE FUNCTION f1() RETURNS INT RETURN 1"); + myquery(rc); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + { + bzero(&out_param_bind, sizeof (out_param_bind)); + + out_param_bind.buffer_type= MYSQL_TYPE_LONG; + out_param_bind.is_null= &is_null; + out_param_bind.buffer= &int_data; + out_param_bind.buffer_length= sizeof (int_data); + + rc= mysql_stmt_bind_result(stmt, &out_param_bind); + check_execute(stmt, rc); + + rc= mysql_stmt_fetch(stmt); + rc= mysql_stmt_fetch(stmt); + DBUG_ASSERT(rc == MYSQL_NO_DATA); + + mysql_stmt_next_result(stmt); + mysql_stmt_fetch(stmt); + } + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP PROCEDURE p1"); + myquery(rc); + + rc= mysql_query(mysql, "DROP FUNCTION f1"); + myquery(rc); + + DBUG_VOID_RETURN; +} + /* Read and parse arguments and MySQL options from my.cnf */ @@ -19264,6 +19373,7 @@ static struct my_tests_st my_tests[]= { #endif { "test_bug41078", test_bug41078 }, { "test_bug44495", test_bug44495 }, + { "test_bug49972", test_bug49972 }, { 0, 0 } }; From 1c327209e1512174d3353af0e079a45b9bb38e1e Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Tue, 29 Dec 2009 21:12:06 +0300 Subject: [PATCH 133/212] Disable test case for Bug#49972. --- tests/mysql_client_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index cce5910311f..1b580f2412b 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -19373,7 +19373,7 @@ static struct my_tests_st my_tests[]= { #endif { "test_bug41078", test_bug41078 }, { "test_bug44495", test_bug44495 }, - { "test_bug49972", test_bug49972 }, + /* XXX { "test_bug49972", test_bug49972 }, */ { 0, 0 } }; From 0228c9893622b1a60a3128b74c35b06d3a63c72b Mon Sep 17 00:00:00 2001 From: Dmitry Lenev Date: Wed, 30 Dec 2009 20:53:30 +0300 Subject: [PATCH 134/212] Implementation of simple deadlock detection for metadata locks. This change is supposed to reduce number of ER_LOCK_DEADLOCK errors which occur when multi-statement transaction encounters conflicting metadata lock in cases when waiting is possible. The idea is not to fail ER_LOCK_DEADLOCK error immediately when we encounter conflicting metadata lock. Instead we release all metadata locks acquired by current statement and start to wait until conflicting lock go away. To avoid deadlocks we use simple empiric which aborts waiting with ER_LOCK_DEADLOCK error if it turns out that somebody is waiting for metadata locks owned by this transaction. This patch also fixes bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed in case of ALTER". The bug was that concurrent execution of UPDATE or MULTI-UPDATE statement as a part of multi-statement transaction that already has used table being updated and ALTER TABLE statement might have resulted of loss of isolation between this transaction and ALTER TABLE statement, which manifested itself as changes performed by ALTER TABLE becoming visible in transaction and wrong binary log order as a consequence. This problem occurred when UPDATE or MULTI-UPDATE's wait in mysql_lock_tables() call was aborted due to metadata lock upgrade performed by concurrent ALTER TABLE. After such abort all metadata locks held by transaction were released but transaction silently continued to be executed as if nothing has happened. We solve this problem by changing our code not to release all locks in such case. Instead we release only locks which were acquired by current statement and then try to reacquire them by restarting open/lock tables process. We piggyback on simple deadlock detector implementation since this change has to be done anyway for it. mysql-test/include/handler.inc: After introduction of basic deadlock detector for metadata locks it became necessary to change parts of test for HANDLER statements which covered some of scenarios in which ER_LOCK_DEADLOCK error was detected in absence of real deadlock (with new deadlock detector this no longer happens). Also adjusted test to the fact that HANDLER READ for the table no longer will be blocked by ALTER TABLE for the same table which awaits for metadata lock upgrade (this is due to removal of mysql_lock_abort() from wait_while_table_is_used()). mysql-test/r/handler_innodb.result: After introduction of basic deadlock detector for metadata locks it became necessary to change parts of test for HANDLER statements which covered some of scenarios in which ER_LOCK_DEADLOCK error was detected in absence of real deadlock (with new deadlock detector this no longer happens). Also adjusted test to the fact that HANDLER READ for the table no longer will be blocked by ALTER TABLE for the same table which awaits for metadata lock upgrade (this is due to removal of mysql_lock_abort() from wait_while_table_is_used()). mysql-test/r/handler_myisam.result: After introduction of basic deadlock detector for metadata locks it became necessary to change parts of test for HANDLER statements which covered some of scenarios in which ER_LOCK_DEADLOCK error was detected in absence of real deadlock (with new deadlock detector this no longer happens). Also adjusted test to the fact that HANDLER READ for the table no longer will be blocked by ALTER TABLE for the same table which awaits for metadata lock upgrade (this is due to removal of mysql_lock_abort() from wait_while_table_is_used()). mysql-test/r/mdl_sync.result: Added test coverage for basic deadlock detection in metadata locking subsystem and for bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed in case of ALTER". mysql-test/r/sp-lock.result: Adjusted test coverage for metadata locking for stored routines since after introduction of basic deadlock detector for metadata locks number of scenarios in which ER_LOCK_DEADLOCK error in absence of deadlock has decreased. mysql-test/t/mdl_sync.test: Added test coverage for basic deadlock detection in metadata locking subsystem and for bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed in case of ALTER". mysql-test/t/sp-lock.test: Adjusted test coverage for metadata locking for stored routines since after introduction of basic deadlock detector for metadata locks number of scenarios in which ER_LOCK_DEADLOCK error in absence of deadlock has decreased. sql/log_event_old.cc: close_tables_for_reopen() now takes one more argument which specifies at which point it should stop releasing metadata locks acquired by this connection. sql/mdl.cc: Changed metadata locking subsystem to support basic deadlock detection with a help of the following simple empiric -- we assume that there is a deadlock if there is a connection which has to wait for a metadata lock which is currently acquired by some connection which is itself waiting to be able to acquire some shared metadata lock. To implement this change: - Added MDL_context::can_wait_lead_to_deadlock()/_impl() methods which allow to find out if there is someone waiting for metadata lock which is held by the connection and therefore deadlocks are possible if this connection is going to wait for some metadata lock. To do this added version of MDL_ticket::has_pending_conflicting_lock() method which assumes that its caller already owns LOCK_mdl mutex. - Changed MDL_context::wait_for_locks() to use one of the above methods to check if somebody is waiting for metadata lock owned by this context (and therefore deadlock is possible) and emit ER_LOCK_DEADLOCK error in this case. Also now we mark context of connections waiting inside of this method by setting MDL_context::m_is_waiting_in_mdl member. Thanks to this such connection could be waken up if some other connection starts waiting for one of its metadata locks and so a deadlock can occur. - Adjusted notify_shared_lock() to wake up connections which wait inside MDL_context::wait_for_locks() while holding shared metadata lock. - Changed MDL_ticket::upgrade_shared_lock_to_exclusive() to add temporary ticket for exclusive lock to MDL_lock::waiting queue, so request for metadata lock upgrade can be properly detected by our empiric. Also now this method invokes a callback which forces transactions holding shared metadata lock on the table to call MDL_context:: can_wait_lead_to_deadlock() method even if they don't need any new metadata locks. Thanks to this such transactions can detect deadlocks/ livelocks between MDL and table-level locks. Also reduced timeouts between calls to notify_shared_lock() in MDL_ticket::upgrade_shared_lock_to_exclusive() and MDL_context::acquire_exclusive_locks(). This was necessary to get rid of call to mysql_lock_abort() in wait_while_table_is_used(). (Now we instead rely on notify_shared_lock() timely calling mysql_lock_abort_for_thread() for the table on which lock is being upgraded/acquired). sql/mdl.h: - Added a version of MDL_ticket::has_pending_conflicting_lock() method to be used in situations when caller already has acquired LOCK_mdl mutex. - Added MDL_context::can_wait_lead_to_deadlock()/_impl() methods which allow to find out if there is someone waiting for metadata lock which is held by this connection and thus deadlocks are possible if this connections will start waiting for some metadata lock. - Added MDL_context::m_is_waiting_in_mdl member to mark connections waiting in MDL_context::wait_for_locks() method of metadata locking subsystem. Added getter method for this private member to make it accessible in notify_shared_lock() auxiliary so we can wake-up such connections if they hold shared metadata locks. - Finally, added mysql_abort_transactions_with_shared_lock() callback to be able force transactions which don't need any new metadata locks still call MDL_context::can_wait_lead_to_deadlock() and detect some of deadlocks between metadata locks and table-level locks. sql/mysql_priv.h: close_tables_for_reopen() now takes one more argument which specifies at which point it should stop releasing metadata locks acquired by this connection. sql/sql_base.cc: Changed approach to metadata locking for multi-statement transactions. We no longer fail ER_LOCK_DEADLOCK error immediately when we encounter conflicting metadata lock. Instead we release all metadata locks acquired by current statement and start to wait until conflicting locks to go away by calling MDL_context::wait_for_locks() method. To avoid deadlocks the latter implements simple empiric which aborts waiting with ER_LOCK_DEADLOCK error if it turns out that somebody is waiting for metadata locks owned by this transaction. To implement the change described above: - Introduced Open_table_context::m_start_of_statement_svp member to store state of metadata locks at the start of the statement. - Changed Open_table_context::request_backoff_action() not to fail with ER_LOCK_DEADLOCK immediately if back-off is requested due to conflicting metadata lock. - Added new argument for close_tables_for_reopen() procedure which allows to specify subset of metadata locks to be released. - Changed open_tables() not to release all metadata locks acquired by current transaction when metadata lock conflict is discovered. Instead we release only locks acquired by current statement. - Changed open_ltable() and open_and_lock_tables_derived() not to emit ER_LOCK_DEADLOCK error when mysql_lock_tables() is aborted in multi-statement transaction when somebody tries to acquire exclusive metadata lock on the table. Instead we release metadata locks acquired by current statement and try to wait until they can be re-acquired. - Adjusted tdc_wait_for_old_versions() to check if there is someone waiting for one of metadata locks held by this connection and run deadlock detection in order to avoid deadlocks in some situations. - Added mysql_abort_transactions_with_shared_lock() callback which allows to force transactions holding shared metadata lock on the table to call MDL_context::can_wait_lead_to_deadlock() even if they don't need any new metadata locks so they can detect potential deadlocks between metadata locking subsystem and table-level locks. - Adjusted wait_while_table_is_used() not to set TABLE::version to 0 as it is now done only when necessary by the above-mentioned callback. Also removed unnecessary call to mysql_lock_abort(). Instead we rely on code performing metadata lock upgrade aborting waits on the table-level lock for this table by calling mysql_lock_abort_for_thread() (invoked by mysql_notify_thread_having_shared_lock()). In future this should allow to reduce number of scenarios in which we produce ER_LOCK_DEADLOCK error even though no real deadlock exists. sql/sql_class.h: Introduced Open_table_context::m_start_of_statement_svp member to store state of metadata locks at the start of the statement. Replaced Open_table_context::m_can_deadlock member with m_has_locks member to reflect the fact that we no longer unconditionally emit ER_LOCK_DEADLOCK error for transaction having some metadata locks when conflicting metadata lock is discovered. sql/sql_insert.cc: close_tables_for_reopen() now takes one more argument which specifies at which point it should stop releasing metadata locks acquired by this connection. sql/sql_plist.h: Made I_P_List_iterator usable with const lists. sql/sql_show.cc: close_tables_for_reopen() now takes one more argument which specifies at which point it should stop releasing metadata locks acquired by this connection. sql/sql_update.cc: Changed UPDATE and MULTI-UPDATE code not to release all metadata locks when calls to mysql_lock_tables() are aborted. Instead we release only locks which are acquired by this statement and then try to reacquire them by calling open_tables(). This solves bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed in case of ALTER". --- mysql-test/include/handler.inc | 32 ++- mysql-test/r/handler_innodb.result | 27 +- mysql-test/r/handler_myisam.result | 27 +- mysql-test/r/mdl_sync.result | 251 +++++++++++++++++ mysql-test/r/sp-lock.result | 96 +++---- mysql-test/t/mdl_sync.test | 420 +++++++++++++++++++++++++++++ mysql-test/t/sp-lock.test | 210 ++++++++------- sql/log_event_old.cc | 2 +- sql/mdl.cc | 172 +++++++++++- sql/mdl.h | 13 + sql/mysql_priv.h | 3 +- sql/sql_base.cc | 165 +++++++----- sql/sql_class.h | 15 +- sql/sql_insert.cc | 2 +- sql/sql_plist.h | 6 +- sql/sql_show.cc | 4 +- sql/sql_update.cc | 6 +- 17 files changed, 1189 insertions(+), 262 deletions(-) diff --git a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc index a9965f97926..5562f7b2558 100644 --- a/mysql-test/include/handler.inc +++ b/mysql-test/include/handler.inc @@ -732,7 +732,7 @@ connection default; --disable_warnings drop table if exists t1; --enable_warnings -create table t1 (a int); +create table t1 (a int, key a (a)); insert into t1 values (1); handler t1 open; connection con1; @@ -743,7 +743,6 @@ let $wait_condition= where state = "Waiting for table" and info = "alter table t1 engine=memory"; --source include/wait_condition.inc connection default; ---error ER_ILLEGAL_HA handler t1 read a next; handler t1 close; connection con1; @@ -983,11 +982,12 @@ lock table t2 read; --echo # --> connection con2 connection con2; --echo # Sending: ---send drop table t2 +send rename table t2 to t3, t1 to t2, t3 to t1; --echo # --> connection con1 connection con1; ---echo # Waiting for 'drop table t2' to get blocked... -let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--echo # Waiting for 'rename table ...' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='rename table t2 to t3, t1 to t2, t3 to t1'; --source include/wait_condition.inc --echo # --> connection default connection default; @@ -997,25 +997,26 @@ handler t2 open; select * from t2; handler t1 open; commit; ---error ER_LOCK_DEADLOCK -handler t2 open; handler t1 close; --echo # --> connection con1 connection con1; unlock tables; --echo # --> connection con2 connection con2; ---echo # Reaping 'drop table t2'... +--echo # Reaping 'rename table ...'... --reap --echo # --> connection default connection default; handler t1 open; handler t1 read a prev; handler t1 close; +drop table t2; --echo # ---echo # Likewise, this doesn't require a multi-statement transaction. ---echo # ER_LOCK_DEADLOCK is also produced when we have an open ---echo # HANDLER and try to acquire locks for a single statement. +--echo # Originally there was a deadlock error in this test. +--echo # With implementation of deadlock detector +--echo # we no longer deadlock, but block and wait on a lock. +--echo # The HANDLER is auto-closed as soon as the connection +--echo # sees a pending conflicting lock against it. --echo # create table t2 (a int, key a (a)); handler t1 open; @@ -1033,10 +1034,12 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where --source include/wait_condition.inc --echo # --> connection default connection default; ---error ER_LOCK_DEADLOCK -select * from t2; +--echo # Sending 'select * from t2' +send select * from t2; --echo # --> connection con1 connection con1; +--echo # Waiting for 'select * from t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='select * from t2'; unlock tables; --echo # --> connection con2 connection con2; @@ -1044,6 +1047,9 @@ connection con2; --reap --echo # --> connection default connection default; +--echo # Reaping 'select * from t2' +--error ER_NO_SUCH_TABLE +reap; handler t1 close; --echo # diff --git a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result index df948f3d0b6..807e8becea8 100644 --- a/mysql-test/r/handler_innodb.result +++ b/mysql-test/r/handler_innodb.result @@ -745,12 +745,13 @@ drop table t1; handler t1 read a next; ERROR 42S02: Unknown table 't1' in HANDLER drop table if exists t1; -create table t1 (a int); +create table t1 (a int, key a (a)); insert into t1 values (1); handler t1 open; alter table t1 engine=memory; handler t1 read a next; -ERROR HY000: Table storage engine for 't1' doesn't have this option +a +1 handler t1 close; drop table t1; USE information_schema; @@ -1002,9 +1003,9 @@ a lock table t2 read; # --> connection con2 # Sending: -drop table t2; +rename table t2 to t3, t1 to t2, t3 to t1; # --> connection con1 -# Waiting for 'drop table t2' to get blocked... +# Waiting for 'rename table ...' to get blocked... # --> connection default handler t2 open; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction @@ -1012,23 +1013,24 @@ select * from t2; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction handler t1 open; commit; -handler t2 open; -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction handler t1 close; # --> connection con1 unlock tables; # --> connection con2 -# Reaping 'drop table t2'... +# Reaping 'rename table ...'... # --> connection default handler t1 open; handler t1 read a prev; a 5 handler t1 close; +drop table t2; # -# Likewise, this doesn't require a multi-statement transaction. -# ER_LOCK_DEADLOCK is also produced when we have an open -# HANDLER and try to acquire locks for a single statement. +# Originally there was a deadlock error in this test. +# With implementation of deadlock detector +# we no longer deadlock, but block and wait on a lock. +# The HANDLER is auto-closed as soon as the connection +# sees a pending conflicting lock against it. # create table t2 (a int, key a (a)); handler t1 open; @@ -1040,13 +1042,16 @@ drop table t2; # --> connection con1 # Waiting for 'drop table t2' to get blocked... # --> connection default +# Sending 'select * from t2' select * from t2; -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction # --> connection con1 +# Waiting for 'select * from t2' to get blocked... unlock tables; # --> connection con2 # Reaping 'drop table t2'... # --> connection default +# Reaping 'select * from t2' +ERROR 42S02: Table 'test.t2' doesn't exist handler t1 close; # # ROLLBACK TO SAVEPOINT releases transactional locks, diff --git a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result index 4b287e6560b..adcbf068b97 100644 --- a/mysql-test/r/handler_myisam.result +++ b/mysql-test/r/handler_myisam.result @@ -743,12 +743,13 @@ drop table t1; handler t1 read a next; ERROR 42S02: Unknown table 't1' in HANDLER drop table if exists t1; -create table t1 (a int); +create table t1 (a int, key a (a)); insert into t1 values (1); handler t1 open; alter table t1 engine=memory; handler t1 read a next; -ERROR HY000: Table storage engine for 't1' doesn't have this option +a +1 handler t1 close; drop table t1; USE information_schema; @@ -999,9 +1000,9 @@ a lock table t2 read; # --> connection con2 # Sending: -drop table t2; +rename table t2 to t3, t1 to t2, t3 to t1; # --> connection con1 -# Waiting for 'drop table t2' to get blocked... +# Waiting for 'rename table ...' to get blocked... # --> connection default handler t2 open; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction @@ -1009,23 +1010,24 @@ select * from t2; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction handler t1 open; commit; -handler t2 open; -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction handler t1 close; # --> connection con1 unlock tables; # --> connection con2 -# Reaping 'drop table t2'... +# Reaping 'rename table ...'... # --> connection default handler t1 open; handler t1 read a prev; a 5 handler t1 close; +drop table t2; # -# Likewise, this doesn't require a multi-statement transaction. -# ER_LOCK_DEADLOCK is also produced when we have an open -# HANDLER and try to acquire locks for a single statement. +# Originally there was a deadlock error in this test. +# With implementation of deadlock detector +# we no longer deadlock, but block and wait on a lock. +# The HANDLER is auto-closed as soon as the connection +# sees a pending conflicting lock against it. # create table t2 (a int, key a (a)); handler t1 open; @@ -1037,13 +1039,16 @@ drop table t2; # --> connection con1 # Waiting for 'drop table t2' to get blocked... # --> connection default +# Sending 'select * from t2' select * from t2; -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction # --> connection con1 +# Waiting for 'select * from t2' to get blocked... unlock tables; # --> connection con2 # Reaping 'drop table t2'... # --> connection default +# Reaping 'select * from t2' +ERROR 42S02: Table 'test.t2' doesn't exist handler t1 close; # # ROLLBACK TO SAVEPOINT releases transactional locks, diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index ec02f29b008..0c9b6432e95 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -20,6 +20,220 @@ ERROR 42S02: Unknown table 't1' drop table t3; SET DEBUG_SYNC= 'RESET'; # +# Test coverage for basic deadlock detection in metadata +# locking subsystem. +# +drop tables if exists t1, t2, t3, t4; +create table t1 (i int); +create table t2 (j int); +create table t3 (k int); +create table t4 (k int); +# +# Test for the case in which no deadlock occurs. +# +# +# Switching to connection 'deadlock_con1'. +begin; +insert into t1 values (1); +# +# Switching to connection 'deadlock_con2'. +begin; +insert into t2 values (1); +# +# Switching to connection 'default'. +# Send: +rename table t2 to t0, t3 to t2, t0 to t3;; +# +# Switching to connection 'deadlock_con1'. +# Wait until the above RENAME TABLE is blocked because it has to wait +# for 'deadlock_con2' which holds shared metadata lock on 't2'. +# The below statement should wait for exclusive metadata lock +# on 't2' to go away and should not produce ER_LOCK_DEADLOCK +# as no deadlock is possible in this situation. +# Send: +select * from t2;; +# +# Switching to connection 'deadlock_con2'. +# Wait until the above SELECT * FROM t2 is starts waiting +# for an exclusive metadata lock to go away. +# +# Unblock RENAME TABLE by releasing shared metadata lock on t2. +commit; +# +# Switching to connection 'default'. +# Reap RENAME TABLE. +# +# Switching to connection 'deadlock_con1'. +# Reap SELECT. +k +# +# Switching to connection 'default'. +# +# Let us check that in the process of waiting for conflicting lock +# on table 't2' to go away transaction in connection 'deadlock_con1' +# has not released metadata lock on table 't1'. +# Send: +rename table t1 to t0, t3 to t1, t0 to t3;; +# +# Switching to connection 'deadlock_con1'. +# Wait until the above RENAME TABLE is blocked because it has to wait +# for 'deadlock_con1' which should still hold shared metadata lock on +# table 't1'. +# Commit transaction to unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reap RENAME TABLE. +# +# Test for case when deadlock occurs and should be detected immediately. +# +# +# Switching to connection 'deadlock_con1'. +begin; +insert into t1 values (2); +# +# Switching to connection 'default'. +# Send: +rename table t2 to t0, t1 to t2, t0 to t1;; +# +# Switching to connection 'deadlock_con1'. +# Wait until the above RENAME TABLE is blocked because it has to wait +# for 'deadlock_con1' which holds shared metadata lock on 't1'. +# +# The below statement should not wait as doing so will cause deadlock. +# Instead it should fail and emit ER_LOCK_DEADLOCK statement. +select * from t2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# +# Let us check that failure of the above statement has not released +# metadata lock on table 't1', i.e. that RENAME TABLE is still blocked. +# Commit transaction to unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reap RENAME TABLE. +# +# Test for the case in which deadlock also occurs but not immediately. +# +# +# Switching to connection 'deadlock_con1'. +begin; +insert into t1 values (1); +# +# Switching to connection 'deadlock_con2'. +begin; +insert into t3 values (1); +# +# Switching to connection 'default'. +# Send: +rename table t2 to t0, t3 to t2, t0 to t3;; +# +# Switching to connection 'deadlock_con1'. +# Wait until the above RENAME TABLE is blocked because it has to wait +# for 'deadlock_con2' which holds shared metadata lock on 't3'. +# The below SELECT statement should wait for metadata lock +# on table 't2' and should not produce ER_LOCK_DEADLOCK +# immediately as no deadlock is possible at the moment. +select * from t2;; +# +# Switching to connection 'deadlock_con3'. +# Wait until the above SELECT * FROM t2 is starts waiting +# for an exclusive metadata lock to go away. +# Send RENAME TABLE statement that will deadlock with the +# SELECT statement and thus should abort the latter. +rename table t1 to t0, t2 to t1, t0 to t2;; +# +# Switching to connection 'deadlock_con1'. +# Since the latest RENAME TABLE entered in deadlock with SELECT +# statement the latter should be aborted and emit ER_LOCK_DEADLOCK +# error. +# Reap SELECT * FROM t2. +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# +# Again let us check that failure of the SELECT statement has not +# released metadata lock on table 't1', i.e. that the latest RENAME +# is blocked. +# Commit transaction to unblock this RENAME TABLE. +commit; +# +# Switching to connection 'deadlock_con3'. +# Reap RENAME TABLE t1 TO t0 ... . +# +# Switching to connection 'deadlock_con2'. +# Commit transaction to unblock the first RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reap RENAME TABLE t2 TO t0 ... . +drop tables t1, t2, t3, t4; +# +# Now, test case which shows that deadlock detection empiric +# also takes into account requests for metadata lock upgrade. +# +create table t1 (i int); +# +# Switching to connection 'deadlock_con1'. +begin; +insert into t1 values (1); +# +# Switching to connection 'default'. +# Send: +alter table t1 add column j int, rename to t2;; +# +# Switching to connection 'deadlock_con1'. +# Wait until the above ALTER TABLE ... RENAME acquires exclusive +# metadata lock on 't2' and starts waiting for connection +# 'deadlock_con1' which holds shared lock on 't1'. +# The below statement should not wait as it will cause deadlock. +# An appropriate error should be reported instead. +select * from t2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Again let us check that failure of the above statement has not +# released all metadata locks in connection 'deadlock_con1' and +# so ALTER TABLE ... RENAME is still blocked. +# Commit transaction to unblock ALTER TABLE ... RENAME. +commit; +# +# Switching to connection 'default'. +# Reap ALTER TABLE ... RENAME. +drop table t2; +# +# Finally, test case in which deadlock (or potentially livelock) occurs +# between metadata locking subsystem and table definition cache/table +# locks, but which should still be detected by our empiric. +# +create table t1 (i int); +# +# Switching to connection 'deadlock_con1'. +begin; +insert into t1 values (1); +# +# Switching to connection 'default'. +lock tables t1 write; +# +# Switching to connection 'deadlock_con1'. +# Send: +insert into t1 values (2);; +# +# Switching to connection 'default'. +# Wait until INSERT in connection 'deadlock_con1' is blocked on +# table-level lock. +# Send: +alter table t1 add column j int;; +# +# Switching to connection 'deadlock_con1'. +# The above ALTER TABLE statement should cause INSERT statement in +# this connection to be aborted and emit ER_LOCK_DEADLOCK error. +# Reap INSERT +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Commit transaction to unblock ALTER TABLE. +commit; +# +# Switching to connection 'default'. +# Reap ALTER TABLE. +unlock tables; +drop table t1; +# # Test for bug #46748 "Assertion in MDL_context::wait_for_locks() # on INSERT + CREATE TRIGGER". # @@ -234,6 +448,43 @@ drop table t2; # Clean-up. drop table t1; # +# Test for bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed +# in case of ALTER". +# +drop table if exists t1; +set debug_sync= 'RESET'; +create table t1 (c1 int primary key, c2 int, c3 int); +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); +begin; +update t1 set c3=c3+1 where c2=3; +# +# Switching to connection 'con46273'. +set debug_sync='after_lock_tables_takes_lock SIGNAL alter_table_locked WAIT_FOR alter_go'; +alter table t1 add column e int, rename to t2;; +# +# Switching to connection 'default'. +set debug_sync='now WAIT_FOR alter_table_locked'; +set debug_sync='wait_for_lock SIGNAL alter_go'; +# The below statement should get ER_LOCK_DEADLOCK error +# (i.e. it should not allow ALTER to proceed, and then +# fail due to 't1' changing its name to 't2'). +update t1 set c3=c3+1 where c2=4; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# +# Let us check that failure of the above statement has not released +# metadata lock on table 't1', i.e. that ALTER TABLE is still blocked. +# Unblock ALTER TABLE by commiting transaction and thus releasing +# metadata lock on 't1'. +commit; +# +# Switching to connection 'con46273'. +# Reap ALTER TABLE. +# +# Switching to connection 'default'. +# Clean-up. +set debug_sync= 'RESET'; +drop table t2; +# # Test for bug #46673 "Deadlock between FLUSH TABLES WITH READ LOCK # and DML". # diff --git a/mysql-test/r/sp-lock.result b/mysql-test/r/sp-lock.result index 65524d02d08..e9087f61807 100644 --- a/mysql-test/r/sp-lock.result +++ b/mysql-test/r/sp-lock.result @@ -125,16 +125,15 @@ drop temporary table t1; # # For that, start a transaction, use a routine. In a concurrent # connection, try to drop or alter the routine. It should place -# a pending or exlusive lock and block. In a concurrnet -# connection, try to use the routine under LOCK TABLES. -# That should yield ER_LOCK_DEADLOCK. +# a pending or exclusive lock and block. In another concurrnet +# connection, try to use the routine. +# That should block on the pending exclusive lock. # # Establish helper connections. # # Test DROP PROCEDURE. # # --> connection default -create table t1 (a int); create procedure p1() begin end; create function f1() returns int begin @@ -151,14 +150,17 @@ drop procedure p1; # --> connection con2 # Waitng for 'drop procedure t1' to get blocked on MDL lock... # Demonstrate that there is a pending exclusive lock. -lock table t1 read; +# Sending 'select f1()'... select f1(); -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -unlock tables; +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... # --> connection default commit; # --> connection con1 # Reaping 'drop procedure p1'... +# --> connection con2 +# Reaping 'select f1()' +ERROR 42000: PROCEDURE test.p1 does not exist # --> connection default # # Test CREATE PROCEDURE. @@ -174,17 +176,22 @@ create procedure p1() begin end; # --> connection con2 # Waitng for 'create procedure t1' to get blocked on MDL lock... # Demonstrate that there is a pending exclusive lock. -lock table t1 read; +# Sending 'select f1()'... select f1(); -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -unlock tables; +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... # --> connection default commit; # --> connection con1 # Reaping 'create procedure p1'... ERROR 42000: PROCEDURE p1 already exists +# --> connection con2 +# Reaping 'select f1()' +f1() +1 # # Test ALTER PROCEDURE. +# begin; select f1(); f1() @@ -195,14 +202,18 @@ alter procedure p1 contains sql; # --> connection con2 # Waitng for 'alter procedure t1' to get blocked on MDL lock... # Demonstrate that there is a pending exclusive lock. -lock table t1 read; +# Sending 'select f1()'... select f1(); -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -unlock tables; +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... # --> connection default commit; # --> connection con1 # Reaping 'alter procedure p1'... +# --> connection con2 +# Reaping 'select f1()' +f1() +1 # --> connection default # # Test DROP FUNCTION. @@ -217,14 +228,17 @@ drop function f1; # --> connection con2 # Waitng for 'drop function f1' to get blocked on MDL lock... # Demonstrate that there is a pending exclusive lock. -lock table t1 read; +# Sending 'select f1()'... select f1(); -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -unlock tables; +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... # --> connection default commit; # --> connection con1 # Reaping 'drop function f1'... +# --> connection con2 +# Reaping 'select f1()' +ERROR 42000: FUNCTION test.f1 does not exist # --> connection default # # Test CREATE FUNCTION. @@ -240,18 +254,23 @@ create function f1() returns int return 2; # --> connection con2 # Waitng for 'create function f1' to get blocked on MDL lock... # Demonstrate that there is a pending exclusive lock. -lock table t1 read; +# Sending 'select f1()'... select f1(); -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -unlock tables; +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... # --> connection default commit; # --> connection con1 # Reaping 'create function f1'... ERROR 42000: FUNCTION f1 already exists +# --> connection con2 +# Reaping 'select f1()' +f1() +1 # --> connection default # # Test ALTER FUNCTION. +# begin; select f1(); f1() @@ -262,14 +281,18 @@ alter function f1 contains sql; # --> connection con2 # Waitng for 'alter function f1' to get blocked on MDL lock... # Demonstrate that there is a pending exclusive lock. -lock table t1 read; +# Sending 'select f1()'... select f1(); -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -unlock tables; +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... # --> connection default commit; # --> connection con1 # Reaping 'alter function f1'... +# --> connection con2 +# Reaping 'select f1()' +f1() +1 # --> connection default drop function f1; drop procedure p1; @@ -283,6 +306,7 @@ drop procedure p1; # create procedure p1() begin end; create procedure p2() begin end; +create table t1 (a int); create procedure p3() begin call p1(); @@ -415,36 +439,11 @@ drop table t1, t2; # acquisition of a shared lock fails during a transaction or # we need to back off to flush the sp cache. # -# a) A back off due to a lock conflict. -# -create table t1 (a int); -create function f1() returns int return 6; -begin; -select f1(); -f1() -6 -# --> connection con1 -# Sending 'drop function f1'... -drop function f1; -# --> connection con2 -# Waitng for 'drop function f1' to get blocked on MDL lock... -begin; -select * from t1; -a -select f1(); -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -commit; -# --> connection default -commit; -# --> connection con1 -# Reaping 'drop function f1'... -# --> connection default -# -# b) A back off to flush the cache. # Sic: now this situation does not require a back off since we # flush the cache on the fly. # create function f1() returns int return 7; +create table t1 (a int); begin; select * from t1; a @@ -691,6 +690,7 @@ drop function f1; set @@session.max_sp_recursion_depth=default; # --> connection con1 # --> connection con2 +# --> connection con3 # --> connection default # # End of 5.5 tests diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test index e3aceaa05fa..c817012fb2f 100644 --- a/mysql-test/t/mdl_sync.test +++ b/mysql-test/t/mdl_sync.test @@ -73,6 +73,366 @@ SET DEBUG_SYNC= 'RESET'; --enable_warnings +--echo # +--echo # Test coverage for basic deadlock detection in metadata +--echo # locking subsystem. +--echo # +--disable_warnings +drop tables if exists t1, t2, t3, t4; +--enable_warnings + +connect(deadlock_con1,localhost,root,,); +connect(deadlock_con2,localhost,root,,); +connect(deadlock_con3,localhost,root,,); +connection default; +create table t1 (i int); +create table t2 (j int); +create table t3 (k int); +create table t4 (k int); + +--echo # +--echo # Test for the case in which no deadlock occurs. +--echo # + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +begin; +insert into t1 values (1); + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +begin; +insert into t2 values (1); + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Send: +--send rename table t2 to t0, t3 to t2, t0 to t3; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait until the above RENAME TABLE is blocked because it has to wait +--echo # for 'deadlock_con2' which holds shared metadata lock on 't2'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t2 to t0, t3 to t2, t0 to t3"; +--source include/wait_condition.inc +--echo # The below statement should wait for exclusive metadata lock +--echo # on 't2' to go away and should not produce ER_LOCK_DEADLOCK +--echo # as no deadlock is possible in this situation. +--echo # Send: +--send select * from t2; + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +--echo # Wait until the above SELECT * FROM t2 is starts waiting +--echo # for an exclusive metadata lock to go away. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "select * from t2"; +--source include/wait_condition.inc +--echo # +--echo # Unblock RENAME TABLE by releasing shared metadata lock on t2. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap RENAME TABLE. +--reap + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Reap SELECT. +--reap + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # Let us check that in the process of waiting for conflicting lock +--echo # on table 't2' to go away transaction in connection 'deadlock_con1' +--echo # has not released metadata lock on table 't1'. +--echo # Send: +--send rename table t1 to t0, t3 to t1, t0 to t3; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait until the above RENAME TABLE is blocked because it has to wait +--echo # for 'deadlock_con1' which should still hold shared metadata lock on +--echo # table 't1'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t0, t3 to t1, t0 to t3"; +--source include/wait_condition.inc +--echo # Commit transaction to unblock RENAME TABLE. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap RENAME TABLE. +--reap + +--echo # +--echo # Test for case when deadlock occurs and should be detected immediately. +--echo # + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +begin; +insert into t1 values (2); + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Send: +--send rename table t2 to t0, t1 to t2, t0 to t1; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait until the above RENAME TABLE is blocked because it has to wait +--echo # for 'deadlock_con1' which holds shared metadata lock on 't1'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t2 to t0, t1 to t2, t0 to t1"; +--source include/wait_condition.inc +--echo # +--echo # The below statement should not wait as doing so will cause deadlock. +--echo # Instead it should fail and emit ER_LOCK_DEADLOCK statement. +--error ER_LOCK_DEADLOCK +select * from t2; + +--echo # +--echo # Let us check that failure of the above statement has not released +--echo # metadata lock on table 't1', i.e. that RENAME TABLE is still blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t2 to t0, t1 to t2, t0 to t1"; +--source include/wait_condition.inc +--echo # Commit transaction to unblock RENAME TABLE. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap RENAME TABLE. +--reap + +--echo # +--echo # Test for the case in which deadlock also occurs but not immediately. +--echo # + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +begin; +insert into t1 values (1); + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +begin; +insert into t3 values (1); + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Send: +--send rename table t2 to t0, t3 to t2, t0 to t3; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait until the above RENAME TABLE is blocked because it has to wait +--echo # for 'deadlock_con2' which holds shared metadata lock on 't3'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t2 to t0, t3 to t2, t0 to t3"; +--source include/wait_condition.inc +--echo # The below SELECT statement should wait for metadata lock +--echo # on table 't2' and should not produce ER_LOCK_DEADLOCK +--echo # immediately as no deadlock is possible at the moment. +--send select * from t2; + +--echo # +--echo # Switching to connection 'deadlock_con3'. +connection deadlock_con3; +--echo # Wait until the above SELECT * FROM t2 is starts waiting +--echo # for an exclusive metadata lock to go away. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "select * from t2"; +--source include/wait_condition.inc + +--echo # Send RENAME TABLE statement that will deadlock with the +--echo # SELECT statement and thus should abort the latter. +--send rename table t1 to t0, t2 to t1, t0 to t2; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Since the latest RENAME TABLE entered in deadlock with SELECT +--echo # statement the latter should be aborted and emit ER_LOCK_DEADLOCK +--echo # error. +--echo # Reap SELECT * FROM t2. +--error ER_LOCK_DEADLOCK +--reap + +--echo # +--echo # Again let us check that failure of the SELECT statement has not +--echo # released metadata lock on table 't1', i.e. that the latest RENAME +--echo # is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t0, t2 to t1, t0 to t2"; +--source include/wait_condition.inc +--echo # Commit transaction to unblock this RENAME TABLE. +commit; + +--echo # +--echo # Switching to connection 'deadlock_con3'. +connection deadlock_con3; +--echo # Reap RENAME TABLE t1 TO t0 ... . +--reap; + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +--echo # Commit transaction to unblock the first RENAME TABLE. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap RENAME TABLE t2 TO t0 ... . +--reap + +drop tables t1, t2, t3, t4; + +--echo # +--echo # Now, test case which shows that deadlock detection empiric +--echo # also takes into account requests for metadata lock upgrade. +--echo # +create table t1 (i int); + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +begin; +insert into t1 values (1); + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Send: +--send alter table t1 add column j int, rename to t2; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait until the above ALTER TABLE ... RENAME acquires exclusive +--echo # metadata lock on 't2' and starts waiting for connection +--echo # 'deadlock_con1' which holds shared lock on 't1'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column j int, rename to t2"; +--source include/wait_condition.inc + +--echo # The below statement should not wait as it will cause deadlock. +--echo # An appropriate error should be reported instead. +--error ER_LOCK_DEADLOCK +select * from t2; + +--echo # Again let us check that failure of the above statement has not +--echo # released all metadata locks in connection 'deadlock_con1' and +--echo # so ALTER TABLE ... RENAME is still blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column j int, rename to t2"; +--source include/wait_condition.inc + +--echo # Commit transaction to unblock ALTER TABLE ... RENAME. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap ALTER TABLE ... RENAME. +--reap + +drop table t2; + +--echo # +--echo # Finally, test case in which deadlock (or potentially livelock) occurs +--echo # between metadata locking subsystem and table definition cache/table +--echo # locks, but which should still be detected by our empiric. +--echo # +create table t1 (i int); + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +begin; +insert into t1 values (1); + +--echo # +--echo # Switching to connection 'default'. +connection default; +lock tables t1 write; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Send: +--send insert into t1 values (2); + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until INSERT in connection 'deadlock_con1' is blocked on +--echo # table-level lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "insert into t1 values (2)"; +--source include/wait_condition.inc + +--echo # Send: +--send alter table t1 add column j int; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # The above ALTER TABLE statement should cause INSERT statement in +--echo # this connection to be aborted and emit ER_LOCK_DEADLOCK error. +--echo # Reap INSERT +--error ER_LOCK_DEADLOCK +--reap +--echo # Commit transaction to unblock ALTER TABLE. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap ALTER TABLE. +--reap +unlock tables; + +drop table t1; +disconnect deadlock_con1; +disconnect deadlock_con2; +disconnect deadlock_con3; + + --echo # --echo # Test for bug #46748 "Assertion in MDL_context::wait_for_locks() --echo # on INSERT + CREATE TRIGGER". @@ -439,6 +799,66 @@ disconnect con46044_2; drop table t1; +--echo # +--echo # Test for bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed +--echo # in case of ALTER". +--echo # +--disable_warnings +drop table if exists t1; +--enable_warnings +set debug_sync= 'RESET'; +connect (con46273,localhost,root,,test,,); +connection default; +create table t1 (c1 int primary key, c2 int, c3 int); +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); + +begin; +update t1 set c3=c3+1 where c2=3; + +--echo # +--echo # Switching to connection 'con46273'. +connection con46273; +set debug_sync='after_lock_tables_takes_lock SIGNAL alter_table_locked WAIT_FOR alter_go'; +--send alter table t1 add column e int, rename to t2; + +--echo # +--echo # Switching to connection 'default'. +connection default; +set debug_sync='now WAIT_FOR alter_table_locked'; +set debug_sync='wait_for_lock SIGNAL alter_go'; +--echo # The below statement should get ER_LOCK_DEADLOCK error +--echo # (i.e. it should not allow ALTER to proceed, and then +--echo # fail due to 't1' changing its name to 't2'). +--error ER_LOCK_DEADLOCK +update t1 set c3=c3+1 where c2=4; + +--echo # +--echo # Let us check that failure of the above statement has not released +--echo # metadata lock on table 't1', i.e. that ALTER TABLE is still blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column e int, rename to t2"; +--source include/wait_condition.inc + +--echo # Unblock ALTER TABLE by commiting transaction and thus releasing +--echo # metadata lock on 't1'. +commit; + +--echo # +--echo # Switching to connection 'con46273'. +connection con46273; +--echo # Reap ALTER TABLE. +--reap + +--echo # +--echo # Switching to connection 'default'. +connection default; +disconnect con46273; +--echo # Clean-up. +set debug_sync= 'RESET'; +drop table t2; + + --echo # --echo # Test for bug #46673 "Deadlock between FLUSH TABLES WITH READ LOCK --echo # and DML". diff --git a/mysql-test/t/sp-lock.test b/mysql-test/t/sp-lock.test index a90b85a59be..0b31eabb0f1 100644 --- a/mysql-test/t/sp-lock.test +++ b/mysql-test/t/sp-lock.test @@ -153,20 +153,20 @@ drop temporary table t1; --echo # --echo # For that, start a transaction, use a routine. In a concurrent --echo # connection, try to drop or alter the routine. It should place ---echo # a pending or exlusive lock and block. In a concurrnet ---echo # connection, try to use the routine under LOCK TABLES. ---echo # That should yield ER_LOCK_DEADLOCK. +--echo # a pending or exclusive lock and block. In another concurrnet +--echo # connection, try to use the routine. +--echo # That should block on the pending exclusive lock. --echo # --echo # Establish helper connections. connect(con1, localhost, root,,); connect(con2, localhost, root,,); +connect(con3, localhost, root,,); --echo # --echo # Test DROP PROCEDURE. --echo # --echo # --> connection default connection default; -create table t1 (a int); create procedure p1() begin end; delimiter |; create function f1() returns int @@ -180,7 +180,7 @@ select f1(); --echo # --> connection con1 connection con1; --echo # Sending 'drop procedure p1'... ---send drop procedure p1 +send drop procedure p1; --echo # --> connection con2 connection con2; --echo # Waitng for 'drop procedure t1' to get blocked on MDL lock... @@ -188,17 +188,25 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop procedure p1'; --source include/wait_condition.inc --echo # Demonstrate that there is a pending exclusive lock. -lock table t1 read; ---error ER_LOCK_DEADLOCK -select f1(); -unlock tables; +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; --echo # --> connection default connection default; commit; --echo # --> connection con1 connection con1; --echo # Reaping 'drop procedure p1'... ---reap +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +--error ER_SP_DOES_NOT_EXIST +reap; --echo # --> connection default connection default; @@ -211,7 +219,7 @@ select f1(); --echo # --> connection con1 connection con1; --echo # Sending 'create procedure p1'... ---send create procedure p1() begin end +send create procedure p1() begin end; --echo # --> connection con2 connection con2; --echo # Waitng for 'create procedure t1' to get blocked on MDL lock... @@ -219,10 +227,13 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='create procedure p1() begin end'; --source include/wait_condition.inc --echo # Demonstrate that there is a pending exclusive lock. -lock table t1 read; ---error ER_LOCK_DEADLOCK -select f1(); -unlock tables; +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; --echo # --> connection default connection default; commit; @@ -230,16 +241,22 @@ commit; connection con1; --echo # Reaping 'create procedure p1'... --error ER_SP_ALREADY_EXISTS ---reap +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +reap; connection default; + --echo # --echo # Test ALTER PROCEDURE. +--echo # begin; select f1(); --echo # --> connection con1 connection con1; --echo # Sending 'alter procedure p1'... ---send alter procedure p1 contains sql +send alter procedure p1 contains sql; --echo # --> connection con2 connection con2; --echo # Waitng for 'alter procedure t1' to get blocked on MDL lock... @@ -247,17 +264,24 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='alter procedure p1 contains sql'; --source include/wait_condition.inc --echo # Demonstrate that there is a pending exclusive lock. -lock table t1 read; ---error ER_LOCK_DEADLOCK -select f1(); -unlock tables; +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; --echo # --> connection default connection default; commit; --echo # --> connection con1 connection con1; --echo # Reaping 'alter procedure p1'... ---reap +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +reap; --echo # --> connection default connection default; @@ -269,7 +293,7 @@ select f1(); --echo # --> connection con1 connection con1; --echo # Sending 'drop function f1'... ---send drop function f1 +send drop function f1; --echo # --> connection con2 connection con2; --echo # Waitng for 'drop function f1' to get blocked on MDL lock... @@ -277,17 +301,25 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop function f1'; --source include/wait_condition.inc --echo # Demonstrate that there is a pending exclusive lock. -lock table t1 read; ---error ER_LOCK_DEADLOCK -select f1(); -unlock tables; +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; --echo # --> connection default connection default; commit; --echo # --> connection con1 connection con1; --echo # Reaping 'drop function f1'... ---reap +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +--error ER_SP_DOES_NOT_EXIST +reap; --echo # --> connection default connection default; @@ -300,7 +332,7 @@ select f1(); --echo # --> connection con1 connection con1; --echo # Sending 'create function f1'... ---send create function f1() returns int return 2 +send create function f1() returns int return 2; --echo # --> connection con2 connection con2; --echo # Waitng for 'create function f1' to get blocked on MDL lock... @@ -308,10 +340,13 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='create function f1() returns int return 2'; --source include/wait_condition.inc --echo # Demonstrate that there is a pending exclusive lock. -lock table t1 read; ---error ER_LOCK_DEADLOCK -select f1(); -unlock tables; +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; --echo # --> connection default connection default; commit; @@ -319,17 +354,23 @@ commit; connection con1; --echo # Reaping 'create function f1'... --error ER_SP_ALREADY_EXISTS ---reap +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +reap; --echo # --> connection default connection default; + --echo # --echo # Test ALTER FUNCTION. +--echo # begin; select f1(); --echo # --> connection con1 connection con1; --echo # Sending 'alter function f1'... ---send alter function f1 contains sql +send alter function f1 contains sql; --echo # --> connection con2 connection con2; --echo # Waitng for 'alter function f1' to get blocked on MDL lock... @@ -337,17 +378,24 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='alter function f1 contains sql'; --source include/wait_condition.inc --echo # Demonstrate that there is a pending exclusive lock. -lock table t1 read; ---error ER_LOCK_DEADLOCK -select f1(); -unlock tables; +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; --echo # --> connection default connection default; commit; --echo # --> connection con1 connection con1; --echo # Reaping 'alter function f1'... ---reap +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +reap; --echo # --> connection default connection default; drop function f1; @@ -363,6 +411,7 @@ drop procedure p1; --echo # create procedure p1() begin end; create procedure p2() begin end; +create table t1 (a int); delimiter |; create procedure p3() begin @@ -419,7 +468,7 @@ insert into t1 (a) values (1); --echo # --> connection con1 connection con1; --echo # Sending 'drop function f1' ---send drop function f1 +send drop function f1; --echo # --> connection con2 connection con2; --echo # Waitng for 'drop function f1' to get blocked on MDL lock... @@ -432,7 +481,7 @@ commit; --echo # --> connection con1 connection con1; --echo # Reaping 'drop function f1'... ---reap +reap; --echo # --> connection default connection default; --echo # @@ -445,7 +494,7 @@ select * from v1; --echo # --> connection con1 connection con1; --echo # Sending 'drop function f1' ---send drop function f1 +send drop function f1; --echo # --> connection con2 connection con2; --echo # Waitng for 'drop function f1' to get blocked on MDL lock... @@ -458,7 +507,7 @@ commit; --echo # --> connection con1 connection con1; --echo # Reaping 'drop function f1'... ---reap +reap; --echo # --> connection default connection default; --echo # @@ -478,7 +527,7 @@ select * from v1; --echo # --> connection con1 connection con1; --echo # Sending 'drop procedure p1' ---send drop procedure p1 +send drop procedure p1; --echo # --> connection con2 connection con2; --echo # Waitng for 'drop procedure p1' to get blocked on MDL lock... @@ -491,7 +540,7 @@ commit; --echo # --> connection con1 connection con1; --echo # Reaping 'drop procedure p1'... ---reap +reap; --echo # --> connection default connection default; @@ -509,7 +558,7 @@ insert into t1 (a) values (3); --echo # --> connection con1 connection con1; --echo # Sending 'drop function f2' ---send drop function f2 +send drop function f2; --echo # --> connection con2 connection con2; --echo # Waitng for 'drop function f2' to get blocked on MDL lock... @@ -522,7 +571,7 @@ commit; --echo # --> connection con1 connection con1; --echo # Reaping 'drop function f2'... ---reap +reap; --echo # --> connection default connection default; @@ -536,42 +585,11 @@ drop table t1, t2; --echo # acquisition of a shared lock fails during a transaction or --echo # we need to back off to flush the sp cache. --echo # ---echo # a) A back off due to a lock conflict. ---echo # -create table t1 (a int); -create function f1() returns int return 6; -begin; -select f1(); ---echo # --> connection con1 -connection con1; ---echo # Sending 'drop function f1'... ---send drop function f1 ---echo # --> connection con2 -connection con2; ---echo # Waitng for 'drop function f1' to get blocked on MDL lock... -let $wait_condition=select count(*)=1 from information_schema.processlist -where state='Waiting for table' and info='drop function f1'; ---source include/wait_condition.inc -begin; -select * from t1; ---error ER_LOCK_DEADLOCK -select f1(); -commit; ---echo # --> connection default -connection default; -commit; ---echo # --> connection con1 -connection con1; ---echo # Reaping 'drop function f1'... ---reap ---echo # --> connection default -connection default; ---echo # ---echo # b) A back off to flush the cache. --echo # Sic: now this situation does not require a back off since we --echo # flush the cache on the fly. --echo # create function f1() returns int return 7; +create table t1 (a int); begin; select * from t1; # Used to have a back-off here, with optional ER_LOCK_DEADLOCK @@ -602,7 +620,7 @@ select f2(); --echo # --> connection con1 connection con1; --echo # Sending 'drop function f1'... ---send drop function f1 +send drop function f1; --echo # --> connection con2 connection con2; --echo # Waitng for 'drop function f1' to get blocked on MDL lock... @@ -610,7 +628,7 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop function f1'; --source include/wait_condition.inc --echo # Sending 'drop function f2'... ---send drop function f2 +send drop function f2; --echo # --> connection default connection default; --echo # Waitng for 'drop function f2' to get blocked on MDL lock... @@ -621,14 +639,14 @@ rollback to savepoint sv; --echo # --> connection con2 connection con2; --echo # Reaping 'drop function f2'... ---reap +reap; --echo # --> connection default connection default; unlock tables; --echo # --> connection con1 connection con1; --echo # Reaping 'drop function f1'... ---reap +reap; --echo # --> connection default connection default; --error ER_SP_DOES_NOT_EXIST @@ -678,7 +696,7 @@ select f1(); --echo # --> connection con1 connection con1; --echo # Sending 'alter function f1 ...'... ---send alter function f1 comment "comment" +send alter function f1 comment "comment"; --echo # --> connection con2 connection con2; --echo # Waitng for 'alter function f1 ...' to get blocked on MDL lock... @@ -686,7 +704,7 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info like 'alter function f1 comment%'; --source include/wait_condition.inc --echo # Sending 'call p1()'... ---send call p1() +send call p1(); connection default; --echo # Waitng for 'call p1()' to get blocked on MDL lock on f1... let $wait_condition=select count(*)=1 from information_schema.processlist @@ -697,11 +715,11 @@ commit; --echo # --> connection con1 connection con1; --echo # Reaping 'alter function f1 ...' ---reap +reap; --echo # --> connection con2 connection con2; --echo # Reaping 'call p1()'... ---reap +reap; deallocate prepare stmt; --echo # --> connection default connection default; @@ -725,7 +743,7 @@ select f1(); --echo # --> connection con1 connection con1; --echo # Sending 'alter function f1 ...'... ---send alter function f1 comment "comment" +send alter function f1 comment "comment"; --echo # --> connection con2 connection con2; --echo # Waitng for 'alter function f1 ...' to get blocked on MDL lock... @@ -754,7 +772,7 @@ begin end| delimiter ;| --echo # Sending 'call p1()'... ---send call p1() +send call p1(); connection default; --echo # Waitng for 'call p1()' to get blocked on MDL lock on f1... let $wait_condition=select count(*)=1 from information_schema.processlist @@ -765,11 +783,11 @@ commit; --echo # --> connection con1 connection con1; --echo # Reaping 'alter function f1 ...' ---reap +reap; --echo # --> connection con2 connection con2; --echo # Reaping 'call p1()'... ---reap +reap; --echo # --> connection default connection default; drop function f1; @@ -804,7 +822,7 @@ select get_lock("30977", 0); --echo # --> connection default connection default; --echo # Sending 'select f3()'... ---send select f3() +send select f3(); --echo # --> connection con1 connection con1; --echo # Waitng for 'select f3()' to get blocked on the user level lock... @@ -819,7 +837,7 @@ select release_lock("30977"); connection default; --echo # Reaping 'select f3()'... --echo # Routine 'f2()' should exist and get executed successfully. ---reap +reap; select @var; drop function f1; drop function f2; @@ -869,6 +887,10 @@ disconnect con1; connection con2; disconnect con2; --source include/wait_until_disconnected.inc +--echo # --> connection con3 +connection con3; +disconnect con3; +--source include/wait_until_disconnected.inc --echo # --> connection default connection default; --echo # diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 942396fc3da..0f6f4d1d0e5 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -1506,7 +1506,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ thd->binlog_flush_pending_rows_event(false); TABLE_LIST *tables= rli->tables_to_lock; - close_tables_for_reopen(thd, &tables); + close_tables_for_reopen(thd, &tables, NULL); uint tables_count= rli->tables_to_lock_count; if ((error= open_tables(thd, &tables, &tables_count, 0))) diff --git a/sql/mdl.cc b/sql/mdl.cc index 40074879e21..af7f310e598 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -196,6 +196,7 @@ void MDL_context::init(THD *thd_arg) to empty the list. */ m_tickets.empty(); + m_is_waiting_in_mdl= FALSE; } @@ -803,14 +804,28 @@ MDL_context::clone_ticket(MDL_request *mdl_request) @retval FALSE Lock is not a shared one or no thread was woken up */ -static bool notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) +bool notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) { bool woke= FALSE; if (conflicting_ticket->is_shared()) { THD *conflicting_thd= conflicting_ticket->get_ctx()->get_thd(); DBUG_ASSERT(thd != conflicting_thd); /* Self-deadlock */ - woke= mysql_notify_thread_having_shared_lock(thd, conflicting_thd); + + /* + If the thread that holds the conflicting lock is waiting + on an MDL lock, wake it up by broadcasting on COND_mdl. + Otherwise it must be waiting on a table-level lock + or some other non-MDL resource, so delegate its waking up + to an external call. + */ + if (conflicting_ticket->get_ctx()->is_waiting_in_mdl()) + { + pthread_cond_broadcast(&COND_mdl); + woke= TRUE; + } + else + woke= mysql_notify_thread_having_shared_lock(thd, conflicting_thd); } return woke; } @@ -957,7 +972,7 @@ bool MDL_context::acquire_exclusive_locks(MDL_request_list *mdl_requests) to abort this thread once again. */ struct timespec abstime; - set_timespec(abstime, 10); + set_timespec(abstime, 1); pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime); } if (mysys_var->abort) @@ -1032,6 +1047,7 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() const char *old_msg; st_my_thread_var *mysys_var= my_thread_var; THD *thd= m_ctx->get_thd(); + MDL_ticket *pending_ticket; DBUG_ENTER("MDL_ticket::upgrade_shared_lock_to_exclusive"); DEBUG_SYNC(thd, "mdl_upgrade_shared_lock_to_exclusive"); @@ -1045,8 +1061,22 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() /* Only allow upgrades from MDL_SHARED_UPGRADABLE */ DBUG_ASSERT(m_type == MDL_SHARED_UPGRADABLE); + /* + Create an auxiliary ticket to represent a pending exclusive + lock and add it to the 'waiting' queue for the duration + of upgrade. During upgrade we abort waits of connections + that own conflicting locks. A pending request is used + to signal such connections that upon waking up they + must back off, rather than fall into sleep again. + */ + if (! (pending_ticket= MDL_ticket::create(m_ctx, MDL_EXCLUSIVE))) + DBUG_RETURN(TRUE); + pthread_mutex_lock(&LOCK_mdl); + pending_ticket->m_lock= m_lock; + m_lock->waiting.push_front(pending_ticket); + old_msg= MDL_ENTER_COND(thd, mysys_var); /* @@ -1088,6 +1118,30 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() MDL_ticket *conflicting_ticket; MDL_lock::Ticket_iterator it(m_lock->granted); + /* + A temporary work-around to avoid deadlocks/livelocks in + a situation when in one connection ALTER TABLE tries to + upgrade its metadata lock and in another connection + the active transaction already got this lock in some + of its earlier statements. + In such case this transaction always succeeds with getting + a metadata lock on the table -- it already has one. + But later on it may block on the table level lock, since ALTER + got TL_WRITE_ALLOW_READ, and subsequently get aborted + by notify_shared_lock(). + An abort will lead to a back off, and a second attempt to + get an MDL lock (successful), and a table lock (-> livelock). + + The call below breaks this loop by forcing transactions to call + tdc_wait_for_old_versions() (even if the transaction doesn't need + any new metadata locks), which in turn will check if someone + is waiting on the owned MDL lock, and produce ER_LOCK_DEADLOCK. + + TODO: Long-term such deadlocks/livelock will be resolved within + MDL subsystem and thus this call will become unnecessary. + */ + mysql_abort_transactions_with_shared_lock(&m_lock->key); + while ((conflicting_ticket= it++)) { if (conflicting_ticket->m_ctx != m_ctx) @@ -1108,12 +1162,15 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() to abort this thread once again. */ struct timespec abstime; - set_timespec(abstime, 10); + set_timespec(abstime, 1); DBUG_PRINT("info", ("Failed to wake-up from table-level lock ... sleeping")); pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime); } if (mysys_var->abort) { + /* Remove and destroy the auxiliary pending ticket. */ + m_lock->waiting.remove(pending_ticket); + MDL_ticket::destroy(pending_ticket); /* Pending requests for shared locks can be satisfied now. */ pthread_cond_broadcast(&COND_mdl); MDL_EXIT_COND(thd, mysys_var, old_msg); @@ -1124,6 +1181,11 @@ MDL_ticket::upgrade_shared_lock_to_exclusive() m_lock->type= MDL_lock::MDL_LOCK_EXCLUSIVE; /* Set the new type of lock in the ticket. */ m_type= MDL_EXCLUSIVE; + + /* Remove and destroy the auxiliary pending ticket. */ + m_lock->waiting.remove(pending_ticket); + MDL_ticket::destroy(pending_ticket); + if (m_lock->cached_object) (*m_lock->cached_object_release_hook)(m_lock->cached_object); m_lock->cached_object= 0; @@ -1239,6 +1301,59 @@ bool MDL_context::acquire_global_shared_lock() } +/** + Check if there are any pending exclusive locks which conflict + with shared locks held by this thread. + + @pre The caller already has acquired LOCK_mdl. + + @return TRUE If there are any pending conflicting locks. + FALSE Otherwise. +*/ + +bool MDL_context::can_wait_lead_to_deadlock_impl() const +{ + Ticket_iterator ticket_it(m_tickets); + MDL_ticket *ticket; + + while ((ticket= ticket_it++)) + { + /* + In MySQL we never call this method while holding exclusive or + upgradeable shared metadata locks. + Otherwise we would also have to check for the presence of pending + requests for conflicting types of global lock. + In addition MDL_ticket::has_pending_conflicting_lock_impl() + won't work properly for exclusive type of lock. + */ + DBUG_ASSERT(! ticket->is_upgradable_or_exclusive()); + + if (ticket->has_pending_conflicting_lock_impl()) + return TRUE; + } + return FALSE; +} + + +/** + Implement a simple deadlock detection heuristic: check if there + are any pending exclusive locks which conflict with shared locks + held by this thread. In that case waiting can be circular, + i.e. lead to a deadlock. + + @return TRUE if there are any conflicting locks, FALSE otherwise. +*/ + +bool MDL_context::can_wait_lead_to_deadlock() const +{ + bool result; + pthread_mutex_lock(&LOCK_mdl); + result= can_wait_lead_to_deadlock_impl(); + pthread_mutex_unlock(&LOCK_mdl); + return result; +} + + /** Wait until there will be no locks that conflict with lock requests in the given list. @@ -1249,7 +1364,7 @@ bool MDL_context::acquire_global_shared_lock() Does not acquire the locks! @retval FALSE Success. One can try to obtain metadata locks. - @retval TRUE Failure (thread was killed) + @retval TRUE Failure (thread was killed or deadlock is possible). */ bool @@ -1278,6 +1393,26 @@ MDL_context::wait_for_locks(MDL_request_list *mdl_requests) mysql_ha_flush(m_thd); pthread_mutex_lock(&LOCK_mdl); old_msg= MDL_ENTER_COND(m_thd, mysys_var); + + /* + In cases when we wait while still holding some metadata + locks deadlocks are possible. + To avoid them we use the following simple empiric - don't + wait for new lock request to be satisfied if for one of the + locks which are already held by this connection there is + a conflicting request (i.e. this connection should not wait + if someone waits for it). + This empiric should work well (e.g. give low number of false + negatives) in situations when conflicts are rare (in our + case this is true since DDL statements should be rare). + */ + if (can_wait_lead_to_deadlock_impl()) + { + MDL_EXIT_COND(m_thd, mysys_var, old_msg); + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; + } + it.rewind(); while ((mdl_request= it++)) { @@ -1301,7 +1436,9 @@ MDL_context::wait_for_locks(MDL_request_list *mdl_requests) MDL_EXIT_COND(m_thd, mysys_var, old_msg); break; } + m_is_waiting_in_mdl= TRUE; pthread_cond_wait(&COND_mdl, &LOCK_mdl); + m_is_waiting_in_mdl= FALSE; /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */ MDL_EXIT_COND(m_thd, mysys_var, old_msg); } @@ -1550,21 +1687,38 @@ MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, existing shared lock. @pre The ticket must match an acquired lock. + @pre The caller already has acquired LOCK_mdl. - @param ticket Shared lock against which check should be performed. + @return TRUE if there is a conflicting lock request, FALSE otherwise. +*/ - @return TRUE if there are any conflicting locks, FALSE otherwise. +bool MDL_ticket::has_pending_conflicting_lock_impl() const +{ + DBUG_ASSERT(is_shared()); + safe_mutex_assert_owner(&LOCK_mdl); + + return !m_lock->waiting.is_empty(); +} + + +/** + Check if we have any pending exclusive locks which conflict with + existing shared lock. + + @pre The ticket must match an acquired lock. + + @return TRUE if there is a pending conflicting lock request, + FALSE otherwise. */ bool MDL_ticket::has_pending_conflicting_lock() const { bool result; - DBUG_ASSERT(is_shared()); safe_mutex_assert_not_owner(&LOCK_open); pthread_mutex_lock(&LOCK_mdl); - result= !m_lock->waiting.is_empty(); + result= has_pending_conflicting_lock_impl(); pthread_mutex_unlock(&LOCK_mdl); return result; } diff --git a/sql/mdl.h b/sql/mdl.h index e85f1232ff9..8edbfbc0777 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -300,6 +300,8 @@ private: private: MDL_ticket(const MDL_ticket &); /* not implemented */ MDL_ticket &operator=(const MDL_ticket &); /* not implemented */ + + bool has_pending_conflicting_lock_impl() const; }; @@ -380,10 +382,19 @@ public: void release_transactional_locks(); void rollback_to_savepoint(MDL_ticket *mdl_savepoint); + bool can_wait_lead_to_deadlock() const; + inline THD *get_thd() const { return m_thd; } + + bool is_waiting_in_mdl() const { return m_is_waiting_in_mdl; } private: Ticket_list m_tickets; bool m_has_global_shared_lock; + /** + Indicates that the owner of this context is waiting in + wait_for_locks() method. + */ + bool m_is_waiting_in_mdl; /** This member has two uses: 1) When entering LOCK TABLES mode, remember the last taken @@ -397,6 +408,7 @@ private: THD *m_thd; private: void release_ticket(MDL_ticket *ticket); + bool can_wait_lead_to_deadlock_impl() const; MDL_ticket *find_ticket(MDL_request *mdl_req, bool *is_lt_or_ha); void release_locks_stored_before(MDL_ticket *sentinel); @@ -413,6 +425,7 @@ void mdl_destroy(); extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use); extern void mysql_ha_flush(THD *thd); +extern void mysql_abort_transactions_with_shared_lock(const MDL_key *mdl_key); extern "C" const char *set_thd_proc_info(THD *thd, const char *info, const char *calling_function, const char *calling_file, diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 124392f4c63..bb5bf428ef0 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1522,7 +1522,8 @@ void free_io_cache(TABLE *entry); void intern_close_table(TABLE *entry); bool close_thread_table(THD *thd, TABLE **table_ptr); void close_temporary_tables(THD *thd); -void close_tables_for_reopen(THD *thd, TABLE_LIST **tables); +void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, + MDL_ticket *mdl_savepoint); TABLE_LIST *find_table_in_list(TABLE_LIST *table, TABLE_LIST *TABLE_LIST::*link, const char *db_name, diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 78bb9f9bad7..d02e4f38807 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2142,25 +2142,13 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db, bool wait_while_table_is_used(THD *thd, TABLE *table, enum ha_extra_function function) { - enum thr_lock_type old_lock_type; DBUG_ENTER("wait_while_table_is_used"); DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", table->s->table_name.str, (ulong) table->s, table->db_stat, table->s->version)); - /* Ensure no one can reopen table before it's removed */ - pthread_mutex_lock(&LOCK_open); - table->s->version= 0; - pthread_mutex_unlock(&LOCK_open); - - old_lock_type= table->reginfo.lock_type; - mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ - if (table->mdl_ticket->upgrade_shared_lock_to_exclusive()) - { - mysql_lock_downgrade_write(thd, table, old_lock_type); DBUG_RETURN(TRUE); - } pthread_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN, @@ -3722,9 +3710,10 @@ end_with_lock_open: Open_table_context::Open_table_context(THD *thd) :m_action(OT_NO_ACTION), - m_can_deadlock((thd->in_multi_stmt_transaction() || - thd->mdl_context.lt_or_ha_sentinel())&& - thd->mdl_context.has_locks()) + m_start_of_statement_svp(thd->mdl_context.mdl_savepoint()), + m_has_locks((thd->in_multi_stmt_transaction() || + thd->mdl_context.lt_or_ha_sentinel()) && + thd->mdl_context.has_locks()) {} @@ -3741,12 +3730,22 @@ Open_table_context:: request_backoff_action(enum_open_table_action action_arg) { /* - We have met a exclusive metadata lock or a old version of - table and we are inside a transaction that already hold locks. - We can't follow the locking protocol in this scenario as it - might lead to deadlocks. + We are inside a transaction that already holds locks and have + met a broken table or a table which needs re-discovery. + Performing any recovery action requires acquiring an exclusive + metadata lock on this table. Doing that with locks breaks the + metadata locking protocol and might lead to deadlocks, + so we report an error. + + However, if we have only met a conflicting lock or an old + TABLE version, and just need to wait for the conflict to + disappear/old version to go away, allow waiting. + While waiting, we use a simple empiric to detect + deadlocks: we never wait on someone who's waiting too. + Waiting will be done after releasing metadata locks acquired + by this statement. */ - if (m_can_deadlock) + if (m_has_locks && action_arg != OT_WAIT) { my_error(ER_LOCK_DEADLOCK, MYF(0)); return TRUE; @@ -4364,7 +4363,7 @@ restart: elements from the table list (if MERGE tables are involved), */ TABLE_LIST *failed_table= *table_to_open; - close_tables_for_reopen(thd, start); + close_tables_for_reopen(thd, start, ot_ctx.start_of_statement_svp()); /* Here we rely on the fact that 'tables' still points to the valid @@ -4414,7 +4413,8 @@ restart: { if (ot_ctx.can_recover_from_failed_open()) { - close_tables_for_reopen(thd, start); + close_tables_for_reopen(thd, start, + ot_ctx.start_of_statement_svp()); if (ot_ctx.recover_from_failed_open(thd, &rt->mdl_request, NULL)) goto err; @@ -4827,14 +4827,14 @@ retry: while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, 0)) && ot_ctx.can_recover_from_failed_open()) { - /* We can't back off with an open HANDLER, we don't wait with locks. */ + /* We never have an open HANDLER or LOCK TABLES here. */ DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); /* Even though we have failed to open table we still need to call release_transactional_locks() to release metadata locks which might have been acquired successfully. */ - thd->mdl_context.release_transactional_locks(); + thd->mdl_context.rollback_to_savepoint(ot_ctx.start_of_statement_svp()); table_list->mdl_request.ticket= 0; if (ot_ctx.recover_from_failed_open(thd, &table_list->mdl_request, table_list)) @@ -4876,24 +4876,13 @@ retry: { if (refresh) { - if (ot_ctx.can_deadlock()) - { - my_error(ER_LOCK_DEADLOCK, MYF(0)); - table= 0; - } - else - { - close_thread_tables(thd); - table_list->table= NULL; - table_list->mdl_request.ticket= NULL; - /* - We can't back off with an open HANDLER, - we don't wait with locks. - */ - DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); - thd->mdl_context.release_transactional_locks(); - goto retry; - } + close_thread_tables(thd); + table_list->table= NULL; + table_list->mdl_request.ticket= NULL; + /* We never have an open HANDLER or LOCK TABLES here. */ + DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); + thd->mdl_context.rollback_to_savepoint(ot_ctx.start_of_statement_svp()); + goto retry; } else table= 0; @@ -4941,7 +4930,16 @@ bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, { uint counter; bool need_reopen; - bool has_locks= thd->mdl_context.has_locks(); + /* + Remember the set of metadata locks which this connection + managed to acquire before the start of the current statement. + It can be either transaction-scope locks, or HANDLER locks, + or LOCK TABLES locks. If mysql_lock_tables() fails with + need_reopen request, we'll use it to instruct + close_tables_for_reopen() to release all locks of this + statement. + */ + MDL_ticket *start_of_statement_svp= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("open_and_lock_tables_derived"); DBUG_PRINT("enter", ("derived handling: %d", derived)); @@ -4960,13 +4958,7 @@ bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, break; if (!need_reopen) DBUG_RETURN(TRUE); - if ((thd->in_multi_stmt_transaction() || - thd->mdl_context.lt_or_ha_sentinel()) && has_locks) - { - my_error(ER_LOCK_DEADLOCK, MYF(0)); - DBUG_RETURN(TRUE); - } - close_tables_for_reopen(thd, &tables); + close_tables_for_reopen(thd, &tables, start_of_statement_svp); } if (derived && (mysql_handle_derived(thd->lex, &mysql_derived_prepare) || @@ -5280,6 +5272,8 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, flags, need_reopen))) DBUG_RETURN(TRUE); + DEBUG_SYNC(thd, "after_lock_tables_takes_lock"); + if (thd->lex->requires_prelocking() && thd->lex->sql_command != SQLCOM_LOCK_TABLES) { @@ -5379,18 +5373,24 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, } -/* +/** Prepare statement for reopening of tables and recalculation of set of prelocked tables. - SYNOPSIS - close_tables_for_reopen() - thd in Thread context - tables in/out List of tables which we were trying to open and lock - + @param[in] thd Thread context. + @param[in,out] tables List of tables which we were trying to open + and lock. + @param[in] start_of_statement_svp MDL savepoint which represents the set + of metadata locks which the current transaction + managed to acquire before execution of the current + statement and to which we should revert before + trying to reopen tables. NULL if no metadata locks + were held and thus all metadata locks should be + released. */ -void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) +void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, + MDL_ticket *start_of_statement_svp) { TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table(); TABLE_LIST *tmp; @@ -5425,13 +5425,7 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) for (tmp= first_not_own_table; tmp; tmp= tmp->next_global) tmp->mdl_request.ticket= NULL; close_thread_tables(thd); - /* We can't back off with an open HANDLERs, we must not wait with locks. */ - DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); - /* - Due to the above assert, this effectively releases *all* locks - of this session, so that we can safely wait on tables. - */ - thd->mdl_context.release_transactional_locks(); + thd->mdl_context.rollback_to_savepoint(start_of_statement_svp); } @@ -8413,6 +8407,8 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use) But in case a thread has an open HANDLER statement, (and thus already grabbed a metadata lock), it gets blocked only too late -- at the table cache level. + Starting from 5.5, this could also easily happen in + a multi-statement transaction. */ broadcast_refresh(); pthread_mutex_unlock(&LOCK_open); @@ -8420,6 +8416,28 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use) } +/** + Force transactions holding shared metadata lock on the table to call + MDL_context::can_wait_lead_to_deadlock() even if they don't need any + new metadata locks so they can detect potential deadlocks between + metadata locking subsystem and table-level locks. + + @param mdl_key MDL key for the table on which we are upgrading + metadata lock. +*/ + +void mysql_abort_transactions_with_shared_lock(const MDL_key *mdl_key) +{ + if (mdl_key->mdl_namespace() == MDL_key::TABLE) + { + pthread_mutex_lock(&LOCK_open); + tdc_remove_table(NULL, TDC_RT_REMOVE_UNUSED, mdl_key->db_name(), + mdl_key->name()); + pthread_mutex_unlock(&LOCK_open); + } +} + + /** Remove all or some (depending on parameter) instances of TABLE and TABLE_SHARE from the table definition cache. @@ -8525,6 +8543,25 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests) to broadcast on COND_refresh because of this. */ mysql_ha_flush(thd); + + /* + Check if there is someone waiting for one of metadata locks + held by this connection and return an error if that's the + case, since this situation may lead to a deadlock. + This can happen, when, for example, this connection is + waiting for an old version of some table to go away and + another connection is trying to upgrade its shared + metadata lock to exclusive, and thus is waiting + for this to release its lock. We must check for + the condition on each iteration of the loop to remove + any window for a race. + */ + if (thd->mdl_context.can_wait_lead_to_deadlock()) + { + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; + } + pthread_mutex_lock(&LOCK_open); MDL_request_list::Iterator it(*mdl_requests); diff --git a/sql/sql_class.h b/sql/sql_class.h index ff1b51e7e87..5654dcb07a6 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1297,18 +1297,29 @@ public: bool can_recover_from_failed_open() const { return m_action != OT_NO_ACTION; } - bool can_deadlock() const { return m_can_deadlock; } + + /** + When doing a back-off, we close all tables acquired by this + statement. Return an MDL savepoint taken at the beginning of + the statement, so that we can rollback to it before waiting on + locks. + */ + MDL_ticket *start_of_statement_svp() const + { + return m_start_of_statement_svp; + } private: /** List of requests for all locks taken so far. Used for waiting on locks. */ MDL_request_list m_mdl_requests; /** Back off action. */ enum enum_open_table_action m_action; + MDL_ticket *m_start_of_statement_svp; /** Whether we had any locks when this context was created. If we did, they are from the previous statement of a transaction, and we can't safely do back-off (and release them). */ - bool m_can_deadlock; + bool m_has_locks; }; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 40ef55423a9..f2478213bbe 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2530,7 +2530,7 @@ pthread_handler_t handle_delayed_insert(void *arg) aborted. Try to reopen table and if it fails die. */ TABLE_LIST *tl_ptr = &di->table_list; - close_tables_for_reopen(thd, &tl_ptr); + close_tables_for_reopen(thd, &tl_ptr, NULL); di->table= 0; if (di->open_and_lock_table()) { diff --git a/sql/sql_plist.h b/sql/sql_plist.h index 94e437362a9..8f2aee6bd5f 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -132,11 +132,11 @@ public: template class I_P_List_iterator { - I_P_List *list; + const I_P_List *list; T *current; public: - I_P_List_iterator(I_P_List &a) : list(&a), current(a.first) {} - I_P_List_iterator(I_P_List &a, T* current_arg) : list(&a), current(current_arg) {} + I_P_List_iterator(const I_P_List &a) : list(&a), current(a.first) {} + I_P_List_iterator(const I_P_List &a, T* current_arg) : list(&a), current(current_arg) {} inline void init(I_P_List &a) { list= &a; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 72fb49cf38c..e9d1426b3e3 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2941,7 +2941,7 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, table, res, db_name, table_name)); thd->temporary_tables= 0; - close_tables_for_reopen(thd, &show_table_list); + close_tables_for_reopen(thd, &show_table_list, NULL); DBUG_RETURN(error); } @@ -3500,7 +3500,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) res= schema_table->process_table(thd, show_table_list, table, res, &orig_db_name, &tmp_lex_string); - close_tables_for_reopen(thd, &show_table_list); + close_tables_for_reopen(thd, &show_table_list, NULL); } DBUG_ASSERT(!lex->query_tables_own_last); if (res) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 603ab1b9682..980f87f21bb 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -206,6 +206,7 @@ int mysql_update(THD *thd, ulonglong id; List all_fields; THD::killed_state killed_status= THD::NOT_KILLED; + MDL_ticket *start_of_statement_svp= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("mysql_update"); for ( ; ; ) @@ -226,7 +227,7 @@ int mysql_update(THD *thd, break; if (!need_reopen) DBUG_RETURN(1); - close_tables_for_reopen(thd, &table_list); + close_tables_for_reopen(thd, &table_list, start_of_statement_svp); } if (mysql_handle_derived(thd->lex, &mysql_derived_prepare) || @@ -981,6 +982,7 @@ int mysql_multi_update_prepare(THD *thd) const bool using_lock_tables= thd->locked_tables_mode != LTM_NONE; bool original_multiupdate= (thd->lex->sql_command == SQLCOM_UPDATE_MULTI); bool need_reopen= FALSE; + MDL_ticket *start_of_statement_svp= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("mysql_multi_update_prepare"); /* following need for prepared statements, to run next time multi-update */ @@ -1145,7 +1147,7 @@ reopen_tables: */ cleanup_items(thd->free_list); - close_tables_for_reopen(thd, &table_list); + close_tables_for_reopen(thd, &table_list, start_of_statement_svp); goto reopen_tables; } From 04c39b1939abc7f80289b0feffe715e29a07f5ad Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Tue, 5 Jan 2010 13:05:00 +0100 Subject: [PATCH 135/212] Bug #49166 mtr --combination is broken after restrictions of combination names Combinations beginning with -- not allowed Allow them... --- mysql-test/mysql-test-run.pl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 7d2426459d0..27c2ace56b2 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -3251,9 +3251,11 @@ sub run_testcase ($) { mtr_verbose("Running test:", $tinfo->{name}); - # Allow only alpanumerics pluss _ - + . in combination names + # Allow only alpanumerics pluss _ - + . in combination names, + # or anything beginning with -- (the latter comes from --combination) my $combination= $tinfo->{combination}; - if ($combination && $combination !~ /^\w[-\w\.\+]+$/) + if ($combination && $combination !~ /^\w[-\w\.\+]+$/ + && $combination !~ /^--/) { mtr_error("Combination '$combination' contains illegal characters"); } From 4d8b228c8dd9ac6da7e895373bf029826d754279 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Tue, 5 Jan 2010 13:31:38 +0100 Subject: [PATCH 136/212] Bug #49166 mtr --combination is broken after restrictions of combination names Combinations beginning with -- not allowed Allow them... --- mysql-test/mysql-test-run.pl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 7d2426459d0..27c2ace56b2 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -3251,9 +3251,11 @@ sub run_testcase ($) { mtr_verbose("Running test:", $tinfo->{name}); - # Allow only alpanumerics pluss _ - + . in combination names + # Allow only alpanumerics pluss _ - + . in combination names, + # or anything beginning with -- (the latter comes from --combination) my $combination= $tinfo->{combination}; - if ($combination && $combination !~ /^\w[-\w\.\+]+$/) + if ($combination && $combination !~ /^\w[-\w\.\+]+$/ + && $combination !~ /^--/) { mtr_error("Combination '$combination' contains illegal characters"); } From ce019a2fac142fd0370c26e901218850cf36aeb5 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Tue, 5 Jan 2010 21:35:50 +0100 Subject: [PATCH 137/212] Bug #49345 re-introduce gprof to mysql-test-run.pl Was available in v1 Porting to v2 required some rewriting Updated after review comments --- mysql-test/lib/mtr_gprof.pl | 45 +++++++++--------------------------- mysql-test/mysql-test-run.pl | 12 +++++++++- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/mysql-test/lib/mtr_gprof.pl b/mysql-test/lib/mtr_gprof.pl index f6615301dd7..5820a4007b8 100644 --- a/mysql-test/lib/mtr_gprof.pl +++ b/mysql-test/lib/mtr_gprof.pl @@ -20,43 +20,20 @@ use strict; -# These are not to be prefixed with "mtr_" +sub gprof_collect ($@) { + my ($exe_mysqld, @gprof_dirs)= @_; -sub gprof_prepare (); -sub gprof_collect (); + print ("Collecting gprof reports.....\n"); -############################################################################## -# -# -# -############################################################################## - -sub gprof_prepare () { - - rmtree($::opt_gprof_dir); - mkdir($::opt_gprof_dir); -} - -# FIXME what about master1 and slave1?! -sub gprof_collect () { - - if ( -f "$::master->[0]->{'path_myddir'}/gmon.out" ) + foreach my $datadir (@gprof_dirs) { - # FIXME check result code?! - mtr_run("gprof", - [$::exe_master_mysqld, - "$::master->[0]->{'path_myddir'}/gmon.out"], - $::opt_gprof_master, "", "", ""); - print "Master execution profile has been saved in $::opt_gprof_master\n"; - } - if ( -f "$::slave->[0]->{'path_myddir'}/gmon.out" ) - { - # FIXME check result code?! - mtr_run("gprof", - [$::exe_slave_mysqld, - "$::slave->[0]->{'path_myddir'}/gmon.out"], - $::opt_gprof_slave, "", "", ""); - print "Slave execution profile has been saved in $::opt_gprof_slave\n"; + my $gprof_msg= "$datadir/gprof.msg"; + my $gprof_err= "$datadir/gprof.err"; + if ( -f "$datadir/gmon.out" ) + { + system("gprof $exe_mysqld $datadir/gmon.out 2>$gprof_err >$gprof_msg"); + print ("GPROF output in $gprof_msg, errors in $gprof_err\n"); + } } } diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 27c2ace56b2..ff986b9e793 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -87,6 +87,7 @@ use IO::Select; require "lib/mtr_process.pl"; require "lib/mtr_io.pl"; require "lib/mtr_gcov.pl"; +require "lib/mtr_gprof.pl"; require "lib/mtr_misc.pl"; $SIG{INT}= sub { mtr_error("Got ^C signal"); }; @@ -167,6 +168,9 @@ our $opt_gcov_exe= "gcov"; our $opt_gcov_err= "mysql-test-gcov.msg"; our $opt_gcov_msg= "mysql-test-gcov.err"; +our $opt_gprof; +our %gprof_dirs; + our $glob_debugger= 0; our $opt_gdb; our $opt_client_gdb; @@ -745,6 +749,9 @@ sub run_worker ($) { if ($opt_valgrind_mysqld) { valgrind_exit_reports(); } + if ( $opt_gprof ) { + gprof_collect (find_mysqld($basedir), keys %gprof_dirs); + } exit(0); } else { @@ -858,6 +865,7 @@ sub command_line_setup { # Coverage, profiling etc 'gcov' => \$opt_gcov, + 'gprof' => \$opt_gprof, 'valgrind|valgrind-all' => \$opt_valgrind, 'valgrind-mysqltest' => \$opt_valgrind_mysqltest, 'valgrind-mysqld' => \$opt_valgrind_mysqld, @@ -1250,7 +1258,7 @@ sub command_line_setup { # -------------------------------------------------------------------------- # Gcov flag # -------------------------------------------------------------------------- - if ( $opt_gcov and ! $source_dist ) + if ( ($opt_gcov or $opt_gprof) and ! $source_dist ) { mtr_error("Coverage test needs the source - please use source dist"); } @@ -4304,6 +4312,8 @@ sub mysqld_start ($$) { } # Remember this log file for valgrind error report search $mysqld_logs{$output}= 1 if $opt_valgrind; + # Remember data dir for gmon.out files if using gprof + $gprof_dirs{$mysqld->value('datadir')}= 1 if $opt_gprof; if ( defined $exe ) { From 5b66dab00f9f7456632911a41c2b8f701cb21264 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Wed, 6 Jan 2010 09:42:21 +0100 Subject: [PATCH 138/212] Bug #48863 mysql test: enable and disable case insensitive compare mode Implemented --lowercase_result which lower cases next result --- client/mysqltest.cc | 26 ++++++++++++++++++++++-- mysql-test/r/mysqltest.result | 23 +++++++++++++++++++++ mysql-test/t/mysqltest.test | 38 +++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index e2a9a2a5349..f516415f15d 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -97,7 +97,7 @@ static my_bool sp_protocol= 0, sp_protocol_enabled= 0; static my_bool view_protocol= 0, view_protocol_enabled= 0; static my_bool cursor_protocol= 0, cursor_protocol_enabled= 0; static my_bool parsing_disabled= 0; -static my_bool display_result_vertically= FALSE, +static my_bool display_result_vertically= FALSE, display_result_lower= FALSE, display_metadata= FALSE, display_result_sorted= FALSE; static my_bool disable_query_log= 0, disable_result_log= 0; static my_bool disable_warnings= 0; @@ -272,6 +272,7 @@ enum enum_commands { Q_DISABLE_ABORT_ON_ERROR, Q_ENABLE_ABORT_ON_ERROR, Q_DISPLAY_VERTICAL_RESULTS, Q_DISPLAY_HORIZONTAL_RESULTS, Q_QUERY_VERTICAL, Q_QUERY_HORIZONTAL, Q_SORTED_RESULT, + Q_LOWERCASE, Q_START_TIMER, Q_END_TIMER, Q_CHARACTER_SET, Q_DISABLE_PS_PROTOCOL, Q_ENABLE_PS_PROTOCOL, Q_DISABLE_RECONNECT, Q_ENABLE_RECONNECT, @@ -346,6 +347,7 @@ const char *command_names[]= "query_vertical", "query_horizontal", "sorted_result", + "lowercase_result", "start_timer", "end_timer", "character_set", @@ -7876,6 +7878,13 @@ int main(int argc, char **argv) */ display_result_sorted= TRUE; break; + case Q_LOWERCASE: + /* + Turn on lowercasing of result, will be reset after next + command + */ + display_result_lower= TRUE; + break; case Q_LET: do_let(command); break; case Q_EVAL_RESULT: die("'eval_result' command is deprecated"); @@ -8091,8 +8100,9 @@ int main(int argc, char **argv) */ free_all_replace(); - /* Also reset "sorted_result" */ + /* Also reset "sorted_result" and "lowercase"*/ display_result_sorted= FALSE; + display_result_lower= FALSE; } last_command_executed= command_executed; @@ -9496,6 +9506,18 @@ void replace_dynstr_append_mem(DYNAMIC_STRING *ds, fix_win_paths(val, len); #endif + if (display_result_lower) + { + /* Convert to lower case, and do this first */ + char lower[512]; + char *c= lower; + for (const char *v= val; *v; v++) + *c++= my_tolower(charset_info, *v); + *c= '\0'; + /* Copy from this buffer instead */ + val= lower; + } + if (glob_replace_regex) { /* Regex replace */ diff --git a/mysql-test/r/mysqltest.result b/mysql-test/r/mysqltest.result index 671f88cb00a..f327640782c 100644 --- a/mysql-test/r/mysqltest.result +++ b/mysql-test/r/mysqltest.result @@ -682,6 +682,29 @@ INSERT INTO t1 SELECT f1 - 256 FROM t1; INSERT INTO t1 SELECT f1 - 512 FROM t1; SELECT * FROM t1; DROP TABLE t1; +select "500g blåbærsyltetøy" as "will be lower cased"; +will be lower cased +500g blåbærsyltetøy +SELECT "UPPER" AS "WILL NOT BE lower cased"; +WILL NOT BE lower cased +UPPER +UP +SELECT 0 as "UP AGAIN"; +UP AGAIN +0 +select "abcdef" as "uvwxyz"; +uvwxyz +abcdef +select "xyz" as name union select "abc" as name order by name desc; +name +abc +xyz +select 1 as "some new text"; +some new text +1 +select 0 as "will not lower case ÄËÐ"; +will not lower case ÄËÐ +0 CREATE TABLE t1( a int, b varchar(255), c datetime ); diff --git a/mysql-test/t/mysqltest.test b/mysql-test/t/mysqltest.test index b4ea9202df7..ce9b31eb30f 100644 --- a/mysql-test/t/mysqltest.test +++ b/mysql-test/t/mysqltest.test @@ -2059,6 +2059,44 @@ INSERT INTO t1 SELECT f1 - 512 FROM t1; SELECT * FROM t1; --enable_result_log DROP TABLE t1; + +# ---------------------------------------------------------------------------- +# test for lowercase_result +# ---------------------------------------------------------------------------- + +# 1. Basic test +--lowercase_result +SELECT "500g BLÅBÆRSYLTETØY" AS "WILL BE lower cased"; + +# 2. test that it does not apply to next statement +SELECT "UPPER" AS "WILL NOT BE lower cased"; + +# 3. test that it does not affect non-SQL or the following statement +--lowercase_result +--echo UP +SELECT 0 as "UP AGAIN"; + +# 4. test that it works with eval and variables +let $lower_stmt=SELECT "ABCdef" AS "uvwXYZ"; +--lowercase_result +eval $lower_stmt; + +# 5. test that it works in combination with sort +sorted_result; +lowercase_result; +SELECT "Xyz" AS Name UNION SELECT "Abc" as Name ORDER BY Name DESC; + +# 6. Test combination with replace, and that lower casing is done first +--lowercase_result +--replace_result old new +SELECT 1 as "SOME OLD TEXT"; + +# 7. Test missing lower casing of "unknown" characters +--character_set utf8 +--lowercase_result +SELECT 0 as "WILL NOT lower case ÄËÐ"; +--character_set latin1 + # ---------------------------------------------------------------------------- # Some coverage tests # ---------------------------------------------------------------------------- From 08c8863ec41b1168d69f4d78db9bc0606e36f2ad Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Wed, 6 Jan 2010 09:45:28 +0100 Subject: [PATCH 139/212] Bug #49269 mysqltest crashes on 'reap' if query executed after 'send' Set a flag after send to trap the case --- client/mysqltest.cc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index f516415f15d..804db8fbefe 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -228,6 +228,8 @@ struct st_connection char *name; size_t name_len; MYSQL_STMT* stmt; + /* Set after send to disallow other queries before reap */ + my_bool pending; #ifdef EMBEDDED_LIBRARY const char *cur_query; @@ -4691,7 +4693,8 @@ void do_close_connection(struct st_command *command) if (con->util_mysql) mysql_close(con->util_mysql); con->util_mysql= 0; - + con->pending= FALSE; + my_free(con->name, MYF(0)); /* @@ -6513,6 +6516,9 @@ void run_query_normal(struct st_connection *cn, struct st_command *command, if (flags & QUERY_SEND_FLAG) { + if (cn->pending) + die ("Cannot run query on connection between send and reap"); + /* Send the query */ @@ -6532,8 +6538,11 @@ void run_query_normal(struct st_connection *cn, struct st_command *command, wait_query_thread_end(cn); #endif /*EMBEDDED_LIBRARY*/ if (!(flags & QUERY_REAP_FLAG)) + { + cn->pending= TRUE; DBUG_VOID_RETURN; - + } + do { /* @@ -6622,6 +6631,7 @@ void run_query_normal(struct st_connection *cn, struct st_command *command, end: + cn->pending= FALSE; /* We save the return code (mysql_errno(mysql)) from the last call sent to the server into the mysqltest builtin variable $mysql_errno. This From 04a5dcb136c17e471cbb647c2f45d23682adfea2 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Wed, 6 Jan 2010 09:47:25 +0100 Subject: [PATCH 140/212] Bug #49761 mysqltest.test does not have any tests for send/reap Added them NB the 6th case is adapted to Bug no. 49269, gives wrong output without it --- mysql-test/r/mysqltest.result | 20 ++++++++++++++ mysql-test/t/mysqltest.test | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/mysql-test/r/mysqltest.result b/mysql-test/r/mysqltest.result index f327640782c..e77dcd7b0a6 100644 --- a/mysql-test/r/mysqltest.result +++ b/mysql-test/r/mysqltest.result @@ -523,6 +523,26 @@ a D 1 1 1 4 drop table t1; +create table t1 ( f1 char(10)); +insert into t1 values ("Abcd"); +select * from t1; +f1 +Abcd +select * from t2;; +ERROR 42S02: Table 'test.t2' doesn't exist +select * from t1; +f1 +Abcd +select * from t1;; +Result coming up +f1 +Abcd +select * from t1;; +f1 +Abcd +mysqltest: At line 2: Cannot run query on connection between send and reap +select * from t1;; +drop table t1; mysqltest: At line 1: Missing required argument 'filename' to command 'remove_file' mysqltest: At line 1: Missing required argument 'filename' to command 'write_file' mysqltest: At line 1: End of file encountered before 'EOF' delimiter was found diff --git a/mysql-test/t/mysqltest.test b/mysql-test/t/mysqltest.test index ce9b31eb30f..45fc0715312 100644 --- a/mysql-test/t/mysqltest.test +++ b/mysql-test/t/mysqltest.test @@ -1605,6 +1605,57 @@ insert into t1 values (2,4); select * from t1; drop table t1; +# ---------------------------------------------------------------------------- +# Tests of send +# ---------------------------------------------------------------------------- + +create table t1 ( f1 char(10)); +insert into t1 values ("Abcd"); + +# 1. Basic test + +send select * from t1; +reap; + +# 2. Test with error + +--send select * from t2; +--error ER_NO_SUCH_TABLE +--reap + +# 3. test send of next stmt + +--send +select * from t1; +--reap + +# 4. Non-query stmt betwen send and reap allowed + +--send select * from t1; +--sleep 0.05 +--echo Result coming up +--reap + +# 5. Test of send_eval + +--let $my_stmt= select * from t1; +--send_eval $my_stmt +--reap + +# 6. Test that mysqltest does not allow query stmt between send and reap +# Untestable directly as it causes mysqltest to fail + +--write_file $MYSQLTEST_VARDIR/tmp/mysqltest.in +--send select * from t1; +select 1; +--reap +EOF +--error 1 +--exec $MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/mysqltest.in 2>&1 +remove_file $MYSQLTEST_VARDIR/tmp/mysqltest.in; + +drop table t1; + # ---------------------------------------------------------------------------- # test for remove_file # ---------------------------------------------------------------------------- From ed64190b232ed23974bc0dc11e8b586d8bfb6c71 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Wed, 6 Jan 2010 12:56:22 +0100 Subject: [PATCH 141/212] Bug #49269 mysqltest crashes on 'reap' if query executed after 'send' Small amendment: ignore pending reap when switching connection, add test --- client/mysqltest.cc | 1 + mysql-test/r/mysqltest.result | 7 +++++++ mysql-test/t/mysqltest.test | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 804db8fbefe..d0c58d2f57b 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -4622,6 +4622,7 @@ void select_connection_name(const char *name) if (!con) die("connection '%s' not found in connection pool", name); + con->pending= FALSE; set_current_connection(con); DBUG_VOID_RETURN; diff --git a/mysql-test/r/mysqltest.result b/mysql-test/r/mysqltest.result index e77dcd7b0a6..def5d202c2f 100644 --- a/mysql-test/r/mysqltest.result +++ b/mysql-test/r/mysqltest.result @@ -542,6 +542,13 @@ f1 Abcd mysqltest: At line 2: Cannot run query on connection between send and reap select * from t1;; +select * from t1;; +select 1; +1 +1 +select 2; +2 +2 drop table t1; mysqltest: At line 1: Missing required argument 'filename' to command 'remove_file' mysqltest: At line 1: Missing required argument 'filename' to command 'write_file' diff --git a/mysql-test/t/mysqltest.test b/mysql-test/t/mysqltest.test index 45fc0715312..f3fea9348e4 100644 --- a/mysql-test/t/mysqltest.test +++ b/mysql-test/t/mysqltest.test @@ -1654,6 +1654,18 @@ EOF --exec $MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/mysqltest.in 2>&1 remove_file $MYSQLTEST_VARDIR/tmp/mysqltest.in; +# 7. Test that stmt after send without reap IS allowed +# if we have switched connections + +connect (test_con1,localhost,root,,); +--send select * from t1; +connection default; +select 1; +connection test_con1; +select 2; +disconnect test_con1; +connection default; + drop table t1; # ---------------------------------------------------------------------------- From e1b42965a5e95fd8ff331e5c9b84391688285448 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 7 Jan 2010 11:22:45 +0100 Subject: [PATCH 142/212] Bug #49269 mysqltest crashes on 'reap' if query executed after 'send' Undid amendment allowing pending reap after switching connections Moved check for pending reap earlier; failed if running with ps-protocol --- client/mysqltest.cc | 7 +++---- mysql-test/r/mysqltest.result | 7 ------- .../suite/sys_vars/t/innodb_table_locks_func.test | 1 + .../sys_vars/t/sql_low_priority_updates_func.test | 1 + mysql-test/t/mysqltest.test | 12 ------------ mysql-test/t/partition_innodb_semi_consistent.test | 1 + 6 files changed, 6 insertions(+), 23 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index d0c58d2f57b..45f94982472 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -4622,7 +4622,6 @@ void select_connection_name(const char *name) if (!con) die("connection '%s' not found in connection pool", name); - con->pending= FALSE; set_current_connection(con); DBUG_VOID_RETURN; @@ -6517,9 +6516,6 @@ void run_query_normal(struct st_connection *cn, struct st_command *command, if (flags & QUERY_SEND_FLAG) { - if (cn->pending) - die ("Cannot run query on connection between send and reap"); - /* Send the query */ @@ -7111,6 +7107,9 @@ void run_query(struct st_connection *cn, struct st_command *command, int flags) init_dynamic_string(&ds_warnings, NULL, 0, 256); + if (cn->pending && (flags & QUERY_SEND_FLAG)) + die ("Cannot run query on connection between send and reap"); + /* Evaluate query if this is an eval command */ diff --git a/mysql-test/r/mysqltest.result b/mysql-test/r/mysqltest.result index def5d202c2f..e77dcd7b0a6 100644 --- a/mysql-test/r/mysqltest.result +++ b/mysql-test/r/mysqltest.result @@ -542,13 +542,6 @@ f1 Abcd mysqltest: At line 2: Cannot run query on connection between send and reap select * from t1;; -select * from t1;; -select 1; -1 -1 -select 2; -2 -2 drop table t1; mysqltest: At line 1: Missing required argument 'filename' to command 'remove_file' mysqltest: At line 1: Missing required argument 'filename' to command 'write_file' diff --git a/mysql-test/suite/sys_vars/t/innodb_table_locks_func.test b/mysql-test/suite/sys_vars/t/innodb_table_locks_func.test index 6638a20c926..330addd6b3b 100644 --- a/mysql-test/suite/sys_vars/t/innodb_table_locks_func.test +++ b/mysql-test/suite/sys_vars/t/innodb_table_locks_func.test @@ -78,6 +78,7 @@ COMMIT; --echo 'CONNECTION con2' CONNECTION con2; +reap; UNLOCK tables; DROP TABLE t1; diff --git a/mysql-test/suite/sys_vars/t/sql_low_priority_updates_func.test b/mysql-test/suite/sys_vars/t/sql_low_priority_updates_func.test index 2ef6e34b0b3..5e0314c25ae 100644 --- a/mysql-test/suite/sys_vars/t/sql_low_priority_updates_func.test +++ b/mysql-test/suite/sys_vars/t/sql_low_priority_updates_func.test @@ -127,6 +127,7 @@ connection con0; SET SESSION low_priority_updates = OFF; --echo ** Connection con1 ** connection con1; +reap; SET SESSION low_priority_updates = OFF; --echo ** Connection default** connection default; diff --git a/mysql-test/t/mysqltest.test b/mysql-test/t/mysqltest.test index f3fea9348e4..45fc0715312 100644 --- a/mysql-test/t/mysqltest.test +++ b/mysql-test/t/mysqltest.test @@ -1654,18 +1654,6 @@ EOF --exec $MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/mysqltest.in 2>&1 remove_file $MYSQLTEST_VARDIR/tmp/mysqltest.in; -# 7. Test that stmt after send without reap IS allowed -# if we have switched connections - -connect (test_con1,localhost,root,,); ---send select * from t1; -connection default; -select 1; -connection test_con1; -select 2; -disconnect test_con1; -connection default; - drop table t1; # ---------------------------------------------------------------------------- diff --git a/mysql-test/t/partition_innodb_semi_consistent.test b/mysql-test/t/partition_innodb_semi_consistent.test index 6a6a7cf958e..2711d79f194 100644 --- a/mysql-test/t/partition_innodb_semi_consistent.test +++ b/mysql-test/t/partition_innodb_semi_consistent.test @@ -187,6 +187,7 @@ SELECT * FROM t1; --echo # Switch to connection con2 connection con2; +reap; SELECT * FROM t1; connection default; From 9ada265d39f290f9388e64e3ac723e3ee2fd9997 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 7 Jan 2010 13:17:54 +0100 Subject: [PATCH 143/212] backport some changes from WL 4378 --- mysql-test/mysql-test-run.pl | 39 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 83740e08ca6..d0f7d2b2a75 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -68,8 +68,8 @@ use My::File::Path; # Patched version of File::Path use File::Basename; use File::Copy; use File::Find; -use File::Temp qw / tempdir /; -use File::Spec::Functions qw / splitdir /; +use File::Temp qw/tempdir/; +use File::Spec::Functions qw/splitdir/; use My::Platform; use My::SafeProcess; use My::ConfigFactory; @@ -119,6 +119,8 @@ END { } } +sub env_or_val($$) { defined $ENV{$_[0]} ? $ENV{$_[0]} : $_[1] } + my $path_config_file; # The generated config file, var/my.cnf # Visual Studio produces executables in different sub-directories based on the @@ -217,7 +219,7 @@ my $start_only; my $opt_wait_all; my $opt_repeat= 1; my $opt_retry= 3; -my $opt_retry_failure= 2; +my $opt_retry_failure= env_or_val(MTR_RETRY_FAILURE => 2); my $opt_strace_client; @@ -247,9 +249,9 @@ our %mysqld_variables; my $source_dist= 0; -my $opt_max_save_core= $ENV{MTR_MAX_SAVE_CORE} || 5; -my $opt_max_save_datadir= $ENV{MTR_MAX_SAVE_DATADIR} || 20; -my $opt_max_test_fail= $ENV{MTR_MAX_TEST_FAIL} || 10; +my $opt_max_save_core= env_or_val(MTR_MAX_SAVE_CORE => 5); +my $opt_max_save_datadir= env_or_val(MTR_MAX_SAVE_DATADIR => 20); +my $opt_max_test_fail= env_or_val(MTR_MAX_TEST_FAIL => 10); my $opt_parallel= $ENV{MTR_PARALLEL} || 1; @@ -794,11 +796,12 @@ sub set_vardir { sub command_line_setup { my $opt_comment; my $opt_usage; + my $opt_list_options; # Read the command line options # Note: Keep list, and the order, in sync with usage at end of this file Getopt::Long::Configure("pass_through"); - GetOptions( + my %options=( # Control what engine/variation to run 'embedded-server' => \$opt_embedded_server, 'ps-protocol' => \$opt_ps_protocol, @@ -918,9 +921,13 @@ sub command_line_setup { 'timediff' => \&report_option, 'help|h' => \$opt_usage, - ) or usage("Can't read options"); + 'list-options' => \$opt_list_options, + ); + + GetOptions(%options) or usage("Can't read options"); usage("") if $opt_usage; + list_options(\%options) if $opt_list_options; # -------------------------------------------------------------------------- # Setup verbosity @@ -4977,7 +4984,7 @@ sub gdb_arguments { my $type= shift; # Write $args to gdb init file - my $str= join(" ", @$$args); + my $str= join " ", map { s/"/\\"/g; "\"$_\""; } @$$args; my $gdb_init_file= "$opt_vardir/tmp/gdbinit.$type"; # Remove the old gdbinit file @@ -5041,7 +5048,7 @@ sub ddd_arguments { my $type= shift; # Write $args to ddd init file - my $str= join(" ", @$$args); + my $str= join " ", map { s/"/\\"/g; "\"$_\""; } @$$args; my $gdb_init_file= "$opt_vardir/tmp/gdbinit.$type"; # Remove the old gdbinit file @@ -5436,3 +5443,15 @@ HERE } +sub list_options ($) { + my $hash= shift; + + for (keys %$hash) { + s/([:=].*|[+!])$//; + s/\|/\n--/g; + print "--$_\n" unless /list-options/; + } + + exit(1); +} + From 1405f019e75567573ff6c5f4cd6a5f270a561866 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Fri, 8 Jan 2010 11:26:32 +0100 Subject: [PATCH 144/212] Fix for bug #48538 "Assertion in thr_lock() on LOAD DATA CONCURRENT INFILE". Attempts to execute an INSERT statement for a MEMORY table which invoked a trigger or called a stored function which tried to perform LOW_PRIORITY update on the table being inserted into, resulted in debug servers aborting due to an assertion failure. On non-debug servers such INSERTs failed with "Can't update table t1 in stored function/trigger because it is already used by statement which invoked this stored function/trigger" as expected. The problem was that in the above scenario TL_WRITE_CONCURRENT_INSERT is converted to TL_WRITE inside the thr_lock() function since the MEMORY engine does not support concurrent inserts. This triggered an assertion which assumed that for the same table, one thread always requests locks with higher thr_lock_type value first. When TL_WRITE_CONCURRENT_INSERT is upgraded to TL_WRITE after the locks have been sorted, this is no longer true. In this case, TL_WRITE was requested after acquiring a TL_WRITE_LOW_PRIORITY lock on the table, triggering the assert. This fix solves the problem by adjusting this assert to take this scenario into account. An alternative approach to change handler::store_locks() methods for all engines which do not support concurrent inserts in such way that TL_WRITE_CONCURRENT_INSERT is upgraded to TL_WRITE there instead, was considered too intrusive. Commit on behalf of Dmitry Lenev. mysql-test/r/lock.result: Added simplified test for bug #48538 "Assertion in thr_lock() on LOAD DATA CONCURRENT INFILE". mysql-test/t/lock.test: Added simplified test for bug #48538 "Assertion in thr_lock() on LOAD DATA CONCURRENT INFILE". mysys/thr_lock.c: Adjusted assertion to account for situation when TL_WRITE_CONCURRENT_INSERT is converted to TL_WRITE inside of the thr_lock() function because the engine of the table being locked does not support concurrent inserts. This scenario breaks assumption that for the same table one thread always requests locks with higher thr_lock_type value first, since TL_WRITE on the table (converted from TL_WRITE_CONCURRENT_INSERT) can be requested after acquiring a TL_WRITE_LOW_PRIORITY lock on the table. Note that it is still safe to grant a new lock without extra checks and waiting in such situation since TL_WRITE has the same compatibility rules as TL_WRITE_LOW_PRIORITY (their only difference is priority). --- mysql-test/r/lock.result | 13 +++++++++++++ mysql-test/t/lock.test | 22 ++++++++++++++++++++++ mysys/thr_lock.c | 29 ++++++++++++++++++++--------- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index 8f680858fdc..348412ea441 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -356,5 +356,18 @@ ERROR HY000: Can't execute the given command because you have active locked tabl UNLOCK TABLES; DROP TABLE t1; # +# Simplified test for bug #48538 "Assertion in thr_lock() on LOAD DATA +# CONCURRENT INFILE". +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (f1 INT, f2 INT) ENGINE = MEMORY; +CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW +UPDATE LOW_PRIORITY t1 SET f2 = 7; +# Statement below should fail with ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +# error instead of failing on assertion in table-level locking subsystem. +INSERT INTO t1(f1) VALUES(0); +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +DROP TABLE t1; +# # End of 6.0 tests. # diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index 64003c9d861..49e98abdc76 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -441,6 +441,28 @@ FLUSH TABLES WITH READ LOCK; UNLOCK TABLES; DROP TABLE t1; + +--echo # +--echo # Simplified test for bug #48538 "Assertion in thr_lock() on LOAD DATA +--echo # CONCURRENT INFILE". +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (f1 INT, f2 INT) ENGINE = MEMORY; +CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW + UPDATE LOW_PRIORITY t1 SET f2 = 7; + +--echo # Statement below should fail with ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +--echo # error instead of failing on assertion in table-level locking subsystem. +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +INSERT INTO t1(f1) VALUES(0); + +DROP TABLE t1; + + --echo # --echo # End of 6.0 tests. --echo # diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index a3f3e9ee080..a6bc128f32a 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -674,14 +674,23 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, write locks are of TL_WRITE_ALLOW_WRITE type. Note that, since lock requests for the same table are sorted in - such way that requests with higher thr_lock_type value come first, - lock being requested usually has equal or "weaker" type than one - which thread might have already acquired. - The exceptions are situations when: - - old lock type is TL_WRITE_ALLOW_READ and new lock type is - TL_WRITE_ALLOW_WRITE - - when old lock type is TL_WRITE_DELAYED - But these should never happen within MySQL. + such way that requests with higher thr_lock_type value come first + (with one exception (*)), lock being requested usually (**) has + equal or "weaker" type than one which thread might have already + acquired. + *) The only exception to this rule is case when type of old lock + is TL_WRITE_LOW_PRIORITY and type of new lock is changed inside + of thr_lock() from TL_WRITE_CONCURRENT_INSERT to TL_WRITE since + engine turns out to be not supporting concurrent inserts. + Note that since TL_WRITE has the same compatibility rules as + TL_WRITE_LOW_PRIORITY (their only difference is priority), + it is OK to grant new lock without additional checks in such + situation. + **) The exceptions are situations when: + - old lock type is TL_WRITE_ALLOW_READ and new lock type is + TL_WRITE_ALLOW_WRITE + - when old lock type is TL_WRITE_DELAYED + But these should never happen within MySQL. Therefore it is OK to allow acquiring write lock on the table if this thread already holds some write lock on it. @@ -690,7 +699,9 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, different types of write lock on the same table). */ DBUG_ASSERT(! has_old_lock(lock->write.data, data->owner) || - (lock_type <= lock->write.data->type && + ((lock_type <= lock->write.data->type || + (lock_type == TL_WRITE && + lock->write.data->type == TL_WRITE_LOW_PRIORITY)) && ! ((lock_type < TL_WRITE_ALLOW_READ && lock->write.data->type == TL_WRITE_ALLOW_READ) || lock->write.data->type == TL_WRITE_DELAYED))); From be634ee66e0f6c5d69a174d9a59fa5228211b347 Mon Sep 17 00:00:00 2001 From: Tor Didriksen Date: Tue, 12 Jan 2010 12:32:55 +0100 Subject: [PATCH 145/212] Backport of Bug#45523 "Objects of class base_ilist should not be copyable". Suppress the compiler-generated public copy constructor and assignment operator of class base_ilist; instead, implement move_elements_to() function which transfers ownership of elements from one list to another. --- sql/sp_head.cc | 7 ++----- sql/sql_cursor.cc | 9 ++++----- sql/sql_list.h | 36 ++++++++++++++++++++++++++++++------ sql/sql_prepare.cc | 4 ++-- 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 4f5ca1fff04..cd7486dec72 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1171,8 +1171,7 @@ sp_head::execute(THD *thd) We should also save Item tree change list to avoid rollback something too early in the calling query. */ - old_change_list= thd->change_list; - thd->change_list.empty(); + thd->change_list.move_elements_to(&old_change_list); /* Cursors will use thd->packet, so they may corrupt data which was prepared for sending by upper level. OTOH cursors in the same routine can share this @@ -1318,9 +1317,7 @@ sp_head::execute(THD *thd) /* Restore all saved */ old_packet.swap(thd->packet); DBUG_ASSERT(thd->change_list.is_empty()); - thd->change_list= old_change_list; - /* To avoid wiping out thd->change_list on old_change_list destruction */ - old_change_list.empty(); + old_change_list.move_elements_to(&thd->change_list); thd->lex= old_lex; thd->query_id= old_query_id; DBUG_ASSERT(!thd->derived_tables); diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc index 098f049704c..31aa3e7ea52 100644 --- a/sql/sql_cursor.cc +++ b/sql/sql_cursor.cc @@ -321,7 +321,7 @@ Sensitive_cursor::post_open(THD *thd) lock= thd->lock; query_id= thd->query_id; free_list= thd->free_list; - change_list= thd->change_list; + thd->change_list.move_elements_to(&change_list); reset_thd(thd); /* Now we have an active cursor and can cause a deadlock */ thd->lock_info.n_cursors++; @@ -437,7 +437,7 @@ Sensitive_cursor::fetch(ulong num_rows) thd->open_tables= open_tables; thd->lock= lock; thd->query_id= query_id; - thd->change_list= change_list; + change_list.move_elements_to(&thd->change_list); /* save references to memory allocated during fetch */ thd->set_n_backup_active_arena(this, &backup_arena); @@ -459,7 +459,7 @@ Sensitive_cursor::fetch(ulong num_rows) /* Grab free_list here to correctly free it in close */ thd->restore_active_arena(this, &backup_arena); - change_list= thd->change_list; + thd->change_list.move_elements_to(&change_list); reset_thd(thd); for (info= ht_info; info->read_view; info++) @@ -506,7 +506,7 @@ Sensitive_cursor::close() info->ht= 0; } - thd->change_list= change_list; + change_list.move_elements_to(&thd->change_list); { /* XXX: Another hack: we need to set THD state as if in a fetch to be @@ -532,7 +532,6 @@ Sensitive_cursor::close() join= 0; stmt_arena= 0; free_items(); - change_list.empty(); DBUG_VOID_RETURN; } diff --git a/sql/sql_list.h b/sql/sql_list.h index e1bf05fff23..fdc80b116a7 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -504,15 +504,12 @@ public: template class I_List_iterator; -/* - WARNING: copy constructor of this class does not create a usable - copy, as its members may point at each other. -*/ class base_ilist { + struct ilink *first; + struct ilink last; public: - struct ilink *first,last; inline void empty() { first= &last; last.prev= &first; } base_ilist() { empty(); } inline bool is_empty() { return first == &last; } @@ -540,7 +537,31 @@ public: { return (first != &last) ? first : 0; } - friend class base_list_iterator; + + /** + Moves list elements to new owner, and empties current owner (i.e. this). + + @param[in,out] new_owner The new owner of the list elements. + Should be empty in input. + */ + + void move_elements_to(base_ilist *new_owner) + { + DBUG_ASSERT(new_owner->is_empty()); + new_owner->first= first; + new_owner->last= last; + empty(); + } + + friend class base_ilist_iterator; + private: + /* + We don't want to allow copying of this class, as that would give us + two list heads containing the same elements. + So we declare, but don't define copy CTOR and assignment operator. + */ + base_ilist(const base_ilist&); + void operator=(const base_ilist&); }; @@ -573,6 +594,9 @@ public: inline void push_back(T* a) { base_ilist::push_back(a); } inline T* get() { return (T*) base_ilist::get(); } inline T* head() { return (T*) base_ilist::head(); } + inline void move_elements_to(I_List* new_owner) { + base_ilist::move_elements_to(new_owner); + } #ifndef _lint friend class I_List_iterator; #endif diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 838f25320ec..70f69c75de3 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -3407,7 +3407,7 @@ Prepared_statement::execute_server_runnable(Server_runnable *server_runnable) bool error; Query_arena *save_stmt_arena= thd->stmt_arena; Item_change_list save_change_list; - thd->change_list= save_change_list; + thd->change_list.move_elements_to(&save_change_list); state= CONVENTIONAL_EXECUTION; @@ -3431,7 +3431,7 @@ Prepared_statement::execute_server_runnable(Server_runnable *server_runnable) thd->restore_backup_statement(this, &stmt_backup); thd->stmt_arena= save_stmt_arena; - save_change_list= thd->change_list; + save_change_list.move_elements_to(&thd->change_list); /* Items and memory will freed in destructor */ From c7bcb28f0c502ad9028712c7c65834c8905ad88f Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Tue, 12 Jan 2010 16:15:21 +0100 Subject: [PATCH 146/212] Bug #49988 MDL deadlocks with mysql_create_db, reload_acl_and_cache This was a deadlock between LOCK TABLES/CREATE DATABASE in one connection and DROP DATABASE in another. It only happened if the table locked by LOCK TABLES was in the database to be dropped. The deadlock is similar to the one in Bug#48940, but with LOCK TABLES instead of an active transaction. The order of events needed to trigger the deadlock was: 1) Connection 1 locks table db1.t1 using LOCK TABLES. It will now have a metadata lock on the table name. 2) Connection 2 issues DROP DATABASE db1. This will wait inside the MDL subsystem for the lock on db1.t1 to go away. While waiting, it will hold the LOCK_mysql_create_db mutex. 3) Connection 1 issues CREATE DATABASE (database name irrelevant). This will hang trying to lock the same mutex. Since this is the connection holding the metadata lock blocking Connection 2, we have a deadlock. This deadlock would also happen for earlier trees without MDL, but there DROP DATABASE would wait for a table to be removed from the table definition cache. This patch fixes the problem by prohibiting CREATE DATABASE in LOCK TABLES mode. In the example above, this prevents Connection 1 from hanging trying to get the LOCK_mysql_create_db mutex. Note that other commands that use LOCK_mysql_create_db (ALTER/DROP DATABASE) are already prohibited in LOCK TABLES mode. Incompatible change: CREATE DATABASE is now disallowed in LOCK TABLES mode. Test case added to schema.test. mysql-test/t/drop.test: Updates the test for Bug#21216 by swapping the order of CREATE DATABASE and LOCK TABLES. This is now needed as CREATE DATABASE is prohibited in LOCK TABLES mode. mysql-test/t/schema.test: Test case for Bug#49988 added. Also fixes a problem with the test for Bug#48940 where the result would differ for embedded server. --- mysql-test/r/drop.result | 2 +- mysql-test/r/schema.result | 21 +++++++++++++++++- mysql-test/t/drop.test | 2 +- mysql-test/t/schema.test | 45 +++++++++++++++++++++++++++++++++++++- sql/sql_parse.cc | 6 +++++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/drop.result b/mysql-test/r/drop.result index 54bd05e526f..8c6bd582232 100644 --- a/mysql-test/r/drop.result +++ b/mysql-test/r/drop.result @@ -77,8 +77,8 @@ drop table t1; drop database if exists mysqltest; drop table if exists t1; create table t1 (i int); -lock tables t1 read; create database mysqltest; +lock tables t1 read; drop table t1; show open tables; drop database mysqltest; diff --git a/mysql-test/r/schema.result b/mysql-test/r/schema.result index 33a2d4d9448..b43f601caef 100644 --- a/mysql-test/r/schema.result +++ b/mysql-test/r/schema.result @@ -24,7 +24,26 @@ INSERT INTO schema1.t1 VALUES (1); DROP SCHEMA schema1; # Connection default ALTER SCHEMA schema1 DEFAULT CHARACTER SET utf8; -ERROR HY000: Can't create/write to file './schema1/db.opt' (Errcode: 2) +Got one of the listed errors SET autocommit= TRUE; # Connection 2 # Connection default +# +# Bug #49988 MDL deadlocks with mysql_create_db, reload_acl_and_cache +# +DROP SCHEMA IF EXISTS schema1; +# Connection default +CREATE SCHEMA schema1; +CREATE TABLE schema1.t1 (id INT); +LOCK TABLE schema1.t1 WRITE; +# Connection con2 +DROP SCHEMA schema1; +# Connection default +# CREATE SCHEMA used to give a deadlock. +# Now we prohibit CREATE SCHEMA in LOCK TABLES mode. +CREATE SCHEMA IF NOT EXISTS schema1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# UNLOCK TABLES so DROP SCHEMA can continue. +UNLOCK TABLES; +# Connection con2 +# Connection default diff --git a/mysql-test/t/drop.test b/mysql-test/t/drop.test index 4aeb7165bcb..5ef4a28b202 100644 --- a/mysql-test/t/drop.test +++ b/mysql-test/t/drop.test @@ -100,8 +100,8 @@ drop database if exists mysqltest; drop table if exists t1; --enable_warnings create table t1 (i int); -lock tables t1 read; create database mysqltest; +lock tables t1 read; connect (addconroot1, localhost, root,,); --send drop table t1 connect (addconroot2, localhost, root,,); diff --git a/mysql-test/t/schema.test b/mysql-test/t/schema.test index a380a6241dd..f106b9e4865 100644 --- a/mysql-test/t/schema.test +++ b/mysql-test/t/schema.test @@ -46,7 +46,8 @@ let $wait_condition= SELECT COUNT(*)= 1 FROM information_schema.processlist WHERE state= 'Waiting for table' AND info='DROP SCHEMA schema1'; --source include/wait_condition.inc ---error 1 +# Listing the error twice to prevent result diffences based on filename +--error 1,1 ALTER SCHEMA schema1 DEFAULT CHARACTER SET utf8; SET autocommit= TRUE; @@ -59,6 +60,48 @@ connection default; disconnect con2; +--echo # +--echo # Bug #49988 MDL deadlocks with mysql_create_db, reload_acl_and_cache +--echo # + +--disable_warnings +DROP SCHEMA IF EXISTS schema1; +--enable_warnings + +connect (con2, localhost, root); + +--echo # Connection default +connection default; +CREATE SCHEMA schema1; +CREATE TABLE schema1.t1 (id INT); +LOCK TABLE schema1.t1 WRITE; + +--echo # Connection con2 +connection con2; +--send DROP SCHEMA schema1 + +--echo # Connection default +connection default; +let $wait_condition=SELECT COUNT(*)=1 FROM information_schema.processlist + WHERE state='Waiting for table' and info='DROP SCHEMA schema1'; +--source include/wait_condition.inc + +--echo # CREATE SCHEMA used to give a deadlock. +--echo # Now we prohibit CREATE SCHEMA in LOCK TABLES mode. +--error ER_LOCK_OR_ACTIVE_TRANSACTION +CREATE SCHEMA IF NOT EXISTS schema1; + +--echo # UNLOCK TABLES so DROP SCHEMA can continue. +UNLOCK TABLES; + +--echo # Connection con2 +connection con2; +--reap + +--echo # Connection default +connection default; +disconnect con2; + # Check that all connections opened by test cases in this file are really # gone so execution of other tests won't be affected by their presence. --source include/wait_until_count_sessions.inc diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 689b2cec270..486cb9af288 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3402,6 +3402,12 @@ end_with_restore_list: if (check_access(thd,CREATE_ACL,lex->name.str, 0, 1, 0, is_schema_db(lex->name.str))) break; + if (thd->locked_tables_mode) + { + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + goto error; + } res= mysql_create_db(thd,(lower_case_table_names == 2 ? alias : lex->name.str), &create_info, 0); break; From ad6adb1398e08ffa92dd9bce26b04cdf747ff312 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 14 Jan 2010 14:03:24 +0100 Subject: [PATCH 147/212] Partial backport of: revno: 2762 [merge] committer: Matthias Leich branch nick: mysql-6.0-bugteam-push timestamp: Wed 2008-08-13 22:05:34 +0200 message: Upmerge 5.1 -> 6.0 ------------------------------------------------------------ revno: 2497.374.2 committer: Matthias Leich branch nick: mysql-5.1-bugteam-push timestamp: Wed 2008-08-13 21:44:54 +0200 message: Fix for Bug#37853 Test "funcs_1.processlist_val_ps" fails in various ways + corrections of logic in poll routines + minor improvements --- mysql-test/suite/funcs_1/datadict/processlist_val.inc | 10 +++++----- .../suite/funcs_1/r/processlist_val_no_prot.result | 6 +++--- mysql-test/suite/funcs_1/r/processlist_val_ps.result | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/mysql-test/suite/funcs_1/datadict/processlist_val.inc b/mysql-test/suite/funcs_1/datadict/processlist_val.inc index c34fb626bcd..8b10cfc5e97 100644 --- a/mysql-test/suite/funcs_1/datadict/processlist_val.inc +++ b/mysql-test/suite/funcs_1/datadict/processlist_val.inc @@ -367,13 +367,13 @@ echo ; connection default; echo -# Poll till INFO is no more NULL and State = 'Locked'. +# Poll till INFO is no more NULL and State = 'Table Lock'. ; let $wait_condition= SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE INFO IS NOT NULL AND STATE = 'Locked'; + WHERE INFO IS NOT NULL AND STATE = 'Table Lock'; --source include/wait_condition.inc # -# Expect to see the state 'Locked' for the third connection because the SELECT +# Expect to see the state 'Table Lock' for the third connection because the SELECT # collides with the WRITE TABLE LOCK. --replace_column 1 3 6
@@ -102,11 +106,12 @@ public: @param name Name of of the object @param key Where to store the the MDL key. */ - void mdl_key_init(enum_mdl_namespace mdl_namespace, const char *db, const char *name) + void mdl_key_init(enum_mdl_namespace mdl_namespace, + const char *db, const char *name) { m_ptr[0]= (char) mdl_namespace; - m_db_name_length= (uint) (strmov(m_ptr + 1, db) - m_ptr - 1); - m_length= (uint) (strmov(m_ptr + m_db_name_length + 2, name) - m_ptr + 1); + m_db_name_length= (uint16) (strmov(m_ptr + 1, db) - m_ptr - 1); + m_length= (uint16) (strmov(m_ptr + m_db_name_length + 2, name) - m_ptr + 1); } void mdl_key_init(const MDL_key *rhs) { @@ -119,20 +124,34 @@ public: return (m_length == rhs->m_length && memcmp(m_ptr, rhs->m_ptr, m_length) == 0); } + /** + Compare two MDL keys lexicographically. + */ + int cmp(const MDL_key *rhs) const + { + /* + The key buffer is always '\0'-terminated. Since key + character set is utf-8, we can safely assume that no + character starts with a zero byte. + */ + return memcmp(m_ptr, rhs->m_ptr, min(m_length, rhs->m_length)+1); + } + MDL_key(const MDL_key *rhs) { mdl_key_init(rhs); } - MDL_key(enum_mdl_namespace namespace_arg, const char *db_arg, const char *name_arg) + MDL_key(enum_mdl_namespace namespace_arg, + const char *db_arg, const char *name_arg) { mdl_key_init(namespace_arg, db_arg, name_arg); } MDL_key() {} /* To use when part of MDL_request. */ private: + uint16 m_length; + uint16 m_db_name_length; char m_ptr[MAX_MDLKEY_LENGTH]; - uint m_length; - uint m_db_name_length; private: MDL_key(const MDL_key &); /* not implemented */ MDL_key &operator=(const MDL_key &); /* not implemented */ @@ -198,7 +217,7 @@ public: DBUG_ASSERT(ticket == NULL); type= type_arg; } - bool is_shared() const { return type < MDL_EXCLUSIVE; } + bool is_shared() const { return type < MDL_INTENTION_EXCLUSIVE; } static MDL_request *create(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, const char *name, @@ -243,6 +262,17 @@ typedef void (*mdl_cached_object_release_hook)(void *); @note Multiple shared locks on a same object are represented by a single ticket. The same does not apply for other lock types. + + @note There are two groups of MDL_ticket members: + - "Externally accessible". These members can be accessed from + threads/contexts different than ticket owner in cases when + ticket participates in some list of granted or waiting tickets + for a lock. Therefore one should change these members before + including then to waiting/granted lists or while holding lock + protecting those lists. + - "Context private". Such members are private to thread/context + owning this ticket. I.e. they should not be accessed from other + threads/contexts. */ class MDL_ticket @@ -250,12 +280,13 @@ class MDL_ticket public: /** Pointers for participating in the list of lock requests for this context. + Context private. */ MDL_ticket *next_in_context; MDL_ticket **prev_in_context; /** Pointers for participating in the list of satisfied/pending requests - for the lock. + for the lock. Externally accessible. */ MDL_ticket *next_in_lock; MDL_ticket **prev_in_lock; @@ -265,8 +296,8 @@ public: void *get_cached_object(); void set_cached_object(void *cached_object, mdl_cached_object_release_hook release_hook); - const MDL_context *get_ctx() const { return m_ctx; } - bool is_shared() const { return m_type < MDL_EXCLUSIVE; } + MDL_context *get_ctx() const { return m_ctx; } + bool is_shared() const { return m_type < MDL_INTENTION_EXCLUSIVE; } bool is_upgradable_or_exclusive() const { return m_type == MDL_SHARED_UPGRADABLE || m_type == MDL_EXCLUSIVE; @@ -275,6 +306,8 @@ public: void downgrade_exclusive_lock(); private: friend class MDL_context; + friend class MDL_global_lock; + friend class MDL_object_lock; MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg) : m_type(type_arg), @@ -283,31 +316,31 @@ private: m_lock(NULL) {} - static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg); static void destroy(MDL_ticket *ticket); private: - /** Type of metadata lock. */ + /** Type of metadata lock. Externally accessible. */ enum enum_mdl_type m_type; - /** State of the metadata lock ticket. */ + /** State of the metadata lock ticket. Context private. */ enum enum_mdl_state m_state; - /** Context of the owner of the metadata lock ticket. */ + /** + Context of the owner of the metadata lock ticket. Externally accessible. + */ MDL_context *m_ctx; - /** Pointer to the lock object for this lock ticket. */ + /** Pointer to the lock object for this lock ticket. Context private. */ MDL_lock *m_lock; private: MDL_ticket(const MDL_ticket &); /* not implemented */ MDL_ticket &operator=(const MDL_ticket &); /* not implemented */ - - bool has_pending_conflicting_lock_impl() const; }; typedef I_P_List > + &MDL_request::prev_in_list>, + I_P_List_counter> MDL_request_list; /** @@ -326,21 +359,19 @@ public: typedef Ticket_list::Iterator Ticket_iterator; - void init(THD *thd); + MDL_context(); void destroy(); bool try_acquire_shared_lock(MDL_request *mdl_request); bool acquire_exclusive_lock(MDL_request *mdl_request); bool acquire_exclusive_locks(MDL_request_list *requests); bool try_acquire_exclusive_lock(MDL_request *mdl_request); - bool acquire_global_shared_lock(); bool clone_ticket(MDL_request *mdl_request); bool wait_for_locks(MDL_request_list *requests); void release_all_locks_for_name(MDL_ticket *ticket); void release_lock(MDL_ticket *ticket); - void release_global_shared_lock(); bool is_exclusive_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, @@ -368,7 +399,6 @@ public: void set_lt_or_ha_sentinel() { - DBUG_ASSERT(m_lt_or_ha_sentinel == NULL); m_lt_or_ha_sentinel= mdl_savepoint(); } MDL_ticket *lt_or_ha_sentinel() const { return m_lt_or_ha_sentinel; } @@ -385,16 +415,35 @@ public: bool can_wait_lead_to_deadlock() const; inline THD *get_thd() const { return m_thd; } - - bool is_waiting_in_mdl() const { return m_is_waiting_in_mdl; } + + /** + Wake up context which is waiting for a change of MDL_lock state. + */ + void awake() + { + pthread_cond_signal(&m_ctx_wakeup_cond); + } + + bool try_acquire_global_intention_exclusive_lock(MDL_request *mdl_request); + bool acquire_global_intention_exclusive_lock(MDL_request *mdl_request); + + bool acquire_global_shared_lock(); + void release_global_shared_lock(); + + /** + Check if this context owns global lock of particular type. + */ + bool is_global_lock_owner(enum_mdl_type type_arg) + { + MDL_request mdl_request; + bool not_used; + mdl_request.init(MDL_key::GLOBAL, "", "", type_arg); + return find_ticket(&mdl_request, ¬_used); + } + + void init(THD *thd_arg) { m_thd= thd_arg; } private: Ticket_list m_tickets; - bool m_has_global_shared_lock; - /** - Indicates that the owner of this context is waiting in - wait_for_locks() method. - */ - bool m_is_waiting_in_mdl; /** This member has two uses: 1) When entering LOCK TABLES mode, remember the last taken @@ -406,12 +455,27 @@ private: */ MDL_ticket *m_lt_or_ha_sentinel; THD *m_thd; + /** + Condvar which is used for waiting until this context's pending + request can be satisfied or this thread has to perform actions + to resolve potential deadlock (we subscribe for such notification + by adding ticket corresponding to the request to an appropriate + queue of waiters). + */ + pthread_cond_t m_ctx_wakeup_cond; private: - void release_ticket(MDL_ticket *ticket); - bool can_wait_lead_to_deadlock_impl() const; MDL_ticket *find_ticket(MDL_request *mdl_req, bool *is_lt_or_ha); void release_locks_stored_before(MDL_ticket *sentinel); + + bool try_acquire_lock_impl(MDL_request *mdl_request); + bool acquire_lock_impl(MDL_request *mdl_request); + bool acquire_exclusive_lock_impl(MDL_request *mdl_request); + + friend bool MDL_ticket::upgrade_shared_lock_to_exclusive(); +private: + MDL_context(const MDL_context &rhs); /* not implemented */ + MDL_context &operator=(MDL_context &rhs); /* not implemented */ }; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index bb5bf428ef0..9f153b5aa0e 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1689,13 +1689,13 @@ void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt); /* Functions to work with system tables. */ bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, - Open_tables_state *backup); -void close_system_tables(THD *thd, Open_tables_state *backup); + Open_tables_backup *backup); +void close_system_tables(THD *thd, Open_tables_backup *backup); TABLE *open_system_table_for_update(THD *thd, TABLE_LIST *one_table); TABLE *open_performance_schema_table(THD *thd, TABLE_LIST *one_table, - Open_tables_state *backup); -void close_performance_schema_table(THD *thd, Open_tables_state *backup); + Open_tables_backup *backup); +void close_performance_schema_table(THD *thd, Open_tables_backup *backup); bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, bool wait_for_refresh); diff --git a/sql/sp.cc b/sql/sp.cc index 1375d44cb9b..4cbb0f28b59 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -260,7 +260,7 @@ Stored_routine_creation_ctx::load_from_db(THD *thd, \# Pointer to TABLE object of mysql.proc */ -TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup) +TABLE *open_proc_table_for_read(THD *thd, Open_tables_backup *backup) { TABLE_LIST table; @@ -382,7 +382,7 @@ db_find_routine(THD *thd, int type, sp_name *name, sp_head **sphp) String str(buff, sizeof(buff), &my_charset_bin); bool saved_time_zone_used= thd->time_zone_used; ulong sql_mode, saved_mode= thd->variables.sql_mode; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; Stored_program_creation_ctx *creation_ctx; DBUG_ENTER("db_find_routine"); @@ -1432,7 +1432,7 @@ sp_routine_exists_in_table(THD *thd, int type, sp_name *name) { TABLE *table; int ret; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; if (!(table= open_proc_table_for_read(thd, &open_tables_state_backup))) ret= SP_OPEN_TABLE_FAILED; diff --git a/sql/sp.h b/sql/sp.h index cab4d7dbeb5..c051b584031 100644 --- a/sql/sp.h +++ b/sql/sp.h @@ -128,6 +128,6 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, Routines which allow open/lock and close mysql.proc table even when we already have some tables open and locked. */ -TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup); +TABLE *open_proc_table_for_read(THD *thd, Open_tables_backup *backup); #endif /* _SP_H_ */ diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 8d3ef372842..2f891375163 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1519,25 +1519,23 @@ void close_thread_tables(THD *thd) if (thd->open_tables) close_open_tables(thd); - if (thd->state_flags & Open_tables_state::BACKUPS_AVAIL) + /* + - If inside a multi-statement transaction, + defer the release of metadata locks until the current + transaction is either committed or rolled back. This prevents + other statements from modifying the table for the entire + duration of this transaction. This provides commit ordering + and guarantees serializability across multiple transactions. + - If closing a system table, defer the release of metadata locks + to the caller. We have no sentinel in MDL subsystem to guard + transactional locks from system tables locks, so don't know + which locks are which here. + - If in autocommit mode, or outside a transactional context, + automatically release metadata locks of the current statement. + */ + if (! thd->in_multi_stmt_transaction() && + ! (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) { - /* We can't have an open HANDLER in the backup open tables state. */ - DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); - /* - Due to the above assert, this is guaranteed to release *all* locks - in the context. - */ - thd->mdl_context.release_transactional_locks(); - } - else if (! thd->in_multi_stmt_transaction()) - { - /* - Defer the release of metadata locks until the current transaction - is either committed or rolled back. This prevents other statements - from modifying the table for the entire duration of this transaction. - This provides commitment ordering for guaranteeing serializability - across multiple transactions. - */ thd->mdl_context.release_transactional_locks(); } @@ -2336,10 +2334,9 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx, uint flags) { - ot_ctx->add_request(mdl_request); - if (table_list->lock_strategy) { + MDL_request *global_request; /* In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table may not yet exist. Let's acquire an exclusive lock for that @@ -2349,10 +2346,24 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, shared locks. This invariant is preserved here and is also enforced by asserts in metadata locking subsystem. */ + mdl_request->set_type(MDL_EXCLUSIVE); DBUG_ASSERT(! thd->mdl_context.has_locks() || - thd->handler_tables_hash.records); + thd->handler_tables_hash.records || + thd->global_read_lock); + if (!(global_request= ot_ctx->get_global_mdl_request(thd))) + return 1; + + if (! global_request->ticket) + { + ot_ctx->add_request(global_request); + if (thd->mdl_context.acquire_global_intention_exclusive_lock( + global_request)) + return 1; + } + + ot_ctx->add_request(mdl_request); if (thd->mdl_context.acquire_exclusive_lock(mdl_request)) return 1; } @@ -2371,8 +2382,29 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, if (flags & MYSQL_LOCK_IGNORE_FLUSH) mdl_request->set_type(MDL_SHARED_HIGH_PRIO); + if (mdl_request->type == MDL_SHARED_UPGRADABLE) + { + MDL_request *global_request; + + if (!(global_request= ot_ctx->get_global_mdl_request(thd))) + return 1; + if (! global_request->ticket) + { + ot_ctx->add_request(global_request); + if (thd->mdl_context.try_acquire_global_intention_exclusive_lock( + global_request)) + return 1; + if (! global_request->ticket) + goto failure; + } + } + + ot_ctx->add_request(mdl_request); + if (thd->mdl_context.try_acquire_shared_lock(mdl_request)) return 1; + +failure: if (mdl_request->ticket == NULL) { if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT) @@ -2919,8 +2951,6 @@ err_unlock: release_table_share(share); err_unlock2: pthread_mutex_unlock(&LOCK_open); - if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) - thd->mdl_context.release_lock(mdl_ticket); DBUG_RETURN(TRUE); } @@ -3713,10 +3743,33 @@ Open_table_context::Open_table_context(THD *thd) m_start_of_statement_svp(thd->mdl_context.mdl_savepoint()), m_has_locks((thd->in_multi_stmt_transaction() || thd->mdl_context.lt_or_ha_sentinel()) && - thd->mdl_context.has_locks()) + thd->mdl_context.has_locks()), + m_global_mdl_request(NULL) {} +/** + Get MDL_request object for global intention exclusive lock which + is acquired during opening tables for statements which take + upgradable shared metadata locks. +*/ + +MDL_request *Open_table_context::get_global_mdl_request(THD *thd) +{ + if (! m_global_mdl_request) + { + char *buff; + if ((buff= (char*)thd->alloc(sizeof(MDL_request)))) + { + m_global_mdl_request= new (buff) MDL_request(); + m_global_mdl_request->init(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE); + } + } + return m_global_mdl_request; +} + + /** Check if we can back-off and set back off action if we can. Otherwise report and return error. @@ -3777,6 +3830,11 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, TABLE_LIST *table) { bool result= FALSE; + /* + Remove reference to released ticket from MDL_request. + */ + if (m_global_mdl_request) + m_global_mdl_request->ticket= NULL; /* Execute the action. */ switch (m_action) { @@ -3787,11 +3845,26 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, break; case OT_DISCOVER: { + MDL_request mdl_global_request; MDL_request mdl_xlock_request(mdl_request); + + mdl_global_request.init(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE); mdl_xlock_request.set_type(MDL_EXCLUSIVE); + + + if ((result= thd->mdl_context.acquire_global_intention_exclusive_lock( + &mdl_global_request))) + break; + if ((result= thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request))) + { + /* + We rely on close_thread_tables() to release global lock eventually. + */ break; + } DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); pthread_mutex_lock(&LOCK_open); @@ -3805,16 +3878,30 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, thd->warning_info->clear_warning_info(thd->query_id); thd->clear_error(); // Clear error message - thd->mdl_context.release_lock(mdl_xlock_request.ticket); + thd->mdl_context.release_transactional_locks(); break; } case OT_REPAIR: { + MDL_request mdl_global_request; MDL_request mdl_xlock_request(mdl_request); + + mdl_global_request.init(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE); mdl_xlock_request.set_type(MDL_EXCLUSIVE); + + if ((result= thd->mdl_context.acquire_global_intention_exclusive_lock( + &mdl_global_request))) + break; + if ((result= thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request))) + { + /* + We rely on close_thread_tables() to release global lock eventually. + */ break; + } DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); pthread_mutex_lock(&LOCK_open); @@ -3824,7 +3911,7 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, pthread_mutex_unlock(&LOCK_open); result= auto_repair_table(thd, table); - thd->mdl_context.release_lock(mdl_xlock_request.ticket); + thd->mdl_context.release_transactional_locks(); break; } default: @@ -3921,6 +4008,13 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, mdl_type != MDL_key::PROCEDURE) { ot_ctx->add_request(&rt->mdl_request); + + /* + Since we acquire only shared lock on routines we don't + need to care about global intention exclusive locks. + */ + DBUG_ASSERT(rt->mdl_request.type == MDL_SHARED); + if (thd->mdl_context.try_acquire_shared_lock(&rt->mdl_request)) DBUG_RETURN(TRUE); @@ -8784,7 +8878,7 @@ has_write_table_with_auto_increment(TABLE_LIST *tables) bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, - Open_tables_state *backup) + Open_tables_backup *backup) { Query_tables_list query_tables_list_backup; LEX *lex= thd->lex; @@ -8830,13 +8924,13 @@ error: SYNOPSIS close_system_tables() thd Thread context - backup Pointer to Open_tables_state instance which holds + backup Pointer to Open_tables_backup instance which holds information about tables which were open before we decided to access system tables. */ void -close_system_tables(THD *thd, Open_tables_state *backup) +close_system_tables(THD *thd, Open_tables_backup *backup) { close_thread_tables(thd); thd->restore_backup_open_tables_state(backup); @@ -8887,7 +8981,7 @@ open_system_table_for_update(THD *thd, TABLE_LIST *one_table) */ TABLE * open_performance_schema_table(THD *thd, TABLE_LIST *one_table, - Open_tables_state *backup) + Open_tables_backup *backup) { uint flags= ( MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK | MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY | @@ -8936,51 +9030,9 @@ open_performance_schema_table(THD *thd, TABLE_LIST *one_table, @param thd The current thread @param backup [in] the context to restore. */ -void close_performance_schema_table(THD *thd, Open_tables_state *backup) +void close_performance_schema_table(THD *thd, Open_tables_backup *backup) { - bool found_old_table; - - /* - If open_performance_schema_table() fails, - this function should not be called. - */ - DBUG_ASSERT(thd->lock != NULL); - - /* - Note: - We do not create explicitly a separate transaction for the - performance table I/O, but borrow the current transaction. - lock + unlock will autocommit the change done in the - performance schema table: this is the expected result. - The current transaction should not be affected by this code. - TODO: Note that if a transactional engine is used for log tables, - this code will need to be revised, as a separate transaction - might be needed. - */ - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; - - pthread_mutex_lock(&LOCK_open); - - found_old_table= false; - /* - Note that we need to hold LOCK_open while changing the - open_tables list. Another thread may work on it. - (See: notify_thread_having_shared_lock()) - */ - while (thd->open_tables) - found_old_table|= close_thread_table(thd, &thd->open_tables); - - if (found_old_table) - broadcast_refresh(); - - pthread_mutex_unlock(&LOCK_open); - - /* We can't have an open HANDLER in the backup context. */ - DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL); - thd->mdl_context.release_transactional_locks(); - - thd->restore_backup_open_tables_state(backup); + close_system_tables(thd, backup); } /** diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 95c985b2c10..62de06d382c 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -471,6 +471,7 @@ THD::THD() { ulong tmp; + mdl_context.init(this); /* Pass nominal parameters to init_alloc_root only to ensure that the destructor works OK in case of an error. The main_mem_root @@ -1007,7 +1008,8 @@ void THD::cleanup(void) */ DBUG_ASSERT(open_tables == NULL); /* All HANDLERs must have been closed by now. */ - DBUG_ASSERT(mdl_context.lt_or_ha_sentinel() == NULL); + DBUG_ASSERT(mdl_context.lt_or_ha_sentinel() == NULL || + global_read_lock); /* Due to the above assert, this is guaranteed to release *all* in this session. @@ -3024,19 +3026,21 @@ bool Security_context::user_matches(Security_context *them) access to mysql.proc table to find definitions of stored routines. ****************************************************************************/ -void THD::reset_n_backup_open_tables_state(Open_tables_state *backup) +void THD::reset_n_backup_open_tables_state(Open_tables_backup *backup) { DBUG_ENTER("reset_n_backup_open_tables_state"); backup->set_open_tables_state(this); + backup->mdl_system_tables_svp= mdl_context.mdl_savepoint(); reset_open_tables_state(this); state_flags|= Open_tables_state::BACKUPS_AVAIL; DBUG_VOID_RETURN; } -void THD::restore_backup_open_tables_state(Open_tables_state *backup) +void THD::restore_backup_open_tables_state(Open_tables_backup *backup) { DBUG_ENTER("restore_backup_open_tables_state"); + mdl_context.rollback_to_savepoint(backup->mdl_system_tables_svp); /* Before we will throw away current open tables state we want to be sure that it was properly cleaned up. @@ -3046,7 +3050,6 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup) lock == 0 && locked_tables_mode == LTM_NONE && m_reprepare_observer == NULL); - mdl_context.destroy(); set_open_tables_state(backup); DBUG_VOID_RETURN; diff --git a/sql/sql_class.h b/sql/sql_class.h index 5654dcb07a6..dce06f7e0c5 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -978,9 +978,6 @@ public: Flags with information about the open tables state. */ uint state_flags; - - MDL_context mdl_context; - /** This constructor initializes Open_tables_state instance which can only be used as backup storage. To prepare Open_tables_state instance for @@ -1010,21 +1007,29 @@ public: locked_tables_mode= LTM_NONE; state_flags= 0U; m_reprepare_observer= NULL; - mdl_context.init(thd); - } - void enter_locked_tables_mode(enum_locked_tables_mode mode_arg) - { - DBUG_ASSERT(locked_tables_mode == LTM_NONE); - mdl_context.set_lt_or_ha_sentinel(); - locked_tables_mode= mode_arg; - } - void leave_locked_tables_mode() - { - locked_tables_mode= LTM_NONE; - mdl_context.clear_lt_or_ha_sentinel(); } }; + +/** + Storage for backup of Open_tables_state. Must + be used only to open system tables (TABLE_CATEGORY_SYSTEM + and TABLE_CATEGORY_LOG). +*/ + +class Open_tables_backup: public Open_tables_state +{ +public: + /** + When we backup the open tables state to open a system + table or tables, points at the last metadata lock + acquired before the backup. Is used to release + metadata locks on system tables after they are + no longer used. + */ + MDL_ticket *mdl_system_tables_svp; +}; + /** @class Sub_statement_state @brief Used to save context when executing a function or trigger @@ -1308,6 +1313,9 @@ public: { return m_start_of_statement_svp; } + + MDL_request *get_global_mdl_request(THD *thd); + private: /** List of requests for all locks taken so far. Used for waiting on locks. */ MDL_request_list m_mdl_requests; @@ -1320,6 +1328,11 @@ private: and we can't safely do back-off (and release them). */ bool m_has_locks; + /** + Request object for global intention exclusive lock which is acquired during + opening tables for statements which take upgradable shared metadata locks. + */ + MDL_request *m_global_mdl_request; }; @@ -1426,6 +1439,8 @@ class THD :public Statement, public Open_tables_state { public: + MDL_context mdl_context; + /* Used to execute base64 coded binlog events in MySQL server */ Relay_log_info* rli_fake; @@ -2314,8 +2329,8 @@ public: void set_status_var_init(); bool is_context_analysis_only() { return stmt_arena->is_stmt_prepare() || lex->view_prepare_mode; } - void reset_n_backup_open_tables_state(Open_tables_state *backup); - void restore_backup_open_tables_state(Open_tables_state *backup); + void reset_n_backup_open_tables_state(Open_tables_backup *backup); + void restore_backup_open_tables_state(Open_tables_backup *backup); void reset_sub_statement_state(Sub_statement_state *backup, uint new_state); void restore_sub_statement_state(Sub_statement_state *backup); void set_n_backup_active_arena(Query_arena *set, Query_arena *backup); @@ -2567,6 +2582,19 @@ public: Protected with LOCK_thd_data mutex. */ void set_query(char *query_arg, uint32 query_length_arg); + void enter_locked_tables_mode(enum_locked_tables_mode mode_arg) + { + DBUG_ASSERT(locked_tables_mode == LTM_NONE); + DBUG_ASSERT(! mdl_context.lt_or_ha_sentinel() || + mdl_context.is_global_lock_owner(MDL_SHARED)); + mdl_context.set_lt_or_ha_sentinel(); + locked_tables_mode= mode_arg; + } + void leave_locked_tables_mode() + { + locked_tables_mode= LTM_NONE; + mdl_context.clear_lt_or_ha_sentinel(); + } private: /** The current internal error handler for this thread, or NULL. */ Internal_error_handler *m_internal_handler; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 26478b31290..228c001f71b 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1100,7 +1100,7 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) TABLE *table; bool error= TRUE; uint path_length; - MDL_request mdl_request; + MDL_request mdl_global_request, mdl_request; /* Is set if we're under LOCK TABLES, and used to downgrade the exclusive lock after the @@ -1207,10 +1207,21 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) the table can be re-created as an empty table with TRUNCATE TABLE, even if the data or index files have become corrupted. */ + + mdl_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); mdl_request.init(MDL_key::TABLE, table_list->db, table_list->table_name, MDL_EXCLUSIVE); - if (thd->mdl_context.acquire_exclusive_lock(&mdl_request)) + if (thd->mdl_context.acquire_global_intention_exclusive_lock( + &mdl_global_request)) DBUG_RETURN(TRUE); + if (thd->mdl_context.acquire_exclusive_lock(&mdl_request)) + { + /* + We rely on that close_thread_tables() to release global lock + in this case. + */ + DBUG_RETURN(TRUE); + } has_mdl_lock= TRUE; pthread_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, @@ -1250,7 +1261,7 @@ end: my_ok(thd); // This should return record count } if (has_mdl_lock) - thd->mdl_context.release_lock(mdl_request.ticket); + thd->mdl_context.release_transactional_locks(); if (mdl_ticket) mdl_ticket->downgrade_exclusive_lock(); } diff --git a/sql/sql_help.cc b/sql/sql_help.cc index af67db45b36..e9b15e07e9d 100644 --- a/sql/sql_help.cc +++ b/sql/sql_help.cc @@ -655,7 +655,12 @@ bool mysqld_help(THD *thd, const char *mask) tables[0].db= tables[1].db= tables[2].db= tables[3].db= (char*) "mysql"; init_mdl_requests(tables); - Open_tables_state open_tables_state_backup; + /* + HELP must be available under LOCK TABLES. + Reset and backup the current open tables state to + make it possible. + */ + Open_tables_backup open_tables_state_backup; if (open_system_tables_for_read(thd, tables, &open_tables_state_backup)) goto error2; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 486cb9af288..397674471c5 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -6502,7 +6502,8 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, DBUG_ASSERT(!thd || thd->locked_tables_mode || !thd->mdl_context.has_locks() || - thd->handler_tables_hash.records); + thd->handler_tables_hash.records || + thd->global_read_lock); /* Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too diff --git a/sql/sql_plist.h b/sql/sql_plist.h index 8f2aee6bd5f..eb239a63467 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -18,7 +18,8 @@ #include -template class I_P_List_iterator; +template class I_P_List_iterator; +class I_P_List_null_counter; /** @@ -47,10 +48,14 @@ template class I_P_List_iterator; return &el->prev; } }; + @param C Policy class specifying how counting of elements in the list + should be done. Instance of this class is also used as a place + where information about number of list elements is stored. + @sa I_P_List_null_counter, I_P_List_counter */ -template -class I_P_List +template +class I_P_List : public C { T *first; @@ -61,7 +66,7 @@ class I_P_List */ public: I_P_List() : first(NULL) { }; - inline void empty() { first= NULL; } + inline void empty() { first= NULL; C::reset(); } inline bool is_empty() const { return (first == NULL); } inline void push_front(T* a) { @@ -70,6 +75,7 @@ public: *B::prev_ptr(first)= B::next_ptr(a); first= a; *B::prev_ptr(a)= &first; + C::inc(); } inline void push_back(T *a) { @@ -107,21 +113,23 @@ public: if (next) *B::prev_ptr(next)= *B::prev_ptr(a); **B::prev_ptr(a)= next; + C::dec(); } inline T* front() { return first; } inline const T *front() const { return first; } - void swap(I_P_List &rhs) + void swap(I_P_List &rhs) { swap_variables(T *, first, rhs.first); if (first) *B::prev_ptr(first)= &first; if (rhs.first) *B::prev_ptr(rhs.first)= &rhs.first; + C::swap(rhs); } #ifndef _lint - friend class I_P_List_iterator; + friend class I_P_List_iterator; #endif - typedef I_P_List_iterator Iterator; + typedef I_P_List_iterator Iterator; }; @@ -129,15 +137,15 @@ public: Iterator for I_P_List. */ -template +template class I_P_List_iterator { - const I_P_List *list; + const I_P_List *list; T *current; public: - I_P_List_iterator(const I_P_List &a) : list(&a), current(a.first) {} - I_P_List_iterator(const I_P_List &a, T* current_arg) : list(&a), current(current_arg) {} - inline void init(I_P_List &a) + I_P_List_iterator(const I_P_List &a) : list(&a), current(a.first) {} + I_P_List_iterator(const I_P_List &a, T* current_arg) : list(&a), current(current_arg) {} + inline void init(const I_P_List &a) { list= &a; current= a.first; @@ -160,4 +168,39 @@ public: } }; + +/** + Element counting policy class for I_P_List to be used in + cases when no element counting should be done. +*/ + +class I_P_List_null_counter +{ +protected: + void reset() {} + void inc() {} + void dec() {} + void swap(I_P_List_null_counter &rhs) {} +}; + + +/** + Element counting policy class for I_P_List which provides + basic element counting. +*/ + +class I_P_List_counter +{ + uint m_counter; +protected: + I_P_List_counter() : m_counter (0) {} + void reset() {m_counter= 0;} + void inc() {m_counter++;} + void dec() {m_counter--;} + void swap(I_P_List_counter &rhs) + { swap_variables(uint, m_counter, rhs.m_counter); } +public: + uint elements() const { return m_counter; } +}; + #endif diff --git a/sql/sql_show.cc b/sql/sql_show.cc index e9d1426b3e3..278e0c1445f 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2871,7 +2871,7 @@ make_table_name_list(THD *thd, List *table_names, LEX *lex, due to metadata locks, so to avoid them we should not wait in case if conflicting lock is present. - @param[in] open_tables_state_backup pointer to Open_tables_state object + @param[in] open_tables_state_backup pointer to Open_tables_backup object which is used to save|restore original status of variables related to open tables state @@ -2885,7 +2885,7 @@ static int fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, ST_SCHEMA_TABLE *schema_table, bool can_deadlock, - Open_tables_state *open_tables_state_backup) + Open_tables_backup *open_tables_state_backup) { LEX *lex= thd->lex; bool res; @@ -2941,7 +2941,8 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, table, res, db_name, table_name)); thd->temporary_tables= 0; - close_tables_for_reopen(thd, &show_table_list, NULL); + close_tables_for_reopen(thd, &show_table_list, + open_tables_state_backup->mdl_system_tables_svp); DBUG_RETURN(error); } @@ -3236,8 +3237,12 @@ end_share: end_unlock: pthread_mutex_unlock(&LOCK_open); + /* + Don't release the MDL lock, it can be part of a transaction. + If it is not, it will be released by the call to + MDL_context::rollback_to_savepoint() in the caller. + */ - thd->mdl_context.release_lock(table_list.mdl_request.ticket); thd->clear_error(); return res; } @@ -3281,7 +3286,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) COND *partial_cond= 0; uint derived_tables= lex->derived_tables; int error= 1; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; bool save_view_prepare_mode= lex->view_prepare_mode; Query_tables_list query_tables_list_backup; #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -3500,7 +3505,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) res= schema_table->process_table(thd, show_table_list, table, res, &orig_db_name, &tmp_lex_string); - close_tables_for_reopen(thd, &show_table_list, NULL); + close_tables_for_reopen(thd, &show_table_list, + open_tables_state_backup.mdl_system_tables_svp); } DBUG_ASSERT(!lex->query_tables_own_last); if (res) @@ -4302,7 +4308,7 @@ int fill_schema_proc(THD *thd, TABLE_LIST *tables, COND *cond) TABLE *table= tables->table; bool full_access; char definer[USER_HOST_BUFF_SIZE]; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; DBUG_ENTER("fill_schema_proc"); strxmov(definer, thd->security_ctx->priv_user, "@", diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 30d6efff7ec..e8c2af4c87b 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -2207,22 +2207,26 @@ err: locked. Additional check for 'non_temp_tables_count' is to avoid leaving LOCK TABLES mode if we have dropped only temporary tables. */ - if (thd->locked_tables_mode && - thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0) + if (! thd->locked_tables_mode) + unlock_table_names(thd); + else { - thd->locked_tables_list.unlock_locked_tables(thd); - goto end; - } - for (table= tables; table; table= table->next_local) - { - if (table->mdl_request.ticket) + if (thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0) { - /* - Under LOCK TABLES we may have several instances of table open - and locked and therefore have to remove several metadata lock - requests associated with them. - */ - thd->mdl_context.release_all_locks_for_name(table->mdl_request.ticket); + thd->locked_tables_list.unlock_locked_tables(thd); + goto end; + } + for (table= tables; table; table= table->next_local) + { + if (table->mdl_request.ticket) + { + /* + Under LOCK TABLES we may have several instances of table open + and locked and therefore have to remove several metadata lock + requests associated with them. + */ + thd->mdl_context.release_all_locks_for_name(table->mdl_request.ticket); + } } } } @@ -4349,6 +4353,14 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, if (!(table= table_list->table)) { + /* + If the table didn't exist, we have a shared metadata lock + on it that is left from mysql_admin_table()'s attempt to + open it. Release the shared metadata lock before trying to + acquire the exclusive lock to satisfy MDL asserts and avoid + deadlocks. + */ + thd->mdl_context.release_transactional_locks(); /* Attempt to do full-blown table open in mysql_admin_table() has failed. Let us try to open at least a .FRM for this table. @@ -4360,6 +4372,14 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, table_list->mdl_request.init(MDL_key::TABLE, table_list->db, table_list->table_name, MDL_EXCLUSIVE); + + MDL_request mdl_global_request; + mdl_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); + + if (thd->mdl_context.acquire_global_intention_exclusive_lock( + &mdl_global_request)) + DBUG_RETURN(0); + if (thd->mdl_context.acquire_exclusive_lock(&table_list->mdl_request)) DBUG_RETURN(0); has_mdl_lock= TRUE; @@ -4491,7 +4511,7 @@ end: } /* In case of a temporary table there will be no metadata lock. */ if (error && has_mdl_lock) - thd->mdl_context.release_lock(table_list->mdl_request.ticket); + thd->mdl_context.release_transactional_locks(); DBUG_RETURN(error); } @@ -6544,6 +6564,13 @@ view_err: { target_mdl_request.init(MDL_key::TABLE, new_db, new_name, MDL_EXCLUSIVE); + /* + Global intention exclusive lock must have been already acquired when + table to be altered was open, so there is no need to do it here. + */ + DBUG_ASSERT(thd-> + mdl_context.is_global_lock_owner(MDL_INTENTION_EXCLUSIVE)); + if (thd->mdl_context.try_acquire_exclusive_lock(&target_mdl_request)) DBUG_RETURN(TRUE); if (target_mdl_request.ticket == NULL) diff --git a/sql/tztime.cc b/sql/tztime.cc index 2ec641071ee..aa9780754d7 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -1563,7 +1563,6 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) { THD *thd; TABLE_LIST tz_tables[1+MY_TZ_TABLES_COUNT]; - Open_tables_state open_tables_state_backup; TABLE *table; Tz_names_entry *tmp_tzname; my_bool return_val= 1; @@ -1642,7 +1641,8 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) We need to open only mysql.time_zone_leap_second, but we try to open all time zone tables to see if they exist. */ - if (open_system_tables_for_read(thd, tz_tables, &open_tables_state_backup)) + if (open_and_lock_tables_derived(thd, tz_tables, FALSE, + MYSQL_LOCK_IGNORE_FLUSH)) { sql_print_warning("Can't open and lock time zone table: %s " "trying to live without them", thd->stmt_da->message()); @@ -1651,6 +1651,9 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) goto end_with_setting_default_tz; } + for (TABLE_LIST *tl= tz_tables; tl; tl= tl->next_global) + tl->table->use_all_columns(); + /* Now we are going to load leap seconds descriptions that are shared between all time zones that use them. We are using index for getting @@ -1739,7 +1742,8 @@ end_with_close: if (time_zone_tables_exist) { thd->version--; /* Force close to free memory */ - close_system_tables(thd, &open_tables_state_backup); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); } end_with_cleanup: @@ -2293,7 +2297,7 @@ my_tz_find(THD *thd, const String *name) else if (time_zone_tables_exist) { TABLE_LIST tz_tables[MY_TZ_TABLES_COUNT]; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; tz_init_table_list(tz_tables); init_mdl_requests(tz_tables); From e371d96a78fb9e530654c280ea4a405af0cbb758 Mon Sep 17 00:00:00 2001 From: Bernd Ocklin Date: Fri, 22 Jan 2010 14:12:17 +0100 Subject: [PATCH 156/212] fix LD_LIBRARY_PATH (bug#50540) --- mysql-test/mysql-test-run.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index d0f7d2b2a75..2805b156487 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -1787,11 +1787,11 @@ sub environment_setup { { push(@ld_library_paths, "$basedir/libmysql/.libs/", "$basedir/libmysql_r/.libs/", - "$basedir/zlib.libs/"); + "$basedir/zlib/.libs/"); } else { - push(@ld_library_paths, "$basedir/lib"); + push(@ld_library_paths, "$basedir/lib", "$basedir/lib/mysql"); } } From e8a05b414021ea787ba4f8e40423b67c9334850e Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Mon, 25 Jan 2010 11:20:52 +0100 Subject: [PATCH 157/212] Bug #50182 mtr: allow $MTR_PORT_BASE or --port-base= as alternative to MTR_BUILD_THREAD As suggested, convert internally to value of build_thread --- mysql-test/mysql-test-run.pl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 2805b156487..99d4774866f 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -191,7 +191,9 @@ our $opt_experimental; our $experimental_test_cases; my $baseport; +# $opt_build_thread may later be set from $opt_port_base my $opt_build_thread= $ENV{'MTR_BUILD_THREAD'} || "auto"; +my $opt_port_base= $ENV{'MTR_PORT_BASE'} || "auto"; my $build_thread= 0; my $opt_record; @@ -838,6 +840,7 @@ sub command_line_setup { # Specify ports 'build-thread|mtr-build-thread=i' => \$opt_build_thread, + 'port-base|mtr-port-base=i' => \$opt_port_base, # Test case authoring 'record' => \$opt_record, @@ -1100,6 +1103,16 @@ sub command_line_setup { $opt_mem= undef; } + if ($opt_port_base ne "auto") + { + if (my $rem= $opt_port_base % 10) + { + mtr_warning ("Port base $opt_port_base rounded down to multiple of 10"); + $opt_port_base-= $rem; + } + $opt_build_thread= $opt_port_base / 10 - 1000; + } + # -------------------------------------------------------------------------- # Check if we should speed up tests by trying to run on tmpfs # -------------------------------------------------------------------------- @@ -5326,6 +5339,11 @@ Options to control what test suites or cases to run Options that specify ports + mtr-port-base=# Base for port numbers, ports from this number to + port-base=# number+9 are reserved. Should be divisible by 10; + if not it will be rounded down. May be set with + environment variable MTR_PORT_BASE. If this value is + set and is not "auto", it overrides build-thread. mtr-build-thread=# Specify unique number to calculate port number(s) from. build-thread=# Can be set in environment variable MTR_BUILD_THREAD. Set MTR_BUILD_THREAD="auto" to automatically aquire From 480054c64d39c840756cb7dbece5e951d7d1006c Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 28 Jan 2010 13:01:01 +0100 Subject: [PATCH 158/212] upmerge 49210 --- mysql-test/lib/v1/mysql-test-run.pl | 6 ++++-- mysql-test/mysql-test-run.pl | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mysql-test/lib/v1/mysql-test-run.pl b/mysql-test/lib/v1/mysql-test-run.pl index 9630c65ade4..5d06d9c4dd8 100755 --- a/mysql-test/lib/v1/mysql-test-run.pl +++ b/mysql-test/lib/v1/mysql-test-run.pl @@ -1107,14 +1107,16 @@ sub command_line_setup () { if ( ! $opt_testcase_timeout ) { - $opt_testcase_timeout= $default_testcase_timeout; + $opt_testcase_timeout= + $ENV{MTR_TESTCASE_TIMEOUT} || $default_testcase_timeout; $opt_testcase_timeout*= 10 if $opt_valgrind; $opt_testcase_timeout*= 10 if ($opt_debug and $glob_win32); } if ( ! $opt_suite_timeout ) { - $opt_suite_timeout= $default_suite_timeout; + $opt_suite_timeout= + $ENV{MTR_SUITE_TIMEOUT} || $default_suite_timeout; $opt_suite_timeout*= 6 if $opt_valgrind; $opt_suite_timeout*= 6 if ($opt_debug and $glob_win32); } diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index e42ec86a7f6..b006145a677 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -206,10 +206,10 @@ my $opt_mark_progress; my $opt_sleep; -my $opt_testcase_timeout= 15; # minutes -my $opt_suite_timeout = 300; # minutes -my $opt_shutdown_timeout= 10; # seconds -my $opt_start_timeout = 180; # seconds +my $opt_testcase_timeout= $ENV{MTR_TESTCASE_TIMEOUT} || 15; # minutes +my $opt_suite_timeout = $ENV{MTR_SUITE_TIMEOUT} || 300; # minutes +my $opt_shutdown_timeout= $ENV{MTR_SHUTDOWN_TIMEOUT} || 10; # seconds +my $opt_start_timeout = $ENV{MTR_START_TIMEOUT} || 180; # seconds sub testcase_timeout { return $opt_testcase_timeout * 60; }; sub suite_timeout { return $opt_suite_timeout * 60; }; From 235e7b8f1138a29b57bd33f9256ba344ea679f83 Mon Sep 17 00:00:00 2001 From: Bjorn Munch Date: Thu, 28 Jan 2010 15:19:18 +0100 Subject: [PATCH 159/212] merge 49210 --- mysql-test/lib/v1/mysql-test-run.pl | 6 ++++-- mysql-test/mysql-test-run.pl | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mysql-test/lib/v1/mysql-test-run.pl b/mysql-test/lib/v1/mysql-test-run.pl index 9630c65ade4..5d06d9c4dd8 100755 --- a/mysql-test/lib/v1/mysql-test-run.pl +++ b/mysql-test/lib/v1/mysql-test-run.pl @@ -1107,14 +1107,16 @@ sub command_line_setup () { if ( ! $opt_testcase_timeout ) { - $opt_testcase_timeout= $default_testcase_timeout; + $opt_testcase_timeout= + $ENV{MTR_TESTCASE_TIMEOUT} || $default_testcase_timeout; $opt_testcase_timeout*= 10 if $opt_valgrind; $opt_testcase_timeout*= 10 if ($opt_debug and $glob_win32); } if ( ! $opt_suite_timeout ) { - $opt_suite_timeout= $default_suite_timeout; + $opt_suite_timeout= + $ENV{MTR_SUITE_TIMEOUT} || $default_suite_timeout; $opt_suite_timeout*= 6 if $opt_valgrind; $opt_suite_timeout*= 6 if ($opt_debug and $glob_win32); } diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 99d4774866f..a2ea36aa34e 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -206,10 +206,10 @@ my $opt_mark_progress; my $opt_sleep; -my $opt_testcase_timeout= 15; # minutes -my $opt_suite_timeout = 300; # minutes -my $opt_shutdown_timeout= 10; # seconds -my $opt_start_timeout = 180; # seconds +my $opt_testcase_timeout= $ENV{MTR_TESTCASE_TIMEOUT} || 15; # minutes +my $opt_suite_timeout = $ENV{MTR_SUITE_TIMEOUT} || 300; # minutes +my $opt_shutdown_timeout= $ENV{MTR_SHUTDOWN_TIMEOUT} || 10; # seconds +my $opt_start_timeout = $ENV{MTR_START_TIMEOUT} || 180; # seconds sub testcase_timeout { return $opt_testcase_timeout * 60; }; sub suite_timeout { return $opt_suite_timeout * 60; }; From eba5d30e67aedf4a8d55380ec933306cce7b7563 Mon Sep 17 00:00:00 2001 From: Dmitry Lenev Date: Mon, 1 Feb 2010 14:43:06 +0300 Subject: [PATCH 160/212] Implement new type-of-operation-aware metadata locks. Add a wait-for graph based deadlock detector to the MDL subsystem. Fixes bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock" and bug #37346 "innodb does not detect deadlock between update and alter table". The first bug manifested itself as an unwarranted abort of a transaction with ER_LOCK_DEADLOCK error by a concurrent ALTER statement, when this transaction tried to repeat use of a table, which it has already used in a similar fashion before ALTER started. The second bug showed up as a deadlock between table-level locks and InnoDB row locks, which was "detected" only after innodb_lock_wait_timeout timeout. A transaction would start using the table and modify a few rows. Then ALTER TABLE would come in, and start copying rows into a temporary table. Eventually it would stumble on the modified records and get blocked on a row lock. The first transaction would try to do more updates, and get blocked on thr_lock.c lock. This situation of circular wait would only get resolved by a timeout. Both these bugs stemmed from inadequate solutions to the problem of deadlocks occurring between different locking subsystems. In the first case we tried to avoid deadlocks between metadata locking and table-level locking subsystems, when upgrading shared metadata lock to exclusive one. Transactions holding the shared lock on the table and waiting for some table-level lock used to be aborted too aggressively. We also allowed ALTER TABLE to start in presence of transactions that modify the subject table. ALTER TABLE acquires TL_WRITE_ALLOW_READ lock at start, and that block all writes against the table (naturally, we don't want any writes to be lost when switching the old and the new table). TL_WRITE_ALLOW_READ lock, in turn, would block the started transaction on thr_lock.c lock, should they do more updates. This, again, lead to the need to abort such transactions. The second bug occurred simply because we didn't have any mechanism to detect deadlocks between the table-level locks in thr_lock.c and row-level locks in InnoDB, other than innodb_lock_wait_timeout. This patch solves both these problems by moving lock conflicts which are causing these deadlocks into the metadata locking subsystem, thus making it possible to avoid or detect such deadlocks inside MDL. To do this we introduce new type-of-operation-aware metadata locks, which allow MDL subsystem to know not only the fact that transaction has used or is going to use some object but also what kind of operation it has carried out or going to carry out on the object. This, along with the addition of a special kind of upgradable metadata lock, allows ALTER TABLE to wait until all transactions which has updated the table to go away. This solves the second issue. Another special type of upgradable metadata lock is acquired by LOCK TABLE WRITE. This second lock type allows to solve the first issue, since abortion of table-level locks in event of DDL under LOCK TABLES becomes also unnecessary. Below follows the list of incompatible changes introduced by this patch: - From now on, ALTER TABLE and CREATE/DROP TRIGGER SQL (i.e. those statements that acquire TL_WRITE_ALLOW_READ lock) wait for all transactions which has *updated* the table to complete. - From now on, LOCK TABLES ... WRITE, REPAIR/OPTIMIZE TABLE (i.e. all statements which acquire TL_WRITE table-level lock) wait for all transaction which *updated or read* from the table to complete. As a consequence, innodb_table_locks=0 option no longer applies to LOCK TABLES ... WRITE. - DROP DATABASE, DROP TABLE, RENAME TABLE no longer abort statements or transactions which use tables being dropped or renamed, and instead wait for these transactions to complete. - Since LOCK TABLES WRITE now takes a special metadata lock, not compatible with with reads or writes against the subject table and transaction-wide, thr_lock.c deadlock avoidance algorithm that used to ensure absence of deadlocks between LOCK TABLES WRITE and other statements is no longer sufficient, even for MyISAM. The wait-for graph based deadlock detector of MDL subsystem may sometimes be necessary and is involved. This may lead to ER_LOCK_DEADLOCK error produced for multi-statement transactions even if these only use MyISAM: session 1: session 2: begin; update t1 ... lock table t2 write, t1 write; -- gets a lock on t2, blocks on t1 update t2 ... (ER_LOCK_DEADLOCK) - Finally, support of LOW_PRIORITY option for LOCK TABLES ... WRITE was abandoned. LOCK TABLE ... LOW_PRIORITY WRITE from now on has the same priority as the usual LOCK TABLE ... WRITE. SELECT HIGH PRIORITY no longer trumps LOCK TABLE ... WRITE in the wait queue. - We do not take upgradable metadata locks on implicitly locked tables. So if one has, say, a view v1 that uses table t1, and issues: LOCK TABLE v1 WRITE; FLUSH TABLE t1; -- (or just 'FLUSH TABLES'), an error is produced. In order to be able to perform DDL on a table under LOCK TABLES, the table must be locked explicitly in the LOCK TABLES list. mysql-test/include/handler.inc: Adjusted test case to trigger an execution path on which bug 41110 "crash with handler command when used concurrently with alter table" and bug 41112 "crash in mysql_ha_close_table/get_lock_data with alter table" were originally discovered. Left old test case which no longer triggers this execution path for the sake of coverage. Added test coverage for HANDLER SQL statements and type-aware metadata locks. Added a test for the global shared lock and HANDLER SQL. Updated tests to take into account that the old simple deadlock detection heuristics was replaced with a graph-based deadlock detector. mysql-test/r/debug_sync.result: Updated results (see debug_sync.test). mysql-test/r/handler_innodb.result: Updated results (see handler.inc test). mysql-test/r/handler_myisam.result: Updated results (see handler.inc test). mysql-test/r/innodb-lock.result: Updated results (see innodb-lock.test). mysql-test/r/innodb_mysql_lock.result: Updated results (see innodb_mysql_lock.test). mysql-test/r/lock.result: Updated results (see lock.test). mysql-test/r/lock_multi.result: Updated results (see lock_multi.test). mysql-test/r/lock_sync.result: Updated results (see lock_sync.test). mysql-test/r/mdl_sync.result: Updated results (see mdl_sync.test). mysql-test/r/sp-threads.result: SHOW PROCESSLIST output has changed due to the fact that waiting for LOCK TABLES WRITE now happens within metadata locking subsystem. mysql-test/r/truncate_coverage.result: Updated results (see truncate_coverage.test). mysql-test/suite/funcs_1/datadict/processlist_val.inc: SELECT FROM I_S.PROCESSLIST output has changed due to fact that waiting for LOCK TABLES WRITE now happens within metadata locking subsystem. mysql-test/suite/funcs_1/r/processlist_val_no_prot.result: SELECT FROM I_S.PROCESSLIST output has changed due to fact that waiting for LOCK TABLES WRITE now happens within metadata locking subsystem. mysql-test/suite/rpl/t/rpl_sp.test: Updated to a new SHOW PROCESSLIST state name. mysql-test/t/debug_sync.test: Use LOCK TABLES READ instead of LOCK TABLES WRITE as the latter no longer allows to trigger execution path involving waiting on thr_lock.c lock and therefore reaching debug sync-point covered by this test. mysql-test/t/innodb-lock.test: Adjusted test case to the fact that innodb_table_locks=0 option is no longer supported, since LOCK TABLES WRITE handles all its conflicts within MDL subsystem. mysql-test/t/innodb_mysql_lock.test: Added test for bug #37346 "innodb does not detect deadlock between update and alter table". mysql-test/t/lock.test: Added test coverage which checks the fact that we no longer support DDL under LOCK TABLES on tables which were locked implicitly. Adjusted existing test cases accordingly. mysql-test/t/lock_multi.test: Added test for bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock". Adjusted other test cases to take into account the fact that waiting for LOCK TABLES ... WRITE now happens within MDL subsystem. mysql-test/t/lock_sync.test: Since LOCK TABLES ... WRITE now takes SNRW metadata lock for tables locked explicitly we have to implicitly lock InnoDB tables (through view) to trigger the table-level lock conflict between TL_WRITE and TL_WRITE_ALLOW_WRITE. mysql-test/t/mdl_sync.test: Added basic test coverage for type-of-operation-aware metadata locks. Also covered with tests some use cases involving HANDLER statements in which a deadlock could arise. Adjusted existing tests to take type-of-operation-aware MDL into account. mysql-test/t/multi_update.test: Update to a new SHOW PROCESSLIST state name. mysql-test/t/truncate_coverage.test: Adjusted test case after making LOCK TABLES WRITE to wait until transactions that use the table to be locked are completed. Updated to the changed name of DEBUG_SYNC point. sql/handler.cc: Global read lock functionality has been moved into a class. sql/lock.cc: Global read lock functionality has been moved into a class. Updated code to use the new MDL API. sql/mdl.cc: Introduced new type-of-operation aware metadata locks. To do this: - Changed MDL_lock to use one list for waiting requests and one list for granted requests. For each list, added a bitmap that holds information what lock types a list contains. Added a helper class MDL_lock::List to manipulate with granted and waited lists while keeping the bitmaps in sync with list contents. - Changed lock-compatibility functions to use bitmaps that define compatibility. - Introduced a graph based deadlock detector inspired by waiting_threads.c from Maria implementation. - Now that we have a deadlock detector, and no longer have a global lock to protect individual lock objects, but rather use an rw lock per object, removed redundant code for upgrade, and the global read lock. Changed the MDL API to no longer require the caller to acquire the global intention exclusive lock by means of a separate method. Removed a few more methods that became redundant. - Removed deadlock detection heuristic, it has been made obsolete by the deadlock detector. - With operation-type-aware metadata locks, MDL subsystem has become aware of potential conflicts between DDL and open transactions. This made it possible to remove calls to mysql_abort_transactions_with_shared_lock() from acquisition paths for exclusive lock and lock upgrade. Now we can simply wait for these transactions to complete without fear of deadlock. Function mysql_lock_abort() has also become unnecessary for all conflicting cases except when a DDL conflicts with a connection that has an open HANDLER. sql/mdl.h: Introduced new type-of-operation aware metadata locks. Introduced a graph based deadlock detector and supporting methods. Added comments. God rid of redundant API calls. Renamed m_lt_or_ha_sentinel to m_trans_sentinel, since now it guards the global read lock as well as LOCK TABLES and HANDLER locks. sql/mysql_priv.h: Moved the global read lock functionality into a class. Added MYSQL_OPEN_FORCE_SHARED_MDL flag which forces open_tables() to take MDL_SHARED on tables instead of metadata locks specified in the parser. We use this to allow PREPARE run concurrently in presence of LOCK TABLES ... WRITE. Added signature for find_table_for_mdl_ugprade(). sql/set_var.cc: Global read lock functionality has been moved into a class. sql/sp_head.cc: When creating TABLE_LIST elements for prelocking or system tables set the type of request for metadata lock according to the operation that will be performed on the table. sql/sql_base.cc: - Updated code to use the new MDL API. - In order to avoid locks starvation we take upgradable locks all at once. As result implicitly locked tables no longer get an upgradable lock. Consequently DDL and FLUSH TABLES for such tables is prohibited. find_write_locked_table() was replaced by find_table_for_mdl_upgrade() function. open_table() was adjusted to return TABLE instance with upgradable ticket when necessary. - We no longer wait for all locks on OT_WAIT back off action -- only on the lock that caused the wait conflict. Moreover, now we distinguish cases when we have to wait due to conflict in MDL and old version of table in TDC. - Upate mysql_notify_threads_having_share_locks() to only abort thr_lock.c waits of threads that have open HANDLERs, since lock conflicts with only these threads now can lead to deadlocks not detectable by the MDL deadlock detector. - Remove mysql_abort_transactions_with_shared_locks() which is no longer needed. sql/sql_class.cc: Global read lock functionality has been moved into a class. Re-arranged code in THD::cleanup() to simplify assert. sql/sql_class.h: Introduced class to incapsulate global read lock functionality. Now sentinel in MDL subsystem guards the global read lock as well as LOCK TABLES and HANDLER locks. Adjusted code accordingly. sql/sql_db.cc: Global read lock functionality has been moved into a class. sql/sql_delete.cc: We no longer acquire upgradable metadata locks on tables which are locked by LOCK TABLES implicitly. As result TRUNCATE TABLE is no longer allowed for such tables. Updated code to use the new MDL API. sql/sql_handler.cc: Inform MDL_context about presence of open HANDLERs. Since HANLDERs break MDL protocol by acquiring table-level lock while holding only S metadata lock on a table MDL subsystem should take special care about such contexts (Now this is the only case when mysql_lock_abort() is used). sql/sql_parse.cc: Global read lock functionality has been moved into a class. Do not take upgradable metadata locks when opening tables for CREATE TABLE SELECT as it is not necessary and limits concurrency. When initializing TABLE_LIST objects before adding them to the table list set the type of request for metadata lock according to the operation that will be performed on the table. We no longer acquire upgradable metadata locks on tables which are locked by LOCK TABLES implicitly. As result FLUSH TABLES is no longer allowed for such tables. sql/sql_prepare.cc: Use MYSQL_OPEN_FORCE_SHARED_MDL flag when opening tables during PREPARE. This allows PREPARE to run concurrently in presence of LOCK TABLES ... WRITE. sql/sql_rename.cc: Global read lock functionality has been moved into a class. sql/sql_show.cc: Updated code to use the new MDL API. sql/sql_table.cc: Global read lock functionality has been moved into a class. We no longer acquire upgradable metadata locks on tables which are locked by LOCK TABLES implicitly. As result DROP TABLE is no longer allowed for such tables. Updated code to use the new MDL API. sql/sql_trigger.cc: Global read lock functionality has been moved into a class. We no longer acquire upgradable metadata locks on tables which are locked by LOCK TABLES implicitly. As result CREATE/DROP TRIGGER is no longer allowed for such tables. Updated code to use the new MDL API. sql/sql_view.cc: Global read lock functionality has been moved into a class. Fixed results of wrong merge that led to misuse of GLR API. CREATE VIEW statement is not a commit statement. sql/table.cc: When resetting TABLE_LIST objects for PS or SP re-execution set the type of request for metadata lock according to the operation that will be performed on the table. Do the same in auxiliary function initializing metadata lock requests in a table list. sql/table.h: When initializing TABLE_LIST objects set the type of request for metadata lock according to the operation that will be performed on the table. sql/transaction.cc: Global read lock functionality has been moved into a class. --- mysql-test/include/handler.inc | 180 +- mysql-test/r/debug_sync.result | 2 +- mysql-test/r/handler_innodb.result | 154 +- mysql-test/r/handler_myisam.result | 154 +- mysql-test/r/innodb-lock.result | 41 +- mysql-test/r/innodb_mysql_lock.result | 32 + mysql-test/r/lock.result | 79 + mysql-test/r/lock_multi.result | 67 +- mysql-test/r/lock_sync.result | 7 +- mysql-test/r/mdl_sync.result | 1854 ++++++++++++- mysql-test/r/sp-threads.result | 2 +- mysql-test/r/truncate_coverage.result | 21 +- .../funcs_1/datadict/processlist_val.inc | 2 +- .../funcs_1/r/processlist_val_no_prot.result | 4 +- mysql-test/suite/rpl/t/rpl_sp.test | 2 +- mysql-test/t/debug_sync.test | 2 +- mysql-test/t/innodb-lock.test | 49 +- mysql-test/t/innodb_mysql_lock.test | 54 + mysql-test/t/lock.test | 76 + mysql-test/t/lock_multi.test | 113 +- mysql-test/t/lock_sync.test | 9 +- mysql-test/t/mdl_sync.test | 2472 ++++++++++++++++- mysql-test/t/multi_update.test | 11 +- mysql-test/t/truncate_coverage.test | 39 +- sql/handler.cc | 4 +- sql/lock.cc | 86 +- sql/mdl.cc | 1958 ++++++------- sql/mdl.h | 390 ++- sql/mysql_priv.h | 15 +- sql/set_var.cc | 10 +- sql/sp_head.cc | 6 +- sql/sql_base.cc | 385 +-- sql/sql_class.cc | 20 +- sql/sql_class.h | 55 +- sql/sql_db.cc | 12 +- sql/sql_delete.cc | 24 +- sql/sql_handler.cc | 19 +- sql/sql_parse.cc | 41 +- sql/sql_prepare.cc | 27 +- sql/sql_rename.cc | 4 +- sql/sql_show.cc | 6 +- sql/sql_table.cc | 53 +- sql/sql_trigger.cc | 18 +- sql/sql_view.cc | 4 +- sql/table.cc | 12 +- sql/table.h | 4 +- sql/transaction.cc | 4 +- 47 files changed, 6740 insertions(+), 1843 deletions(-) diff --git a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc index 8342a072ef9..e16c53bc1ee 100644 --- a/mysql-test/include/handler.inc +++ b/mysql-test/include/handler.inc @@ -732,7 +732,31 @@ connection default; --disable_warnings drop table if exists t1; --enable_warnings -create table t1 (a int, key a (a)); +--echo # First test case which is supposed trigger the execution +--echo # path on which problem was discovered. +create table t1 (a int); +insert into t1 values (1); +handler t1 open; +connection con1; +lock table t1 write; +send alter table t1 engine=memory; +connection con2; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 engine=memory"; +--source include/wait_condition.inc +connection default; +--error ER_ILLEGAL_HA +handler t1 read a next; +handler t1 close; +connection con1; +--reap +unlock tables; +drop table t1; +--echo # Now test case which was reported originally but which no longer +--echo # triggers execution path which has caused the problem. +connection default; +create table t1 (a int, key(a)); insert into t1 values (1); handler t1 open; connection con1; @@ -743,10 +767,13 @@ let $wait_condition= where state = "Waiting for table" and info = "alter table t1 engine=memory"; --source include/wait_condition.inc connection default; +--echo # Since S metadata lock was already acquired at HANDLER OPEN time +--echo # and TL_READ lock requested by HANDLER READ is compatible with +--echo # ALTER's TL_WRITE_ALLOW_READ the below statement should succeed +--echo # without waiting. The old version of table should be used in it. handler t1 read a next; handler t1 close; connection con1; ---reap drop table t1; disconnect con1; --source include/wait_until_disconnected.inc @@ -1228,15 +1255,27 @@ create table t2 like t1; handler t1 open; --echo # --> connection con1 connection con1; -lock table t2 read; +lock table t1 write, t2 write; --echo # --> connection default connection default; +send drop table t2; +--echo # --> connection con2 +connection con2; +--echo # Waiting for 'drop table t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--source include/wait_condition.inc +--echo # --> connection con1 +connection con1; --error ER_LOCK_DEADLOCK -drop table t2; ---error ER_LOCK_DEADLOCK -rename table t2 to t3; +drop table t1; +unlock tables; +--echo # --> connection default +connection default; +reap; + --echo # Demonstrate that there is no deadlock with FLUSH TABLE, --echo # even though it is waiting for the other table to go away +create table t2 like t1; --echo # Sending: --send flush table t2 --echo # --> connection con2 @@ -1256,6 +1295,7 @@ drop table t2; --echo # lead to deadlocks --echo # create table t1 (a int, key a(a)); +insert into t1 values (1), (2); --echo # --> connection default connection default; @@ -1265,7 +1305,31 @@ handler t1 open; --echo # --> connection con1 connection con1; -lock tables t1 write; +--echo # Sending: +--send lock tables t1 write + +--echo # --> connection con2 +connection con2; +--echo # Check that 'lock tables t1 write' waits until transaction which +--echo # has read from the table commits. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "lock tables t1 write"; +--source include/wait_condition.inc + +--echo # --> connection default +connection default; +--echo # The below 'handler t1 read ...' should not be blocked as +--echo # 'lock tables t1 write' has not succeeded yet. +handler t1 read a next; + +--echo # Unblock 'lock tables t1 write'. +commit; + +--echo # --> connection con1 +connection con1; +--echo # Reap 'lock tables t1 write'. +--reap --echo # --> connection default connection default; @@ -1279,29 +1343,18 @@ let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Table lock" and info = "handler t1 read a next"; --source include/wait_condition.inc ---echo # Sending: ---send drop table t1 ---echo # --> connection con2 -connection con2; ---echo # Waiting for 'drop table t1' to get blocked... -let $wait_condition= - select count(*) = 1 from information_schema.processlist - where state = "Waiting for table" and info = "drop table t1"; ---source include/wait_condition.inc +--echo # The below 'drop table t1' should be able to proceed without +--echo # waiting as it will force HANDLER to be closed. +drop table t1; +unlock tables; --echo # --> connection default connection default; --echo # Reaping 'handler t1 read a next'... ---error ER_LOCK_DEADLOCK +--error ER_NO_SUCH_TABLE --reap handler t1 close; -commit; - ---echo # --> connection con1 -connection con1; ---echo # Reaping 'drop table t1'... ---reap --echo # --> connection con1 connection con1; @@ -1357,3 +1410,84 @@ rename table t4 to t5, t3 to t4, t5 to t3; handler t1 read first; handler t2 read first; drop table t1, t2, t3, t4; + +--echo # +--echo # A test for FLUSH TABLES WITH READ LOCK and HANDLER statements. +--echo # +set autocommit=0; +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +create table t2 like t1; +insert into t2 (a, b) select a, b from t1; +create table t3 like t1; +insert into t3 (a, b) select a, b from t1; +commit; +flush tables with read lock; +handler t1 open; +lock table t1 read; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t1 read next; +--echo # This implicitly leaves LOCK TABLES but doesn't drop the GLR +--error ER_NO_SUCH_TABLE +lock table not_exists_write read; +--echo # We still have the read lock. +--error ER_CANT_UPDATE_WITH_READLOCK +drop table t1; +handler t1 open; +select a from t2; +handler t1 read next; +flush tables with read lock; +handler t2 open; +flush tables with read lock; +handler t1 read next; +select a from t3; +handler t2 read next; +handler t1 close; +rollback; +handler t2 close; +--error ER_CANT_UPDATE_WITH_READLOCK +drop table t1; +commit; +flush tables; +--error ER_CANT_UPDATE_WITH_READLOCK +drop table t1; +unlock tables; +drop table t1; +set autocommit=default; +drop table t2, t3; + +--echo # +--echo # HANDLER statement and operation-type aware metadata locks. +--echo # Check that when we clone a ticket for HANDLER we downrade +--echo # the lock. +--echo # +--echo # Establish an auxiliary connection con1. +connect (con1,localhost,root,,); +--echo # -> connection default +connection default; +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +begin; +insert into t1 (a, b) values (6, 6); +handler t1 open; +handler t1 read a last; +insert into t1 (a, b) values (7, 7); +handler t1 read a last; +commit; +--echo # -> connection con1 +connection con1; +--echo # Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE. +lock table t1 write; +unlock tables; +--echo # -> connection default +connection default; +handler t1 read a prev; +handler t1 close; +--echo # Cleanup. +drop table t1; +--echo # -> connection con1 +connection con1; +disconnect con1; +--source include/wait_until_disconnected.inc +--echo # -> connection default +connection default; diff --git a/mysql-test/r/debug_sync.result b/mysql-test/r/debug_sync.result index 8b46334204c..25fdf523200 100644 --- a/mysql-test/r/debug_sync.result +++ b/mysql-test/r/debug_sync.result @@ -263,7 +263,7 @@ DROP TABLE t1; SET DEBUG_SYNC= 'RESET'; DROP TABLE IF EXISTS t1; CREATE TABLE t1 (c1 INT); -LOCK TABLE t1 WRITE; +LOCK TABLE t1 READ; connection con1 SET DEBUG_SYNC= 'wait_for_lock SIGNAL locked EXECUTE 2'; INSERT INTO t1 VALUES (1); diff --git a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result index a3e3e325e7d..6ed7b572403 100644 --- a/mysql-test/r/handler_innodb.result +++ b/mysql-test/r/handler_innodb.result @@ -745,10 +745,28 @@ drop table t1; handler t1 read a next; ERROR 42S02: Unknown table 't1' in HANDLER drop table if exists t1; -create table t1 (a int, key a (a)); +# First test case which is supposed trigger the execution +# path on which problem was discovered. +create table t1 (a int); +insert into t1 values (1); +handler t1 open; +lock table t1 write; +alter table t1 engine=memory; +handler t1 read a next; +ERROR HY000: Table storage engine for 't1' doesn't have this option +handler t1 close; +unlock tables; +drop table t1; +# Now test case which was reported originally but which no longer +# triggers execution path which has caused the problem. +create table t1 (a int, key(a)); insert into t1 values (1); handler t1 open; alter table t1 engine=memory; +# Since S metadata lock was already acquired at HANDLER OPEN time +# and TL_READ lock requested by HANDLER READ is compatible with +# ALTER's TL_WRITE_ALLOW_READ the below statement should succeed +# without waiting. The old version of table should be used in it. handler t1 read a next; a 1 @@ -1217,14 +1235,19 @@ create table t1 (a int, key a(a)); create table t2 like t1; handler t1 open; # --> connection con1 -lock table t2 read; +lock table t1 write, t2 write; # --> connection default drop table t2; +# --> connection con2 +# Waiting for 'drop table t2' to get blocked... +# --> connection con1 +drop table t1; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -rename table t2 to t3; -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default # Demonstrate that there is no deadlock with FLUSH TABLE, # even though it is waiting for the other table to go away +create table t2 like t1; # Sending: flush table t2; # --> connection con2 @@ -1239,29 +1262,43 @@ drop table t2; # lead to deadlocks # create table t1 (a int, key a(a)); +insert into t1 values (1), (2); # --> connection default begin; select * from t1; a +1 +2 handler t1 open; # --> connection con1 +# Sending: lock tables t1 write; +# --> connection con2 +# Check that 'lock tables t1 write' waits until transaction which +# has read from the table commits. +# --> connection default +# The below 'handler t1 read ...' should not be blocked as +# 'lock tables t1 write' has not succeeded yet. +handler t1 read a next; +a +1 +# Unblock 'lock tables t1 write'. +commit; +# --> connection con1 +# Reap 'lock tables t1 write'. # --> connection default # Sending: handler t1 read a next; # --> connection con1 # Waiting for 'handler t1 read a next' to get blocked... -# Sending: +# The below 'drop table t1' should be able to proceed without +# waiting as it will force HANDLER to be closed. drop table t1; -# --> connection con2 -# Waiting for 'drop table t1' to get blocked... +unlock tables; # --> connection default # Reaping 'handler t1 read a next'... -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; -commit; -# --> connection con1 -# Reaping 'drop table t1'... # --> connection con1 # --> connection con2 # --> connection con3 @@ -1324,3 +1361,98 @@ a b handler t2 read first; a b drop table t1, t2, t3, t4; +# +# A test for FLUSH TABLES WITH READ LOCK and HANDLER statements. +# +set autocommit=0; +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +create table t2 like t1; +insert into t2 (a, b) select a, b from t1; +create table t3 like t1; +insert into t3 (a, b) select a, b from t1; +commit; +flush tables with read lock; +handler t1 open; +lock table t1 read; +handler t1 read next; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# This implicitly leaves LOCK TABLES but doesn't drop the GLR +lock table not_exists_write read; +ERROR 42S02: Table 'test.not_exists_write' doesn't exist +# We still have the read lock. +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +handler t1 open; +select a from t2; +a +1 +2 +3 +4 +5 +handler t1 read next; +a b +1 1 +flush tables with read lock; +handler t2 open; +flush tables with read lock; +handler t1 read next; +a b +1 1 +select a from t3; +a +1 +2 +3 +4 +5 +handler t2 read next; +a b +1 1 +handler t1 close; +rollback; +handler t2 close; +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +commit; +flush tables; +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +unlock tables; +drop table t1; +set autocommit=default; +drop table t2, t3; +# +# HANDLER statement and operation-type aware metadata locks. +# Check that when we clone a ticket for HANDLER we downrade +# the lock. +# +# Establish an auxiliary connection con1. +# -> connection default +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +begin; +insert into t1 (a, b) values (6, 6); +handler t1 open; +handler t1 read a last; +a b +6 6 +insert into t1 (a, b) values (7, 7); +handler t1 read a last; +a b +7 7 +commit; +# -> connection con1 +# Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE. +lock table t1 write; +unlock tables; +# -> connection default +handler t1 read a prev; +a b +6 6 +handler t1 close; +# Cleanup. +drop table t1; +# -> connection con1 +# -> connection default diff --git a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result index f5c5bfebd15..0dd039eeb23 100644 --- a/mysql-test/r/handler_myisam.result +++ b/mysql-test/r/handler_myisam.result @@ -743,10 +743,28 @@ drop table t1; handler t1 read a next; ERROR 42S02: Unknown table 't1' in HANDLER drop table if exists t1; -create table t1 (a int, key a (a)); +# First test case which is supposed trigger the execution +# path on which problem was discovered. +create table t1 (a int); +insert into t1 values (1); +handler t1 open; +lock table t1 write; +alter table t1 engine=memory; +handler t1 read a next; +ERROR HY000: Table storage engine for 't1' doesn't have this option +handler t1 close; +unlock tables; +drop table t1; +# Now test case which was reported originally but which no longer +# triggers execution path which has caused the problem. +create table t1 (a int, key(a)); insert into t1 values (1); handler t1 open; alter table t1 engine=memory; +# Since S metadata lock was already acquired at HANDLER OPEN time +# and TL_READ lock requested by HANDLER READ is compatible with +# ALTER's TL_WRITE_ALLOW_READ the below statement should succeed +# without waiting. The old version of table should be used in it. handler t1 read a next; a 1 @@ -1214,14 +1232,19 @@ create table t1 (a int, key a(a)); create table t2 like t1; handler t1 open; # --> connection con1 -lock table t2 read; +lock table t1 write, t2 write; # --> connection default drop table t2; +# --> connection con2 +# Waiting for 'drop table t2' to get blocked... +# --> connection con1 +drop table t1; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -rename table t2 to t3; -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default # Demonstrate that there is no deadlock with FLUSH TABLE, # even though it is waiting for the other table to go away +create table t2 like t1; # Sending: flush table t2; # --> connection con2 @@ -1236,29 +1259,43 @@ drop table t2; # lead to deadlocks # create table t1 (a int, key a(a)); +insert into t1 values (1), (2); # --> connection default begin; select * from t1; a +1 +2 handler t1 open; # --> connection con1 +# Sending: lock tables t1 write; +# --> connection con2 +# Check that 'lock tables t1 write' waits until transaction which +# has read from the table commits. +# --> connection default +# The below 'handler t1 read ...' should not be blocked as +# 'lock tables t1 write' has not succeeded yet. +handler t1 read a next; +a +1 +# Unblock 'lock tables t1 write'. +commit; +# --> connection con1 +# Reap 'lock tables t1 write'. # --> connection default # Sending: handler t1 read a next; # --> connection con1 # Waiting for 'handler t1 read a next' to get blocked... -# Sending: +# The below 'drop table t1' should be able to proceed without +# waiting as it will force HANDLER to be closed. drop table t1; -# --> connection con2 -# Waiting for 'drop table t1' to get blocked... +unlock tables; # --> connection default # Reaping 'handler t1 read a next'... -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; -commit; -# --> connection con1 -# Reaping 'drop table t1'... # --> connection con1 # --> connection con2 # --> connection con3 @@ -1322,6 +1359,101 @@ handler t2 read first; a b drop table t1, t2, t3, t4; # +# A test for FLUSH TABLES WITH READ LOCK and HANDLER statements. +# +set autocommit=0; +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +create table t2 like t1; +insert into t2 (a, b) select a, b from t1; +create table t3 like t1; +insert into t3 (a, b) select a, b from t1; +commit; +flush tables with read lock; +handler t1 open; +lock table t1 read; +handler t1 read next; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# This implicitly leaves LOCK TABLES but doesn't drop the GLR +lock table not_exists_write read; +ERROR 42S02: Table 'test.not_exists_write' doesn't exist +# We still have the read lock. +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +handler t1 open; +select a from t2; +a +1 +2 +3 +4 +5 +handler t1 read next; +a b +1 1 +flush tables with read lock; +handler t2 open; +flush tables with read lock; +handler t1 read next; +a b +1 1 +select a from t3; +a +1 +2 +3 +4 +5 +handler t2 read next; +a b +1 1 +handler t1 close; +rollback; +handler t2 close; +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +commit; +flush tables; +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +unlock tables; +drop table t1; +set autocommit=default; +drop table t2, t3; +# +# HANDLER statement and operation-type aware metadata locks. +# Check that when we clone a ticket for HANDLER we downrade +# the lock. +# +# Establish an auxiliary connection con1. +# -> connection default +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +begin; +insert into t1 (a, b) values (6, 6); +handler t1 open; +handler t1 read a last; +a b +6 6 +insert into t1 (a, b) values (7, 7); +handler t1 read a last; +a b +7 7 +commit; +# -> connection con1 +# Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE. +lock table t1 write; +unlock tables; +# -> connection default +handler t1 read a prev; +a b +6 6 +handler t1 close; +# Cleanup. +drop table t1; +# -> connection con1 +# -> connection default +# # BUG #46456: HANDLER OPEN + TRUNCATE + DROP (temporary) TABLE, crash # CREATE TABLE t1 AS SELECT 1 AS f1; diff --git a/mysql-test/r/innodb-lock.result b/mysql-test/r/innodb-lock.result index 4ace4065c34..ab7e9aa7b25 100644 --- a/mysql-test/r/innodb-lock.result +++ b/mysql-test/r/innodb-lock.result @@ -25,6 +25,12 @@ id x 0 2 commit; drop table t1; +# +# Old lock method (where LOCK TABLE was ignored by InnoDB) no longer +# works due to fix for bugs #46272 "MySQL 5.4.4, new MDL: unnecessary +# deadlock" and bug #37346 "innodb does not detect deadlock between +# update and alter table". +# set @@innodb_table_locks=0; create table t1 (id integer primary key, x integer) engine=INNODB; insert into t1 values(0, 0),(1,1),(2,2); @@ -32,26 +38,27 @@ commit; SELECT * from t1 where id = 0 FOR UPDATE; id x 0 0 +# Connection 'con2'. set autocommit=0; set @@innodb_table_locks=0; -lock table t1 write; -update t1 set x=10 where id = 2; -SELECT * from t1 where id = 2; -id x -2 2 -UPDATE t1 set x=3 where id = 2; -commit; -SELECT * from t1; -id x -0 0 -1 1 -2 3 -commit; -unlock tables; -commit; +# The following statement should block because SQL-level lock +# is taken on t1 which will wait until concurrent transaction +# is commited. +# Sending: +lock table t1 write;; +# Connection 'con1'. +# Wait until LOCK TABLE is blocked on SQL-level lock. +# We should be able to do UPDATEs and SELECTs within transaction. +update t1 set x=1 where id = 0; select * from t1; id x -0 0 +0 1 1 1 -2 10 +2 2 +# Unblock LOCK TABLE. +commit; +# Connection 'con2'. +# Reap LOCK TABLE. +unlock tables; +# Connection 'con1'. drop table t1; diff --git a/mysql-test/r/innodb_mysql_lock.result b/mysql-test/r/innodb_mysql_lock.result index 374f67358eb..375ae8aeb12 100644 --- a/mysql-test/r/innodb_mysql_lock.result +++ b/mysql-test/r/innodb_mysql_lock.result @@ -26,6 +26,38 @@ commit; set @@autocommit=1; set @@autocommit=1; # +# Test for bug #37346 "innodb does not detect deadlock between update +# and alter table". +# +drop table if exists t1; +create table t1 (c1 int primary key, c2 int, c3 int) engine=InnoDB; +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); +begin; +# Run statement which acquires X-lock on one of table's rows. +update t1 set c3=c3+1 where c2=3; +# +# Switching to connection 'con37346'. +# The below ALTER TABLE statement should wait till transaction +# in connection 'default' is complete and then succeed. +# It should not deadlock or fail with ER_LOCK_DEADLOCK error. +# Sending: +alter table t1 add column c4 int;; +# +# Switching to connection 'default'. +# Wait until the above ALTER TABLE gets blocked because this +# connection holds SW metadata lock on table to be altered. +# The below statement should succeed. It should not +# deadlock or end with ER_LOCK_DEADLOCK error. +update t1 set c3=c3+1 where c2=4; +# Unblock ALTER TABLE by committing transaction. +commit; +# +# Switching to connection 'con37346'. +# Reaping ALTER TABLE. +# +# Switching to connection 'default'. +drop table t1; +# # Bug #42147 Concurrent DML and LOCK TABLE ... READ for InnoDB # table cause warnings in errlog # diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index 5dbfa66b43e..c1e1ccb5bce 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -151,6 +151,12 @@ select * from t2; a select * from t3; ERROR HY000: Table 't3' was not locked with LOCK TABLES +Dropping of implicitly locked table is disallowed. +drop table t1; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +unlock tables; +Now let us also lock table explicitly and drop it. +lock tables t1 write, v_bug5719 write; drop table t1; sic: left LOCK TABLES mode @@ -282,6 +288,79 @@ insert into t1 values (1); # Ensure that metadata locks held by the transaction are released. drop table t1; # +# Coverage for situations when we try to execute DDL on tables +# which are locked by LOCK TABLES only implicitly. +# +drop tables if exists t1, t2; +drop view if exists v1; +drop function if exists f1; +create table t1 (i int); +create table t2 (j int); +# +# Try to perform DDL on table which is locked through view. +create view v1 as select * from t2; +lock tables t1 write, v1 write; +flush table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +drop table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +alter table t2 add column k int; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +create trigger t2_bi before insert on t2 for each row set @a:=1; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +# Repair produces error as part of its result set. +repair table t2; +Table Op Msg_type Msg_text +test.t2 repair Error Table 't2' was locked with a READ lock and can't be updated +test.t2 repair status Operation failed +unlock tables; +drop view v1; +# +# Now, try DDL on table which is locked through routine. +create function f1 () returns int +begin +insert into t2 values (1); +return 0; +end| +create view v1 as select f1() from t1; +lock tables v1 read; +flush table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +drop table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +alter table t2 add column k int; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +create trigger t2_bi before insert on t2 for each row set @a:=1; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +# Repair produces error as part of its result set. +repair table t2; +Table Op Msg_type Msg_text +test.t2 repair Error Table 't2' was locked with a READ lock and can't be updated +test.t2 repair status Operation failed +unlock tables; +drop view v1; +drop function f1; +# +# Finally, try DDL on table which is locked thanks to trigger. +create trigger t1_ai after insert on t1 for each row insert into t2 values (1); +lock tables t1 write; +flush table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +drop table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +alter table t2 add column k int; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +create trigger t2_bi before insert on t2 for each row set @a:=1; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +# Repair produces error as part of its result set. +repair table t2; +Table Op Msg_type Msg_text +test.t2 repair Error Table 't2' was locked with a READ lock and can't be updated +test.t2 repair status Operation failed +unlock tables; +drop trigger t1_ai; +drop tables t1, t2; +# # Bug#45035 " Altering table under LOCK TABLES results in # "Error 1213 Deadlock found..." # diff --git a/mysql-test/r/lock_multi.result b/mysql-test/r/lock_multi.result index 5d12e0efd64..4b08c175ee2 100644 --- a/mysql-test/r/lock_multi.result +++ b/mysql-test/r/lock_multi.result @@ -1,21 +1,39 @@ drop table if exists t1,t2; create table t1(n int); insert into t1 values (1); -lock tables t1 write; +select get_lock("mysqltest_lock", 100); +get_lock("mysqltest_lock", 100) +1 +update t1 set n = 2 and get_lock('mysqltest_lock', 100); update low_priority t1 set n = 4; select n from t1; -unlock tables; +select release_lock("mysqltest_lock"); +release_lock("mysqltest_lock") +1 +select release_lock("mysqltest_lock"); +release_lock("mysqltest_lock") +1 n 4 drop table t1; create table t1(n int); insert into t1 values (1); -lock tables t1 read; +select get_lock("mysqltest_lock", 100); +get_lock("mysqltest_lock", 100) +1 +select n from t1 where get_lock('mysqltest_lock', 100); update low_priority t1 set n = 4; select n from t1; n 1 -unlock tables; +select release_lock("mysqltest_lock"); +release_lock("mysqltest_lock") +1 +n +1 +select release_lock("mysqltest_lock"); +release_lock("mysqltest_lock") +1 drop table t1; create table t1 (a int, b int); create table t2 (c int, d int); @@ -35,6 +53,7 @@ create table t2 (a int); lock table t1 write, t2 write; insert t1 select * from t2; drop table t2; +unlock tables; ERROR 42S02: Table 'test.t2' doesn't exist drop table t1; create table t1 (a int); @@ -42,6 +61,7 @@ create table t2 (a int); lock table t1 write, t2 write, t1 as t1_2 write, t2 as t2_2 write; insert t1 select * from t2; drop table t2; +unlock tables; ERROR 42S02: Table 'test.t2' doesn't exist drop table t1; End of 4.1 tests @@ -221,6 +241,36 @@ connection: default flush tables; drop table t1; # +# Test for bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock". +# +drop table if exists t1; +create table t1 (c1 int primary key, c2 int, c3 int); +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); +begin; +update t1 set c3=c3+1 where c2=3; +# +# Switching to connection 'con46272'. +# The below ALTER TABLE statement should wait till transaction +# in connection 'default' is complete and then succeed. +# It should not deadlock or fail with ER_LOCK_DEADLOCK error. +# Sending: +alter table t1 add column c4 int;; +# +# Switching to connection 'default'. +# Wait until the above ALTER TABLE gets blocked because this +# connection holds SW metadata lock on table to be altered. +# The below statement should succeed. It should not +# deadlock or end with ER_LOCK_DEADLOCK error. +update t1 set c3=c3+1 where c2=4; +# Unblock ALTER TABLE by committing transaction. +commit; +# +# Switching to connection 'con46272'. +# Reaping ALTER TABLE. +# +# Switching to connection 'default'. +drop table t1; +# # Bug#47249 assert in MDL_global_lock::is_lock_type_compatible # DROP TABLE IF EXISTS t1; @@ -228,14 +278,15 @@ DROP VIEW IF EXISTS v1; # # Test 1: LOCK TABLES v1 WRITE, t1 READ; # +# Thanks to the fact that we no longer allow DDL on tables +# which are locked for write implicitly, the exact scenario +# in which assert was failing is no longer repeatable. CREATE TABLE t1 ( f1 integer ); CREATE VIEW v1 AS SELECT f1 FROM t1 ; -# Connection 2 LOCK TABLES v1 WRITE, t1 READ; FLUSH TABLE t1; -# Connection 1 -LOCK TABLES t1 WRITE; -FLUSH TABLE t1; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +UNLOCK TABLES; DROP TABLE t1; DROP VIEW v1; # diff --git a/mysql-test/r/lock_sync.result b/mysql-test/r/lock_sync.result index fc4e8c850f6..0b57b38f5ec 100644 --- a/mysql-test/r/lock_sync.result +++ b/mysql-test/r/lock_sync.result @@ -6,6 +6,7 @@ # statements which tried to acquire stronger write lock (TL_WRITE, # TL_WRITE_ALLOW_READ) on this table might have led to deadlock. drop table if exists t1; +drop view if exists v1; # Create auxiliary connections used through the test. # Reset DEBUG_SYNC facility before using it. set debug_sync= 'RESET'; @@ -14,6 +15,9 @@ set debug_sync= 'RESET'; set @old_general_log = @@global.general_log; set @@global.general_log= OFF; create table t1 (i int) engine=InnoDB; +# We have to use view in order to make LOCK TABLES avoid +# acquiring SNRW metadata lock on table. +create view v1 as select * from t1; insert into t1 values (1); # Prepare user lock which will be used for resuming execution of # the first statement after it acquires TL_WRITE_ALLOW_WRITE lock. @@ -36,7 +40,7 @@ select count(*) > 0 from t1 as a, t1 as b for update;; # acquiring lock for the the first instance of 't1'. set debug_sync= 'now WAIT_FOR parked'; # Send LOCK TABLE statement which will try to get TL_WRITE lock on 't1': -lock table t1 write;; +lock table v1 write;; # Switch to connection 'default'. # Wait until this LOCK TABLES statement starts waiting for table lock. # Allow SELECT ... FOR UPDATE to resume. @@ -63,4 +67,5 @@ unlock tables; # Do clean-up. set debug_sync= 'RESET'; set @@global.general_log= @old_general_log; +drop view v1; drop table t1; diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index 8c4d7272e29..8d8672377f0 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -10,7 +10,7 @@ alter table t1 rename t3; connection: default set debug_sync= 'now WAIT_FOR parked'; connection: con2 -set debug_sync='mdl_acquire_exclusive_locks_wait SIGNAL go'; +set debug_sync='mdl_acquire_lock_wait SIGNAL go'; drop table t1,t2; connection: con1 connection: default @@ -20,6 +20,1740 @@ ERROR 42S02: Unknown table 't1' drop table t3; SET DEBUG_SYNC= 'RESET'; # +# Basic test coverage for type-of-operation aware metadata locks. +# +drop table if exists t1, t2, t3; +set debug_sync= 'RESET'; +create table t1 (c1 int); +# +# A) First let us check compatibility rules between differend kinds of +# type-of-operation aware metadata locks. +# Of course, these rules are already covered by the tests scattered +# across the test suite. But it still makes sense to have one place +# which covers all of them. +# +# 1) Acquire S (simple shared) lock on the table (by using HANDLER): +# +handler t1 open; +# +# Switching to connection 'mdl_con1'. +# Check that S, SH, SR and SW locks are compatible with it. +handler t1 open t; +handler t close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +0 +insert into t1 values (1), (1); +# Check that SNW lock is compatible with it. To do this use ALTER TABLE +# which will fail after opening the table and thus obtaining SNW metadata +# lock. +alter table t1 add primary key (c1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# Check that SNRW lock is compatible with S lock. +lock table t1 write; +insert into t1 values (1); +unlock tables; +# Check that X lock is incompatible with S lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of S lock. +# +# Switching to connection 'default'. +# Unblock RENAME TABLE. +handler t1 close; +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE. +# Restore the original state of the things. +rename table t2 to t1; +# +# Switching to connection 'default'. +handler t1 open; +# +# Switching to connection 'mdl_con1'. +# Check that upgrade from SNW to X is blocked by presence of S lock. +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER TABLE is blocked because of S lock. +# +# Switching to connection 'default'. +# Unblock ALTER TABLE. +handler t1 close; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +# +# Switching to connection 'default'. +handler t1 open; +# +# Switching to connection 'mdl_con1'. +# Check that upgrade from SNRW to X is blocked by presence of S lock. +lock table t1 write; +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above upgrade of SNRW to X in ALTER TABLE is blocked +# because of S lock. +# +# Switching to connection 'default'. +# Unblock ALTER TABLE. +handler t1 close; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +unlock tables; +# +# Switching to connection 'default'. +# +# 2) Acquire SH (shared high-priority) lock on the table. +# We have to involve DEBUG_SYNC facility for this as usually +# such kind of locks are short-lived. +# +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that S, SH, SR and SW locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +3 +insert into t1 values (1); +# Check that SNW lock is compatible with it. To do this use ALTER TABLE +# which will fail after opening the table and thus obtaining SNW metadata +# lock. +alter table t1 add primary key (c1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# Check that SNRW lock is compatible with SH lock. +lock table t1 write; +delete from t1 limit 1; +unlock tables; +# Check that X lock is incompatible with SH lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of SH lock. +# Unblock RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping SELECT ... FROM I_S. +table_name table_type auto_increment table_comment +t1 BASE TABLE NULL +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE. +# Restore the original state of the things. +rename table t2 to t1; +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that upgrade from SNW to X is blocked by presence of SH lock. +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER TABLE is blocked because of SH lock. +# Unblock RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping SELECT ... FROM I_S. +table_name table_type auto_increment table_comment +t1 BASE TABLE NULL +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that upgrade from SNRW to X is blocked by presence of S lock. +lock table t1 write; +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above upgrade of SNRW to X in ALTER TABLE is blocked +# because of S lock. +# Unblock RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping SELECT ... FROM I_S. +table_name table_type auto_increment table_comment +t1 BASE TABLE NULL +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +unlock tables; +# +# Switching to connection 'default'. +# +# +# 3) Acquire SR lock on the table. +# +# +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Check that S, SH, SR and SW locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +3 +insert into t1 values (1); +# Check that SNW lock is compatible with it. To do this use ALTER TABLE +# which will fail after opening the table and thus obtaining SNW metadata +# lock. +alter table t1 add primary key (c1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# Check that SNRW lock is not compatible with SR lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Check that the above LOCK TABLES is blocked because of SR lock. +# Unblock LOCK TABLES. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES. +delete from t1 limit 1; +unlock tables; +# +# Switching to connection 'default'. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Check that X lock is incompatible with SR lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of SR lock. +# +# Switching to connection 'default'. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE. +# Restore the original state of the things. +rename table t2 to t1; +# +# Switching to connection 'default'. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Check that upgrade from SNW to X is blocked by presence of SR lock. +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER TABLE is blocked because of SR lock. +# +# Switching to connection 'default'. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +# +# There is no need to check that upgrade from SNRW to X is blocked +# by presence of SR lock because SNRW is incompatible with SR anyway. +# +# +# Switching to connection 'default'. +# +# +# 4) Acquire SW lock on the table. +# +# +begin; +insert into t1 values (1); +# +# Switching to connection 'mdl_con1'. +# Check that S, SH, SR and SW locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +4 +insert into t1 values (1); +# Check that SNW lock is not compatible with SW lock. +# Again we use ALTER TABLE which fails after opening +# the table to avoid upgrade of SNW -> X. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'default'. +# Check that the above ALTER TABLE is blocked because of SW lock. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +begin; +insert into t1 values (1); +# +# Switching to connection 'mdl_con1'. +# Check that SNRW lock is not compatible with SW lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Check that the above LOCK TABLES is blocked because of SW lock. +# Unblock LOCK TABLES. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES. +delete from t1 limit 2; +unlock tables; +# +# Switching to connection 'default'. +begin; +insert into t1 values (1); +# +# Switching to connection 'mdl_con1'. +# Check that X lock is incompatible with SW lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of SW lock. +# +# Switching to connection 'default'. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE. +# Restore the original state of the things. +rename table t2 to t1; +# +# There is no need to check that upgrade from SNW/SNRW to X is +# blocked by presence of SW lock because SNW/SNRW is incompatible +# with SW anyway. +# +# +# Switching to connection 'default'. +# +# +# 5) Acquire SNW lock on the table. We have to use DEBUG_SYNC for +# this, to prevent SNW from being immediately upgraded to X. +# +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that S, SH and SR locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +5 +# Check that SW lock is incompatible with SNW lock. +# Sending: +delete from t1 limit 2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE is blocked because of SNW lock. +# Unblock ALTER and thus DELETE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that SNW lock is incompatible with SNW lock. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER is blocked because of SNW lock. +# Unblock ALTERs. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping first ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping another ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that SNRW lock is incompatible with SNW lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con2'. +# Check that the above LOCK TABLES is blocked because of SNW lock. +# Unblock ALTER and thus LOCK TABLES. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES +insert into t1 values (1); +unlock tables; +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that X lock is incompatible with SNW lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of SNW lock. +# Unblock ALTER and thus RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE +# Revert back to original state of things. +rename table t2 to t1; +# +# There is no need to check that upgrade from SNW/SNRW to X is +# blocked by presence of another SNW lock because SNW/SNRW is +# incompatible with SNW anyway. +# +# Switching to connection 'default'. +# +# +# 6) Acquire SNRW lock on the table. +# +# +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that S and SH locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +# Check that SR lock is incompatible with SNRW lock. +# Sending: +select count(*) from t1;; +# +# Switching to connection 'default'. +# Check that the above SELECT is blocked because of SNRW lock. +# Unblock SELECT. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT. +count(*) +4 +# +# Switching to connection 'default'. +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that SW lock is incompatible with SNRW lock. +# Sending: +delete from t1 limit 1;; +# +# Switching to connection 'default'. +# Check that the above DELETE is blocked because of SNRW lock. +# Unblock DELETE. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# +# Switching to connection 'default'. +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that SNW lock is incompatible with SNRW lock. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'default'. +# Check that the above ALTER is blocked because of UNWR lock. +# Unblock ALTER. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that SNRW lock is incompatible with SNRW lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Check that the above LOCK TABLES is blocked because of SNRW lock. +# Unblock waiting LOCK TABLES. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES +insert into t1 values (1); +unlock tables; +# +# Switching to connection 'default'. +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that X lock is incompatible with SNRW lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'default'. +# Check that the above RENAME is blocked because of SNRW lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE +# Revert back to original state of things. +rename table t2 to t1; +# +# There is no need to check that upgrade from SNW/SNRW to X is +# blocked by presence of another SNRW lock because SNW/SNRW is +# incompatible with SNRW anyway. +# +# Switching to connection 'default'. +# +# +# 7) Now do the same round of tests for X lock. We use additional +# table to get long-lived lock of this type. +# +create table t2 (c1 int); +# +# Switching to connection 'mdl_con2'. +# Take a lock on t2, so RENAME TABLE t1 TO t2 will get blocked +# after acquiring X lock on t1. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that S lock in incompatible with X lock. +# Sending: +handler t1 open;; +# +# Switching to connection 'mdl_con2'. +# Check that the above HANDLER statement is blocked because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping HANDLER. +handler t1 close; +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SH lock in incompatible with X lock. +# Sending: +select column_name from information_schema.columns where table_schema='test' and table_name='t1';; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT ... FROM I_S ... statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT ... FROM I_S. +column_name +c1 +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SR lock in incompatible with X lock. +# Sending: +select count(*) from t1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT. +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SW lock in incompatible with X lock. +# Sending: +delete from t1 limit 1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SNW lock is incompatible with X lock. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SNRW lock is incompatible with X lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con2'. +# Check that the above LOCK TABLE statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLE. +unlock tables; +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that X lock is incompatible with X lock. +# Sending: +rename table t1 to t3;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME. +rename table t3 to t1; +# +# B) Now let us test compatibility in cases when both locks +# are pending. I.e. let us test rules for priorities between +# different types of metadata locks. +# +# +# Switching to connection 'mdl_con2'. +# +# 1) Check compatibility for pending SNW lock. +# +# Acquire SW lock in order to create pending SNW lock later. +begin; +insert into t1 values (1); +# +# Switching to connection 'default'. +# Add pending SNW lock. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +# Check that ALTER TABLE is waiting with pending SNW lock. +# Check that S, SH and SR locks are compatible with pending SNW +handler t1 open t; +handler t close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +4 +# Check that SW is incompatible with pending SNW +# Sending: +delete from t1 limit 1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE is blocked because of pending SNW lock. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping ALTER. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# +# We can't do similar check for SNW, SNRW and X locks because +# they will also be blocked by active SW lock. +# +# +# Switching to connection 'mdl_con2'. +# +# 2) Check compatibility for pending SNRW lock. +# +# Acquire SR lock in order to create pending SNRW lock. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'default'. +# Add pending SNRW lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con1'. +# Check that LOCK TABLE is waiting with pending SNRW lock. +# Check that S and SH locks are compatible with pending SNRW +handler t1 open t; +handler t close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +# Check that SR is incompatible with pending SNRW +# Sending: +select count(*) from t1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT is blocked because of pending SNRW lock. +# Unblock LOCK TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping LOCK TABLE. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT. +count(*) +3 +# Restore pending SNRW lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'default'. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con1'. +# Check that LOCK TABLE is waiting with pending SNRW lock. +# Check that SW is incompatible with pending SNRW +# Sending: +insert into t1 values (1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above INSERT is blocked because of pending SNRW lock. +# Unblock LOCK TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping LOCK TABLE. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping INSERT. +# Restore pending SNRW lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'default'. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con1'. +# Check that LOCK TABLE is waiting with pending SNRW lock. +# Check that SNW is compatible with pending SNRW +# So ALTER TABLE statements are not starved by LOCK TABLEs. +alter table t1 add primary key (c1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con2'. +# Unblock LOCK TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping LOCK TABLE. +unlock tables; +# +# We can't do similar check for SNRW and X locks because +# they will also be blocked by active SR lock. +# +# +# Switching to connection 'mdl_con2'. +# +# 3) Check compatibility for pending X lock. +# +# Acquire SR lock in order to create pending X lock. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SH locks are compatible with pending X +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +# Check that S is incompatible with pending X +# Sending: +handler t1 open;; +# +# Switching to connection 'mdl_con2'. +# Check that the above HANDLER OPEN is blocked because of pending X lock. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping HANDLER t1 OPEN. +handler t1 close; +# Restore pending X lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SR is incompatible with pending X +# Sending: +select count(*) from t1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT is blocked because of pending X lock. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT. +count(*) +4 +# Restore pending X lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SW is incompatible with pending X +# Sending: +delete from t1 limit 1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE is blocked because of pending X lock. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# Restore pending X lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SNW is incompatible with pending X +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER TABLE is blocked because of pending X lock. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# Restore pending X lock. +# +# Switching to connection 'mdl_con2'. +handler t1 open; +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SNRW is incompatible with pending X +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con3'. +# Check that the above LOCK TABLES is blocked because of pending X lock. +# +# Switching to connection 'mdl_con2'. +# Unblock RENAME TABLE. +handler t1 close; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES. +unlock tables; +# +# Switching to connection 'default'. +# +# +# C) Now let us test how type-of-operation locks are handled in +# transactional context. Obviously we are mostly interested +# in conflicting types of locks. +# +# +# 1) Let us check how various locks used within transactional +# context interact with active/pending SNW lock. +# +# We start with case when we are acquiring lock on the table +# which was not used in the transaction before. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create an active SNW lock on t2. +# We have to use DEBUG_SYNC facility as otherwise SNW lock +# will be immediately released (or upgraded to X lock). +insert into t2 values (1), (1); +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t2 add primary key (c1);; +# +# Switching to connection 'default'. +set debug_sync= 'now WAIT_FOR locked'; +# SR lock should be acquired without any waiting. +select count(*) from t2; +count(*) +2 +commit; +# Now let us check that we will wait in case of SW lock. +begin; +select count(*) from t1; +count(*) +3 +# Sending: +insert into t2 values (1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above INSERT is blocked. +# Unblock ALTER TABLE and thus INSERT. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +# Reap INSERT. +commit; +# +# Now let us see what happens when we are acquiring lock on the table +# which is already used in transaction. +# +# *) First, case when transaction which has SR lock on the table also +# locked in SNW mode acquires yet another SR lock and then tries +# to acquire SW lock. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create an active SNW lock on t1. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'default'. +set debug_sync= 'now WAIT_FOR locked'; +# We should still be able to get SR lock without waiting. +select count(*) from t1; +count(*) +3 +# Since the above ALTER TABLE is not upgrading SNW lock to X by waiting +# for SW lock we won't create deadlock. +# So the below INSERT should not end-up with ER_LOCK_DEADLOCK error. +# Sending: +insert into t1 values (1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above INSERT is blocked. +# Unblock ALTER TABLE and thus INSERT. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +# Reap INSERT. +commit; +# +# **) Now test in which transaction that has SW lock on the table +# against which there is pending SNW lock acquires SR and SW +# locks on this table. +# +begin; +insert into t1 values (1); +# +# Switching to connection 'mdl_con1'. +# Create pending SNW lock on t1. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'default'. +# Wait until ALTER TABLE starts waiting for SNW lock. +# We should still be able to get both SW and SR locks without waiting. +select count(*) from t1; +count(*) +5 +delete from t1 limit 1; +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +# +# 2) Now similar tests for active SNW lock which is being upgraded +# to X lock. +# +# Again we start with case when we are acquiring lock on the +# table which was not used in the transaction before. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Start transaction which will prevent SNW -> X upgrade from +# completing immediately. +begin; +select count(*) from t2; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create SNW lock pending upgrade to X on t2. +# Sending: +alter table t2 add column c2 int;; +# +# Switching to connection 'default'. +# Wait until ALTER TABLE starts waiting X lock. +# Check that attempt to acquire SR lock on t2 causes waiting. +# Sending: +select count(*) from t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT is blocked. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +# +# Switching to connection 'default'. +# Reap SELECT. +count(*) +3 +commit; +# Do similar check for SW lock. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Start transaction which will prevent SNW -> X upgrade from +# completing immediately. +begin; +select count(*) from t2; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create SNW lock pending upgrade to X on t2. +# Sending: +alter table t2 drop column c2;; +# +# Switching to connection 'default'. +# Wait until ALTER TABLE starts waiting X lock. +# Check that attempt to acquire SW lock on t2 causes waiting. +# Sending: +insert into t2 values (1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above INSERT is blocked. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +# +# Switching to connection 'default'. +# Reap INSERT. +commit; +# +# Test for the case in which we are acquiring lock on the table +# which is already used in transaction. +# +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +# Create SNW lock pending upgrade to X. +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'default'. +# Wait until ALTER TABLE starts waiting X lock. +# Check that transaction is still able to acquire SR lock. +select count(*) from t1; +count(*) +4 +# Waiting trying to acquire SW lock will cause deadlock and +# therefore should cause an error. +delete from t1 limit 1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +# +# Switching to connection 'default'. +# +# 3) Check how various locks used within transactional context +# interact with active/pending SNRW lock. +# +# Once again we start with case when we are acquiring lock on +# the table which was not used in the transaction before. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +lock table t2 write; +# +# Switching to connection 'default'. +# Attempt to acquire SR should be blocked. It should +# not cause errors as it does not creates deadlock. +# Sending: +select count(*) from t2;; +# +# Switching to connection 'mdl_con1'. +# Check that the above SELECT is blocked +# Unblock SELECT. +unlock tables; +# +# Switching to connection 'default'. +# Reap SELECT. +count(*) +4 +commit; +# Repeat the same test for SW lock. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +lock table t2 write; +# +# Switching to connection 'default'. +# Again attempt to acquire SW should be blocked and should +# not cause any errors. +# Sending: +delete from t2 limit 1;; +# +# Switching to connection 'mdl_con1'. +# Check that the above DELETE is blocked +# Unblock DELETE. +unlock tables; +# +# Switching to connection 'default'. +# Reap DELETE. +commit; +# +# Now coverage for the case in which we are acquiring lock on +# the table which is already used in transaction and against +# which there is a pending SNRW lock request. +# +# *) Let us start with case when transaction has only a SR lock. +# +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Wait until LOCK TABLE is blocked creating pending request for X lock. +# Check that another instance of SR lock is granted without waiting. +select count(*) from t1; +count(*) +4 +# Attempt to wait for SW lock will lead to deadlock, thus +# the below statement should end with ER_LOCK_DEADLOCK error. +delete from t1 limit 1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Unblock LOCK TABLES. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap LOCK TABLES. +unlock tables; +# +# Switching to connection 'default'. +# +# **) Now case when transaction has a SW lock. +# +begin; +delete from t1 limit 1; +# +# Switching to connection 'mdl_con1'. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Wait until LOCK TABLE is blocked creating pending request for X lock. +# Check that both SR and SW locks are granted without waiting +# and errors. +select count(*) from t1; +count(*) +3 +insert into t1 values (1, 1); +# Unblock LOCK TABLES. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap LOCK TABLES. +unlock tables; +# +# Switching to connection 'default'. +# +# 4) Check how various locks used within transactional context +# interact with active/pending X lock. +# +# As usual we start with case when we are acquiring lock on +# the table which was not used in the transaction before. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Start transaction which will prevent X lock from going away +# immediately. +begin; +select count(*) from t2; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create pending X lock on t2. +# Sending: +rename table t2 to t3;; +# +# Switching to connection 'default'. +# Wait until RENAME TABLE starts waiting with pending X lock. +# Check that attempt to acquire SR lock on t2 causes waiting. +# Sending: +select count(*) from t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT is blocked. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap RENAME TABLE. +# +# Switching to connection 'default'. +# Reap SELECT. +ERROR 42S02: Table 'test.t2' doesn't exist +commit; +rename table t3 to t2; +# The same test for SW lock. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Start transaction which will prevent X lock from going away +# immediately. +begin; +select count(*) from t2; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create pending X lock on t2. +# Sending: +rename table t2 to t3;; +# +# Switching to connection 'default'. +# Wait until RENAME TABLE starts waiting with pending X lock. +# Check that attempt to acquire SW lock on t2 causes waiting. +# Sending: +delete from t2 limit 1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE is blocked. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap RENAME TABLE. +# +# Switching to connection 'default'. +# Reap DELETE. +ERROR 42S02: Table 'test.t2' doesn't exist +commit; +rename table t3 to t2; +# +# Coverage for the case in which we are acquiring lock on +# the table which is already used in transaction and against +# which there is a pending X lock request. +# +# *) The first case is when transaction has only a SR lock. +# +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'default'. +# Wait until RENAME TABLE is blocked creating pending request for X lock. +# Check that another instance of SR lock is granted without waiting. +select count(*) from t1; +count(*) +4 +# Attempt to wait for SW lock will lead to deadlock, thus +# the below statement should end with ER_LOCK_DEADLOCK error. +delete from t1 limit 1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'default'. +# +# **) The second case is when transaction has a SW lock. +# +begin; +delete from t1 limit 1; +# +# Switching to connection 'mdl_con1'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'default'. +# Wait until RENAME TABLE is blocked creating pending request for X lock. +# Check that both SR and SW locks are granted without waiting +# and errors. +select count(*) from t1; +count(*) +3 +insert into t1 values (1, 1); +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'default'. +# Clean-up. +set debug_sync= 'RESET'; +drop table t1, t2; +# +# Additional coverage for some scenarios in which not quite +# correct use of S metadata locks by HANDLER statement might +# have caused deadlocks. +# +drop table if exists t1, t2; +create table t1 (i int); +create table t2 (j int); +insert into t1 values (1); +# +# First, check scenario in which we upgrade SNRW lock to X lock +# on a table while having HANDLER READ trying to acquire TL_READ +# on the same table. +# +handler t1 open; +# +# Switching to connection 'handler_con1'. +lock table t1 write; +# Upgrade SNRW to X lock. +# Sending: +alter table t1 add column j int;; +# +# Switching to connection 'handler_con2'. +# Wait until ALTER is blocked during upgrade. +# +# Switching to connection 'default'. +# The below statement should not cause deadlock. +handler t1 read first;; +# +# Switching to connection 'handler_con1'. +# Reap ALTER TABLE. +unlock tables; +# +# Switching to connection 'default'. +# Reap HANDLER READ. +i j +1 NULL +handler t1 close; +# +# Now, check scenario in which upgrade of SNRW lock to X lock +# can be blocked by HANDLER which is open in connection currently +# waiting to get table-lock owned by connection doing upgrade. +# +handler t1 open; +# +# Switching to connection 'handler_con1'. +lock table t1 write, t2 read; +# +# Switching to connection 'default'. +# Execute statement which will be blocked on table-level lock +# owned by connection 'handler_con1'. +# Sending: +insert into t2 values (1);; +# +# Switching to connection 'handler_con1'. +# Wait until INSERT is blocked on table-level lock. +# The below statement should not cause deadlock. +alter table t1 drop column j; +unlock tables; +# +# Switching to connection 'default'. +# Reap INSERT. +handler t1 close; +# +# Then, check the scenario in which upgrade of SNRW lock to X +# lock is blocked by HANDLER which is open in connection currently +# waiting to get SW lock on the same table. +# +handler t1 open; +# +# Switching to connection 'handler_con1'. +lock table t1 write; +# +# Switching to connection 'default'. +# The below insert should be blocked because active SNRW lock on 't1'. +# Sending: +insert into t1 values (1);; +# +# Switching to connection 'handler_con1'. +# Wait until INSERT is blocked because of SNRW lock. +# The below ALTER TABLE will be blocked because of presence of HANDLER. +# Sending: +alter table t1 add column j int;; +# +# Switching to connection 'default'. +# INSERT should be chosen as victim for resolving deadlock. +# Reaping INSERT. +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Close HANDLER to unblock ALTER TABLE. +handler t1 close; +# +# Switching to connection 'handler_con1'. +# Reaping ALTER TABLE. +unlock tables; +# +# Switching to connection 'default'. +# +# Finally, test in which upgrade of SNRW lock to X lock is blocked +# by HANDLER which is open in connection currently waiting to get +# SR lock on the table on which lock is upgraded. +# +handler t1 open; +# +# Switching to connection 'handler_con1'. +lock table t1 write, t2 write; +# +# Switching to connection 'default'. +# The below insert should be blocked because active SNRW lock on 't1'. +# Sending: +insert into t2 values (1);; +# +# Switching to connection 'handler_con1'. +# Wait until INSERT is blocked because of SNRW lock. +# The below ALTER TABLE will be blocked because of presence of HANDLER. +# Sending: +alter table t1 drop column j;; +# +# Switching to connection 'default'. +# INSERT should be chosen as victim for resolving deadlock. +# Reaping INSERT. +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Close HANDLER to unblock ALTER TABLE. +handler t1 close; +# +# Switching to connection 'handler_con1'. +# Reaping ALTER TABLE. +unlock tables; +# +# Switching to connection 'default'. +# Clean-up. +drop tables t1, t2; +# # Test coverage for basic deadlock detection in metadata # locking subsystem. # @@ -118,53 +1852,46 @@ commit; # # Switching to connection 'deadlock_con1'. begin; -insert into t1 values (1); -# -# Switching to connection 'deadlock_con2'. -begin; -insert into t3 values (1); +insert into t2 values (1); # # Switching to connection 'default'. -# Send: -rename table t2 to t0, t3 to t2, t0 to t3;; +lock table t1 write; # # Switching to connection 'deadlock_con1'. -# Wait until the above RENAME TABLE is blocked because it has to wait -# for 'deadlock_con2' which holds shared metadata lock on 't3'. # The below SELECT statement should wait for metadata lock -# on table 't2' and should not produce ER_LOCK_DEADLOCK +# on table 't1' and should not produce ER_LOCK_DEADLOCK # immediately as no deadlock is possible at the moment. -select * from t2;; +select * from t1;; # -# Switching to connection 'deadlock_con3'. -# Wait until the above SELECT * FROM t2 is starts waiting -# for an exclusive metadata lock to go away. +# Switching to connection 'deadlock_con2'. +# Wait until the above SELECT * FROM t1 is starts waiting +# for an UNRW metadata lock to go away. # Send RENAME TABLE statement that will deadlock with the # SELECT statement and thus should abort the latter. -rename table t1 to t5, t2 to t1, t5 to t2;; +rename table t1 to t0, t2 to t1, t0 to t2;; +# +# Switching to connection 'default'. +# Wait till above RENAME TABLE is blocked while holding +# pending X lock on t1. +# Allow the above RENAME TABLE to acquire lock on t1 and +# create pending lock on t2 thus creating deadlock. +unlock tables; # # Switching to connection 'deadlock_con1'. # Since the latest RENAME TABLE entered in deadlock with SELECT # statement the latter should be aborted and emit ER_LOCK_DEADLOCK # error. -# Reap SELECT * FROM t2. +# Reap SELECT * FROM t1. ERROR 40001: Deadlock found when trying to get lock; try restarting transaction # # Again let us check that failure of the SELECT statement has not -# released metadata lock on table 't1', i.e. that the latest RENAME +# released metadata lock on table 't2', i.e. that the latest RENAME # is blocked. # Commit transaction to unblock this RENAME TABLE. commit; # # Switching to connection 'deadlock_con2'. -# Commit transaction to unblock the first RENAME TABLE. -commit; -# -# Switching to connection 'default'. -# Reap RENAME TABLE t2 TO t0 ... . -# -# Switching to connection 'deadlock_con3'. -# Reap RENAME TABLE t1 TO t5 ... . +# Reap RENAME TABLE ... . # # Switching to connection 'default'. drop tables t1, t2, t3, t4; @@ -173,10 +1900,19 @@ drop tables t1, t2, t3, t4; # also takes into account requests for metadata lock upgrade. # create table t1 (i int); +insert into t1 values (1); +# Avoid race which occurs when SELECT in 'deadlock_con1' connection +# accesses table before the above INSERT unlocks the table and thus +# its result becomes visible to other connections. +select * from t1; +i +1 # # Switching to connection 'deadlock_con1'. begin; -insert into t1 values (1); +select * from t1; +i +1 # # Switching to connection 'default'. # Send: @@ -200,42 +1936,6 @@ commit; # Reap ALTER TABLE ... RENAME. drop table t2; # -# Finally, test case in which deadlock (or potentially livelock) occurs -# between metadata locking subsystem and table definition cache/table -# locks, but which should still be detected by our empiric. -# -create table t1 (i int); -# -# Switching to connection 'deadlock_con1'. -begin; -insert into t1 values (1); -# -# Switching to connection 'default'. -lock tables t1 write; -# -# Switching to connection 'deadlock_con1'. -# Send: -insert into t1 values (2);; -# -# Switching to connection 'default'. -# Wait until INSERT in connection 'deadlock_con1' is blocked on -# table-level lock. -# Send: -alter table t1 add column j int;; -# -# Switching to connection 'deadlock_con1'. -# The above ALTER TABLE statement should cause INSERT statement in -# this connection to be aborted and emit ER_LOCK_DEADLOCK error. -# Reap INSERT -ERROR 40001: Deadlock found when trying to get lock; try restarting transaction -# Commit transaction to unblock ALTER TABLE. -commit; -# -# Switching to connection 'default'. -# Reap ALTER TABLE. -unlock tables; -drop table t1; -# # Test for bug #46748 "Assertion in MDL_context::wait_for_locks() # on INSERT + CREATE TRIGGER". # @@ -297,7 +1997,7 @@ SET DEBUG_SYNC= 'now WAIT_FOR locked'; # Now INSERT has a MDL on the non-existent table t1. # # Continue the INSERT once CREATE waits for exclusive lock -SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish'; +SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish'; # Try to create that table. CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)); # Connection 2 @@ -323,7 +2023,7 @@ SET DEBUG_SYNC= 'now WAIT_FOR locked'; # Now INSERT has a MDL on the non-existent table t1. # # Continue the INSERT once CREATE waits for exclusive lock -SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish'; +SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish'; # Try to create that table. CREATE TABLE t1 LIKE t2; # Connection 2 @@ -347,10 +2047,10 @@ create table t1 (i int); # Let us check that we won't deadlock if during filling # of I_S table we encounter conflicting metadata lock # which owner is in its turn waiting for our connection. -lock tables t1 write; +lock tables t1 read; # Switching to connection 'con46044'. # Sending: -create table t2 select * from t1;; +create table t2 select * from t1 for update;; # Switching to connection 'default'. # Waiting until CREATE TABLE ... SELECT ... is blocked. # First let us check that SHOW FIELDS/DESCRIBE doesn't @@ -386,10 +2086,10 @@ drop table t2; # # We check same three queries to I_S in this new situation. # Switching to connection 'con46044_2'. -lock tables t1 write; +lock tables t1 read; # Switching to connection 'con46044'. # Sending: -create table t2 select * from t1;; +create table t2 select * from t1 for update;; # Switching to connection 'default'. # Waiting until CREATE TABLE ... SELECT ... is blocked. # Let us check that SHOW FIELDS/DESCRIBE gets blocked. @@ -406,10 +2106,10 @@ Field Type Null Key Default Extra i int(11) YES NULL drop table t2; # Switching to connection 'con46044_2'. -lock tables t1 write; +lock tables t1 read; # Switching to connection 'con46044'. # Sending: -create table t2 select * from t1;; +create table t2 select * from t1 for update;; # Switching to connection 'default'. # Waiting until CREATE TABLE ... SELECT ... is blocked. # Check that I_S query which reads only .FRMs gets blocked. @@ -426,10 +2126,10 @@ column_name i drop table t2; # Switching to connection 'con46044_2'. -lock tables t1 write; +lock tables t1 read; # Switching to connection 'con46044'. # Sending: -create table t2 select * from t1;; +create table t2 select * from t1 for update;; # Switching to connection 'default'. # Waiting until CREATE TABLE ... SELECT ... is blocked. # Finally, check that I_S query which does full-blown table open @@ -458,7 +2158,9 @@ set debug_sync= 'RESET'; create table t1 (c1 int primary key, c2 int, c3 int); insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); begin; -update t1 set c3=c3+1 where c2=3; +select * from t1 where c2 = 3; +c1 c2 c3 +3 3 0 # # Switching to connection 'con46273'. set debug_sync='after_lock_tables_takes_lock SIGNAL alter_table_locked WAIT_FOR alter_go'; @@ -466,11 +2168,11 @@ alter table t1 add column e int, rename to t2;; # # Switching to connection 'default'. set debug_sync='now WAIT_FOR alter_table_locked'; -set debug_sync='wait_for_lock SIGNAL alter_go'; +set debug_sync='before_open_table_wait_refresh SIGNAL alter_go'; # The below statement should get ER_LOCK_DEADLOCK error # (i.e. it should not allow ALTER to proceed, and then # fail due to 't1' changing its name to 't2'). -update t1 set c3=c3+1 where c2=4; +update t1 set c3=c3+1 where c2 = 3; ERROR 40001: Deadlock found when trying to get lock; try restarting transaction # # Let us check that failure of the above statement has not released diff --git a/mysql-test/r/sp-threads.result b/mysql-test/r/sp-threads.result index d974cfb9605..a14d099c673 100644 --- a/mysql-test/r/sp-threads.result +++ b/mysql-test/r/sp-threads.result @@ -35,7 +35,7 @@ call bug9486(); show processlist; Id User Host db Command Time State Info # root localhost test Sleep # NULL -# root localhost test Query # Table lock update t1, t2 set val= 1 where id1=id2 +# root localhost test Query # Waiting for table update t1, t2 set val= 1 where id1=id2 # root localhost test Query # NULL show processlist # root localhost test Sleep # NULL unlock tables; diff --git a/mysql-test/r/truncate_coverage.result b/mysql-test/r/truncate_coverage.result index bb036329f6f..7a5021f55e2 100644 --- a/mysql-test/r/truncate_coverage.result +++ b/mysql-test/r/truncate_coverage.result @@ -7,18 +7,20 @@ CREATE TABLE t1 (c1 INT); INSERT INTO t1 VALUES (1); # # connection con1 -START TRANSACTION; -INSERT INTO t1 VALUES (2); +HANDLER t1 OPEN; # # connection default LOCK TABLE t1 WRITE; SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; TRUNCATE TABLE t1; # -# connection con1 +# connection con2 SET DEBUG_SYNC='now WAIT_FOR waiting'; KILL QUERY @id; -COMMIT; +# +# connection con1 +# Release shared metadata lock by closing HANDLER. +HANDLER t1 CLOSE; # # connection default ERROR 70100: Query execution was interrupted @@ -29,17 +31,18 @@ CREATE TABLE t1 (c1 INT); INSERT INTO t1 VALUES (1); # # connection con1 -START TRANSACTION; -INSERT INTO t1 VALUES (2); +HANDLER t1 OPEN; # # connection default LOCK TABLE t1 WRITE; SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; TRUNCATE TABLE t1; # -# connection con1 +# connection con2 SET DEBUG_SYNC='now WAIT_FOR waiting'; -COMMIT; +# +# connection con1 +HANDLER t1 CLOSE; # # connection default ERROR 42S02: Table 'test.t1' doesn't exist @@ -55,7 +58,7 @@ START TRANSACTION; INSERT INTO t1 VALUES (2); # # connection default -SET DEBUG_SYNC='mdl_acquire_exclusive_locks_wait SIGNAL waiting'; +SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting'; TRUNCATE TABLE t1; # # connection con1 diff --git a/mysql-test/suite/funcs_1/datadict/processlist_val.inc b/mysql-test/suite/funcs_1/datadict/processlist_val.inc index 8b10cfc5e97..6fcaf45c848 100644 --- a/mysql-test/suite/funcs_1/datadict/processlist_val.inc +++ b/mysql-test/suite/funcs_1/datadict/processlist_val.inc @@ -425,7 +425,7 @@ echo # Poll till INFO is no more NULL and State = 'Table Lock'. ; let $wait_condition= SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE INFO IS NOT NULL AND STATE = 'Table Lock'; + WHERE INFO IS NOT NULL AND STATE = 'Waiting for table'; --source include/wait_condition.inc echo # Expect result: diff --git a/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result b/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result index 34b2e48fc7e..e8ee784bec4 100644 --- a/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result +++ b/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result @@ -195,9 +195,11 @@ SELECT COUNT(*) FROM test.t1; # Poll till INFO is no more NULL and State = 'Table Lock'. +Timeout in wait_condition.inc for SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST +WHERE INFO IS NOT NULL AND STATE = 'Table Lock' SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST; ID USER HOST DB COMMAND TIME STATE INFO - test_user information_schema Query