• mysql5.7事务的原理和MVCC,redo log与bin log的区别


    WAL机制 write ahead logging  预写日志,写完日志,再写入实际数据文件。

    redo log  保证事务的持久性
    undo log 保证事务的一致性

    redo日志记录内容:表空间id,页id,页面上的偏移量,偏移量改了什么值,这样记录空间就很小。而且记录是一条一条产生的,是一个顺序IO。而insert、update、delete都是随机IO。redo type大概有53种。

    每逢row id是256的倍数时,就会写入系统表空间第7号页面的某个位置,位于information库的。因为redo type很多,所以redo日志格式会有不同,比如MLOG_8BYTE redo日志格式:

    举例:在0号表空间(space ID)7号页面(page Number)200的位置(offset)写入一个值1024(data)。每个row id都占据8个字节,因为是对8个字节的修改,所以type是MLOG_8TYPE,如果是修改一个字节,那么就是MLOG_1TYPE等等,MLOG_WRITE_STRING表示修改的是string。

    当fsync函数有返回值时,mysql才会确确实实把数据落盘到磁盘上,做持久化了。
    show variables like 'innodb_flush_log_at_trx_commit';  --默认值是1,表示当事务提交时,确保redo日志落盘到磁盘。值为2的话,只能保证写到缓冲区,但是日志能不能落盘不保证。值为0的话,事务提交时,不立即提交redo日志,将由后台线程去做,缺点是如果事务提交时,mysql挂了,后台线程来不及同步redo日志,那么将不保证完全的事务持久性。

    每一个redo日志都有一个序列号Log Sequence Number 简称LSN。flushed_to_disk_lsn表示已落盘到磁盘上的事务,mysql在恢复数据的时候就是根据这两个LSN的差值来决定事务从哪里开始到哪里结束,所以LSN是非常有用。

    崩溃后的恢复为什么不用bin log? bin log主要用于mysql的主从复制上,主库会把ddl和dml的修改通过网络传递给从库,从库接收后执行。

    bin log 与redo log有何不同?

    1、使用方式不一样。bin log会用于所有的更改操作,主要用于人工恢复数据。而redo log是mysql自己恢复使用的。

    2、使用机制不一样。redo log是innodb独有的,bin log是mysql server层实现的,所有存储引擎均可使用bin log。

    3、redo 日志所记录的是物理日志,记载的是页面上的某个offset做了什么修改。而bin log是逻辑日志,比如id = 2的字段我做了 +1操作,记录的是原始逻辑。

    4、写盘机制不一样。redo log只有两个,循环写,记录的是事务已经提交但是数据尚未落盘的数据,一旦落盘就会从redo log中删除。而bin log是追加日志,保存的是全量修改日志。

    5、通过redo日志文件保存内容,可以判断出哪些数据已经落盘。

    【undo日志】

    如果要进行delete和update操作,我要记录它本来的值,记录下来的信息就是undo日志,是为事务的一致性操作准备的。然后在一堆的undo日志里,需要分配一个事务id,事务可以分为两种,只读和读写,只读事务不可以对普通的用户表,就是create table这些表进行增删改查,但是可以对用户的临时表进行增删改查,比如create tempory table,在事务创建过程中,创建的临时表。

    【事务】

    begin开始一个事务,只有事务中出现了对表的增删改的时候,才会分配事务ID。delete操作对应的undo日志 TRX_UNDO_DEL_MARK_REC,Page Header里包含Page_Free垃圾链表,delete操作的第一步:在page header里面有个delete mask标记,未删除的标为0,已删除的标为1,会产生一条undo日志,第二步:当事务commit提交之后,便会将该记录放到page free垃圾链表里,这时数据才会被真正删除。这个过程称为合并过程。

    【MVCC】Multi-Version Concurrency Control

    行格式里有个roll_ptr回滚指针,会指向trx_id事务id,而trx_id又会指向undo log,这样就形成了一个回滚链。MVCC只对普通的select查询生效。

    那我们再讲讲MVCC里的版本链:

    【版本链】

    比如我们要插入一行数据:number(主键) = 1, name = Mark, password = mypassword 那么行存储格式大概如下:

    1 (列1值) myname (列2值) mypassword (列3值) trx_id  (事务ID) roll_ptr (回滚指针)

    现在回滚指针指向了insert undo,那么insert undo有一条记录了,此后事务1做了两次update,事务2做了两次update,在都未提交的情况下,就会形成一个版本链:

    【Read View】

    每个事务都会产生一个Read View,简称RV,RV包含:

    m_ids : 记录生产RV时,活跃的读写事务id列表

    min_trx_id :     m_ids 中最小事务id

    max_trx_id :    系统中将要分配的下一个id值

    creator_trx_id :    生成RV(Read View)的事务id,就是创建RV的事务

    假如我们现在有3个事务,那么m_ids = 1,2,3   min_trx_id = 1, max_trx_id = 4, create_trx_id = 0

    那么问题来了,访问某条记录时,如何判断记录的某个版本是否可见?(判断规则)

    1、如果被访问版本的trx_id属性值与Read View中的create_trx_id相同,表示本条记录是自己这个事务创建的,可见。

    2、如果被访问版本的trx_id属性值小于Read View中的min_trx_id值,表示访问到了比活跃事务id最小值还要小,表示不活跃了,表示已经提交了,可见。

    3、如果被访问版本的trx_id属性值大于或等于Read View中的max_trx_id值,表示被访问的事务创建时间晚于自己这个事务,所以不能访问,不可见。

    4、如果被访问版本的trx_id属性值在Read View的min_trx_id和max_trx_id之间,min_trx_id < trx_id < max_trx_id,就需要判断被访问的trx_id是否还在m_ids活跃事务列表里,如果不活跃了表示事务已提交可访问,如果还在表示事务还在活跃尚未提交不可访问。这个过程就是遍历版本链,一个一个找,看哪个能访问,如果一个都找不到,那么查询结果里这条记录就不可见。

    5、如果某个版本的数据对当前事务不可见的话

    【判断规则举例】

    比如当前Read View事务是这样的: m_ids[80,120]    min_trx_id = 80     max_trx_id = 121    creator_trx_id = 0,而版本链是这样的:

    那么对应各个事务隔离级别,mysql将会读取哪一条数据呢?参考《mysql的innodb存储引擎事务隔离级别》我们来一个一个对应:

    Read uncommitted    未提交读。只要m_ids活跃事务列表里有数据,就读到了,所以会出现脏读。

    Read committed    已提交读。在每次读取数据之前,都会生成一个Read View。因为m_ids事务活跃列表里有80,120两个尚未提交事务,所以会读取到trx_id = 60这个已提交事务行。

    Repeatable read   可重复读。只在第一次读取数据时生成一个Read View。因为RV不会变,所以即使在两个update过程中有其它事务已提交,mysql仍然会以旧RV的标准永远只读到相同的那一条记录。

    Serializable      可序列化。

    【Read Committed 已提交读,很大程度上避免了幻读,但是在某些特殊情况下还是会产生幻读现象,举例】

    事务1 事务2
    begin;  
    select * from teacher where number = 35;      -- 没有存在  
      begin;
      insert into teacher values(35, 'Mark2', 'ELK');
      commit;
    select * from teacher where number = 35;      -- 依然查不到  
    update teacher set domain = 'mysql' where number = 35;  

    select * from teacher where number =35;      -- 查到了,出现了幻读!

    那为什么这里出现了幻读?是因为当上一条update sql时的trx_id是本事务id,所以这个值就这样被特殊的读到了。

     
    commit;  

    end.

    支付宝扫一扫,为女程序员打赏!
    作者:梦幻朵颜
    版权:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    写了一个Windows服务,通过C#模拟网站用户登录并爬取BUG列表查询有没有新的BUG,并提醒我
    WCF快速上手(二)
    Oracle递归查询
    100多行代码实现6秒完成50万条多线程并发日志文件写入
    C#写日志工具类
    WPF定时刷新UI界面
    HttpUtil工具类
    WPF GridView的列宽度设置为按比例分配
    Visifire图表
    C# BackgroundWorker 的使用、封装
  • 原文地址:https://www.cnblogs.com/zhuwenjoyce/p/15055566.html
Copyright © 2020-2023  润新知