内容提纲
一、简介
二、Update 生命周期
2.1 连接器
2.2 分析器
2.3 优化器
2.4 执行器
2.4.1 事务执行阶段
2.4.2 事务提交阶段
2.4.3 假设事务 ROLLBACK
三、影响事务提交延迟的几种情况
四、Update 更新慢的排查思路
五、常见问题
在开始排错Update之前,我们需要知道 Update 在 MySQL 中的生命周期是什么,MySQL 如何执行一个事务的。理解了如何执行,才知道如何去排查故障。
Server 层阶段
客户端发起一个 TCP 请求后,MySQL Server 端会负责通信协议处理、线程处理、账号认证、安全检查。
MySQL Server 端对一个 SQL 请求进行词法分析(识别 select、from),然后会对语法 进行分析判断语法是否正确。
优化器会分析 SQL 语句,选择合适的索引,根据预结果集判断是否使用全表扫描。
InnoDB引擎层阶段
1) 请求进入 InnoDB 引擎后,首先判断该事务涉及到的数据页是否在 BP 中,不存在则会从磁盘中加载此事务涉及的数据页到 BP 缓冲池中,并对相应的索引数据页加锁
> 思考
数据是如何从磁盘加载到 BP 中的?
BP 中的新老生代是如何交替及回收?
如何对相应数据加锁?
解答: 通过 B+Tree 读取到磁盘的索引页加载到 BP 缓冲池中。
1、通过 space id 和 page no 哈希计算之后把索引页加载到指定的 buffer pool instance 中。
2、判断 Free List 是否有空闲页可用(innodb_buffer_pool_pages_free、innodb_buffer_pool_wait_free),没有 则淘汰脏页或 LRU List 的 old。
3、将数据页加载到Free List 中,然后加载到 LRU List 的 old 区的 midpoint(头部)。
4、通过二分查找法,找该页对应的记录,试图给该事物涉及到的行记录加上排他锁。
判断该事物当前记录的行锁被其他事物占用的话,需要进入锁等待。
进入锁等待后,同时判断会不会由于自己的加入导致了死锁。
检测到没有锁等待和不会造成死锁后,行记录加上排他锁。
2) 将修改前的数据写入到 Undo 中,修改后将回滚针执行 Undo log 中修改前的行
> 思考
为什么要写Undo 日志?
Undo 的存储方式是什么?
解答: Undo log 一般是逻辑日志,记录每行记录。有两个作用:提供回滚和 MVCC。
事务因为某些原因需要回滚时,可以借助 Undo 日志进行回滚,保证事务的一致性 在事务的不同隔离级别需要通过Undo log 实现。
当读取某一行加锁的数据时,可以通过 Undo log 实现(比 如:RR 级别),事务不结束,Undo log 就不删除。
Undo log 的存储方式是用段(segment)记录在表空间中:
InnoDB 存储引擎对 Undo 采用段方式管理,rollack segment 称为回滚段,每个回滚段有 1024 个 。
Undo log segment,5.6 之后可以通过 innodb_undo_logs 自定义多少个回滚段,默认 128 个。
Undo log 默认存储在共享表空间中,开启了 innodb_file_per_table 将存在独立表空间中。
3) 写 redo log buffer 在 BP 中对数据进行修改操作,并将修改后的值写入到 redo log buffer 中等待异步 sync到磁盘
> 思考
什么时候写入 redo log buffer?
commit 后 log buffer 如何落盘到 redo log?
日志刷盘规则是什么?
解答: 什么时候写入 redo log buffer:
1、先通过状态值 Innodb_log_waits 判断 redo log buffer 是否够用,不够用就等待。
2、在 BP 缓冲池的 LRU List 中 old 区的 midpont 中对改数据页的行记录的字段值做更新操作。
3、把修改之后的字段值写入到redo log buffer 中,并给 LSN 加上当前 redo log 写入的长度(写入长度为length 的 redo log,LSN 就会加上 length)。
4、因为 redo group commit,事务所产生的 redo log buffer 可能会和其他事务一同 flush 并且 sync 到磁盘上。
5、字段值在 BP 缓冲池更新成功后,对应的数据页就是脏页。
什么时候落盘到 redo log 中:
1、每次会将 log buffer 中的日志写入到 log file(这里指 os buffer),然后在调用系统的 fsync 操作进行落盘。
在 commit 之后,通过 innodb_flush_log_at_trx_commit 来决定什么时候将 log buffer 刷盘。
2、值为 1(默认为 1):事务每次提交会写入 log buffer–>然后写入 os buffer—>调用系统 fsync 刷到 log file on disk。
3、值为 0:事务提交是先写入 log buffer–>每秒写入 os buffer 并调用 fsync 落盘(最多丢失 1s 数据)。
4、值为 2:每次提交只写入 os buffer,然后每秒调用 fsync()将 os buffer 的日志写入到 log file on disk(最多丢失 1s 数据)。
日志刷盘规则:
默认情况下事务每次提交会刷盘,是因为 innodb_flush_log_at_trx_commit 的值为 1。
这只是 InnoDB 在有 commit 动作后才会将日志刷盘,属于 InnoDB 存储引擎刷盘规则之一。
日志刷盘的几种规则 :
1、发出 commit 动作之后。由 innodb_flush_log_at_trx_commit 控制。
2、每秒刷一次,刷新频率由 innodb_flush_log_at_timeout 值决定,默认为 1,刷日志频率与 commit 动作无关。
3、当 log buffer 中使用内存超过一半。
4、当有 checkpoint 时,checkpoint(数据页刷盘)在一定程度上代表刷盘时日志所处的 LSN 位置。
Checkpoint 刷盘规则:
InnoDB 中,数据刷盘的规则只有一个:checkpoint,触发 checkpoint 后,会将 BP 中脏数据和脏日志页都刷新到磁盘。
Checkpoint 分为两种:
Sharp checkpoint:在重用 redo log 文件时(切割日志),将所有记录到 redo log 中对应的脏数据刷新到磁盘。
Fuzzy checkpoint:一次刷新一小部分日志到磁盘,并非所有脏日志。
1、master thread checkpoint:master 线程每秒或每 10 秒刷一定比例脏页到磁盘。
2、Flush_lru_list checkpoint:5.6 之后通过 innodb_page_cleaners 变量指定 page cleaner 线程个数。
3、Async/sync/ flush checkpoint,同步刷盘还是异步刷盘。
4、Dirty page too much checkpoint :脏页怠惰强制触发检查点,保证缓存空间空闲,由变量innodb_max_dirty_pages_pct 控制。
4) 写 binlog cache
同时将修改的信息按照 event 格式记录到 binlog_cache 中,等待落盘。
如果 binlog cache 不够用时,会写入到 binlog 临时文件。
> 思考
事务 binlog event 的写入流程是什么?
解答: 一旦有事务提交,binlog cache 和 binlog 临时文件都会释放(已经写入 binlog file) 同一事务包含多个 DML 会共用同一个 binlog cache 和 binlog 临时文件。
1、事务开启。
2、执行 dml 语句,dml 语句第一次执行时会分配 binlog cache。
3、执行 dml 语句期间生成的 event 不断写入 binlog cache。
4、binlog cache 满了事务还没执行完,会将 binlog cache 中的数据写入到 binlog 临时文件同时清空 binlog cache,临时文件大小大于 max_binlog_cache_size 则报 error 1197。
5、事务提交,整个 binlog cache 和 binlog 临时文件数据全部写入 binlog file,释放 binlog cache(IO_CACHE) 和 binlog 临时文件 binlog 临时文件大小为 0,保留文件描述符。
6、断开连接,释放 IO_CACHE。
5) 写 change buffer
如果这个事务需要在二级索引上做修改,写入到 change buffer page 中,等待之后,事务需要读取该二级索引时进行 merge。
> 思考
什么时候会用到 change buffer?
为什么仅适用于普通索引页?
哪些场景会触发刷新 change buffer?
什么业务不适合/适合开启 change buffer?
change buffer 相关参数有哪些?
解答:
什么时候会用到 change buffer。
MySQL 5.5 之前叫 insert buffer,只针对 insert,之后叫 change buffer 对 delete 和 Update 也有效。
在对普通索引数据页不在 BP 中,对页进行写操作,不会将磁盘数据加载到缓冲池中,仅仅记录缓冲变更(可以理解为只记录操作变更,不做真实数据操作)。
等待数据被读取时,将数据 merge 到 BP 中,目的是降低写操作磁盘 IO,提高性能。
为什么仅适用于普通索引页?
唯一索引或主键索引每次修改操作时,InnoDB 必须进行唯一性检查。
即使索引页不在缓冲池,也会去读取磁盘页,一次随机 IO(通过 B+tree 查找数据页),一次顺序 IO(写 redo log)避免不了。
哪些场景会触发刷新 change buffer:
1、数据页被访问。
2、master thread 每隔 10s 会进行操作。
3、数据库 BP 不够用时。
4、数据库正常关闭时。
5、redo log 写满时(几乎不会出现,redo log 被写满数据库处于无法写入状态)。
什么业务不适合/适合开启 change buffer:
-不适合:
1、数据库都是唯一索引。
2、写入一个数据后,立刻读取上述场景,在写操作之后,本来就要进入 BP 中,此时 change buffer 反而成了累赘。
适合:
1、数据库大部分是非唯一索引。
2、业务是写多读少,或者写后不是立刻读。
3、读写分离下主库可以使用。
可以使用 change buffer,减少一次随机 IO,优化定期批量写磁盘。
change buffer 相关参数有哪些:
show global variables like ‘%innodb_change_buffer%’;
innodb_change_buffer_max_size;
配置写缓冲的大小,占整个缓冲池的比例,默认值是 25%,最大值是 50%(写多读少才需调大,读多写少 25%就够)。
innodb_change_buffering;
配置那些写操作启用写缓冲,可以设置成 all/none/inserts/deletes 等。
打开 binlog 选项之后,执行事务提交会进入二阶段提交模式(prepare 阶段和 commit 阶段。
两阶段涉及两个参数(sync_binlog和innodb_flush_log_at_trx_commit)。
1) 事务提交分为 prepare 阶段与 commit 阶段(两阶段提交)
事务的 commit 操作在存储引擎和 server 层采用内部 XA。
两阶段提交协议保证事务的一致性,主要保证 redo log 和 binlog 的原子性。
2) Redo log prepare
写入 redo log 处于 prepare 阶段,并且写入事务的 xid。
将redo log buffer刷新到redo log磁盘文件中,用于崩溃恢复。
刷盘的方式由innodb_flush_log_at_trx_commit 决定。
3) Binlog write&fync: 执行器把 binlog cache 里的完整事务和 redo log prepare 中的 xid 写入到 binlog 中
Dump 线程会从 binlog cache 里把 event 主动发送给 slave 的 I/O 线程,同时执行 fsync 刷盘(大事务的话比 较耗时)并清空 binlog cache。
Binlog 刷盘的方式由 sync_binlog 决定。binlog 写入完成,事务就算成功。
总结:
事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写入到 binlog file 中。
当 sync_binlog 为 1 时,binlog 落盘后才会通知 dump thread 进行主从复制。
4) Redo log commit commit
提交阶段中,该事务产生的 redo log 已经 sync 到磁盘中,在 redo log 里标记 commit,说明事务提交成功。
5) 事务提交,释放行记录持有的排它锁
6)Binlog 和 redo log 落盘后触发刷新脏页操作
先把该脏页复制到 doublewrite buffer 里,再把 dobulewrite buffer 里的数据,刷新到共享表空间(ibdata),然后脏页刷新到磁盘中,此时内存页和数据页一致。
> 思考
BP 中的脏页刷盘机制是什么?
解答: 当 InnoDB 中脏页比例超过 innodh_max_dirty_pages_pct_lwm 的值时,开始刷盘。
因为系统异常或显示回滚,所有数据变更会变成原来的,通过回滚日志中数据进行恢复。
对于 in-place(原地)更新,将数据回滚到最老版本;
对于 delete+insert 方式,标记删除的记录清理删除标记,同时把插入的聚集索引和二级索引记录也会删除。
在事务执行阶段:
(1)RR 模式下 insert 锁等待 gap lock 锁等待导致;
(2)Insert 等待 MDL 锁导致;
(3)Table lock。
(1)慢 sql 导致 io 高;
(2)其他程序占用比价高;
(3)BP 命中率比较低;
(4)并发导致;
(5)innodb buffer pool 不够用;
(6)Update、delete 更新数据行数大(>W)。
(1)redo log buffer 是否够用通过 Innodb_log_waits 确认。
(2)Redo log buffer 刷盘方式通过 innodb_flush_log_at_trx_commit。
(3)Binlog cache 是否够用,创建临时文件、消耗 IO。
(4)Change buffer 是否够用。
(1)sync_binlog 参数。
(2)binlog_group_commit_sync_delay 参数。
(3)innodb_flush_commit 参数。
(4)查看 innodb_buffer_pool 的命中率,查看脏页刷新频率效果。
排查思路:
(IO、CPU、memory),排除性能干扰
如果 CPU 高、IO 高、wa 大:先排查慢 SQL,再查当前并发数,一般是大量并发慢 SQL 导致;
如果 CPU 高、IO 中、wa 小:排查慢 SQL,在查看当前并发数,一般是单个计算 SQL 导致;
如果 CPU 低、IO 高、wa 低:排查当前占用 io 高的线程,有可能是 page clean 导致或日志刷新频繁导致。
查看 mysql porcesslist,查看当前是否有 wait lock(表锁,行锁,meata lock 等);
查看 mysql processlist,是否有大量 send data、init、commit、clean up 状态;
查看 mysql processlist,计算并发,排查是否有并发压力;
查看 innodb buffer pool 命中率,排查 buffer 是否够用;
查看 mysql tmp,是否够用,open tables 是否等于 table_open_cache。
通过 explain 分析 SQL 的执行情况,是否走索引,是否存在 union;
通过 explain 分析 SQL 的执行情况,是否存在大表驱动小表,多表 join;
检查 SQL 是否存在产生额外临时表;
使用 profile 分析单条 SQL 语句。
观察是单个 SQL 执行慢,还是所有语句都慢;
慢的 SQL 的时间是否有规律,有助于排查 MysSQL 的相关参数。
使用 tcpdump 进行抓包,分析是 MySQL 返回慢,还是网络慢;
使用 strace 分析 MySQL 内部哪里慢,哪个函数导致的。
1、Update 全表更新一个字段,数据量为 10w,更新特别慢;
2、Update 引起死锁问题;
3、Update 几百条数据消耗了 10s;
4、Update 同一个表,有些更新快,有些更新慢。
Update的问题还不止于此,通过阅读本篇文章,相信您对如何发现、排查、解决Update可能引发的问题,有了更进一步的认识。
在本小节中,列举了部分常见的案例,希望对您学习有所帮助。此外,也推荐您自己动手搭建测试环境,发现新的问题。
Enjoy GreatSQL 😃
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69997641/viewspace-2785171/ ,如需转载,请注明出处,否则将追究法律责任。