Mysql 事务隔离级别&MVCC机制&锁机制(Innodb)

一、事务隔离级别

1、事务是什么

        事务是一组sql语句组成的逻辑处理单元,具有四个属性(ACID):

        原子性(Atomicity) :事务的操作看做是一个原子不可分割,同一个事务中的语句要么全部执行成功,要么全部执行不成功。例如A给B转账1000元,必须是A扣款1000元,B同时也收到1000元,而不能是A转账扣款1000元,B却没收到。

        一致性(Consistent) :在事务开始和完成时,数据都必须保持一致性状态。例如A给B转账1000元,B不可能收到2000元。
  
        隔离性(Isolation) :各个事务之间相互隔离,不会相互影响。例如A给B转账1000元,和C给D转账1000元,是完全独立的两个操作,不会相互影响。

        持久性(Durable) :事务提交完成后,数据会永久的保存,例如A给B转账完1000元后,B的账户会增加1000元,这时候就算手机关机了,或者银行服务器宕机了,1000元依然在账户上。

2、事务可能存在的问题

        在并发环境中,多个事务对同一条数据进行操作,可能会发生下列问题。

        脏读:事务A读取到了事务B还未提交的数据,例如有张银行卡里有1000元,A和B一起去这张银行卡取钱,A取500元,B取1000元,但是B在操作的过程中突然退出,此时A读取到了B取1000元的操作,认为银行卡内还剩余0元,于是取钱的时候报错余额不足。

        不可重复读:事务A读取到了事务B已经提交的数据。事务A多次读取某条数据的时候,结果不一致,不符合隔离性的特征。例如A在取钱的时候,发现卡里还剩余1000元,此时B往卡里打了500元,A读取到卡里有1500元,B又取出1500元,此时A读取到卡里剩余0元,也就是说在同一个事务A中,三次读取同一条数据的结果不一致,被另一个事务B的操作影响到了,不符合隔离性。

        幻读:事务A读取到了事务B新增的数据。例如有家新成立的公司,一个HR招聘了100个人,A读取的人数是100,此时另一个HR又招聘了1个人,A再次读取的时候发现变成了101个人,于是A以为产生了幻觉。

3、事务隔离级别

        读未提交(Read Uncommitted):事务A可以读取到事务B未提交的数据,会有脏读、不可重复读、幻读问题。

        读已提交(Read Committed):事务A可以读取到事务B已提交的数据,于是可以避免脏读,但是会有不可重复读和幻读问题。

        可重复读(Repeatable Read):事务A多次读取某条数据得到的结果是一致的,除非是本事务自己修改了某条数据,否则如果是事务B修改了某条数据也不影响事务A的查询,因此可以避免不可重复读问题,但是避免不了幻读。

        串行化(Serializable):事务A执行的时候会对记录进行加锁,可以看作是单线程工作,事务A完全执行完,才能轮到事务B执行,可以避免上述三个问题,但是效率太低,在生产环境中不适用。

二、MVCC机制

1、引言

        经过事务隔离级别的分析,好像串行化才能保证并发环境下不出问题,那么mysql会用串行化吗?答案是不会,串行化连select语句都会加锁,开销大且效率低。所以mysql选择使用可重复读隔离级别,在可重复读隔离级别下查询操作不加锁,可以很好的应对并发场景,但是却有上述的并发问题,该如何解决?

2、MVCC机制介绍

        MVCC(Multi-Version Concurrency Control),即多版本并发控制。是一种用于解决数据库读写冲突的技术。它通过创建数据的多个版本来实现并发控制,使得在一个事务正在修改数据的同时,其他事务仍然可以读取到数据的旧版本,从而避免了传统数据库锁定机制中可能导致的资源浪费和效率降低的问题。

        MVCC的核心理念:undolog版本链、read-view视图、事务时间戳

        概念介绍:每个事务在启动时,系统会为其分配一个唯一的事务ID。当一个事务要访问数据库中的某个数据时,系统会检查该数据的版本号和事务的启动时间。如果该数据的版本号早于该事务的启动时间,则该事务可以访问该数据;否则,该事务需要等待其他事务完成对该数据的访问。当一个事务修改某个数据时,系统会为该数据创建一个新版本号,并将修改后的数据存储在一个新的位置,同时旧版本的数据仍然可用供其他事务访问。当一个事务提交时,系统会将其所做的所有修改操作都合并到数据库中,同时删除旧版本的数据。

3、详细介绍

如图MVCC会生成一条undo版本链,例如:

mysql的undolog中会一次记录这三次update操作,但是此时update并不是一定生效的,因为事务必须是提交后才生效。

拿图1来说,假设这条数据的id为1,一个新的事务(事务4)在11点结束时对user表进行查询

SQL1:select name from user where id = 1

此时查询到的值是:张DD,以下是解释:

ReadView中主要包含4个比较重要的内容:

        1、m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。注意m_ids中是包含自己的事务id的。
        2、min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,即m_ids中的最小值。
        3、max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
但是max_trx_id并不是m_ids中的最大值,事务id是递增分配的,max_trx_id表示下一个即将被分配到事务id。
        4、creator_trx_id:表示生成该ReadView的事务的事务id
        以上只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。即使在一个事务中既有select语句,又有INSERT、DELETE、UPDATE语句,也只会在执行INSERT、DELETE、UPDATE语句时才会分配一个事务id。

 在访问某条记录时,通过ReadView按照下边的步骤判断记录的某个版本是否可见:

        1、若被访问记录的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
        2、若被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
        3、若被访问版本的trx_id属性值大于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
        4、若被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

