MYSQL---锁

通过本文我们将了解以下几个问题?

一、思考

问题一:多线程下,MYSQL是如何同步的?

比如多个线程同时要更新某一行数据,这时候MYSQL的处理机制是什么?

问题二:MYSQL中有哪些锁?其作用都是什么?

相比大家听说过行锁、表锁、乐观锁、悲观锁、间隙锁、死锁、共享锁、排它锁这些概念,本文将探索这些锁是干嘛的。

 二、分析

接下来我们将一一揭开MYSQL中各种锁的面纱。

2.1行锁和表锁

针对锁粒度的不同,分别有行锁和表锁之分,表锁针对的是整个表,而行锁针对的是数据行。MYSIAM采用表锁机制,而InnoDB默认采用行锁机制,但是InnoDB也支持表锁。

行级锁和表级锁比较:

表级锁

优点

  1. 加锁速度更快,开销更小。
  2. 不会产生死锁情况。

缺点

  1. 因为其针对整个表加锁,所以锁粒度更大,并发情况下竞争锁时冲突概率更高,即并发度更低。

行级锁

优点

  1. 锁粒度更小,并发情况下竞争锁时冲突概率更低,即并发度更高。

缺点

  1. 加锁速度慢,开销大。
  2. 可能产生死锁。

这里为什么表锁不会产生死锁呢,假如一个线程获取锁的顺序是A表,B表,另一线程是B表,A表,不是仍然可能死锁吗?

这是因为MyISAM存储引擎通过总是一次性同时获取所有需要的锁以及总是按相同的顺序获取表锁来避免死锁。

MySIAM中表级锁的实现模式:

表共享读锁:当查询某个表,MySIAM会给此表加读锁,允许其他线程对该表的读操作,阻塞对该表的写操作。

表独占写锁:当更新某个表数据时,MySIAM会给该表加写锁,阻塞其他线程对该表的读和写。

MyISAM存储引擎中默认写锁的优先级会高于读锁。但是也可以通过以下参数降低更新操作的优先级,避免查询时间太长,导致查询线程饿死情况。具体操作请参考博文:https://www.cnblogs.com/zhoujinyi/p/3337347.html

如何加表锁:

MySIAM存储引擎在读取数据时,会自动为表加共享读锁,更新数据时为表加独占写锁,因此一般情况下不需要手动加表锁。那如何采用手动方式加表锁呢?可以通过如下方式:

加共享读锁:lock tables t   read/read local;read和read local的区别在于对于read local这种共享读锁,不会阻塞其它线程的插入操作,但是更新操作依然阻塞。

加独占写锁:lock tables t  write;

2.2共享锁和排它锁

上文中介绍了行锁和表锁的区别,以及MySIAM中表锁的锁模式,该节将介绍InnoDB行锁的实现模式:共享锁和排它锁。

共享锁和排它锁的比较:

  1. 共享锁(S锁):当某个事务获取共享锁,允许其他事务读一行和加共享锁,但是禁止其他事务获取相同数据的排它锁和修改数据,当前事务也不可修改数据。
  2. 排它锁(X锁):当某个事务获取排它锁,其它事务无法获取相同数据的共享锁和排它锁,其他事务可以读取数据,但是不能修改数据,当前事务可修改数据。

除了共享锁和排它锁,InnoDB为了支持行锁和表锁共存特性(多粒度锁),还提供了意向共享锁和意向排它锁,意向锁(Intention Locks)是一种不与行级锁冲突的表级锁。我们先了解一下意向锁。

意向共享锁和意向排它锁

  1. 意向共享锁(IS):事务有意向给数据行加共享锁(S),在加共享锁之前必须先获得该表的意向共享锁。
  2. 意向排它锁(IX):事务有意向给数据行加排它锁(X),在加排它锁之前必须先获得该表的意向排它锁。

互斥性

备注:该图来源于博文《MYSQL锁总结:https://zhuanlan.zhihu.com/p/29150809

兼容:代表获取锁成功。

冲突:获取锁失败,阻塞。

注意:

  1. 意向锁和意向锁之前是不会产生互斥的
  2. 意向锁和非意向锁之间的互斥针对的是表锁,不同的事务对于不同的行加锁是不会互斥的。

意向锁存在的意义

上问分析过假如事务A要修改表t的某一行,必然获取该行数据的X锁和该表的IX锁。但是因为意向锁之间不互斥,其他事务要修改该t表的另一行数据,当然不会阻塞,因为其获取IX锁和X锁都将成功。那么意向锁存在的意义是什么?

假如有这种情况:事务A要修改表t的某一行,必然获取该行数据的X锁和该表的IX锁。而事务B执行lock tables t write,即事务表要锁定该表。那么事务B如何得知该表有没有已经被锁定呢?最快的方式当然是判断该表是否已经加了IX锁,而不是遍历该表是否有一行数据加了X锁。这也说明了IX锁和X锁互斥,而且互斥针对的是表锁。

如何加共享锁和排它锁

意向锁是InnoDB引擎内部的实现机制,不需要开发者去做任何事情。所以这里只讨论共享锁和排它锁的加锁方式:

对于更新操作(UPDATE\DELETE\INSERT)操作,InnoDB会自动为数据行加排它锁(X),无须开发者干预。

对于查询操作(SELECT),InnoDB并未对其加任何锁,开发者可以手动加锁(显示锁定),方式如下:

共享锁(S):SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE;此时其他事务仍然可以查询该数据,或者并在该数据上加共享锁;但是如果当前事务内后续有对于该行的更新操作则有可能造成死锁。

排它锁(X):SELECT * FROM t WHERE id=1 FOR UPDATE;此时其他事务是可以查询到该数据的,但是如果其他事务也要获取共享锁或者排它锁,将被阻塞。

