MySQL锁相关问题的学习

1. 锁的概述

1.1 锁的定义

锁是计算机协调多个进程或线程并发访问某一资源的机制

在计算机中,数据是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的问题,锁冲突也是影响数据并发访问性能的一个重要因素。从这个角度来说,锁对数据库显得尤其重要,也尤其复杂。

1.2 锁的分类

  • 性能上分为乐观锁悲观锁

    • 乐观锁:乐观的以为不会发生冲突,不会加锁,只有在提交更新的时候回去判断是否冲突,然后决定是否更新值测试。
    • 悲观锁:悲观的认为对一个数据的并发访问,一定会发生冲突。因此,会为进行操作的数据进行加锁,悲观的认为不加锁的数据一定会出问题。
  • 对数据库操作的类型分为:读锁写锁。注意:读锁和写锁都属于悲观锁

    • 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会相互影响。
    • 写锁(排它锁):针对同一份数据,当前写操作没有完成时,会阻断其他写锁和读锁。
  • 按照锁的共享策略分为:

    • 共享锁:针对同一份数据,多个读操作可以同时进行而不会相互影响。
    • 排它锁:针对同一份数据,当前写操作没有完成时,会阻断其他写锁和读锁。
    • 意向共享锁(IS):在给行记录加上S锁后,就会给表加IS锁。主要是为了在给表加表锁时,判断该表中有没有行锁。
    • 意向排它锁(IX):在给行记录加上X锁后,就会给表加IX锁。主要是为了在给表加表锁时,判断该表中有没有行锁。
  • 从对数据的操作粒度分为:

    • 行级锁:针对的是表中的记录行
    • 页级锁:针对的是页
    • 表级锁:针对的是表

2. 两种锁(表锁、页级锁、行锁)

2.1 表锁(偏读)

表锁是MySQL中锁定粒度最大的锁,开销小不会出现死锁,但是冲突高并发度低

2.1.1 读锁和写锁

表锁分类

  • 表读锁(表共享锁)
  • 表写锁(表排他锁)

表锁特点:

- 开销小,加锁快,不会出现死锁,发生锁冲突的概率最高,并发度也最低。

不同引擎中的表锁

  • InnoDB引擎中,在对某个表执行增删改查操作时,InnoDB引擎并不会对这个表加表锁(InnoDB引擎采用的是行锁),如果想要加表锁需要手动添加(可以加表的读锁或表的写锁)。

  • MYISAM引擎中,在执行select语句前,会自动加上表的读锁,在执行更新操作时(update、delete、insert),会自动给表加上写锁,这个过程不需要用户干预(因为是默认自动添加)。

表锁的基本操作

  • 手动添加表锁

    lock table 表名称 read/write;
    
  • 查看表上加过的锁

    show open tables;
    
  • 删除表锁

    unlock tables;
    

注意

一个事务给一个表加了读锁。其他的事务可以对相同的表加读锁,但是不可以加写锁。
一个事务给一个表加了写锁,其他的事务不可以对该表加任何锁。

在InnoDB引擎中,尽量不要使用表锁,因为InnoDB引擎的优点就是在于行锁。

2.1.2 IX锁和IS锁

  • IX锁(意向排它锁):当事务准备给某条记录加上X锁时,需要先给该表加上IX锁。
  • IS锁(意向共享锁):当事务准备给某条记录加上S锁时,需要先给该表加上IS锁。
IX、IS锁属于表级锁,它们的提出是为了之后在加表级别的X和S锁时为了快速判断表中有没有行加锁,以免使用遍历的方式判断表中的记录有没有加锁。就是说当对一个行加上锁之后,如果有意向为该表加一个表锁,必须先看看该表有没有其他的行锁,否则会出现行锁。IS锁和IS锁就避免了判断表中行有没有锁时采取便利的方式查找,直接查看表有没有意向锁就知道表中有没有其他行锁。

注意:如果表中有多个行锁,那么在加每一个行锁的同时都会给该表加一个意向锁,意向锁之间并不冲突。

2.1.3 AUTO-INC锁

  • 在执行插入语句时就在表级别加一个AUTO-INC锁,然后为每条待插入的AUTO-INCREMENT修饰的列修饰递增的值,在该语句执行结束后,再把AUTO-INC锁释放掉。这样一个事务在持有AUTO-INC锁的过程中,其他事务的插入语句都要被阻塞,可以保证一个语句中分配的递增值是连续的。
  • 采用一个轻量级的锁,在为插入语句生成AUTO-INCREMENT修饰的列的值时获取一个这个轻量级锁,然后生成本次插入语句需要用到的AUTO-INCREMENT修饰的列的值之后,就把该轻量级锁释放掉,并不需要等到整个插入语句执行完才释放锁。