针对上面 SQL1的例子,read-view生成的视图就是 (【1,2】,4)

其中1是最小未提交的事务1,2是最大未提交的事务2,4是当前执行的事务4,根据上述理论,事务1和2都在区间【1,2】中,那么证明事务1和2都是不可见的,事务3在2和4之间,所以可见,因此事务4读取的数据是:张DD。

同时要注意的是,一个事务生成的read-view视图是固定的,不会根据查询的时间变化而变化,例如事务4在12点结束的时候再次执行SQL1,此时事务1已经提交了,但是事务4的read-view依旧是 (【1,2】,4),所以事务4依旧读不到事务1提交的数据,读取的数据依然是:张DD。

但是假设在12结束的时候又新增了一个事务(事务5),同样执行SQL1,由于此时事务1已经提交,所以未提交事务的最小id和最大id都是2,那么事务5的read-view就是 (【2】,5),所以能读取到事务1的更新,读取到的数据为:张BB。

三、锁机制

1、行锁和表锁

        锁,分别行锁和表锁,行锁即事务只锁定与自己要操作的记录有关的行,粒度较小,所耗费资源较大,可能出现死锁;表锁即事务操作数据时给整张表加锁,不会出现死锁,粒度较大,但是并发度很低,当前事务占据表后,别的事务无法拿到表资源。

        行锁,innodb实现了两种标准行锁,分别是共享锁(shared locks,S锁)和排他锁(exclusive locks,X锁)。

        S锁允许当前持有该锁的事务读取行。X锁允许当前持有该锁的事务更新或删除行。
        S锁:如果事务A持有了某一行记录上的S锁,则其他事务可以同时持有这行记录的S锁,但是不能对这行记录加X锁。
        X锁:如果事务A持有了某一行记录上的X锁,则其他任何事务不能持有这行记录的X锁,必须等待A在这行记录上的X锁释放。

2、死锁

        在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种:
        1、如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;
        2、如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
        当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。
        在发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。

3、意向锁

        为了方便检测行锁和表锁之间的冲突,意向锁诞生了。

        意向锁即在给一行记录S锁前,首先给所在表加IS锁(意向共享锁)。同理,给行记录加X锁之前一定会加上IX锁(意向排他锁)。意向锁是InnoDB自动加的,不需用户干预。

        在没有意向锁的情况下,若事务A用行锁对表中的某一行记录进行修改时,此时事务B要对全表进行修改,那么就需要对所有的行是否被锁定进行扫描,效率较低;
        有意向锁的情况下,在事务A用行锁对表中的某一行记录进行修改之前,会先为表添加意向互斥锁(IX),再为行记录添加互斥锁(X),此时若事务B想全表进行修改就不需要判断表中的每一行数据是否被加锁了,只需要通过等待意向互斥锁被释放,效率提升。

4、间隙锁

假设图中id为主键,那么图中的id有这几个间隙:(1,5),(5,10),(10,20),(20,正无穷)

间隙锁则会给这几个间隙加上锁,例如有条sql:

select * from user where id > 8 and id < 28 for update
--for update 可以锁定查询的记录,本事务提交或回滚才会释放资源

如图中sql中锁定的记录是(8,28),但是对比图中的id间隙,可以发现8和28分别位于(5,10)和(20,正无穷)区间内,此时sql会锁定 (5,10),(10,20),(20,正无穷)三个区间,此时别的事务不能对(5,正无穷)区间内的数据进行插入或更新。

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL事务隔离级别原理是基于并发控制来实现的。在MySQL中,有四个事务隔离级别:读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。每个隔离级别都有不同的数据可见性和并发控制机制。 在MySQL中,默认的隔离级别是可重复读(Repeatable Read),它通过使用多版本并发控制(MVCC)来实现。MVCC使用了两个关键的数据结构:Undo Log和Read View。 Undo Log是用于实现事务的回滚和并发控制的机制。当一个事务对数据进行修改时,会将旧版本的数据保存在Undo Log中,以便在事务回滚或其他事务需要访问该数据时使用。 Read View是每个事务的一个快照,用于确定事务可见的数据范围。每个事务开始时,会创建一个Read View,并根据事务隔离级别确定可见的数据范围。在可重复读隔离级别下,Read View会在事务开始时记录当前数据库中的所有活跃事务ID,并将这些事务对应的Undo Log应用到Read View中,从而确定事务能够看到的数据范围。 在并发执行的过程中,MySQL会根据不同的隔离级别事务的读写操作进行数据的读取和写入。对于读操作,会根据事务隔离级别和Read View进行判断,确定读取的数据是否可见。对于写操作,会使用锁机制和Undo Log来保证事务的一致性和隔离性。 需要注意的是,MySQL的不同存储引擎对事务隔离级别的支持也有所不同。例如,MyISAM引擎不支持事务,而InnoDB引擎则支持事务,并提供了更强的并发控制机制。 总结起来,MySQL事务隔离级别原理是通过使用多版本并发控制(MVCC)和锁机制来实现的。每个事务在开始时会创建一个Read View来确定可见的数据范围,同时使用Undo Log来支持事务的回滚和并发控制。不同的隔离级别会决定事务能够看到的数据范围和并发控制的方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值