写点什么

MySQL- 技术专题 -Lock 入门到精通

发布于: 2021 年 04 月 06 日
MySQL-技术专题-Lock入门到精通

锁的由来

概念定义

锁是计算机协调多个进程或线程并发访问共享资源的保证(一致性、有效性)访问机制;共享资源包含了很多类型:比如计算机资源中:CPU、RAM、ROM、CACHE、BUFFER、I/O、共享数据。

本质解释

数据库是多用户实时使用的共享资源系统当多个用户并发地存取数据时,数据库中就会产生多个事务同时存取同一数据页的情况。(数据库引擎基本都是以数据页为基本单位去加载数据)

若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性锁是用于管理对公共资源的并发控制。也就是说在并发的情况下,会出现资源竞争,所以需要加锁。加锁解决了多用户环境下保证数据库完整性和一致性。

Lock 的对象是事务用来锁定的是数据库中的对象,如表、页、行。并且一般 lock 的对象仅在事务 commit 或 rollback 后进行释放(不同事务隔离级别释放的时间可能不同)。

负面问题

出现了锁机制,那么存在锁冲突就会成为影响数据库并发访问性能的一个重要因素

锁的分类

锁粒度分类

行锁

(line-level lock)

行级锁Mysql 中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。有可能会出现死锁的情况行级锁按照使用方式分为共享锁和排他锁。所属常用的数据库引擎:InnoDB(默认锁类型)

特点:开销大、加锁慢、容易出现死锁、锁定粒度很小、发生锁冲突的机率最低、并发性能最好。

表锁

(table-level lock)

表级锁是 mysql 锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。被大部分的 mysql 引擎支持,MemoryMyISAM 和 InnoDB 都支持表级锁,但是 InnoDB 默认的是行级锁。MySQL 表级别锁为两类:元数据锁(Metadata Lock,MDL)、表锁。所属常用的数据库引擎:MyISAM(默认锁类型)、MEMORY(默认锁类型)。

特点:开销小、加锁快、不会出现死锁、锁定粒度大,发生锁冲突机率最高、并发性能最差。

元数据锁

(Metadata Lock -> MDL)

元数据锁(MDL) 不需要显式使用,访问一个表的时候会被自动加上。这个特性需要 MySQL5.5 版本以上才会支持

当对一个表做增删改查的时候,表会被加 MDL 读锁;当对表做结构变更的时候,加 MDL 写锁。

MDL 锁规则
  • 读锁之间不互斥;

  • 读写锁、写锁之间是互斥的,为了保证表结构变更的安全性,如果要多线程对同一个表加字段等表结构操作,就会变成串行化,需要进行锁等待;

  • MDL 的写锁优先级比 MDL 读锁的优先级高;

  • MDL 的锁释放必须要等到事务结束才会释放;


默认情况下,写锁比读锁具有更高的优先级:当一个锁释放时,这个锁会优先给写锁队列中等候的获取锁请求,然后再给读锁队列中等候的获取锁请求这也正是 MyISAM 表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。

MyISAM 总是一次获得 SQL 语句所需要的全部锁,这也正是 MyISAM 表不会出现死锁的原因。

MDL 锁例子

若没有 MDL 锁的保护,则事务 2 可以直接执行 DDL 操作,并且导致事务 1 出错,5.1 版本即是如此。5.5 版本加入 MDL 锁就在于保护这种情况的发生,由于事务 1 开启了查询,那么获得了 MDL 读锁,锁的模式为 SHARED_READ,事务 2 要执行 DDL,则需获得 EXCLUSIVE 锁,两者互斥,所以事务 2 需要等待。

页锁

(page-level lock)

页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录BDB 支持页级锁

锁分类的兼容性

共享锁||读锁||S 锁

(share lock)||Read 锁||S 锁:其他事务可以读,但不能写。允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。

例如事务 T 对数据对象 A 加上 S 锁,则事务 T 可以读 A 但不能修改 A,其他事务只能再对 A 加 S 锁,而不能加 X 锁,直到 T 释放 A 上的 S 锁。保证其他事务可以读 A但在 T 释放 A 上的 S 锁之前不能对 A 做任何修改。

select ... lock in share mode;
复制代码

排他锁||写锁||X 锁

事务不能读取,也不能写。允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

例如:若事务 T 对数据对象 A 加上 X 锁,事务 T 可以读 A 也可以修改 A,其他事务不能再对 A 加任何锁,直到 T 释放 A 上的锁。这保证了其他事务在 T 释放 A 上的锁之前不能再读取和修改 A。

select ... for update;
复制代码

锁的模式

一.意向锁

1.意向共享锁

(IS Lock/intent share lock)

事务想要获得一张表中某几行的共享锁事务务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁

2.意向排他锁||互斥锁

(IX Lock/intent exclusive lock)

事务想要获得一张表中某几行的排他锁事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。

意向锁加锁方式

