MySQL主从,分库分表以及调优

206 阅读10分钟

MySQL主从架构

主从架构即设置主机和从机实现读写分离,提高数据库吞吐量,同时从机可以作为主机的数据备份,但是主从结构也会带来一些问题。

主从同步

主机和从机的数据要保持同步就必须执行主从复制,主从复制依靠三个线程实现:

  • 数据更新时,主库将操作和数据写入binlog,主库的binlog dump线程将binlog日志发送给从库
  • 从库的IO线程负责向主库请求binlog并写入自己的中继日志。
  • 从库的SQL线程读取中继日志将更新操作写入数据库。

主从延迟

使用主从架构不可避免会带来主从延迟问题,即在某一时刻,主机与从机的数据存在一定的差异,这是因为主从数据同步的过程是异步的,不保证强一致性。正常情况下,主从延迟一般不会很大,但是如果某些SQL执行时间过长或某个事务锁表时间过长,就会导致较大程度的主从延迟。

  • 使用半同步复制确保主从延迟不会导致数据丢失
    • MySQL默认使用的是异步复制,主机执行完事务后直接提交,不管从机是否接收到同步数据。
    • 开启半同步复制,主机执行完事务后至少要收到一个从机的ack后才认为同步成功,从而提交事务,这样可以确保即使主机宕机数据也不会丢失。
    • 当然,主机等待时间有限制,若超时主机可以选择直接提交。
  • 从机速度跟不上主机时让从机开启并行复制
    • 从机开启多个SQL线程,并行读取relaylog中的日志,加快从机同步数据的速度。可以根据需要在不同表上或在同一个表上的不同行并行(行没有被两个事务同时更新时)。
    • 如果从机承担大量查询请求,也可能会影响同步速度,可以开启多个从机分摊压力。
    • 某个从机和主机的延迟达到阈值就不从这个从机上读取数据,避免读到这个从机的脏数据。
  • 主机限制操作避免主从延迟,降低主从延迟影响
    • 避免大事务,慢SQL,大数据量的操作,因为它们需要锁定资源且执行时间长,导致从机和主机之间的差距被拉大。
    • 实时性要求强的业务强制走主机。
    • 在从机读取数据失败时再去主机读取数据,即二次读取。
  • 要求强一致性时需要放弃使用主从
    • redis中的分布式锁实现redlock,为了避免主从延迟导致同时获取到多把锁,放弃了主从结构,而是使用多个实例共同获取锁的方式。

读写分离如何实现

读写分离可以分类为两种实现方式,分别是程序代码封装方式和中间件封装方式。

  • 程序代码封装:在代码中根据不同条件动态切换数据源。常见实现为使用AOP拦截方法,获取其目标数据源并保存到上下文,执行sql时调整动态路由数据源类选择具体的数据源。但是这种方式存在一些问题:
    • AOP注解只适用于方法级别粒度。如果想要更细粒度,可以使用手动编程的方式。
    • 数据源信息保存在ThreadLocal上下文中,存在开启子线程导致上下文丢失的问题,可以使用InheritableThreadLocal。
    • 不支持嵌套数据源设置,例如方法1指定了数据源,但是它又调用了方法2,方法2也指定了数据源,但是在ThreadLocal中数据源只能指定一次。解决方案是在上下文中设置一个栈,用来保存嵌套的数据源选择方式,即不止保存当前方法的数据源还保存之前方法设置过的数据源。
  • 中间件封装:引入中间件实现读写分离,业务无需关注读写分离,而是直接访问数据库中间件,再由中间件根据一定规则判断去访问具体的数据库。中间件如ShardingSphere。
    • 但是引入中间件由于需要更多次的数据传输,可能会导致性能降低。

MySQL主从结构中master宕机

  • 此时要手动选择一个从库升级为主库。
  • 检查确定所有从库的relaylog都写入完毕(保证主库完整性)。
  • 查看所有从库看哪个从库与主库的数据最接近(主从复制位置最靠后),选为新主库。
  • 修改其配置文件转化为主库,修改其他从库(包括旧主库)的配置文件将其指向新的主库。

MySQL分库分表

什么是分库分表

当一个数据库或一张表的数据量过大时,就需要对其进行分库分表。分库分表又分为水平和垂直两种方式。

  • 分表
    • 水平分表:把一个表的数据分散到多个表中,但是每个表的结构是一样的,只是存放的数据不同,如用户表1,用户表2。
    • 垂直分表:把一个表的部分字段拆分到不同表中,每个表的结构不同,如冷热数据分离(商品表和商品详情表)。
  • 分库
    • 水平分库:将同一个库拆分为多个,如用户库1,用户库2。
    • 垂直分库:按照业务模块划分为不同的库,如用户数据库,商品数据库。

