Mysql(十一) mysql的锁归纳

目录

 

一、读锁(又叫共享锁、S锁)和写锁(又叫排它锁、X锁)

事务与锁

什么时候上锁

InnoDB引擎:

Myisam引擎:

二、行锁和表锁

行锁和表锁的优缺点:

行锁触发死锁的原因

表锁为什么不会触发死锁?

1.记录锁 

2. 间歇锁

3. 临键锁

这章要点:

三、 意向锁

存在意义

为什么没有意向锁的话,行锁和表锁就不能共存

意向锁如何让行锁和表锁共存?

四、乐观锁和悲观锁

悲观锁

乐观锁

总结:


​​​​​​​

一、读锁(又叫共享锁、S锁)和写锁(又叫排它锁、X锁)

共享锁:用于不更改数据的操作,如select操作。

排它锁:用于数据修改操作,如 insert、update、delete,确保同一时间内,加锁的数据只被一个事务修改,不会有多个事务同时修改这个数据。

事务与锁

如果一个事务T对A数据上共享锁后,其他事务只能对A上共享锁,不能上排它锁。获得共享锁的事务只能读该数据,而不能修改该数据。

如果一个事务T对A数据上的是排它锁的,那么其他事务不能对A数据上任何类型的锁。而获得排它锁的事务既可以读取该数据,也可以修改该数据。

什么时候上锁

这跟数据库引擎有关(即InnoDB还是Myisam)

InnoDB引擎:

1.对于UPDATE,INSERT,DELETE语句,InnoDB会自动给设计的数据加排它锁。

2.对于普通的 SELECT语句,InnoDB不会加任何锁。

3.事务可以通过一下语句,显示地给数据加共享锁或排它锁:

    共享锁:如: SELECT * from table_name where .... LOCK IN SHARE MODE。 其他session 仍可以查询数据,并也可以对该记录加 share mode 的共享锁。

    排它锁:如: SELECT * from table_name where .... FOR UPDATE。其他session可以查询该数据,但不能对A数据加写锁或者读锁。

Myisam引擎:

Myisam会在执行 SELECT语句时给相关的数据表自动加共享锁。(而InnoDB执行 SELECT是不会加任何锁的)

在执行 UPDATE、INSERT、DELETE时,会自动给相关数据表加排它锁。

二、行锁和表锁

谈到mysql,就很容易想到它的两个常用的数据表引擎---InnoDB和Myisam。InnoDB是支持行锁和表锁的,但是默认是行锁。而Myisam只支持表锁。

行锁:只锁定操作数据所在的那一行数据。默认的是排它锁。就是本事务锁定的内容,其他事务能查询,但不能加共享锁和排它锁。

表锁:锁定数据所在的整张数据表。加锁的方式:自动加锁。查询操作(SELECT),会自动给涉及的所有表加读锁,更新操作(UPDATE、DELETE、INSERT),会自动给涉及的表加写锁。也可以显示加锁:

行锁和表锁的优缺点:

行锁:

优点:并发度高。

缺点:开销大,加锁慢,容易死锁。

表锁:

优点:开销小,加锁快,不会死锁。

缺点:并发度低。

默认情况下,表锁和行锁都是自动获得的, 不需要额外的命令。

行锁触发死锁的原因

从操作系统的原理来看,触发死锁肯定都满足以下四个条件:

1.互斥

2.不可剥夺

3.请求与保持

4.循环等待

如下图,就死锁了,事务A给id=1的数据上锁,然后事务B给id=2的数据上锁,然后事务A请求id=2的数据,但事务B又请求id=1的数据,事务A,B都不释放锁,当又请求其他事务上锁的数据。就造成了死锁。

表锁为什么不会触发死锁?

从操作系统的原理来看,触发死锁肯定都满足以下四个条件:

1.互斥

2.不可剥夺

3.请求与保持

4.循环等待

既然表锁不会发生死锁,那肯定不满足上面的一个或多个条件。我们一个个来看,互斥和不可剥夺,肯定满足的,那么请求与保持呢,我们假设一下,如果满足了请求与保持,那么循环等待是不是一定也会满足,因为数据库不可能只有一个事务的啊,所以肯定是不满足请求与保持条件了。我们 再看看表锁的上锁原理:执行操作时,会自动给涉及的所有表加锁。那就意味着,在做操作前,会把需要的表都锁上,都拿到那些表的锁,那就不存在请求与保持了,因为你都把你需要的东西都拿到了,你还请求什么呢?所以表锁不会发生死锁。

行锁细分---记录锁、间隙锁、临键锁

1.记录锁 

注意:记录锁、间隙锁、临键锁都是排它锁。所以行锁肯定也是排它锁。

记录锁就是最普通的行锁,它只锁住某一行。

如下图,锁住的是id=1的数据行。

需要注意的是:

1.  上面的 id 列必须是 唯一索引 或者 主键。否则会退化成临键锁。(所以记录锁是 基于 唯一索引的锁)

2.  查询条件也必须是精确查询(=),而不能是 >、<、like等范围的,否则也会退化成临键锁。

2. 间歇锁

间歇锁锁住的是一个区间。

间歇锁可以由唯一索引触发,也可以由非唯一索引触发。

唯一索引触发:

结论:

1.对于指定查询某一条记录的加锁语句如果该记录不存在,会产生记录锁和间隙锁,如果记录存在,则只会产生记录锁,如:WHERE `id` = 5 FOR UPDATE;(其中id=5的数据存在)

2.对于查找某一范围内的查询语句并加锁,会产生间歇锁。如:WHERE `id` BETWEEN 5 AND 7 FOR UPDATE;

1-例子:假设我们有如下表,只有两个字段,主键id 和 name:

如果我们试图锁住不存在的 id = 3的数据:

