注:本博客参照b站视频 深入解析update语句
文章目录
样例
strat transaction;
update bilibili set status= '关注+一键三连' where upname='maple';
commit;
执行以上sql语句打给分为以下9步
- 开启事务
- sql解析,查询计划生成
- 查询要修改的 数据
- 校验锁 和 加锁
- 修改数据和生成日志
- 本地提交
- 主备复制
- 返回提交成功
- 脏页刷入磁盘
分析这9步前先了解mysql的结构
mysql结构
开启事务
mysql默认情况下 autocommit=1,会隐式开启事务 如果显式开启事务(begin/start transaction)或者set autocommit=0
则需要手动commit提交事务或者回滚rollback
sql解析,查询计划生成
开启事务后,在执行第一个sql时会给事务一个单调递增的事务号。
mysql服务器层接收到sql后会生成执行计划(AST树)
查询缓存在mysql5.7已经禁用,在 8.0被删除
这一步不是重点
查询要修改的数据
buffer pool
缓冲池中的三条链的关系
在缓冲池找到所需的页:
哈希表
Innodb将页号当作key 页 在存储空间的位置作为value以实现快速检索
这里 的hash与Innodb的自适应哈希索引不同
自适应哈希索引时直接索引到数据的
对磁盘和buffer pool的读取都是以页为单位的
Innodb,现根据根节点页号和表空间号获取到
根节点,解析根节点 索引页找到下一层根节点
直至找到目标页(B+树索引)在查找时会对B+树和其中的页加latch(闩锁),实现并发控制
页的结构
在一个页中查找数据的过程,是先通过二分查找法查找Page Dictory定位到数据所在的组
再找这个组中 最小的记录,再通过记录的next_record属性向后遍历
校验锁 和 加锁
mysql中的锁
这里不讨论上述的B+树的闩锁
在元数据锁处若有增加索引,建分区 的DDL语句,则会被阻塞,这个时间很短,一般不会等锁超时
行级锁
如果 其上有其他锁,则会等待,直到锁释放后会有一个线程来唤醒它。
如果没有锁,则会生成锁内存结构上锁
假设隔离级别可重复读:加行锁
生成的锁结构:
修改数据和生成日志
修改时会写三部分内容
缓冲页/数据页(修改数据的本体)
若增加的字符超过页的最大容量,则涉及页的分裂
数据页的改写改了以下三点
status,事务号,undolog指针
undo log
记录旧值,用于MVCC(多版本并发控制)和回滚
版本链
查询时查询语句在查询前会生成一个ReadView(含活跃的trx_id
),和查找到的数据trx_id进行比较,若当前事务的trx_id为活跃的或还未被分配的,就可以根据roll_pointer找到上一条undo log在进行比较,直至找到所需的tre_id(及比trx_id最小的还要小,不活跃的)。
这里可以理解mysql的RR(repeatableRead)和RC(readCommited)隔离级别的差异
RC隔离级别下每次sql都生成一个新的ReadView,而RR只生成一次。
redo log
记录的是事务T修改了x元素,修改后的值为u的 一个<T,x,u>三元组
起到Write Ahead的作用,保证数据页和undo log的安全
结构
磁盘写入
LSN时一组redo log的结尾。
注:sql执行完,事务提交前redo log并不需要落盘。
本地提交
提交时不仅要落盘redo log还有落盘服务器曾的binlog
这里主要关注binlog
binlog在主备复制的逻辑和实现难度相比于redo log低,但是也占用了空间内存。
具体落盘策略
默认每次提交会把binlog 和redo log都落盘
主备复制
目前mysql的binlog日志基本都是row
通过binlog实现主从复制
主从复制三种策略
用的最多的是半同步复制
如果半同步复制一直没有响应吗,
超过等待时间后专为异步复制,
直接提交事务
返回提交成功
脏页刷入磁盘
mysql落盘时要将页刷入磁盘,出现问题时
可以现根据double中的脏页恢复为原来的数据,再通过redo log恢复数据