2.1.4 锁的兼容性

下面是关于S、X、IX、IS锁的兼容性的表格

横纵坐标分别代表不同事务的锁

ISIXSX
IS兼容兼容兼容不兼容
IX兼容兼容不兼容不兼容
S兼容不兼容兼容不兼容
X不兼容不兼容不兼容不兼容

2.2 行锁(偏写)

行锁是作用在行记录上的锁(单行或多行),粒度小,我们接着往下看。

行级锁是MySQL中锁定力度最小的锁。表示行记录上的锁,行锁一定是作用在索引上的。行锁冲突小,加锁力度小,但是开销大。

**重点:**行锁一定作用在索引上

InnoDB默认采用行锁,行锁开销大、加锁慢、锁的粒度小、会出现死锁、锁的冲突小、并发度最高。

InnoDB与MYISAM引擎最大的不同就是:1. 支持事务; 2. 采用行锁。								

行级锁可以分为

2.2.1 行锁的种类

  • 记录锁(Record Locks):单个行记录上的锁。
  • 间隙锁(Gap Locks):间隙锁,锁定一个范围,但不包括记录本身。比如锁定 id = 5 及其前后两个范围内的数据,也就是将 id = 3,4,6,7这些行都锁了起来,不包括本身 id = 5 的行。间隙锁的目的,是为了防止同一事务的两次当前读出现幻读的情况。
  • 临键锁(Next - key Locks):锁定一个范围,并且锁定记录本身。比如锁定 id = 5 及其前后两个范围内的数据,也就是将 id = 3,4,5,6,7这些行都锁了起来。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。

2.2.2 记录锁(Record Locks)

行级锁可以分为

  • 读锁:当一个事务对数据加上读锁,其他事务仍然可以对该数据加读锁,但是不可以加写锁。
  • 写锁:当一个事物对数据加上写锁,其他事务不可以对该数据加任何锁。

2.2.3 间隙锁(Gap Lock)

间隙锁,锁定一个范围,但不包括记录本身,(间隙锁的粒度是要比锁定单行记录的记录锁的粒度要大一些的,因为间隙锁是锁住了多行,包括根本不存在的行),间隙锁的间隙一定是开区间,比如(3,5)。

GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的现象。间隙锁只会在隔离级别为RR(可重复读)及以上隔离级别才会存在。间隙锁的目的是为了让其他事务无法在间隙中新增数据

间隙锁在本质上是不区分共享间隙锁或互斥间隙锁的,而且间隙锁是不互斥的,即两个事务可以同时持有包含共同间隙的间隙锁。这里的共同间隙包括两种场景:其一是两个间隙锁的间隙区间完全一样;其二是一个间隙锁包含的间隙区间是另一个间隙锁包含的间隙区间子集。间隙锁本质上是用于阻止其他事务在该间隙内插入新数据,而自身事务是允许在该间隙内插入数据的。也就是说间隙锁的应用场景包括并发读取、并发更新、并发删除和并发插入

注意:在读未提交和读已提交两种隔离级别下只有行锁,是没有间隙锁的。在可重复读和串行化两种隔离级别下是有间隙锁的。

2.2.4 临建锁(Next-Key Lock)

临建锁是记录锁和间隙锁的结合,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题,临建锁是InnoDB默认的锁,临建锁的区间是左开右闭的,比如(3,5]。


临建锁的效果相当于记录锁加上间隙锁。当间隙锁加在某索引上,则该纪录和它前面的区间都会被锁定。假如有记录1,3,5,7,现在记录5上加Next-Key Lock,则会锁定区间(3,5],任何试图插入到这个区间的记录都会被阻塞。


record lock、gap lock、 next-key lock都是加在索引上的。假如有记录1,3,5,7,则5上的记录锁会锁住5,5上的间隙锁会锁住(3,5)5上的临键锁会锁住(3,5]。 注意:临键锁规定是左开右闭区间


