MySQL事务与锁机制详解

1.事务

1.1 什么是事务?
事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。

1.2 哪些引擎支持事务
InnoDB支持事务

1.3 事务的四大特性
原子性(Atomicity):强调事务的不可分割,也就意味着我们对数据库的一系列操作,要么都是成功,要么都是失败。在InnoDB里面,是通过undo log来实现的,它记录了数据修改之前的值(逻辑日志),一旦发生异常,就可以用undo log来实现回滚操作。

一致性(Consistency):事务执行的前后数据的完整性保持一致。

隔离性(Isolation):一个事务的执行过程中,不应该受到其他事务的干扰。对隔离性的定义,就是多个事务对表或者行的并发操作,是互不干扰的。通过这种方式,也是保证数据的一致性。

持久性(Durability):事务一旦提交成功,那么结果就是永久性的,不可能因为我们系统宕机或者重启了数据库的服务器,它又恢复到原来的状态了。持久性是通过redo log 来实现的,我们操作数据的时候,会先写到buffer pool里面,同时记录redo log,如果在刷盘之前出现异常,在重启后就可以读取redo log 的内容,写入到磁盘,保证数据的持久性。实际上还有一个双写缓冲(double write buffer)机制,因为存储引擎和操作系统的页的大小不一致,一个存储引擎page的数据要写4次,如果中间发生异常,造程页数据的不可用,所以,必须把数据备份起来,这个就是双写缓冲。
原子性,隔离性,持久性,最后都是为了实现一致性。

1.4 MySQL中事务的开启
1)自动开启:任意一个增删改的语句,都会开启一个事务,并且是自动提交的。InnoDB里面有一个autocommit的参数(分成两个级别,session级别和global级别)

show variables like 'autocommit';

2)手动开启:通过begin 或者 start transaction 开启一个事务,并且通过commit 或者 rollback 语句提交事务

begin;
update customer set cust_name = '马大炮' where cust_id = 14;
commit;

1. 5 如果不考虑隔离性(事务并发)会带来的读问题:
脏读: 一个事务读到另一个事务未提交的数据。
不可重复读: 同样的SQL语句,得到的查询结果不一样,是因为一个事务读到另一个事务已经提交的update数据。
幻读: 同样的SQL语句,得到的查询结果不一样,是因为一个事务读到另一个事务已提交的insert的数据。
脏读和不可重复读的区别是事务是否提交,不可重复读和幻读的区别是,前者是update、delete,后者是insert。

1.6解决读问题:设置事务的隔离级别
未提交读(Read Uncommitted ):,一个事务可以读取到其他事务未提交的数据,会出现脏读,所以叫做RU,它没有解决任何的问题。
已提交读(Read Commit): 一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是会出现不可重复读和幻读的问题。

可重复读(Repeatable Read ): 解决了不可重复读的问题,也就是在同一个事务里面多次读取同样的数据结果是一样的,但是在这个级别下,没有定义解决幻读的问题。

串行化(Serializable): 这个隔离级别里面,所有的事务都是串行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作了,所以它解决了所有的问题。
事务隔离级别,是数据库专家联合制定的一个标准,由数据库厂商按照这个标准来实现事务隔离,这个标准是SQL 92标准(SQL92,是数据库的一个ANSI/ISO标准。它定义了一种语言(SQL)以及数据库的行为(事务、隔离级别等)。)

P1、P2、P3分别为脏读、不可重复读、幻读。

1.7 MySQL InnoDB对隔离级别的支持
InnoDB实现了上面的四种标准,并且都解决了相应的问题,唯一不同的是InnoDB不需要用Serializable这么高的隔离级别就能解决幻读,InnoDB默认使用的是Repeatable Read的隔离级别
在这里插入图片描述

1.8 MySQL实现事务隔离的两大方案
1)LBCC
第一种,读取数据的时候,锁定我们要操作的数据,不允许其他事务修改就行了。这种方案我们叫做基于锁的并发控制Lock Based Concurrency Control(LBCC)。
2)MVCC
在修改数据的时候给它建立一个备份或者叫快照,后面再来读取这个快照就行了。这种方案我们叫做多版本的并发控制Multi Version Concurrency Control(MVCC)。
MVCC的核心思想是:可以查到在这个事务之前已经存在的已提交的数据,即使它在后面被修改或者删除了,和之前查到的数据还是一样,不会改变,在我这个事务之后新增的数据,我是查不到的。可以看一下这个简化的模型来理解MVCC简化模型

在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:
快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外)

select * from table where ?;

当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
下面语句都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。

select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values ();
update table set ? where ?;
delete from table where ?;

2 MySQL InnoDB锁的基本类型

2.1 共享锁(Shared Locks)
又称为读锁,简称S锁。共享锁就是多个事务对于同一个数据可以共享一把锁,都能访问到数据,但是只能读不能进行修改
加锁方式:
begin; select cust_name from customer where cust_id = 14 lock in share mode;
释放锁:事务结束,包括提交事务和结束事务。

