数据库内核月报 - 2014 / 12

MySQL · 捉虫动态·Opened tables block read only

背景

MySQL通过read_only参数来设置DB只读,这样MySQL实例就可以作为slave角色,只应用binlog,不接受用户修改数据。这样就可以保护master-slave结构中的数据一致性,防止双写风险。

global read_only的实现方式

MySQL5.5版本通过三个步骤来设置read_only:

步骤1:获取global read lock,阻塞所有的写入请求 步骤2:flush opened table cache,阻塞所有的显示读写锁 步骤3:获取commit lock,阻塞commit写入binlog 步骤4:设置read_only=1 步骤5:释放global read lock和commit lock。 MySQL 5.5的版本,通过这5步完成设置read only的功能。

Bug描述

比如如下场景:

 session1:
  lock table t read;
 
 session2:
  set global read_only=1;

先执行session1,然后session2会一直被session1阻塞。

原因是:session1的显示锁,虽然与步骤1中的global read lock相容, 但session2因为session1一直持有读锁并保持t表opened而被阻塞。

但是,实际上,显示的读写锁产生的opened table并不影响read_only的功能,这里的flush tables也并非是必须的。

这也是我们的实际应用环境中,因主备切换而要在master实例上设置read_only的时候,经常被大查询所阻塞的原因。

修复方法

修复方法非常简单,只需要把步骤2删除即可,不影响read only的语义。

官方在MySQL 5.6.5中进行了修复:

If tables were locked by LOCK TABLES ... READ in another session, SET GLOBAL read_only = 1 failed to complete. (Bug #57612, Bug #11764747)

RDS功能增强

设置read_only阻塞用户写入,但只能阻塞普通用户的更新,RDS为了最大可能的保护数据一致性,增强了read_only功能,通过设置super read only,阻塞除了slave线程以为的所有用户的写入,包括super用户。