点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃
关于事务的所有知识上篇博客我们都说过了,今天这篇博客主要是为了解密,RC和RR隔离级别,它怎么做到一个事务提交了,其他事务还看不到。数据不是只有一份吗,他改了我怎么看不到,这个原理是怎么样的?
之前说的隔离性和隔离级别的话题,前提是多事务进行并发运行,所以我们应该想明白的是一个数据库在被并发访问时它的场景有那些,因为只有知道场景才能针对不同场景提供不同方案。
1.数据库并发的场景
数据库并发的场景有三种:
- 读-读 :不存在任何问题,也不需要并发控制,因为没有人去修改
- 读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
- 写-写 :数据库只会被人写,事务都是写事务,一定通过加锁来保证数据安全。否则有线程安全问题,事务不是有回滚吗,有可能一个事务在更新另一个事务回滚了彼此交叉运行,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失(后面补充),
2.读-写
就像之前做的实验,读到的数据和写的数据是不一样的,其实这已经证明读写的数据不是同一份。后面再说。在读写并发实现很好的隔离性采用的核心技术之一多版本并发控制。
多版本并发控制( MVCC ) 是一种用来解决 读-写冲突 的无锁并发控制
历史上在说事务的时候一直强调事务是原子的,但是事务在执行一定是有执行中,mysql为了解决执行中对应的并发问题,也一定要让事务在执行的时候有先有后,保证事务那些数据能看到那些数据看不到,所以它一定要判定事务的先后问题!事务在怎么同时到来一定有先有后。那问题是怎么区分事务的先后问题?
mysql为事务分配单向增长的事务ID,事务与事务ID是一对一的关系。事务ID越小代表来的越早,ID越大代表来的越晚,所以可以通过ID来判定事务的先后顺序。为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照。 所以 MVCC 可以为数据库解决以下问题
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
- 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
每个事务都要有自己的事务ID,可以根据事务ID的大小,来决定事务到来的先后顺序。
一个事务可以交给mysql运行,两个十个都可以由多个客户端并发的交给mysql,也就是说mysqld可能会面临同时处理多个事务的情况, 事务在使用mysql的人看来它是原子的,但在mysql内部它一定要有个执行的过程,所以它的执行过程就证明mysql中事务也有自己的生命周期,事务要被创建,要被放到某个等待队列里,要被执行,执行出错要被回滚,执行完毕事务要被消除,这些都指向一点mysqld要对多个事务进行管理,先描述,在组织! 换句话说事务在我看来,mysqld中一定是对应的一个或者一套结构体对象/类对象,事务也要有自己的结构体那每个事务都要有自己的事务ID是不是就好理解了。来一个事务就new一个事务对象,对事务的管理就变成了对某种数据结构的增删查改。
有了这个概念,我们再来谈事务隔离级别具体的解决方案MVCC。不过在谈MVCC之前我们要先知道三个前提知识:
- 3个记录隐藏字段
- undo 日志
- Read View
2.1 3个记录隐藏字段
其实我们在建表的时候指明有多少列,你以为有4列、5列等等,可实际上mysql都要默认给添加上3个隐藏字段。
- DB_TRX_ID :6 byte,最近修改( 修改/插入 )事务ID,记录这条记录/最后一次修改该记录的事务ID。
比如说未来在表中插入任何数据,插入的这条记录是那个事务插入的,事务ID是谁,要把事务ID放在表中。无论是手动启动事务还是单SQL由系统默认封装的事务,最终在数据库中所以操作的SQL必须以事务的方式让mysql统一执行。每一个事务都有ID,所以所有表的操作都要和事务ID关联起来保存到表里。
- DB_ROLL_PTR : 7 byte,回滚指针,指向这条记录的上一个版本(简单理解成,指向历史版本就行,这些数据一般在 undo log 中)
实际上你对表中某一行记录做修改,mysql在特定的隔离级别下,不是让直接去改表中的数据,它会把你要改的这条记录先保存一份,让你改最新表中的数据,这样的话就可以在改之后也可以知道历史的数据是什么,这种策略特别想像 写时拷贝。增加、修改、删除都是要先把数据保存一份,然后改最新的数据。然后最新记录要能找到它历史的最新信息,所以有了这个 回滚指针。指向被修改之前的上一个版本。
- DB_ROW_ID : 6 byte,隐含的自增ID(隐藏主键),如果数据表没有主键, InnoDB 会自动以DB_ROW_ID 产生一个聚簇索引
- 补充:实际还有一个删除flag隐藏字段, 记录当前记录的状态,是被更新过还是被删除了。一般删除并不是把这条记录真的删除了,只是把flag变了。我们建立的表结构是在聚簇索引的叶子节点中以page方式存在,它是内存级的。所以删除的时候我们并不需要把数据情况还要做各自表结构的移动那太麻烦了。所以我只需要把它清掉就可以,清掉之后只需要最终维持page是脏的或者干净的,后面刷盘的时候在把数据排列到磁盘中。下次在不就连续了嘛。
建一个学生表
create table if not exists student(
name varchar(11) not null,
age int not null
);
我们查的时候只能看到两列,自动提交是被打开的,实际上insert就是一个事务,在插入张三 28后面也一定会有这个数据是那个事务插入的,没有指明主键mysql会有一个默认主键,因为历史上没有数据所以回滚指针为null。
name | gae | DB_TRX_ID(创建该记录的事务ID) | DB_ROW_ID(隐式主键) | DB_ROLL_PTR(回滚指针) |
---|---|---|---|---|
张三 | 28 | null | 1 | null |
我们目前并不知道创建该记录的事务ID,隐式主键,我们就默认设置成null,1。第一条记录也没有其他版本,我们设置回滚指针为null。
2.2 undo日志
以前说过mysql中有很多日志,其中undo日志是mysql中比较重要的模板。这个模块是什么东西呢
mysql在启动的的时候会申请对应的缓存区,实际上msyql中还有一大堆日志缓存区,其中有一块叫做undo log,从名字上看 undo 是撤销的意思 log 是日志的意思,关于它我们今天给它就一个结论,它是我们在应用层由Mysql维护的内存空间!
MySQL 将来是以服务进程的方式,在内存中运行。我们之前所讲的所有机制:索引,事务,隔离性,日志等,都是在内存中完成的,即在 MySQL 内部的相关缓冲区中,保存相关数据,完成各种判断操作。然后在合适的时候,将相关数据刷新到磁盘当中的。
所以,我们这里理解undo log,简单理解成,就是 MySQL 中的一段内存缓冲区,用来保存日志数据的就行。
有了上面两个预备知识,一个是3个隐藏字段,一个是undo log,下面我们来模拟一下多版本并发控制( MVCC )是怎么做的。
2.3 模拟 MVCC
现在假设我们目前表中就一条张三的数据。是事务9将它insert进来的。这个记录在B+数的叶子节点存着。
我们的场景是有一个事务10(仅仅为了好区分),对student表中记录进行修改(update):将name(张三)改成name(李四)。
- 因为事务10要对数据进行修改,所以一定要先