MySQL实战45讲(21--25)-笔记

21 | 为什么我只改一行的语句,锁这么多?

加锁规则里面:包含了两个“原则”、两个“优化”和一个“bug”。

  1. 原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区
    间。
  2. 原则 2:查找过程中访问到的对象才会加锁。
  3. 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
  4. 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
  5. 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

22 | MySQL有哪些“饮鸩止渴”提高性能的方法?

业务高峰期,生产环境的 MySQL 压力太大,没法正常响应,需要短期内、临时性地提升一些性能。

短连接风暴

正常的短连接模式就是连接到数据库后,执行很少的 SQL 语句就断开,下次需要的时候再重连。

如果使用的是短连接,在业务高峰期的时候,就可能出现连接数突然暴涨的情况。

除了正常的网络连接三次握手外,还需要做登录权限判断和获得这个连接的数据读写权限。

max_connections 参数,用来控制一个 MySQL 实例同时存在的连接数的上限,超过这个值,系统就会拒绝接下来的连接请求,并报错提示“Too many connections”。

第一种方法:先处理掉那些占着连接但是不工作的线程

max_connections 的计算,不是看谁在 running,是只要连着就占用一个计数位置。

对于那些不需要保持的连接,我们可以通过 kill connection 主动踢掉。

这个行为跟事先设置wait_timeout 的效果是一样的。

wait_timeout 参数表示的是,一个线程空闲wait_timeout 这么多秒之后,就会被 MySQL 直接断开连接。

第二种方法:减少连接过程的消耗

有的业务代码会在短时间内先大量申请数据库连接做备用,如果现在数据库确认是被连接行为打挂了,那么一种可能的做法,是让数据库跳过权限验证阶段。

跳过权限验证的方法是:重启数据库,并使用–skip-grant-tables 参数启动。

这样,整个MySQL 会跳过所有的权限验证阶段,包括连接过程和语句执行过程在内。

但是,这种方法特别符合我们标题里说的“饮鸩止渴”,风险极高,是我特别不建议使用的方案。

尤其你的库外网可访问的话,就更不能这么做了。

在 MySQL 8.0 版本里,如果你启用 –skip-grant-tables 参数,

MySQL 会默认把 --skip-networking 参数打开,表示这时候数据库只能被本地的客户端连接。

可见,MySQL 官方对skip-grant-tables 这个参数的安全问题也很重视。

慢查询性能问题

MySQL慢查询就是在日志中记录运行比较慢的SQL语句,这个功能需要开启才能用。

在 MySQL 中,会引发性能问题的慢查询,大体有以下三种可能:

  1. 索引没有设计好;

  2. SQL 语句没写好;

  3. MySQL 选错了索引。

索引没有设计好

这种场景一般就是通过紧急创建索引来解决。

MySQL 5.6 版本以后,创建索引都支持 Online DDL 了,

对于那种高峰期数据库已经被这个语句打挂了的情况,最高效的做法就是直接执行alter table 语句。

比较理想的是能够在备库先执行。假设你现在的服务是一主一备,主库 A、备库 B,这个方案的
大致流程是这样的:

  1. 在备库 B 上执行 set sql_log_bin=off,也就是不写 binlog,然后执行 alter table 语句加+索引;
  2. 执行主备切换;
  3. 这时候主库是 B,备库是 A。在 A 上执行 set sql_log_bin=off,然后执行 alter table 语句
    加上索引。

这是一个“古老”的 DDL 方案。

平时在做变更的时候,你应该考虑类似 gh-ost 这样的方案,更加稳妥。

但是在需要紧急处理时,上面这个方案的效率是最高的。

语句没有写好

我们可以通过改写 SQL 语句来处理。MySQL 5.7 提供了 query_rewrite 功能,

可以把输入的一种语句改写成另外一种模式。

MySQL 选错了索引

应急方案就是给这个语句加上 force index。

QPS 突增问题

