MySQL事务详解(三):脏写与幻读的绝路—锁

在上次MySQL事务详解(二):隔离级别的实现--MVCC的学习中,我们认识到了并发事务访问相同的数据时,
读—写情况下带来的脏读、不可重复读问题,可以通过MySQL的MVCC机制解决。
而对于读—写情况下的幻读问题和写—写情况下的脏写问题,我们提到可以通过MySQL的锁机制解决。

锁的概念

锁的作用于现实生活中的锁的作用类似,都是控制对资源的访问。
而MySQL的锁更侧重于对于资源的并发访问,来保证访问数据的完整性于一致性

锁,其实就是与访问数据关联的一种数据结构。如下图:


当访问(多为修改)一条数据时,首先查询是否有与之关联的数据结构,没有就生成一个,并占有(即加锁成功),如果没有也生成一个并等待(加锁失败)。
当占有锁的事务释放锁之后,等待的事务就会被唤醒执行。


锁的分类

锁的提出是为了解决并发访问带来的问题,但是如果一味的对访问的数据进行加锁,将访问串行化,使其排队等待执行,同时又会降低并发量,降低性能。
因此对锁进行了不同级别的分类,来应对不同的操作场景,如下图:


操作类型

读锁(S锁、共享锁):读取数据用到的锁。给数据加S锁后,其他事务依然可以加S锁(即共享),但是不可以加X锁。
写锁(X锁、排他锁):更改数据用到的锁。给数据加X锁后,其他事务不可以加S锁和X锁(即排他)

态度

乐观锁:假设事务之间很少发生冲突,不主动对数据进行加锁。通常使用版本号时间戳在事务提交时检测是否冲突。
悲观锁与乐观锁相反,假设事务之间经常发生冲突,主动给数据加锁,获取事务所需资源。

粒度

全局锁:对整个数据库实例进行加锁。Flush tables with read lock ,常用于对整个库进行逻辑备份。全局锁对数据库影响较大,锁主库会导致不能更新,业务暂停,锁从库导致主从延迟。
表级锁:对整张表进行锁定,开销小,粒度大,发生冲突的概率高,并发低,可以避免死锁
行级锁:对某一行记录进行锁定,开销大,粒度小,发生冲突概率低,并发高,不能避免死锁
页级锁:对数据库的最小存储单元——页进行锁定,粒度介于表和行之间。应用在BDB存储引擎中,了解即可。

表级锁

表级别的S锁、X锁和元数据锁

# 上锁
lock table tableName read; # 读锁/共享锁
lock table tableName write;# 写锁/排他锁
#解锁
unlock tables; # 客户端断开的时候也会自动释放锁。

使用上面的语言可以为表加上S锁和X锁,以及解锁。

但是如果仅仅是对表进行select、insert、update、delete操作,存储引擎是不会默认为表加上表级锁
而实现这些操作与其他事务中的DDL语句冲突的是表级锁种的元数据锁(MDL)

元数据锁是系统自动控制的,每一条DML、DDL语句执行时都会申请MDL。
DML申请元数据读锁,DDL申请元数据写锁。且DML与DML之间并不相互阻塞,因此DML可以并发执行。
所以表级别的S锁、X锁很少用到。

表级别的意向锁

意向读锁(IS):当我们想向表中的记录加上S锁时,首先给表加上IS锁。
意向写锁(IX):当我们想向表中的记录加上X锁时,首先给表加上IX锁。
IS和IX作用是为了后续给表加上S锁和X锁做的准备工作,避免加上表级别S锁和X锁时,需要遍历所有的数据来判断是否有记录被锁上。

表级别自增锁

自增锁主要是用来给表中声明为AUTO_INCREMENT的字段,在插入记录是递增而用。

  • 在我们不知道要插入的数据量时,例如使用 insert...select;replace...select或者load data时,通常先给表加上一个自增锁,然后生成对应的值,插入完成之后,删除自增锁;
  • 在我们知道具体的插入数据量时,使用轻量级锁,未插入语句生成所需的值后即可释放锁,提高性能。

行级锁

行级锁才是使用最频繁的锁,在开发中遇到的死锁问题一般都是行级锁引起的。
根据不同的情况, 行级锁被分为不同的类型,解决不同的问题,幻读就是其中之一。

行级S锁、X锁

为我们要访问的某一条记录加上读锁或者写锁。S锁与X锁的关系之前已经学习过。

间隙锁

用来解决幻读的锁。因为幻读是在其他事务插入数据后产生,当前事务在第一次select时,数据还不存在。如果使用行级的X锁,找不到上锁的对象。因此提出间隙锁,如下图:

给no=7的数据加上gap锁后,表明在这条数据之前(no为(3,7)间)不能插入新的数据。以此来解决幻读。

临键锁

当我们想锁住访问数据本身以及这条数据之前的间隙时,使用临建锁。即行级SX锁与gap锁的结合。如下图:

插入意向锁

当我们想插入一条数据时,首先要判断插入的位置是有存在gap锁,如果存在就先生成一个插入意向锁,向系统表明有事务在等在插入当前位置。

两种数据读

快照读

MVCC使用的就是快照读,在读取事务是好像拍了一张照片,当前事务读取的是照片快照下的数据。
普通的select 语句都是快照读,不会给记录有加锁操作。

当前读

读取到的数据为最新的的数据。
select ..lock in share mode;对记录加上S锁;
selelct ... for update;对记录加上X锁;
以上两种select语句给记录加上了锁,并且读取到数据是最新的数据。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容