此外只有根据索引检索数据时,InnoDB引擎才会使用行锁,否则将使用表锁。

2.3乐观锁和悲观锁

乐观锁和悲观锁比较

乐观锁:类似于JAVA中的CAS失败重试机制,乐观的认为在同一时刻并发的情况还是少数,不显示加锁,可以通过版本控制的方式实现(稍后介绍)。所以这只是一种思想,并未正真加锁。

悲观锁:悲观锁认为接下来的操作一定会产生并发问题,所以提前加上排它锁。

乐观锁和悲观锁的加锁方式

乐观锁(版本控制):

假如要采用乐观锁机制更新表t的某个字段x,可以为该表添加一个version字段。采用如下方式更新:

UPDATE t  SET x='a' WHERE id=1  AND version=#{version},version为当前事务查询出来的值。如果更新失败,则说明已经有其他事务修改了该数据行。

悲观锁(排它锁):

在更新数据之前直接加锁,可以利用上节所讲行锁排它锁:

SELECT x  FROM t WHERE id=1 FOR UPDATE;获取排它锁,如果获取成功可以安全的更新,如果获取失败,说明其他事务已经获取排它锁,当前事务将阻塞。

2.4间隙锁

概念

何为“间隙”?

当我们根据索引进行范围查询时,比如有表t,id为主键索引,age为非唯一普通索引(唯一索引会降级为行锁)。存在如下记录:

(1,10),(2,20),(50);

有如下查询语句:SELECT name FROM t WHERE age>=20 AND id<=50;

那么在20<id<50之间就被称之为“间隙”。当你执行上述语句时,InnoDB引擎会自动为你加上间隙锁,防止并发情况下其他事务插入间隙范围内的数据。 

InnoDB支持的行锁定方式有:

行锁(Record Lock):锁定某一行记录。

间隙锁(GAP Lock):锁定间隙范围内记录。

行锁和间隙锁组合(Next -key Lock):自动升降级为间隙锁和行锁。

加锁时机

除了上文所说的范围检索以外,在以下情况下会加间隙锁:

  1. 范围查询,对于范围内不存在的记录加间隙锁。
  2. 指定条件查询,对于查询结果不存在的记录也会加间隙锁。

间隙锁的意义

  1. InnoDB存储引擎默认的事务隔离级别为可重复读(RR),而其默认会使用Next-key Lock来防止幻读的产生。
  2. 保证MySQL主从复制的正确性,具体请参考:https://www.cnblogs.com/rjzheng/p/10510174.html

2.5死锁

死锁的一般概念

有线程A和B,共享资源S1和S2且资源同时只能被某一个线程占用,A持有资源S1等待获取资源S2,B持有资源S2等待获取S1,这时A和B将互相等待,若无外界干扰,这种情况将无限期持续,造成死锁。

死锁产生的必要条件:

  1. 互斥条件:即资源在某段时间内只能由一个线程占用。比如上述S1同时只能被A所占用。
  2. 不可抢占条件:即线程已经拥有的资源在未被自己释放之前,不可由其它线程抢占。比如上述A拥有S1在A未主动释放之前,B不能抢夺。
  3. 请求和保持条件:即某个线程已经至少拥有了一个资源,又再次请求其它资源。比如上述A已经拥有S1再次请求S2。
  4. 循环等待条件。即发生死锁时必然存在多个线程循环等待的情况。比如上述A等待被B占用的资源S2,B等待被A占用的资源S1,造成循环。

InnoDB中死锁发生条件

假如有表t1,字段id,age(唯一索引),存在数据:1,18),(2,20)表t2,字段id,tel(唯一索引),存在数据:(2,188),(2,187)

现有事务A和事务B。情况如下:

事务A:

  1. 执行SELECT * FROM t1 WHERE age=18 FOR UPDATE,获取排它锁,满足互斥条件和不可抢夺条件;
  2. 执行SELECT * FROM t2 WHERE tel=188 FOR UPDATE,满足请求和保持条件。

事务A:

  1. 执行SELECT * FROM t1 WHERE tel=188 FOR UPDATE,获取排它锁,满足互斥条件和不可抢夺条件;
  2. 执行SELECT * FROM t2 WHERE age=18 FOR UPDATE,满足请求和保持条件。

假设两个事务并发执行,则很大可能造成循环等待条件,那么就会触发死锁产生。

这只是根据死锁产生的必要条件列举了死锁发生的可能情况之一。在MYSQL中其他可能产生死锁的情况如下:

  1. 不同表相同的记录行锁冲突。
  2. 相同表不同记录行锁冲突。
  3. 间隙锁造成锁冲突。

如何排查和避免死锁

我们知道死锁处理方式有两种:

  1. 预防方式,防患于未然。
  2. 死锁恢复,已经发生死锁之后如何处理。

这里转自博文:https://www.aneasystone.com/archives/2018/04/solving-dead-locks-four.html,如果您有兴趣,可仔细阅读一下。

三、其他

行锁升级为表锁

因为MYSQL的行锁是针对索引加的锁,如果检索条件不走索引或者索引失效,那么就会从行锁升级为表锁。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

参考博文:

  1. MYSQL锁总结:https://zhuanlan.zhihu.com/p/29150809
  2. 详解MYSQL中意向锁的作用:https://juejin.im/post/5b85124f5188253010326360
  3. MYSQL间隙锁原理:https://www.cnblogs.com/aspirant/p/9177978.html
  4. 解决死锁之路:https://www.aneasystone.com/archives/2018/04/solving-dead-locks-four.html
  5. MYSQL应该选什么事务隔离级别:https://www.cnblogs.com/rjzheng/p/10510174.html
  6. 常见SQL语句的加锁分析:https://www.aneasystone.com/archives/2017/12/solving-dead-locks-three.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值