2.2 排他锁(Exclusive Locks)
又称为写锁,简称X锁。排他锁不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能在获取改行的锁(排他锁、共享锁),只有该获取了排他锁的事务才可以对数据行进行读取和修改。
加锁方式
自动加锁:delete / update / insert 默认加上X锁;
手动加锁:select cust_name from customer where cust_id = 14 for update;
释放锁:事务结束,包括提交事务和结束事务。

用SELECT …LOCK IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT… FOR UPDATE方式获得排他锁。

2.3 意向共享锁(IS)/ 意向排他锁(IX)
意向锁是由数据引擎自己维护的,用户无法手动操作意向锁,相当于一个标志。
意向共享锁(Intention Shared Lock,简称IS锁)   
表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁 前必须先取得该表的IS锁。

意向排他锁(Intention Exclusive Lock,简称IX锁)
表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他 锁前必须先取得该表的IX锁。

2.4 行锁的原理
行锁,是通过给索引上的索引项加锁来实现的,如果数据库中没有索引,锁的是聚簇索引,InnoDB中,不可能没有聚簇索引,会将整个表都锁住,因为查询不会使用索引,就会进行全表扫描,然后会把每一个隐藏的聚簇索引都锁住了。
另外,给唯一索引加锁,主键索引项也会被锁住,在辅助索引里面, 索引存储的是二级索引和主键的值。 比如name=4,存储的是name的索引和主键id的值4。而主键索引里面除了索引之外,还存储了完整的数据。所以我们通过辅助索引锁定一行数据的时候,它跟我们检索数据的步骤是一样的,会通过主键值找到主键索引,然后也锁定。

2.5 锁的算法
在理解记录锁、间隙锁、临键锁之前,要先明白这几个概念。
有一张表t2,里面有四条数据
在这里插入图片描述在这里插入图片描述这些数据库里面存在的主键值,我们把它叫做Record,记录,那么这里我们就有4个Record。
根据主键,这些存在的Record隔开的数据不存在的区间,我们把它叫做 Gap,间隙,它是一个左开右开的区间。
间隙(Gap)连同它左边的记录(Record),我们把它叫做临键的区间,它是一个左开右闭的区间。

2.6 记录锁(Record Lock)
当我们对于唯一性的索引(包括唯一索引和主键索引)使用等值查询,精准匹配到一条记录的时候,这个时候使用的就是记录锁。

-- id为主键或者唯一索引
select * from t2 where id = 1; 

2.7 间隙锁(Gap Lock)
当我们查询的记录不存在,没有命中任何一个record,无论是用等值查询还是范围查询的时候,它们使用的都是间隙锁,锁住的是间隙。
举个例子,where id >4 and id <7,where id = 6。
在这里插入图片描述
注意,间隙锁主要是阻塞插入insert。相同的间隙锁之间不冲突。
Gap Lock 只在 RR 中存在,如果要关闭间隙锁,就是把事务隔离级别设置成RC,并且把innodb_locks_unsafe_for_binlog设置为ON。
这种情况下除了外键约束和唯一性检查会加间隙锁,其他情况都不会用间隙锁。

2.8 临键锁(Next-Key Lock)
当我们使用了范围查询,不仅仅命中了Record记录,还包含了Gap间隙,在这种情况下我们使用的就是临键锁,它是MySQL里面默认的行锁算法,相当于记录锁加上间隙锁
比如我们使用>5 <9, 它包含了记录不存在的区间,也包含了一个Record 7。
在这里插入图片描述临键锁,锁住最后一个Key的下一个左开右闭的区间。

select*fromt2 where id>5 and id<=7 for update; -- 锁住(4,7]和(7,10] 
select*fromt2 where id>8 and id<=10 for update; -- 锁住 (7,10],(10,+∞)

就是因为InnoDB中使用了Next-Key Lock,所以幻读才不会发生。

3.小结:InnoDB隔离级别的实现
在这里插入图片描述3.1 Read Uncommit
RU隔离级别:不加锁。

3.2 Serializable
Serializable 所有的 select 语句都会被隐式的转化为select …Lock in share mode,会和update、delete互斥。

3.3 Repeatable Read
RR隔离级别下,普通的select使用快照读(snapshot read),底层使用MVCC来实现。
加锁的select(select … lock in share mode / select … for update)以及更新操作update、delete等语句使用当前读,底层使用记录锁或者间隙锁或者是临键锁。

3.4 Read Committed
RC隔离级别下,普通的select都是快照读,使用MVCC实现。
加锁的select都是用记录锁,因为没有Gap Lock。除了两种特殊情况——外键约束检查(foreign-key constraint checking)以及重复键检查(duplicate-key checking)时会使用间隙锁封锁区间。所以RC会出现幻读的问题。

如有不足之处,欢迎指正,谢谢!

发布了12 篇原创文章 · 获赞 12 · 访问量 2435

猜你喜欢

转载自blog.csdn.net/weixin_43691723/article/details/105682258