mysql中的锁

本文转载自https://blog.csdn.net/qq_37555071/article/details/120472767、https://mp.weixin.qq.com/s/Wq8-fdH_WR-2lvf-Lut5Yw

 

MySQL 的并发控制是在数据安全性和并发处理能力之间的权衡,通过不同的锁策略来决定对系统开销和性能的影响。

只要存在多个客户端同时修改更新数据,就会存在并发问题,MySQL 通过 MVCC 和锁来处理这一问题。

Mysql中的行锁和表锁

锁的粒度

MySQL 源码中定义了两种锁的粒度,分别是表锁和行锁。

表锁是指对一整张表加锁,一般是 DDL 处理时使用;而行锁则是锁定某一行或者某几行,或者行与行之间的间隙。

 表锁由 MySQL Server 实现,行锁则是存储引擎实现,不同的引擎实现的不同。在 MySQL 的常用引擎中 InnoDB 支持行锁,而 MyISAM 则只能使用 MySQL Server 提供的表锁。

表锁

表锁由 MySQL Server 控制,优点是开销小、加锁快,不会产生死锁,缺点是加锁粒度大,发生锁冲突的概率大,并发度比较低。

 一般 DDL Data Define Languge,如createdropalter)语句会自动加表锁,也可以手动指定。表锁分为读锁和写锁。

//加读锁
lock table products read;
//加写锁
lock table products write;

当对表加了读锁,则会话只能读取当前被加锁的表,其它会话仍然可以对表进行读取但不能写入。

当对表加了写锁,则会话可以读取或写入被加锁的表,其它会话不能对加锁的表进行读取或写入。

行锁

行锁由存储引擎实现,InnoDB 支持,而 MyISAM 不支持。行锁的优点是锁粒度小,发生锁冲突概率小,并发度高,缺点是开销大、加锁慢,并且可能产生死锁。

InnoDB 行锁是通过索引项加锁来实现的,只有通过索引条件检索数据,才能锁住指定的索引记录,否则将使用行锁锁住全部数据(有文章称会退化为表锁,是错误的理解)。

表级锁适合查询多、更新少的场景,行级锁适合按索引更新频率高的场景。InnoDB 默认使用行级锁。

 

锁的模式 Lock Mode

MySQL 源码中定义了多种锁的模式,如下:

共享锁和排它锁

共享锁和排它锁都是行级锁。

Shared Lock (S 锁),共享锁,也称为读锁。当事务对行加共享锁后,允许其它事务对相同行加共享锁,但不允许加排它锁。

Exclusive Lock (X 锁),排它锁,也称为写锁。当事务对行加排它锁后,不允许其它事务对相同行加共享锁或排它锁。

意向锁

意向锁分为意向共享锁和意向排它锁,意向锁是表锁。

Intention Shared Lock (IS),意向共享锁,也称为意向读锁。意向共享锁表示有事务打算在行记录上加共享锁,在事务获取行 S 锁前,必须先获得 IS 锁或更高级别的锁。 

Intention Exclusive Lock (IX),意向排它锁,也称为意向写锁。意向排它锁表示有事务打算在行记录上加排它锁,在事务获取行 X 锁前,必须先获 IX 锁。

意向锁之间不会发生冲突,但共享锁、排它锁、意向锁之间会发生冲突。

自增锁

AUTO-INC Locks,自增锁,它是一种特殊的表锁。当表有设置自增 auto_increment 列,在插入数据时会先获取自增锁,其它事务将会被阻塞插入操作,自增列 +1 后释放锁,如果事务回滚,自增值也不会回退,所以自增列并不一定是连续自增的。

 

行锁的分类

MySQL 中定义了四种行锁的分类:

记录锁

Record Locks,记录锁是索引记录的锁定。例如 SELECT a FROM t WHERE a = 15 FOR UPDATE,对索引记录 15 进行锁定,防止其它事务插入、删除、更新值为 15 的记录行。 

记录锁是通过索引加锁,如果列没有设置索引,则将使用聚簇索引,如果没有人为指定聚簇索引,MySQL 会自动建立一个聚簇索引。

间隙锁

Gap Locks,间隙锁是对索引记录之间的间隙的锁定。对于键值在条件范围内但并不存在的记录,叫做间隙(gap)。例如 SELECT a FROM t WHERE a > 15 and a < 20 FOR UPDATE,且 a 存在的值为 1、2、5、10、15、20,则将 15,20 中的间隙锁住。 

间隙锁和间隙锁之间是互不冲突的,间隙锁唯一的作用就是为了防止其他事务的插入,在 RR(可重复读)级别下解决了幻读的问题。

临键锁

Next-Key Lock,临键锁,是记录锁和间隙锁的合集。例如 SELECT a FROM t WHERE a > 15 FOR UPDATE,且 a 存在的值为 1、2、5、10、15、20,则将 (15,20]、(20, +∞] 的中 15、20 及其间隙锁住。