意向锁是 InnoDB 自动加的, 不需用户干预。

意向锁有什么用

主要作用是处理行锁和表锁之间的矛盾,能够显示“某个事务正在某一行上持有了锁,或者准备去持有锁”当我们需要加一个排他锁时,需要根据意向锁去判断表中有没有数据行被锁定

比如事务 A 要在一个表上加 S 锁,如果表中的一行已被事务 B 加了 X 锁那么该锁的申请也应被阻塞。如果表中的数据很多,逐行检查锁标志的开销将很大,系统的性能将会受到影响。为了解决这个问题,可以在表级上引入新的锁类型来表示其所属行的加锁情况,这就引出了“意向锁(表级)”的概念。

举个例子,如果表中记录 1 亿,事务 A 把其中有几条记录上了行锁了,这时事务 B 需要给这个表加表级锁,如果没有意向锁的话,那就要去表中查找这一亿条记录是否上锁了


如果存在意向锁,那么假如事务A在更新一条记录之前,先加意向锁,再加X锁,事务 B 先检查该表上是否存在意向锁,存在的意向锁是否与自己准备加的锁冲突,如果有冲突,则等待直到事务A释放,而无须逐条记录去检测。

事务B更新表时,无须知道到底哪一行被锁了,它只要知道反正有一行被锁了就行了。

二.行锁算法

1.Record Lock(单行记录)

单条索引上加锁,record lock 永远锁的是索引,而非数据本身,如果 innodb 表中没有索引,那么会自动创建一个隐藏的聚集索引,锁住的就是这个聚集索引当一条 sql 没有走任何索引时,那么将会在每一条聚集索引后面加 X 锁,这个类似于表锁,但原理上和表锁应该是完全不同的

记录锁的条件

命中单行记录并且命中的条件字段是唯一索引或者主索引

update user_info set name=’张三’ where id=1;//这里的id是唯一索引,使用了Record Lock
复制代码

Record Lock 总是会去锁住索引记录,如果 InnoDB 存储引擎表在建立的时候没有设置任何一个索引那么这时 InnoDB 存储引擎会使用隐式的主键来进行锁定

2.Gap Lock(间隙锁)

间隙锁产生的原因是因为 Query 执行过程中通过范围查找的话,它会锁定整个范围内所有的索引键值,即使这个键值并不存在。

我们先理解什么是间隙,如例:

假如 user 表中只有 101 条记录,其 id 的值分别是 1,2,…,100,101,下面的 SQL:

mysql> select * from user where id > 100 for update;
复制代码

是一个范围条件的检索,InnoDB 不仅会对符合条件的 id 值为 101 的记录加锁,也会对 id 大于 101(这些记录并不存在)的“间隙”加锁。当然间隙锁的好处主要是防止防幻读。此时如果还能插入一条 102 的数据,则会导致幻读。缺点是在插入频繁的操作使用不当会造成大量阻塞,所以我们要尽量避免这种范围条件。

3.Dead Lock(死锁)

  • 死锁产生:死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环。当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁。锁的行为和顺序和存储引擎相关。以同样的顺序执行语句,有些存储引擎会产生死锁有些不会——死锁有双重原因:真正的数据冲突;存储引擎的实现方式。

  • 检测死锁:数据库系统实现了各种死锁检测和死锁超时的机制。InnoDB 存储引擎能检测到死锁的循环依赖并立即返回一个错误。

  • 死锁恢复:死锁发生以后,只有部分或完全回滚其中一个事务,才能打破死锁,InnoDB 目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。所以事务型应用程序在设计时必须考虑如何处理死锁,多数情况下只需要重新执行因死锁回滚的事务即可。

  • 外部锁的死锁检测:发生死锁后,InnoDB 一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB 并不能完全自动检测到死锁, 这需要通过设置锁等待超时参数 innodb_lock_wait_timeout 来解决

  • 死锁影响性能:死锁会影响性能而不是会产生严重错误,因为 InnoDB 会自动检测死锁状况并回滚其中一个受影响的事务。在高并发系统上,当许多线程等待同一个锁时,死锁检测可能导致速度变慢。 有时当发生死锁时,禁用死锁检测(使用 innodb_deadlock_detect 配置选项)可能会更有效,这时可以依赖 innodb_lock_wait_timeout 设置进行事务回滚。

  • 死锁预防:可以尽量在不同的事物中指定操作顺序,达到事物顺序一致,来避免多个事务锁住同一资源的互相占用

发布于: 2021 年 04 月 06 日阅读数: 60
用户头像

我们始于迷惘,终于更高水平的迷惘。 2020.03.25 加入

🏆 【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 🤝未来我们希望可以共同进步🤝

评论 (1 条评论)

发布
用户头像
很不错的文章,学习了,期待更多优质文章的发布,嘻嘻嘻
2021 年 04 月 07 日 22:22
回复
没有更多了
MySQL-技术专题-Lock入门到精通