水平分表/分库如何实现

以使用中间件ShardingSphere为例,需要在配置文件中设置分库分表规则,主要配置数据源,定义分片键和分片算法,业务代码逻辑不用变化。

  • 分片键决定根据哪个字段分片,故分片键应该均匀分布避免数据倾斜,并且是经常被读写的字段。
  • 分片算法决定如何根据分片键路由到不同表,常见路由方式有三种:
    • 范围路由:按照某个字段值的范围划分,适用于分片键具有顺序性的场景,但可能数据倾斜。例如时间戳。
    • Hash路由:即对分片字段进行哈希计算然后取模,但扩容麻烦且范围查询可能涉及多个表。
    • 配置路由:即用一个具体的配置表来确定数据存储在哪个表中。

分库分表带来的问题

  • 分库
    • 分库后本地事务就失效了,只能用分布式事务。
    • 分库后就无法使用join进行连表查询了,只能先查出一个表的数据并保存再去另一个表查。
  • 分表
    • 分表后跨表的计数,排序,聚合等操作困难,只能手动汇总数据再操作。
    • 分表后ID就不能再依赖数据库自身主键了,而是需要使用分布式ID生成方案如雪花算法。

MySQL的CPU飙升处理

  1. top命令看哪个进程占用的cpu(并及时kill掉,观察cpu是否下降)。
  2. 如果是mysql进程,show processlist查看当前所有SQL语句执行情况,看哪个SQL语句在消耗资源多,执行时间长。
  3. show processList也可以查看session连接情况,看是否突然有大量请求打到MySQL上,例如缓存失效或瞬时高并发情况。
  4. explain看SQL的执行计划,看索引是否有问题,SQL语句本身是否有问题。
  5. 如果是数据量太大造成SQL查询缓慢,可能需要设置主从或分库分表。

MySQL调优手段

  • SQL语句是否合理

    • 不要用select *查询不需要的字段,会增加网络负担和磁盘IO操作,且无法走覆盖索引导致大量回表操作。
    • 减少使用order by,group by,distinct,union,这些语句较为耗费CPU,数据库的CPU资源很宝贵,最好将这些功能放到程序端做。
    • 避免多表连接(适当增加冗余字段),禁止3个表以上的连接。实在要连接也要优化(小表驱动大表)。
      • 小表驱动大表原理是MySQL在做join操作时会将驱动表加载进缓冲区,遍历驱动表的数据去匹配被驱动表的数据,此时驱动表不用索引,被驱动表用索引,如果将大表加载进缓冲区可能无法一次加载进来,导致多次IO。以左外连接为例(左为驱动表,右为被驱动表),join相当于每次取左表一条数据与右表所有数据进行匹配,应该优先设置右表的数据项作为索引(最好两者都设置索引)。
    • 避免子查询,因为子查询需要创建临时表,且无法使用索引,可以使用join代替子查询。
    • 分页优化
      • 首先对于大表限制查询范围,禁止不带任何限制的查询。
      • 对于深分页问题,例如“select 列名 from 表名 where 条件 limit 10000 10”,需要扫描10010条数据再抛弃前10000条,并且需要10010次回表从而使性能很差。
      • 可以使用延迟关联,即先找出第10000条数据后10条数据的id(使用索引,不回表),再做关联去回表。
      • 可以记住上次分页的最大id,下次查询直接利用id索引到要查找的数据的开始位置,再用limit。
      • 使用between and将其转化为范围查询。
    • 多次操作时可以合并为批量操作,避免多次连接数据库,当然批量操作也不是越多越好。
    • 避免大事务,及时commit释放占用的资源,选择合适的锁和隔离级别,防止产生死锁。
    • 写操作时禁用唯一性检查和外键检查,并且写或当前读时一定要走索引,否则锁表。
  • 是否使用到了索引

    • 使用explain分析,充分使用索引,索引覆盖,索引下推,最左前缀原则等。
    • 避免索引失效,只为合适的列设置索引且不要设置过多索引。
    • 防止加锁语句不走索引锁住全表。
  • 数据量是否过大

    • 分库分表。
    • 主从架构,集群架构。
    • 使用缓存。

MySQL如何实现不停机扩容

  1. 在线双写,查询走老库。即建立好新的库表结构,新数据同时写入老库和新库,然后将老库中的历史数据迁移到新库,定时对比新老库的数据并补齐差异。
  2. 在线双写,查询走新库。完成历史数据的同步和校验后,把读数据切换到新库。
  3. 老库下线。老库不再写入新数据,等老库完全没有请求后就可以下线了。