一条 update 语句的生命历程
无为,多年 MySQL DBA 工作经验,现就职于某知名互联网公司,对 MySQL、 Redis、PostgrepSQL 等主流数据库有一定了解,拥有丰富的一线运维经验。
update test set a=5 where id = 10;
1 Server 层
1.1 连接层
1.2 分析器
1.3 优化器
1.5 执行器
二 、InnoDB 引擎层
2.1 事务执行
读取数据页面 进入 InnoDB 引擎层后,首先会判断该 SQL 涉及到的数据页是否存在于 BP(buffer pool)中; 如果不存在则通过 B+Tree 读取到磁盘的数据页,然后加载到 BP: 通过二分法查找该页对应的记录 通过 space id 和 page no 哈希计算之后把 索引页加载到指定的 buffer pool instance 中 判断 free list 是否存在可用空闲页( Innodb_buffer_pool_pages_free、 Innodb_buffer_pool_wait_free ),没有则淘汰脏页或者 lru list 的 old 页 把数据页 copy 到 free list 中,然后加载到 lru list 的 old 区的 midpoint(头部) 加锁 尝试给对应行记录加上排他锁,过程如下: 对应行记录的行锁是否被其他事务占用,占用则进入锁等待; 进入锁等待之后,同时判断会不会由于自己的加入导致了死锁; 检测到没有锁等待和不会造成死锁之后,行记录加上排他锁; 写逻辑 undo log 将修改前的记录写入undo中; 修改当前行的值,填写事务编号; 使用回滚指针指向 undo log 中的修改前的行,构建回滚段,用于回滚数据和实现 MVCC 的多版本。 写 redo log buffer 先判断 redo log buffer 是否够用,不够用则等待,可通过 Innodb_log_waits 值查看; 对应行记录的字段值做更新操作,并把修改操作记录到 redo log buffer 中; 对应数据页面加入 flush list 链表中。 写 binlog cache 修改的信息会以对应 event 格式写入 binlog cache 中。 写 change buffer 如果此次 update 操作涉及到二级索引的修改,则写入 change buffer page 。
2.2 事务提交
redo log prepare 将 redo log buffer 刷新到磁盘文件中,用于崩溃恢复;刷盘的方式由 innodb_flush_log_at_trx_commit 决定(未标记commit),存储引擎层处于 prepare 状态. binlog write & fsync 执行器把 binlog cache 里的完整事务和 redo log prepare 中的 XID event 写入到 binlog 中; 发送 binlog_cache 里的 event 到 slave 并等待(异步模式不等待) slave ack ; 执行 fsync 刷盘(大事务的话这步非常耗时),并清空 binlog cache;# binlog 刷盘的方式由 sync_binlog 决定。 redo log commit commit 阶段,由于之前该事务产生的 redo log 已经 sync 到磁盘了,所以这步只是在 redo log 里标记 commit,表明事务提交成功。半同步模式下如果收不到 slave ack 此步骤会处于等待状态。 事务提交成功,释放行记录持有的排他锁。 刷新脏页 数据库按照一定的策略执行刷脏页的操作。
2.3 事务回滚
对于 in-place(原地)更新,将数据回滚到最老版本; 对于 delete + insert 方式进行的,标记删除的记录清理删除标记,同时把插入的聚集索引和二级索引记录也会被直接删除。
评论