背景

    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描述

    比如如下场景:

    1. session1
    2. lock table t read;
    3. session2
    4. 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中进行了修复:

    1. 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用户。