Commit 4361c864 authored by Sergei Golubchik's avatar Sergei Golubchik

MDEV-136 Non-blocking "set read_only"

backport dmitry.shulga@oracle.com-20120209125742-w7hdxv0103ymb8ko from mysql-trunk:

  Patch for bug#11764747 (formerly known as 57612): SET GLOBAL READ_ONLY=1 cannot
  progress when a table is locked with LOCK TABLES.
  
  The reason for the bug was that mysql server makes a flush of all open tables
  during handling of statement 'SET GLOBAL READ_ONLY=1'. Therefore if some of
  these tables were locked by "LOCK TABLE ... READ" from a different connection,
  then execution of statement 'SET GLOBAL READ_ONLY=1' would be waiting for
  the lock for such table even if the table was locked in a compatible read mode.
  
  Flushing of all open tables before setting of read_only system variable
  is inherited from 5.1 implementation since this was the only possible approach
  to ensure that there isn't any pending write operations on open tables.
  
  Start from version 5.5 and above such behaviour is guaranteed by the fact
  that we acquire global_read_lock before setting read_only flag. Since
  acquiring of global_read_lock is successful only when there isn't any 
  active write operation then we can remove flushing of open tables from
  processing of SET GLOBAL READ_ONLY=1.
  
  This modification changes the server behavior so that read locks held
  by other connections (LOCK TABLE ... READ) no longer will block attempts
  to enable read_only.
parent 3e3606d2
...@@ -75,18 +75,16 @@ connection default; ...@@ -75,18 +75,16 @@ connection default;
set global read_only=1; set global read_only=1;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
unlock tables ; unlock tables ;
send set global read_only=1;
set global read_only=1; set global read_only=1;
connection con1;
select @@global.read_only; select @@global.read_only;
@@global.read_only @@global.read_only
0 1
unlock tables ; connection con1;
select @@global.read_only; select @@global.read_only;
@@global.read_only @@global.read_only
1 1
unlock tables ;
connection default; connection default;
reap;
connection default; connection default;
set global read_only=0; set global read_only=0;
BEGIN; BEGIN;
......
...@@ -43,27 +43,14 @@ a ...@@ -43,27 +43,14 @@ a
1 1
COMMIT; COMMIT;
UNLOCK TABLES; UNLOCK TABLES;
FLUSH STATUS;
# Expected 0 at the beginning of the test
show status like 'Opened_tables';
Variable_name Value
Opened_tables 0
connection con1; connection con1;
lock table t1 write; lock table t1 read;
connection default; connection default;
set global read_only=1; set global read_only=1;
# Expected 1 as the slow_log was reopened
show status like 'Opened_tables';
Variable_name Value
Opened_tables 1
connection con1; connection con1;
unlock tables; unlock tables;
connection default; connection default;
SET GLOBAL read_only=0; SET GLOBAL read_only=0;
# Expected 2 as the slow_log was reopened
show status like 'Opened_tables';
Variable_name Value
Opened_tables 2
UNLOCK TABLES; UNLOCK TABLES;
DROP TABLE t1; DROP TABLE t1;
DROP USER test@localhost; DROP USER test@localhost;
......
...@@ -169,23 +169,19 @@ connection default; ...@@ -169,23 +169,19 @@ connection default;
--error ER_LOCK_OR_ACTIVE_TRANSACTION --error ER_LOCK_OR_ACTIVE_TRANSACTION
set global read_only=1; set global read_only=1;
unlock tables ; unlock tables ;
# The following call blocks until con1 releases the read lock.
# Blocking is a limitation, and could be improved. # after unlock tables in current connection
--echo send set global read_only=1; # the next command must be executed successfully
send set global read_only=1; set global read_only=1;
select @@global.read_only;
--echo connection con1; --echo connection con1;
connection con1; connection con1;
select @@global.read_only; select @@global.read_only;
unlock tables ; unlock tables ;
let $wait_condition= SELECT @@global.read_only= 1;
--source include/wait_condition.inc
select @@global.read_only;
--echo connection default; --echo connection default;
connection default; connection default;
--echo reap;
reap;
# pending transaction / READ_ONLY # pending transaction / READ_ONLY
# - is an error in the same connection # - is an error in the same connection
......
...@@ -81,19 +81,14 @@ COMMIT; ...@@ -81,19 +81,14 @@ COMMIT;
connection default; connection default;
UNLOCK TABLES; UNLOCK TABLES;
FLUSH STATUS;
--echo # Expected 0 at the beginning of the test
show status like 'Opened_tables';
--echo connection con1; --echo connection con1;
connection con1; connection con1;
lock table t1 write; lock table t1 read;
--echo connection default; --echo connection default;
connection default; connection default;
set global read_only=1; set global read_only=1;
--echo # Expected 1 as the slow_log was reopened
show status like 'Opened_tables';
--echo connection con1; --echo connection con1;
connection con1; connection con1;
...@@ -102,8 +97,6 @@ unlock tables; ...@@ -102,8 +97,6 @@ unlock tables;
--echo connection default; --echo connection default;
connection default; connection default;
SET GLOBAL read_only=0; SET GLOBAL read_only=0;
--echo # Expected 2 as the slow_log was reopened
show status like 'Opened_tables';
UNLOCK TABLES; UNLOCK TABLES;
DROP TABLE t1; DROP TABLE t1;
......
...@@ -1596,14 +1596,15 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type) ...@@ -1596,14 +1596,15 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type)
} }
/* /*
Perform a 'FLUSH TABLES WITH READ LOCK'. READ_ONLY=1 prevents write locks from being taken on tables and
This is a 3 step process: blocks transactions from committing. We therefore should make sure
- [1] lock_global_read_lock() that no such events occur while setting the read_only variable.
- [2] close_cached_tables() This is a 2 step process:
- [3] make_global_read_lock_block_commit() [1] lock_global_read_lock()
[1] prevents new connections from obtaining tables locked for write. Prevents connections from obtaining new write locks on
[2] waits until all existing connections close their tables. tables. Note that we can still have active rw transactions.
[3] prevents transactions from being committed. [2] make_global_read_lock_block_commit()
Prevents transactions from committing.
*/ */
read_only= opt_readonly; read_only= opt_readonly;
...@@ -1612,19 +1613,6 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type) ...@@ -1612,19 +1613,6 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type)
if (thd->global_read_lock.lock_global_read_lock(thd)) if (thd->global_read_lock.lock_global_read_lock(thd))
goto end_with_mutex_unlock; goto end_with_mutex_unlock;
/*
This call will be blocked by any connection holding a READ or WRITE lock.
Ideally, we want to wait only for pending WRITE locks, but since:
con 1> LOCK TABLE T FOR READ;
con 2> LOCK TABLE T FOR WRITE; (blocked by con 1)
con 3> SET GLOBAL READ ONLY=1; (blocked by con 2)
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, TRUE,
thd->variables.lock_wait_timeout)))
goto end_with_read_lock;
if ((result= thd->global_read_lock.make_global_read_lock_block_commit(thd))) if ((result= thd->global_read_lock.make_global_read_lock_block_commit(thd)))
goto end_with_read_lock; goto end_with_read_lock;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment