在构建稳定可靠的应用架构时, 数据库是最底层、最稳定的组件之一;而在云环境中,RDS 提供一个7*24小时不间接访问的云服务,可用性达到99.95%.

RDS 采用主备复制架构,用户购买一个实例,RDS都会提供一个性能对等的备库用于保证高可用。 高可用性组件(AURORA)会每3秒检查主库(Master)状态,当发现 Master 出现Down机时可以将用户的SQL请求快速转移到备库(Slave)上面。


rds-1

图1 – RDS 架构图

在这样的架构设计下, RDS需要保证主备数据一致性并且延时不超过10秒,以快速完成主备切换;否则,RDS会保证一致性而牺牲可用性,必须等待数据同步一致再进行切换;所以主备延时会直接影响服务的高可用性;

数据复制可靠性

MySQL复制模式可以通过参数:BINLOG_FORMAT进行配置 ;在MySQL5.1以前,MySQL默认采用 Statement 模式进行数据复制,这种模式下有可能会让主备数据产生不一致情况,比如使用UUID等函数;MySQL在5.1版本以后,提供了基于ROW 模式的复制模式,从而大大提高了数据复制的可靠性;但这种模式在以下场景下会让备库的数据延时很大;

1) 存在没有主键的表,导致备库应用每个Event 都需要全表扫描 ;
2) 主库执行了大表DDL 或大事务,导致备库也要相同时间执行完 ;

RDS在实际的运行过程中发现,99%以上的主备延时,都是因为用户在建表的时候没有指定主键;RDS 曾经尝试过临时解决方案, 把有延迟的实例日志格式改为MIXED,无主键表的操作用STATEMENT 格式记录,但这种方案还是有可能产生主备数据不一致;

ROW模式数据复制
ROW 模式之所以能保证复制可靠性,是其在BINLOG里记录每一行完整记录,包括所有列的值;在备库应用日志时,MySQL 会先尝试用行里的主键去匹配自身的记录,如果没有主键, 则进行全表扫描所有的行,每一行都与日志进行匹配,直到发现完全匹配的行;

rds-2

图2- ROW模式日志匹配处理流程

方案设计

在保证主备数据复制可靠性的前提下,减小主备延时;

方案一: 提醒用户去加上主键,问题迎刃而解;但在实际的实施过程中,这根本不现实,用户的学习成本、应用兼容、实施成本远远超过我们的想像;

方案二: 这也是云平台自身要解决的问题,用户不应该去关注这些问题;让MySQL 在底层能智能的处理,对用户透明,兼大欢喜;

对于方案二,有两个解决思路:

1. 为什么ROW格式日志一定要用主键定位记录,如果用二级索引行不行?虽然没有主键那么精准,但至少可以避免全表扫描

2. InnoDB 引擎也是严重依赖主键,它对于没有主键的表,就自己强制加进去一个主键对用户隐藏,MySQL Server层可否也这样实现?

思路一,需要考虑的问题主要是成本开销:

rds-3

图3-利用二级索引处理无主键的ROW格式Event

如果像执行SQL一样,每一行都走一遍执行计划看哪个二级索引比较好,那么速度一样快不起来.主库只对每个SQL走一次执行计划选择一个索引,备库需要对这个SQL影响的所有行记录都重新生成一次执行计划。

因为ROW格式中的行包含了所有列,所以更合理的方案是,选择一个固定规则的二级索引即可,总是有列可以被用上进行过滤。例如总是利用第一个二级索引,这样不需要走执行计划,可以大大节省生成执行计划的时间,而且有这个规则,也可以调整二级索引的位置,来匹配这个规则,让过滤性好的二级索引调整到可以被利用的位置。

幸好,MariaDB开发了一个这样的补丁,对于有二级索引而没有主键的表来说,效果还不错。

思路二,要解决的情况是:完全没有任何索引、以及二级索引过滤性都不好的情况(比如,性别字段)。这里我们考虑过把InnoDB的二级索引直接引用到Server层来,但是如此一来,对于使用MyISAM表的用户,还是没有效果,所以需要一个更通用的设计方案:MySQL可以自动会用户添加主键而对应用透明 – 隐式主键(Implict Primary Key)。 最终,我们采取了这样的设计方案:

1 打开RDS 特有的参数implict_primary_key,让隐式主键功能生效 ;
2 当用户建表(CREATE TABLE)时,判断表结构

2.1 如果表上有主键,则pass
2.2 如果表上没有主键,有唯一键,则把唯一键放在索引的第一个位置,可以利用二级索引补丁 ;
2.3 如果表上没有主键,也没有唯一键,则为用户建立一个特定名称的自增主键 ;

3 当用户修改表结构(ALTER TABLE)时,判断新表结构

3.1 如果用户自己添加了主键或唯一键,则删除系统添加的主键
3.2 如果用户删除了原有的主键和唯一键,则为用户建立一个特定名称的自增主键

4 用户做DML操作时,屏蔽这个隐式主键的存在

4.1 INSERT INTO table VALUES (…),用户不需要在VALUES中填写主键的值,系统会自动填充NULL,从而在写入数据库时自动填入自增值
4.2 SELECT * FROM table,行数据返回给用户前,自动过滤了隐式主键列
4.3 LOAD DATA INFILE,用户不会感知到表中存在主键,系统会自动填充NULL来使用自增主键值
4.4 SHOW CREATE TABLE/SHOW COLUMNS等SHOW语句,生成结构语句时自动过滤隐式主键列,用户不会看到有主键列

5 对于系统用户(root)需要查看真实情况的,提供show_ipk_info参数,SET show_ipk_info=1,则可以查看隐式主键,不会进行任何隐藏操作
6 如果implict_primary_key参数关闭,则隐式主键功能不再发挥作用,即当用户进行DDL操作时,如果原来表上有隐式主键,则会趁用户DDL之机一起删除。但是原有的没有删除的隐式主键列并不会显示给用户,会一直隐藏。

rds-4

图4 – 隐含主键操作展示;

基于思路2,RDS MySQL 源代码团队已经完成开发适应于RDS场景的MySQL Patch,并全面覆盖到RDS所有MySQL5.1和 MySQL 5.5服务器中,目前运行稳定 ;