mysql set global read_only操作

       最近了解mysql MDL的设计。发现mysql "set global read_only=on/off"操作也依赖metadata lock(5.6.16, 推测是从5.5引入metadata lock后就这样了,没有查看更早版本的代码确认)。下面是set global read_only=on/off的实现。

      调用路径如下: 

#0  fix_read_only (self=0x134f5a0, thd=0x2470e0a0, type=OPT_GLOBAL)
    at /home/mysql-5.6.16/sql/sys_vars.cc:2219
#1  0x000000000064ab91 in sys_var::update (this=0x134f5a0, thd=0x2470e0a0, var=0x24691df0)
    at /home/mysql-5.6.16/sql/set_var.cc:194
#2  0x000000000064afad in set_var::update (this=0x24691df0, thd=0x2470e0a0)
    at /home/mysql-5.6.16/sql/set_var.cc:670
#3  0x000000000064a6c9 in sql_set_variables (thd=0x2470e0a0, var_list=<value optimized out>)
    at /home/mysql-5.6.16/sql/set_var.cc:573
#4  0x00000000006d779b in mysql_execute_command (thd=0x2470e0a0)
    at /home/mysql-5.6.16/sql/sql_parse.cc:3704
#5  0x00000000006dcb9b in mysql_parse (thd=0x2470e0a0, rawbuf=0x24691c90 "set global read_only=on", 
    length=0, parser_state=<value optimized out>) at /home/mysql-5.6.16/sql/sql_parse.cc:6235
#6  0x00000000006de6b0 in dispatch_command (command=COM_QUERY, thd=0x2470e0a0, 
    packet=0x246c7f61 "set global read_only=on", packet_length=23)
    at /home/mysql-5.6.16/sql/sql_parse.cc:1334
#7  0x00000000006df977 in do_command (thd=0x2470e0a0)
    at /home/mysql-5.6.16/sql/sql_parse.cc:1036
#8  0x00000000006a1f95 in do_handle_one_connection (thd_arg=0x2470e0a0)
    at /home/mysql-5.6.16/sql/sql_connect.cc:982

       主要的实现都在fix_read_only函数内。主要分为2步:第一步初始化GRL 锁需要的第一个metadata lock -- 一个GLOBAL级别的显式的MDL_SHARED类型的metadata lock,第一步完成之后GRL的状态是GRL_ACQUIRED,并且这一步之后其他连接是无法拿到写锁的(新进来的写请求会被拒绝);第二步获取一个COMMIT级别的显式的MDL_SHARED类型的metadata lock,并将GRL的状态修改为GRL_ACQUIRED_AND_BLOCKS_COMMIT,这一步之后所有的事务都不再允许在提交了(所以执行set global read_only操作时如果有大事务正在执行,set global read_only操作是会被卡住的)。 

fix_read_only(),sys_vars.cc:2219
  --my_bool new_read_only= read_only;
  --read_only= opt_readonly;
...
  --lock_global_read_lock(),lock.cc:966 (enum_grl_state m_state = Global_read_lock::GRL_ACQUIRED_AND_BLOCKS_COMMIT -> m_state = GRL_ACQUIRED)
    --mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED, MDL_EXPLICIT);# 初始化一个namespace=GLOBAL的显式的MDL_SHARED的metadata lock, dbname 和 name为“”
    --thd->mdl_context.acquire_lock(&mdl_request,thd->variables.lock_wait_timeout)         # 获取刚刚初始化过的metadata lock
  --make_global_read_lock_block_commit(),lock.cc:1041 (m_state = GRL_ACQUIRED -> m_state = GRL_ACQUIRED_AND_BLOCKS_COMMIT)
    --mdl_request.init(MDL_key::COMMIT, "", "", MDL_SHARED, MDL_EXPLICIT);
    --thd->mdl_context.acquire_lock(&mdl_request,thd->variables.lock_wait_timeout))
...
  --opt_readonly= new_read_only;
  --read_only= opt_readonly;

      细看这段逻辑,其实 set global read_only=on/off最主要的操作还是要把全局变量opt_readonly设置为on/off。之所以有上面这段fix_read_only的逻辑,我觉得是要考虑如何处理处于提交阶段的事务:第一步可以阻止新的写请求,第二步阻止事务提交。

      Global_read_lock也很容易理解:

Global_read_lock主要有下面几个函数:
lock_global_read_lock()
unlock_global_read_lock()
make_global_read_lock_block_commit()

Global_read_lock主要有下面几个变量:
  /**
    In order to acquire the global read lock, the connection must
    acquire shared metadata lock in GLOBAL namespace, to prohibit
    all DDL.
  */
  MDL_ticket *m_mdl_global_shared_lock;
  /**
    Also in order to acquire the global read lock, the connection
    must acquire a shared metadata lock in COMMIT namespace, to
    prohibit commits.
  */
  MDL_ticket *m_mdl_blocks_commits_lock;
  enum_grl_state m_state;

       前面说了set global read_only=on/off最重要的还是设置全局变量opt_readonly。那么只读是如何生效的呢?mysql在执行写操作前/commit的时候会去判断read_only状态(变量opt_readponly),如果read_only=on && 当前用户没有超级权限,就会报1290错误(ER_OPTION_PREVENTS_STATEMENT)--“ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement”

bool trans_begin(THD *thd, uint flags) //transaction.cc,事务开始start transaction/begin
{
...
    if (opt_readonly && !user_is_super)
    {
      my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
      DBUG_RETURN(true);
    }
...
int ha_commit_trans(THD *thd, bool all, bool ignore_global_read_lock) //事务commit的时候
...
    if (rw_trans &&
        opt_readonly &&
        !(thd->security_ctx->master_access & SUPER_ACL) &&
        !thd->slave_thread)
    {
      my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
      ha_rollback_trans(thd, all);
...

 

 

 PS:

set global read_only=on/off是DBA经常用的一个操作:进行主备切换的时候,一般都会先对主库进行只读操作(on),然后主备同步完成后,再把备库置为可读写(off)。这样可以避免切换的过程中双写引起脏数据。

 mysqld.cc中同时定义了2个变量:my_bool read_only= 0, opt_readonly= 0; opt_readonly是当前系统的read_only状态,read_only是要把read_only设置成的值。

猜你喜欢

转载自guduwhuzhe.iteye.com/blog/2162529
今日推荐