则会锁住 id区间(2,5),即不允许 INSERT 语句插入 id = 2~5 的数据。所以可以看出来间歇锁锁住 唯一索引的前后区间的。

2-例子:同样假设我们有 1-例子的数据表。

然后对唯一索引使用范围加锁。

这时候会锁住 (5,7] 和 (7,11] 的区间,这两个区间都是不允许插入数据的。

 

非唯一索引触发:

实验的数据表:

两个字段,第一个是主键id,第二个是number字段,且在number字段中创建了一个普通索引。

结论:

1. 普通索引上,只要加锁了,肯定会触发间隙锁,这跟唯一索引不一样。

2. 如果在INSERT操作中,同时包含了唯一索引和非唯一索引,那么在分析间隙区间时就要注意了,因为这时候INSERT阻不阻塞不仅仅跟非唯一索引有关,还跟唯一索引有关。用2-例子说明把。

1-例子:在实验数据表上开启开启事务1:

在number的范围[1,8)上 INSERT数据会阻塞。

2-例子:

开启事务操作如下:给非唯一索引number=5的数据加锁:

INSERT数据格式如下:INSERT时把唯一索引和非唯一索引都用到:

结果如下图:蓝色的为本来就在数据表中的数据,红色的是尝试INSERT进去的数据。

通过经过我们可以看到,红色部分,同样number都是8,但是id=6的却插入时阻塞了,但是id=8的却插入成功了。那是因为插入数据时,会把所插入的数据在原有 的数据表中做一个排序,首先通过非唯一索引number排,若number相同再通过唯一索引id排。可以看到id=6的数据其实是处于间隙锁范围内的,所以才插入不成功。

3. 临键锁

临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。

注:临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。

 每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

 

这章要点:

  1. 记录锁、间隙锁、临键锁,都属于排它锁;
  2. 记录锁就是锁住一行记录;
  3. 间隙锁只有在事务隔离级别 RR 中才会产生;
  4. 唯一索引只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁,指定给某条存在的记录加锁的时候,只会加记录锁,不会产生间隙锁;
  5. 普通索引不管是锁住单条,还是多条记录,都会产生间隙锁;
  6. 间隙锁会封锁该条记录相邻两个键之间的空白区域,防止其它事务在这个区域内插入、修改、删除数据,这是为了防止出现 幻读 现象;
  7. 普通索引的间隙,优先以普通索引排序,然后再根据主键索引排序;
  8. 事务级别是RC(读已提交)级别的话,间隙锁将会失效。

三、 意向锁

意向锁分为 共享意向锁(IS锁)排它意向锁(IX锁)。意向锁是数据引擎自己维护的,用户无法自己操作意向锁。在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在数据表的对应意向锁。

存在意义

InnoDB为了让表锁和行锁共存而使用了意向锁。而且,意向锁是表锁。意向锁不与行锁冲突,只与表锁冲突。

 

为什么没有意向锁的话,行锁和表锁就不能共存

例如,如果事务A要锁住某个表的某行数据, 但是事务B要锁住整个表。那么问题就是,事务A既然锁住了一行数据,那么那一行数据是不允许别的事务修改的,但是事务B锁住了整个表,那按理说事务B能修改表上的任何数据,所以如果没有意向锁的话,行锁和表锁就会出现这样的问题。

 

意向锁如何让行锁和表锁共存?

有了意向锁后,当事务A去申请一张数据表的某行数据时(行锁---排它锁)之前,数据库会自动给这张数据表上意向排它锁。这个时候如果事务B再去申请这张数据表的表锁(排它锁)时,事务B就会阻塞。

下图是意向锁和表锁的互斥和兼容关系:

例如,如果一个数据表已经上了意向共享锁(IS),那么这时候有别的事务给他上一个共享的表锁,那他们是可以共存的。但是如果上的是排它的表锁,那就是不能共存的。

注意:这里的排他 / 共享锁指的都是表锁!!!意向锁不会与行级的共享 / 排他锁互斥!!!

 

四、乐观锁和悲观锁

上面所说的都属于悲观锁。

悲观锁

悲观锁是对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。所以在整个数据处理过程中,需要先将数据锁定。

悲观锁的实现,通常依靠数据库提供的锁机制实现,比如mysql的排他锁,select .... for update来实现悲观锁。

乐观锁

顾名思义,就是对数据的处理持乐观态度,乐观的认为处理数据时一般情况下不会发生并发冲突,只有提交数据更新时,才会对数据是否冲突进行检测。

乐观锁的实现,是不基于mysql提供的锁机制的,需要我们自已实现,实现方式一般是记录数据版本,一种是通过版本号,一种是通过时间戳。

具体方式:

给表加一个版本号或时间戳的字段,

1. 读取数据时,将版本号一同读出

2. 无论哪个事务更新更新数据时,都将版本号加1。

假设现在有个事务A要更新某行数据,它会先:

1. 查询准备修改的数据 和 得到它的版本号a

2. 这时候如果有其他事务B修改了这行数据的话,事务B肯定会给这行数据的版本号加1.

3. 然后当事务A开始修改这行数据时,再获取一次这行数据的版本号,发现它已经变了(被事务B修改了),那么事务A就不会继续修改这行数据,因为出现并发冲突了。反之如果事务A发现再次获取的版本号跟第一步获得的版本号一致,则说明,没有其他事务修改过这行数据,没有发生并发冲突,这时候事务A就会执行修改命令,修改这行数据了。

总结:

1.悲观锁使用了排他锁,当程序独占锁时,其他程序就连查询都是不允许的,导致吞吐较低。如果在查询较多的情况下,可使用乐观锁。

2.乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入较频繁,对吞吐要求不高,可使用悲观锁。

也就是一句话:读用乐观锁,写用悲观锁。


 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值