插入意向锁

Insert Intention Locks,插入意向锁,是一种特殊的间隙锁,只有在执行 INSERT操作时才会加锁,插入意向锁之间不冲突,可以向一个间隙中同时插入多行数据,但插入意向锁与间隙锁是冲突的,当有间隙锁存在时,插入语句将被阻塞,正是这个特性解决了幻读的问题。

 

何时加锁

  • SELECT xxx 查询语句正常情况下为快照读,不加锁;
  • SELECT xxx LOCK IN SHARE MODE 语句为当前读,加 S 锁;
  • SELECT xxx FOR UPDATE 语句为当前读,加 X 锁;
  • DML 语句(INSERT、DELETE、UPDATE)为当前读,加 X 锁;
  • DDL 语句(ALTER、CREATE 等)加表级锁,且是隐式提交不能回滚;

在不同的事务隔离级别下,会有不同的锁机制,也可以说是通过不同的锁机制实现了不同的事务隔离级别。在 RC(读已提交)级别下,只会有记录锁,不存在间隙锁和 Next-Key 锁,RR(可重复读)级别下才会有间隙锁及 Next-Key 锁。

仅通过锁来控制实现事务隔离级别会存在一些问题,比如要实现 RC(读已提交)级别,事务 a 更新一行数据,需要对行(实际是索引记录)加 X 锁,阻塞其它事务对该行的读写,事务 b 想要读取该行必须等到 a 提交或回滚释放锁,这样的话就会很大程度上限制读写的并发能力。

MVCC 的原理是通过在每行记录上加了隐藏的三列(隐式的 ID 字段、事务 ID、回滚指针),事务在写一条记录时会将其拷贝一份生成这条记录的一个原始拷贝,写操作是会对原记录加锁,但是读操作会读取未加锁的拷贝快照记录,这就保证了读写并行。

RC 和 RR 级别下,才会使用 MVCC 机制,RC 级别下事务总是读取最新的快照版本,RR 级别下事务总是读取事务开启时的快照版本,这称为快照读。当前读是指读取数据的最新版本,而非快照,也称为加锁读。

 

悲观锁和乐观锁

乐观锁

乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后提交事务时,再去判断是否有冲突了,如果有则进行回滚,如果没有则执行提交操作。

 通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。如果要对那条记录进行操作(更新),先查询出那条记录,获取出version字段 ,判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他事务对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他事务对其进行操作了,则不进行更新操作。

悲观锁

在执行操作时,假定会发生并发冲突,在进行每次操作时都要通过获取锁才能进行对数据的进行操作。

共享锁和排它锁是悲观锁的不同的实现,它们都需要先获取锁才能进行操作。

 

活锁和死锁

活锁

如果事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待;T3也请求封锁R,当T1释放了R上的封锁之后系统首先批准了T3的请求,T2仍然等待;然后T4又请求封锁R,当T3释放了 R上的封锁之后系统又批准了T4的请求· · · · · · T2有可能永远等待,这就活锁的情况。

避免活锁的简单方法是采用先来先服务策略。

死锁

如果事务T1封锁了数据R1,T2封锁了数据R2,然后T1又请求封锁R2,因T2已封锁了R2,于是T1等待T2释放R2上的锁;接着T2又申请封锁R1,因T1已封锁了R1,T2也只能等待T1释放R1上的锁。这样就出现了 T1在等待T2,而T2又在等待T1的局面,T1和T2两个事务永远不能结束,形成死锁。

目前在数据库中解决死锁问题主要有两类方法,一类方法是采取一定措施来预防死锁的发生,另一类方法是允许发生死锁,采用一定手段定期诊断系统中有无死锁,若有则解除之。

 

三级封锁协议

并发执行的事务会带来一些问题,比如脏读、不可重复读、幻读还有第一类更新丢失(指A事务回滚时覆盖了B事务已经提交的数据)和第二类更新丢失等(指A事务提交时覆盖了B事务已经提交的数据)。为了解决这些问题,MySQL事务提出了4个不同的隔离级别,而这些隔离级别的实现本质上就是通过加锁、解锁来实现的。

 一级封锁协议是指,事务T在修改数据R之前必须先对其加排他锁(X锁),直到事务结束才释放。

二级封锁协议是指,在一级封锁协议基础上增加事务T在读取数据R之前必须先对其加共享锁(S锁),读完后即可释放共享锁S锁)。

三级封锁协议是指,在一级封锁协议的基础上增加事务T在读取数据R之前必须先对其加共享锁(S锁),直到事务结束才释放。

posted @ 2023-01-30 16:45  carol2014  阅读(127)  评论(0编辑  收藏  举报