共计 5425 个字符,预计需要花费 14 分钟才能阅读完成。
一、表级锁、行级锁、页级锁
数据库锁定机制简略来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发拜访变得有序所设计的一种规定。
MySQL 数据库因为其本身架构的特点,存在多种数据存储引擎,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。
MySQL 各存储引擎应用了三种类型(级别)的锁定机制:表级锁定,行级锁定和页级锁定。
1、表级锁
表级别的锁定是 MySQL 各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的零碎负面影响最小。所以获取锁和开释锁的速度很快。
当然,锁定颗粒度大所带来最大的负面影响就是呈现锁定资源争用的概率也会最高,以致并发度大打折扣。
应用表级锁定的次要是 MyISAM,MEMORY,CSV 等一些非事务性存储引擎。
2、行级锁
行级锁定最大的特点就是锁定对象的颗粒度很小,因为锁定颗粒度很小,所以产生锁定资源争用的概率也最小,可能给予应用程序尽可能大的并发解决能力而进步一些须要高并发利用零碎的整体性能。
尽管可能在并发解决能力下面有较大的劣势,然而行级锁定也因而带来了不少弊病。
因为锁定资源的颗粒度很小,所以每次获取锁和开释锁须要做的事件也更多,带来的耗费天然也就更大了。此外,行级锁定也最容易产生死锁。
应用行级锁定的次要是 InnoDB 存储引擎。
3、页级锁
页级锁定是 MySQL 中比拟独特的一种锁定级别。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所须要的资源开销,以及所能提供的并发解决能力也同样是介于下面二者之间。
应用页级锁定的次要是 BerkeleyDB 存储引擎。
4、总结
总的来说,MySQL 这 3 种锁的个性可大抵归纳如下:
表级锁:开销小,加锁快;不会呈现死锁;锁定粒度大,产生锁抵触的概率最高,并发度最低;
行级锁:开销大,加锁慢;会呈现死锁;锁定粒度最小,产生锁抵触的概率最低,并发度也最高;
页面锁:开销和加锁工夫界于表锁和行锁之间;会呈现死锁;锁定粒度界于表锁和行锁之间,并发度个别。
二、共享锁、排它锁
InnoDB 实现了规范的行级锁,包含两种:共享锁(简称 s 锁)、排它锁(简称 x 锁)。
对于共享锁而言,对以后行加共享锁,不会阻塞其余事务对同一行的读申请,但会阻塞对同一行的写申请。只有当读锁开释后,才会执行其它事物的写操作。
对于排它锁而言,会阻塞其余事务对同一行的读和写操作,只有当写锁开释后,才会执行其它事务的读写操作。java 培训
简而言之,就是读锁会阻塞写 (X),然而不会梗塞读(S)。而写锁则会把读(S) 和写 (X) 都梗塞
对于 InnoDB 在 RR(MySQL 默认隔离级别) 而言,对于 update、delete 和 insert 语句,会主动给波及数据集加排它锁(X);
对于一般 select 语句,innodb 不会加任何锁。如果想在 select 操作的时候加上 S 锁 或者 X 锁,须要咱们手动加锁。
— 加共享锁(S)
select * from table_name where … lock in share mode
— 加排它锁(X)
select * from table_name where … for update
复制代码
用 select… in share mode 取得共享锁,次要用在须要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行 update 或者 delete 操作。
然而如果以后事务也须要对该记录进行更新操作, 则有可能造成死锁,对于锁定行记录后须要进行更新操作的利用,应该应用 select… for update 形式取得排他锁。
三、记录锁
1、记录锁(Record Locks)
记录锁其实很好了解,对表中的记录加锁,叫做记录锁,简称行锁。比方
SELECT * FROM test
WHERE id
=1 FOR UPDATE;
复制代码
它会在 id=1 的记录上加上记录锁,以阻止其余事务插入,更新,删除 id=1 这一行。
须要留神的是:
- d 列必须为惟一索引列或主键列,否则上述语句加的锁就会变成临键锁(无关临键锁上面会讲)。
- 同时查问语句必须为精准匹配(=),不能为 >、<、like 等,否则也会进化成临键锁。
复制代码
其余实现
在通过 主键索引 与 惟一索引 对数据行进行 UPDATE 操作时,也会对该行数据加记录锁:
— id 列为主键列或惟一索引列
UPDATE SET age = 50 WHERE id = 1;
复制代码
记录锁是锁住记录,锁住索引记录,而不是真正的数据记录.
如果要锁的列没有索引,进行全表记录加锁
记录锁也是排它 (X) 锁, 所以会阻塞其余事务对其插入、更新、删除。
四、间隙锁
1、间隙锁(Gap Locks)
间隙锁 是 Innodb 在 RR(可反复读) 隔离级别 下为了解决幻读问题时引入的锁机制。间隙锁是 innodb 中行锁的一种。
请务必牢记:应用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
举例来说,如果 emp 表中只有 101 条记录,其 empid 的值别离是 1,2,…,100,101,上面的 SQL:
SELECT * FROM emp WHERE empid > 100 FOR UPDATE
复制代码
当咱们用条件检索数据,并申请共享或排他锁时,InnoDB 不仅会对符合条件的 empid 值为 101 的记录加锁,也会对 empid 大于 101(这些记录并不存在)的“间隙”加锁。
这个时候如果你插入 empid 等于 102 的数据的,如果那边事物还没有提交,那你就会处于期待状态,无奈插入数据。
无关间隙锁所需讲的货色还是蛮多的,我会独自写一篇文章来剖析间隙锁,并在文章中附上残缺的示例。
五、临键锁
1、临键锁(Next-Key Locks)
Next-key 锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录后面间隙上的锁。
也能够了解为一种非凡的间隙锁。通过临建锁能够解决幻读的问题。每个数据行上的非惟一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。须要强调的一点是,
InnoDB 中行级锁是基于索引实现的。
临键锁只与 非惟一索引列 无关,在 惟一索引列(包含主键列)上不存在临键锁。
复制代码
假如有如下表:
id 主键, age 一般索引
该表中 age 列潜在的临键锁有:(-∞, 10],(10, 24],(24, 32],(32, 45],(45, +∞], 在事务 A 中执行如下命令:
— 依据非惟一索引列 UPDATE 某条记录
UPDATE table SET name = Vladimir WHERE age = 24;
— 或依据非惟一索引列 锁住某条记录
SELECT * FROM table WHERE age = 24 FOR UPDATE;
复制代码
不论执行了上述 SQL 中的哪一句,之后如果在事务 B 中执行以下命令,则该命令会被阻塞:
INSERT INTO table VALUES(100, 26, ‘tianqi’);
很显著,事务 A 在对 age 为 24 的列进行 UPDATE 操作的同时,也获取了 (24, 32] 这个区间内的临键锁。
总结
这里对 记录锁、间隙锁、临键锁 做一个总结
InnoDB 中的行锁的实现依赖于索引,一旦某个加锁操作没有应用到索引,那么该锁就会进化为表锁。
记录锁存在于包含主键索引在内的惟一索引中,锁定单条索引记录。
间隙锁存在于非惟一索引中,锁定开区间范畴内的一段距离,它是基于临键锁实现的。
临键锁存在于非惟一索引中,该类型的每条记录的索引上都存在这种锁,它是一种非凡的间隙锁,锁定一段左开右闭的索引区间。
六、意向锁
1、意向锁
意向锁又分为 动向共享锁(IS)和 动向排他锁(IX)
动向共享 (IS) 锁:事务有动向对表中的某些行加共享锁(S 锁)
— 事务要获取某些行的 S 锁,必须先取得表的 IS 锁。
SELECT column FROM table … LOCK IN SHARE MODE;
动向排他 (IX) 锁:事务有动向对表中的某些行加排他锁(X 锁)
— 事务要获取某些行的 X 锁,必须先取得表的 IX 锁。
SELECT column FROM table … FOR UPDATE;
首先咱们要明确四点
动向共享锁(IS)和 动向排他锁(IX)都是表锁。
意向锁是一种 不与行级锁抵触的表级锁,这一点十分重要。
意向锁是 InnoDB 主动加的,不需用户干涉。
意向锁是在 InnoDB 下存在的外部锁,对于 MyISAM 而言 没有意向锁之说。
这里就会有纳闷,既然后面曾经有了共享锁(S 锁)、排它锁(X 锁)。那么为什么须要引入意向锁呢?它能解决什么问题呢?
咱们能够了解 意向锁 存在的目标就是 为了让 InnoDB 中的行锁和表锁更高效的共存。
为什么这么说,咱们来举一个例子。
举例
上面有一张表 InnoDB RR 隔离级别 id 是主键
事务 A 获取了某一行的排他锁,并未提交:
SELECT * FROM users WHERE id = 6 FOR UPDATE;
事务 B 想要获取 users 表的表锁:
LOCK TABLES users READ;
因为共享锁与排他锁互斥,所以事务 B 在视图对 users 表加共享锁的时候,必须保障:
以后没有其余事务持有 users 表的排他锁。
以后没有其余事务持有 users 表中任意一行的排他锁。
为了检测是否满足第二个条件,事务 B 必须在确保 users 表不存在任何排他锁的前提下,去检测表中的每一行是否存在排他锁。很显著这是一个效率很差的做法,然而有了意向锁之后,状况就不一样了:事务 B 只有看表上有没有
动向共享锁,有则阐明表中有些行被共享行锁锁住了,因而,事务 B 申请表的写锁会被阻塞。这样是不是就高效多了。
这也解释就应该分明,为什么有意向锁这个货色存在了。
咱们能够举个生存中的例子,再来了解下为什么须要存在意向锁。
打个比方,就像有个游乐场,很多小朋友进去玩,看门大爷如果要上班锁游乐场的门(加表锁),他必须确保每个角落都要去查看一遍,确保每个小朋友都来到了(开释行锁),才能够锁门。
假如锁门是件频繁产生的事件,大爷就会十分解体。那大爷想了一个方法,每个小朋友进入,就把本人的名字写在本子上,小朋友来到,就把本人的名字划掉,那大爷就能不便把握有没有小朋友在游乐场里,不用每个角落都去寻找一遍。
例子中的“小本子”,就是意向锁,他记录的信息并不精密,他只是揭示大爷,有人在屋里。
这里咱们再来看下 共享 (S) 锁、排他 (X) 锁、动向共享锁(IS)、动向排他锁(IX)的兼容性
能够看出 意向锁之间是相互兼容的. 那你存在的意义是啥?
意向锁不会尴尬意向锁。也不会尴尬行级排他 (X)/ 共享(X) 锁, 它的存在是尴尬表级排他 (X)/ 共享(X) 锁。
留神这里的排他 (X)/ 共享(S) 锁指的都是表锁!意向锁不会与行级的共享 / 排他锁互斥!行级别的 X 和 S 依照下面的兼容性规定即可。
意向锁与意向锁之间永远是兼容的,因为当你不管加行级的 X 锁或 S 锁,都会主动获取表级的 IX 锁或者 IS 锁。也就是你有 10 个事务,对不同的 10 行加了行级 X 锁,那么这个时候就存在 10 个 IX 锁。
这 10IX 存在的目标是啥呢,就是如果这个时候有个事务,想对整个表加排它 X 锁, 那它不须要遍历每一行是否存在 S 或 X 锁,而是看有没有存在意向锁,只有存在一个意向锁,那这个事务就加不了表级排它 X 锁,要等下面 10 个 IX 全副开释才行。
七、插入意向锁
1、插入意向锁
在解说插入意向锁之前,先来思考一个问题
上面有张表 id 主键,age 一般索引
首先事务 A 插入了一行数据,并且没有 commit:
INSERT INTO users SELECT 4, ‘Bill’, 15;
随后事务 B 试图插入一行数据:
INSERT INTO users SELECT 5, ‘Louis’, 16;
请问:
1、事务 A 应用了什么锁?
2、事务 B 是否会被事务 A 阻塞?
插入意向锁是在插入一条记录行前,由 INSERT 操作产生的一种间隙锁。
该锁用以示意插入动向,当多个事务在同一区间(gap)插入地位不同的多条数据时,事务之间不须要相互期待。
假如存在两条值别离为 4 和 7 的记录,两个不同的事务别离试图插入值为 5 和 6 的两条记录,每个事务在获取插入行上独占的(排他)锁前,都会获取(4,7)之间的间隙锁,然而因为数据行之间并不抵触,所以两个事务之间
并不会产生抵触(阻塞期待)。
总结来说,插入意向锁 的个性能够分成两局部:
插入意向锁是一种非凡的间隙锁 —— 间隙锁能够锁定开区间内的局部记录。
插入意向锁之间互不排挤,所以即便多个事务在同一区间插入多条记录,只有记录自身(主键、惟一索引)不抵触,那么事务之间就不会呈现抵触期待。
须要强调的是,尽管插入意向锁中含有意向锁三个字,然而它并不属于意向锁而属于间隙锁,因为意向锁是表锁而插入意向锁是行锁。
当初咱们能够答复结尾的问题了:
1、应用插入意向锁与记录锁。
2、事务 A 不会阻塞事务 B。
为什么不必间隙锁
如果只是应用一般的间隙锁会怎么样呢?咱们在看事务 A, 其实它一共获取了 3 把锁
id 为 4 的记录行的记录锁。
age 区间在(10,15)的间隙锁。
age 区间在(15,20)的间隙锁。
最终,事务 A 插入了该行数据,并锁住了(10,20)这个区间。
随后事务 B 试图插入一行数据:
INSERT INTO users SELECT 5, ‘Louis’, 16;
因为 16 位于(15,20)区间内,而该区间内又存在一把间隙锁,所以事务 B 别说想申请本人的间隙锁了,它甚至不能获取该行的记录锁,天然只能乖乖的期待 事务 A 完结,能力执行插入操作。
很显著,这样做事务之间将会频发陷入阻塞期待,插入的并发性十分之差。这时如果咱们再去回忆咱们刚刚讲过的插入意向锁,就不难发现它是如何优雅的解决了并发插入的问题。