MySQL高性能优化思路
- 读写分离
- 分库分表
读写分离
为什么要读写分离?
写负载是不能够分担的,而且只能在主库上进行写操作。读操作主从上都可以。为了分担主库的读负载,就需要进行读写分离,写操作只能在主库上进行,而读操作尽量的在从库上完成。
而完成读写分离后,对于读操作,如何分配多个服务器的读操作,这就需要我们进行读的负载均衡了。
目前实现读写分离有两种方式:
- 由程序实现读写分离;
- 由中间件实现读写分离,例如 mysql-proxy(高并发下存在一定的问题)、maxScale(相对来说性能损耗小);
优缺点对比
- | 优点 | 缺点 |
---|---|---|
由程序实现读写分离 | 1、由开发人员控制什么样的查询在从库中执行,比较灵活; 2、由程序直接连接数据,性能损耗比较少; | 1、增加了开发的工作量,使程序代码更加复杂; 2、人为控制,容易出现错误; |
由中间件实现读写分离 | 1、由中间件根据查询语法分析,自动完成读写分离; 2、对程序透明,对于已有程序不用做任何调整; | 1、由于增加了中间层,所以对查询性能有损耗(经测试,QPS可能降低50%-70%); 2、对于延迟敏感的业务,无法自动在主库执行,不灵活; |
分库分表
随着业务的不断增长,数据库中的数据也会越来越多,数据库的压力会越来越大,我们会发现,在业务繁忙的时候,数据库的性能会直线下降,这时为了保证良好的性能,需要想办法分担数据库的压力。分担数据库的读负载可以使用主从复制的方式,增加只读从数据库,通过读写分离的方式把数据库的读负载分担到不同的从数据库中,这时在一段时间内已经可以解决问题了。随着业务的发展,会发现,单一的主数据库已经无法承担写负载了,那么这时就需要对单一的主数据库进行拆分了。
数据库分片
为了解决上面的问题,我们需要对一个库中的相关表进行水平拆分到不同实例的数据库中,即需要进行分片处理,我们通常所说的分库,大多数情况下指的就是这种方式。
数据库分片前的准备:
在进行数据库分片前,最重要的一项工作就是如何选择分区键。分区键决定了我们如何对数据库进行分片,以及分片后如何查询数据。分区键选择的是否合适直接决定了分区后数据库的性能,对于分区键的选择,我们应该做到以下几点:
- 分区键要能尽量避免跨分片查询的发生;
- 分区键要能尽量使各个分片中的数据平均。
如何存储无需分片的表?
- 方案一:每个分片中存储一份相同(冗余)的数据。这种方式可以更好的提高查询效率。如果使用这种方式,对于维护每个分片中相同表的一致性就显得非常重要了,一般可以采用多写的方式维护数据;
- 方案二:使用额外的节点统一存储。好处是不存在数据冗余问题。如果分片的表与统一存储的表需要关联查询时,就只能由程序分别查询后进行合并操作了,查询效率比方案一要差一些。
如何在节点上部署分片?
- 方案一:每个分片使用单一数据库,并且数据库名也相同;
- 方案二:将多个分片表存储在一个数据库中, 由于数据的表不能重名,所以需要在表名上加入分片号后缀;
- 方案三:在一个节点中部署多个数据库,每个数据库包含一个分片。
如何分配分片中的数据?
- 方案一:按分区键的 Hash 值取模来分配分片数据。优点是可以相对平均的,在各个分片中分配数据,缺点是很难人为的控制什么样的数据分配到哪个分片中;
- 方案二:按分区键的范围来分配分片数据。常用于分区键是日期类型或数值类型的情况,优点是可以很清楚的知道某条数据分配到了哪个分片中,缺点是很容易产生数据分配不平均和数据访问量不平均的情况;
- 方案三:利用分区键和分片的映射表来分配分片数据,这张映射表需要使用缓存的方式进行缓存,否则这张映射表就可能会成为系统的瓶颈。
如何生成全局唯一ID?
- 方案一:使用 auto_increment_increment 和 auto_increment_offset 参数;
- 方案二:使用全局节点来生成ID,比如雪花ID
- 方案三:使用 Redis 等缓存服务器中创建全局 ID。
全局ID生成策略?
简言之:数据库存2个字段 max_id(当前缓存ID最大值)、 step(每次获取的ID个数),当服务器A启动或者ID使用完时去更新数据库 max_id = max_id+step,本地缓存maxID=max_id
本地缓存currentId表示当前服务器生成的ID,每次生成的时候判断currentID是否小于maxID,小于则生成ID并更新currentID,不足时请求数据库获取新的最大值缓存ID。
参考:美团全局ID生成策略