Queries Per Second,意思是“每秒查询率

一类情况,是由一个新功能的 bug 导致的。

当然,最理想的情况是让业务把这个功能下掉,服务自然就会恢复。

  1. 一种是由全新业务的 bug 导致的。

    假设你的 DB 运维是比较规范的,也就是说白名单是一个个加的。

    这种情况下,如果你能够确定业务方会下掉这个功能,只是时间上没那么快,那么就可以从数据库端直接把白名单去掉。

  2. 如果这个新功能使用的是单独的数据库用户,可以用管理员账号把这个用户删掉,

    然后断开现有连接。这样,这个新功能的连接不成功,由它引发的 QPS 就会变成 0。

  3. 如果这个新增的功能跟主体功能是部署在一起的,那么我们只能通过处理语句来限制。

    这时,我们可以使用上面提到的查询重写功能,把压力最大的 SQL 语句直接重写成"select 1"。

当然,这个操作的风险很高,需要你特别细致。

它可能存在两个副作用:

  1. 如果别的功能里面也用到了这个 SQL 语句模板,会有误伤;

  2. 很多业务并不是靠这一个语句就能完成逻辑的,所以如果单独把这一个语句以 select 1 的结果返回的话,可能会导致后面的业务逻辑一起失败。

所以,方案 3 是用于止血的,跟前面提到的去掉权限验证一样,应该是你所有选项里优先级最低的一个方案。

23 | MySQL是怎么保证数据不丢的?

只要 redo log 和 binlog 保证持久化到磁盘,就能确保 MySQL 异常重启后,数据可以恢复。

探究 MySQL 写入 binlog 和 redo log 的流程。

binlog写入机制

binlog 的写入机制binlog 的写入逻辑比较简单:

事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。

问题来了:一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。这就涉及
到了 binlog cache 的保存问题。

系统给 binlog cache 分配了一片内存,每个线程一个,

参数 binlog_cache_size 用于控制单个线程内 binlog cache 所占内存的大小。

如果超过了这个参数规定的大小,就要暂存到磁盘。

事务提交的时候,执行器把 binlog cache 里的完整事务写入到 binlog 中,并清空 binlog cache。

可以看到,每个线程有自己 binlog cache,但是共用同一份 binlog 文件。

图中的 write,指的就是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快。

图中的 fsync,才是将数据持久化到磁盘的操作。一般情况下,我们认为 fsync 才占磁盘的IOPS。

write 和 fsync 的时机,是由参数 sync_binlog 控制的:

  1. sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;

  2. sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;

  3. sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。

因此,在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。

在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。

但是,将 sync_binlog 设置为 N,对应的风险是:如果主机发生异常重启,会丢失最近 N 个事务的 binlog 日志。

redo log 的写入机制

事务在执行过程中,生成的redo log 是要先写到 redo log buffer 的。

这三种状态分别是:

  1. 存在 redo log buffer 中,物理上是在 MySQL 进程内存中,就是图中的红色部分;
  2. 写到磁盘 (write),但是没有持久化(fsync),物理上是在文件系统的 page cache 里面,也就是图中的黄色部分;
  3. 持久化到磁盘,对应的是 hard disk,也就是图中的绿色部分。

日志写到 redo log buffer 是很快的,wirte 到 page cache 也差不多,但是持久化到磁盘的速度就慢多了。

为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,

它有三种可能取值:

  1. 设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ;
  2. 设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;
  3. 设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。

InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。

注意,事务执行中间过程的 redo log 也是直接写在 redo log buffer 中的,

这些 redo log 也会被后台线程一起持久化到磁盘。也就是说,一个没有提交的事务的 redo log,也是可能已经
持久化到磁盘的。

实际上,除了后台线程每秒一次的轮询操作外,还有两种场景会让一个没有提交的事务的 redolog 写入到磁盘中。

  1. 一种是,redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。

