大厂之路-MySQL-事务是怎么实现的?

大厂之路-MySQL-事务是怎么实现的?,第1张

大厂之路-MySQL-事务是怎么实现的?

*懒得多说废话: *

ACID:原子性,一致性,隔离性,持久性

原子性:表示事务是最小的执行单位,该 *** 作不能再被拆分, 保证事务的要么全部成功,要么就不执行。
一致性:表示数据库进行事务的 *** 作后,由一个正确的状态到另一个正确的状态
隔离性:并发访问数据库的时候,事务与事务之间的 *** 作不会互相受影响,是独立的。
持久性:在数据库进行事务的 *** 作后,是能成功的永久的保存到磁盘里的,即使数据库发生故障也不影响;

我们在上一篇一条更新sql是怎么执行的时候,说明了MySQL就是用redo-log的机制来保证事务的持久性,今天我们主要来针对隔离性来做解释:

当数据库上有多个事务同时执行的时候就可能会出现脏读,不可重复读,幻读的问题

脏读:当一个事务A正在访问数据并对数据进行了修改,还没有提交到数据库中,此时另一个事务B进来也读取这个数 据,但是可能A发生了异常回滚了数据,但是B还是会根据这个修改后的数据进行 *** 作就会发生异常错误。
不可重复读:表示当一个事务中若干个查询出来的结果可能不一样,比如一个事务A首先查询出了数据,此时事务B进 来进行修改了数据,然后事务A再去读就会前后不一致
幻读:表示一个事务中前后查询的数据会有增减。一个事务A首先查询出了数据,然后事务B增加了几条符合查询条件 的数据,然后事务A中再查询就会奇幻地多了几条数据。

为了解决这些问题,就有了隔离级别的概念,四个隔离级别:
  • 读取未提交(RU):最低的隔离级别,更新数据的时候会添加锁,但是查询的时候不会加锁允许读取没有提交成功到数据库的数据,会发生脏读,不可重复读,幻读
  • 读取已提交(RC):允许读取提交的数据,查询的时候会加锁,但是查询完毕之后就会立即放开。可防止脏读。不能防止不可重复读,幻读
  • 可重复读(RR):查询的时候会给结果集添加行级共享锁,直到事务结束之后才放开,即可以防止别的事务对其元数据的修改。防止了不可重复度,但是幻读还是存在。
  • 串行化(Serial):对于同一行记录,“写”会加“写锁”,“读”会加“读锁”,当出现读写锁冲突的时候,后访问的 事务一定要等前一个事务执行完成,才能继续执行!

这四个隔离级别从左往右依次隔离的越严格,但是同样的效率来说依次降低。

tips:在MySQL中InnoDB引擎默认使用的是隔离级别是可重复读,因为它使用的是Next-Key Lock算法,这是一个范围锁。所以可以防止幻读的发生。所以InnoDB虽然是可重复读的隔离级别,但是已经达到了可串行化的级别水平

现在有一张表,我们有两个事务:

我们来看看不同的隔离级别下,事务A每个时刻查询到的值X,Y,Z分别是多少

○ 读未提交:X=李四,Y=李四,Z=李四。因为这个时候事务B虽然还没有提交,但是他的更改是对其他事务是可见的;

○ 读提交:X=张三,Y=李四,Z=李四。因为查询到X的时候事务B还没有提交,所以值依旧为张三,当事务B提价之后,事务A就可以读到事务B做出的更改了

○ 可重复读:X=张三,Y=张三,Z=李四。因为我们可重复读必须保证事务开启跟提交前读到的数据内容都是一样的。

○ 串行化:X=张三,Y=张三,Z=李四。因为隔离级别是串行化,那么在事务B执行“将张三改成李四” 的时候,申请写锁的时候,就会被阻塞,因为事务A首先会获得这一行的读锁,这个锁要在这个事务提交之后才可以放开,又因为写锁要在所有读锁释放完之后才能写。所以申请写锁会阻塞,直到事务A提交,之后才去修改。

再接下来,我们就来看看数据库是怎么实现的:

read-view: 事务与事务之间是怎么看到各自修改的数据的呢?其实数据库里面会创建一个视图read-view,访问的时候以视图的逻辑结果为准。

undo-log: 在MySQL中,每条记录在更新的时候都会记录一条回滚 *** 作。比如我们是insert语句,那么记录的就是delete,记录在undo log中,也就是我们说的回滚日志。

我们现在假设一个值从1被按顺序改成了2、3、4.在undo-log回滚日志里面就会有类似下面的记录:

就是现在当前的值是4,在过往的查询这条记录的时候,不同时刻启动的事务里面就有不同的read-view,每个read-view对应的值就是我们在之前事务启动的时候的彼时的值,分别为1,2,3。其实我们看到的这样同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。对于第一个图中的第一个Read-view。要回滚到1,就必须将当前值4依次执行图中所有的回滚 *** 作得到。
所以数据库中事务之间的互相可见其实就是通过这个视图来实现的:
可重复读隔离级别下:视图是在事务的启动时创建的,整个事务存在期间都用这个视图(所以能解决不可重复度,跟脏读,不能防止幻读)
读提交:视图在每个SQL语句开始执行的时候创建(所以只可以解决脏读问题,不能防止不可重复读,幻读)。
读未提交:直接返回记录上最新的值(所以直接产生了脏读)。
串行化:直接加锁的方式来避免对同一行进行访问。

那么问题又来了:这个回滚是会一直保留的吗?

答案是不会的,它会在系统判断当前没有事务需要用到这些回滚日志的时候,就会被删除,那么系统是如何判断的呢?它会判断当前系统里没有比这个回滚日志更早的read-view!
这很容易理解:因为我们的read-view是我们改动前的状态,比如我们取一个节点是上图中的视图2,因为随着事务的提交,这个视图也随之删除(第一个视图已经没有了),当发现系统里没有比当前日志更早的视图了,是不是就意味着你这个回滚日志对应的事务已经提交了,已经提交的事务怎么进行回滚呢?,所以就可以把这条日志(将2改成1)记录删除了。

基于上面的理论,我们就可以探究下为什么我们尽量不要使用长事务!

因为长事务的话就意味着系统里会存在很老的事务视图,因为这个事务可能随时回滚,所以数据库里可能用到1回滚记录都必须保存,就会导致大量的占用空间。而且同时长事务会长时间占用锁资源,也有可能拖垮整个库。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5596085.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存