InnoDB的默认事务隔离级别是可重复读,在这种级别下,如果使用select ···· where ···· lock in share mode 或者select ···· where ···· for update 语句,那么InnoDB会使用临建锁,因而可以防止幻读;但即使隔离级别是可重复读,如果只是使用普通的select语句,那么InnoDB将是快照读,不会使用任何锁,因而还是无法防止幻读

2.3 页级锁

页级锁介绍

页级锁是MySQL中锁的粒度介于行锁和表锁之间的锁,表级锁速度快,但冲突多,行级速度慢,但冲突少,因此采用折中的页级锁,一次锁定一组相邻的数据。BOB引擎默认支持页级锁。

页级锁的特点

开销和加锁时间介于表锁和行锁之间,会出现死锁。锁的粒度介于行级锁和表级锁之间,并发度一般。

3. 常见锁

3.1 常见锁

下面我们来介绍一些常见的锁

  • 行锁(Record Locks):属于行级锁,悲观锁
  • 间隙锁(Gap Locks):属于行级锁,悲观锁
  • 临键锁(Next-Key Locks):属于行级锁,悲观锁
  • 共享/排它锁(S/X):悲观锁
  • 意向共享/意向排它锁(IS/IX):属于表级锁,悲观锁
  • 插入意向锁(Insert Intention Locks):属于特殊的间隙锁,悲观锁
  • 自增锁:属于表级锁

3.2 插入意向锁

插入意向锁是本质上是一个间隙锁

  • 普通的间隙锁不允许在(上一条记录,本记录范围内)插入数据
  • 插入意向锁允许在(上一条记录,本记录范围内)插入数据

插入意向锁的作用是为了提高并发插入的性能,多个事务同时写入不同数据至同一索引范围(区间)内,并不需要等待其他事务完成,不会发生锁等待。

尽管插入意向锁属于间隙锁,但两个事务不能在同一时间内一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(当然,插入意向锁不在间隙区间内则是可以的)

补充:
	共享锁用于读取操作
	排它锁用于更新或删除操作
	插入意向锁用于插入操作
也就是说,共享锁、排它锁、插入意向锁涵盖了常用的增、删、查、改四个操作

3.3 共享锁/排它锁

共享锁排它锁都只是行锁,与间隙锁无关。共享锁是一个事务并发读取某一行记录所需要持有的锁。排它锁是一个事务并发更新或删除某一行记录所需要持有的锁。

不过需要说明的是,尽管共享锁和排它锁都是行锁,与间隙锁无关,但一个事务在请求共享锁或排它锁时,获取到的结果可能是行锁间隙锁临建锁。这取决于事务的隔离级别以及查询的数据是否存在

3.4 悲观锁和乐观锁

悲观锁

悲观的认为对数据的并发操作一定会发生修改,因此,对于同一个数据的并发操作,悲观所采用加锁的方式。

悲观锁就是数据库中的行锁,悲观的认为一定会发生修改,在操作之前加锁。

乐观锁

乐观锁认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用不断更新的方式来修改数据。也就是先不管资源有没有被别的线程占用,直接申请操作,如果没有发生冲突,那就是操作成功,如果发生冲突,有其他的线程已经在使用了,那么就不断地轮询。乐观地认为,不加锁的并发操作时没有事情的。就是通过记录一个数据历史记录的多个版本,如果修改完之后发现有冲突就将版本返回到没修改的样子。乐观锁就是不加锁,好处就是减少上下文切换,坏处是浪费cpu时间。

乐观锁相对于悲观锁而言,它认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测。如果发生冲突了,会返回错误信息,让用户决定如何去做。

乐观锁其实是一种思想,认为不会锁定的情况下去更新数据,如果发现不对劲,才回滚。在数据库中往往添加一个version字段来实现。乐观锁可以用来避免更新丢失

乐观锁数据库表中的实现

利用数据版本号机制是乐观锁最常用的一种实现方式。一般通过为数据库表增加一个数字类型的“version”字段,当读取数据时,将version字段的值一起读出,数据每更新一次,对此version值+1。让我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行对比,如果数据库当前版本号与第一次取出来的version版本号相等,则予以更新,否则认为是过期数据,返回更新失败。

在这里插入图片描述

文章学习于:
https://csp1999.blog.csdn.net/article/details/113835727
https://blog.csdn.net/Saintyyu/article/details/91269087
https://blog.csdn.net/cy973071263/article/details/104490345

注:文章可能有点枯燥,但是如果耐心看下去的话我相信大家一定会有收获的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值