注意,由于这个事务并没有提交,所以这个写盘动作只是 write,而没有调用 fsync,也就是只留在了文件系统的 page cache。

  1. 另一种是,并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘。

两阶段提交,时序上 redo log 先 prepare, 再写binlog,最后再把 redo log commit。

更新中…………(这里有地方没有理解)

24 | MySQL是怎么保证主备一致的?

MySQL 主备的基本原理

在状态 1 中,虽然节点 B 没有被直接访问,但是我依然建议你把节点 B(也就是备库)设置成只读(readonly)模式,

防止一些误操作

与主库保持同步更新,readonly 设置对超级 (super) 权限用户是无效的,而用于同步更新的线程,就拥有超级权限。

备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。

binlog 的三种格式对比

更新中…………

循环复制问题

binlog 的特性确保了在备库执行相同的 binlog,可以得到与主库相同的状态。

因此,我们可以认为正常情况下主备的数据是一致的。

上面俩个圈的图我们称作“双M结构”

双 M 结构还有一个问题需要解决。

如果节点 A 同时是节点 B 的备库,

相当于又把节点 B 新生成的 binlog 拿过来执行了一次,

然后节点 A 和 B 间,会不断地循环执行这个更新语句,也就是循环复制了。

这个要怎么解决呢?


我们可以用下面的逻辑,来解决两个节点间的循环复制的问题:


1. 规定两个库的 server id 必须不同,如果相同,则它们之间不能设定为主备关系;

2. 一个备库接到 binlog 并在重放的过程中,生成与原 binlog 的 server id 相同的新的binlog;

3. 每个库在收到从自己的主库发过来的日志后,先判断 server id,如果跟自己的相同,
   表示这个日志是自己生成的,就直接丢弃这个日志。

25 | MySQL是怎么保证高可用的?

主备延迟

主备延迟最直接的表现是,备库消费中转日志(relay log)的速度,比主库生产binlog 的速度要慢。

来源

  • 备库所在机器的性能要比主库所在的机器性能差。

主好从弱,更新请求对 IOPS 的压力,在主库和备库上是无差别的,所以不好。

这种部署现在比较少了。因为主备可能发生切换,备库随时可能变成主库,所以主备库选用相同规格的机器,并且做对称部署

  • 备库的压力大

主库直接影响业务,大家使用起来会比较克制,反而忽视了备库的压力控制。

这种情况,我们一般可以这么处理:
1. 一主多从。除了备库外,可以多接几个从库,让这些从库来分担读的压力。
2. 通过 binlog 输出到外部系统,比如 Hadoop 这类系统,让外部系统提供统计类查询的能力。
  • 大事务

主库上必须等事务执行完成才会写入 binlog,再传给备库,如果主库的的执行时间太长,延迟就有了。

可靠性优先策略

备注:图中的 SBM,是 seconds_behind_master 参数的简写。

可用性优先策略

  1. 使用 row 格式的 binlog 时,数据不一致的问题更容易被发现。

而使用 mixed 或者statement 格式的 binlog 时,数据很可能悄悄地就不一致了。

如果你过了很久才发现数据不一致的问题,很可能这时的数据不一致已经不可查,或者连带造成了更多的数据逻辑不一致。

  1. 主备切换的可用性优先策略会导致数据不一致。

因此,大多数情况下,我都建议你使用可靠性优先策略。

毕竟对数据服务来说的话,数据的可靠性一般还是要优于可用性的

我这里只是一个自己的学习笔记,大家有兴趣一定去看原文!!! 谢谢大家的阅读!!

大家有兴趣一定去看原文,这只是我自己的一个笔记总结!!
大家有兴趣一定去看原文,这只是我自己的一个笔记总结!!
大家有兴趣一定去看原文,这只是我自己的一个笔记总结!!

posted @ 2021-09-09 10:19  Ricardo_ML  阅读(77)  评论(0编辑  收藏  举报