概述
业务初期数据库设计大多采用单库单表的结构,随着业务发展QPS和数据量增长到一定瓶颈,单库单表的架构就支撑不住了,这时就要进行数据库层面的扩展。数据库的扩展可以分为如下场景:
- 读性能扩展
- 写性能扩展
- 分库分表
- 表结构扩展(大表DDL)
本篇文章只系统的介绍基于DB的方案。和其他中间件配合的方案,之后有可能也会系统写一篇。
读性能扩展
最基本的表设计指导思想:
- 业务需求决定表的字段
-
查询需求决定表的索引
业内常用的读扩展方案是主从架构。业务上,我们通常至少有两套系统:运营后台和C端应用。两者的查询维度一般不一样,这意味着要有两套不同的索引结构。而我们又不希望运营后台的请求影响到C端,所以数据要隔离。
所以,我们要搞两组slave。slave1用于处理C端读请求,假设C端的请求多以uid为查询条件,创建uid索引。slave2用于处理运营后台读请求,假设运营后台请求多以日期为查询条件,创建date索引。
这种方案既提高了读性能,也实现了请求互不影响,而且实现简单。缺点是不同组的slave存在着不同的索引,对于DBA来说存在一定的维护成本。又引入了主从延时的问题,参考我的另一篇文章Mysql主从延时。
写性能扩展
增加从库扩展读性能,很容易想到增加主库扩展写性能。数据库层面的负载均衡通常用LVS做,成本低,性能好。关于负载均衡,参考我的另一篇文章聊聊负载均衡。
双主同时对外提供服务,需要冗余全部数据。冗余数据,就会有数据不一致的问题。比如:master1生成一条id为3的数据,这条数据同步到master2之前,master2此时收到一个insert请求,也会生成一条id为3的数据。这时,就出现了双主id冲突的问题。这个问题的解决思路是保证双主生成的id不重复,方案有两种
- 数据库层解决方案。master1,master2设置不同的初始值,相同的步长。如图master1生成1,3,5,7。master2生成2,4,6,8。这种方案缺点是:两台机器配置不一样,会提高运维的复杂性。
- 应用层解决方案。主键有应用生成,采用全局id生成器来保证id不重复。
分库分表
参考聊聊分库分表
表结构扩展(大表DDL)
DDL操作一定要在业务低峰期做!切记!切记!切记!
Online DDL原理
MySQL 的 DDL(Data Definition Language) 包括增减字段、增减索引等操作。在 MySQL 5.6 之前,MySQL 的 DDL 操作会按照原来的表复制一份,并做相应的修改,例如,对表 A 进行 DDL 的具体过程如下:
- 按照表 A 的定义新建一个表 B
- 对表 A 加 exclusive metadata lock
- 在表 B 上执行 DDL 指定的操作
- 将 A 中的数据拷贝到 B
- 释放 A 的 exclusive metadata lock
- 删除表 A
- 将表 B 重命名为 A
如果表 A 数据量比较大,拷贝到表 B 的过程会消耗大量时间,并占用额外的存储空间。此外,由于 DDL 操作占用了表 A 的exclusive metadata lock,所以表 A 上的 DDL 和 DML 都将阻塞无法提供服务。
MySQL 5.6之后支持了Online DDL。大致过程如下:
- 按照表 A 的定义新建一个表 B
- 对表 A 加 exclusive metadata lock
- 生成rows_log,用于记录期间发生的DML
- 将exclusive metadata lock 降级为shared
- 在表 B 上执行 DDL 指定的操作
- 将 A 中的数据拷贝到 B
- 将rows_log应用到表B
- 将表A的shared metadata lock 升级为exclusive
- 如果前两步之间又有新的数据产生,再次应用row_log的数据到表B
- 释放表A的exclusive metadata lock
- 删除表 A
- 将表 B 重命名为 A
可以看出Online DDL和DDL原理的区别主要在rows_log和metadata lock的降级,这保证了这个过程中最耗时的部分-复制表数据是允许DML操作的。这也是Online的由来。
Online DDL用法
ALTER TABLE tbl_name ADD PRIMARY KEY (column), ALGORITHM=INPLACE, LOCK=NONE;
ALGORITHM=INPLACE 表示执行DDL的过程中server和engine间不发生表copy,过程中允许并发执行DML(INPLACE表的copy是在engine内部执行的,不需要像COPY一样占用大量的磁盘I/O和CPU,减少了数据库负载)。
LOCK=NONE 表示对 DML 操作不加锁,DDL 过程中允许所有的 DML 操作。此外还有 EXCLUSIVE(持有排它锁,阻塞所有的请求,适用于需要尽快完成DDL或者服务库空闲的场景)、SHARED(允许SELECT,但是阻塞INSERT UPDATE DELETE,适用于数据仓库等可以允许数据写入延迟的场景)和 DEFAULT(根据DDL的类型,在保证最大并发的原则下来选择LOCK的取值)
不过并不是所有的 DDL 操作都能用 INPLACE 的方式执行,具体的支持情况可以在 Mysql5.7官方文档中查看。
业界也有开源的成熟的大表DDL工具:gh-ost
注意:Mysql写锁优先于读锁。Online DDL可能存在如下问题。
sessionA
select * from A where id =1;
sessionB
select * from A where id =1;
sessionC online ddl
sessionD
select * from A where id =1;
假如sessionA和sessionB还未提交时,开始online ddl,也就是sessionC。由于sessionA和sessionB持有 shared metadata lock。sessionC申请 exclusive metadata lock会被阻塞。之后sessionD开始,由于Mysql写锁优先于读锁,所以sessionD获取shared metadata lock的请求也会阻塞,直到sessionA, sessionB执行完,sessionC获得exclusive metadata lock,之后释放exclusive metadata lock。
这个问题准确来说不是Online DDL的问题,读写锁的实现机制很多都有这个问题。写下来是为了提醒读者注意。