目录
引文A——活锁
引文B——死锁
1、表锁
1.1 关于 S 锁
1.2 关于 X 锁
1.3 关于意向锁
2、行锁
2.1 关于记录锁
2.2 关于间隙锁
2.3 关于临键锁
2.4 关于插入意向锁
3、页锁
我们在谈表锁、行锁以及页锁之前,先聊一聊 活锁与 死锁的问题 ( •̀ ω •́ )✧
举个栗子:
解决方法:
采用先来先服务的策略,当多个事务请求同一数据时,会按照请求获取锁的先后顺序进行事务排队,前一个事务一旦释放锁,就批准队列中第一个事务前来获取锁
举个栗子:
解决方法:
- 使用 一次封锁法 或者使用 顺序封锁法 来进行死锁的预防
- 使用 超时法 或者 事务等待图法 来进行死锁的诊断问题
- 选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有锁,使其他事务得以继续运行下去来进行死锁的解除;当然,对撤销的事务所执行的数据修改操作必须加以恢复
一般情况下,不会使用 InnoDB 存储引擎提供的表级别的 S锁 和 X锁 。只会在一些特殊情况下,比方说 崩溃恢复 过程中用到。
需要注意的是:尽量不用手动添加表级锁,因为并不会提供什么额外的保护,只会降低并发能力
查看所有表中哪一个表使用了锁:
#这里是查看所有表中的使用锁的情况
show open tables;
#这里是指定查看使用了锁的表
show open tables where in_use > 0;
释放锁:
unlock tables;
读锁(S锁) :也称为 共享锁 、英文用 S 表示。针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻塞的。
添加表级读锁:
lock tables table(表名) read;
添加行级读锁:
#这里进行添加行级锁(共享锁)
SELECT * FROM mylock WHERE id = 1 LOCK IN SHARE MODE
写锁(X锁) :也称为 排他锁 、英文用 X 表示。当前写操作没有完成前,它会阻断其他写锁和读锁。这样就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源。
添加表级写锁:
lock tables table(表名) write;
添加行级写锁:
#这里进行添加行级锁(排他锁)
SELECT * FROM mylock WHERE id = 1 FOR UPDATE
共享锁 与 排他锁 图解:
由于上面的操作的粒度太大,会导致表的效率降低,意向锁随之产生......
定义:
InnoDB 支持 多粒度锁(multiple granularity locking) ,它允许 行级锁 与 表级锁 共存,而 “意向锁” 就是其中的一种 “表锁” 。
举个栗子:
假如说现在有两个事务,分别是 T1 和 T2 ,其中 T2 试图在该表级别上应用共享或者排他锁;若没有意向锁的存在,那么 T2 就需要去检查各个页或行是否存在锁,这样的话,会导致效率很低;若加上意向锁,那么 T2 就不必分别检查各个页或行锁,而只需要检查表上的意向锁,这样效率就会提高很多
综上所述:
如果我们给某一行数据加上了某某锁,数据库内部引擎会自动给更大一级的空间,比如数据页或者数据表加上意向锁,告诉其他人这个数据页或者表已经有人加上过某某锁了
注意事项:
意向锁是由存储引擎自己维护的 ,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行 所在数据表的对应意向锁 。
意向锁之间关系图:
意向锁与非意向锁之间关系图:
意向锁的并发性:
其中,意向锁分为两种,分别为意向共享锁,以及意向排他锁
意向共享锁:
事务有意向(数据库引擎自动添加)对表中的某些行加共享锁(S锁)
#事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
SELECT column(字段) FROM table(表名) (其他条件) LOCK IN SHARE MODE
意向排他锁:
事务有意向(数据库引擎自动添加)对表中的某些行加排他锁(X锁)
#事务要获取某些行的 X 锁,必须先获得表的 IX 锁。
SELECT column(字段) FROM table(表名) (其他条件) FOR UPDATE;
行锁(Row Lock)也称为记录锁,顾名思义,就是将某一行锁住;需要注意的是,MySQL 服务器层并没有实现行锁机制,行级锁只存在于存储引擎层实现。
优点:这样的锁粒度小,发生锁冲突的概率低,因而可以实现并发度高的情况
缺点:对锁的开销比较大(因为行相比于表,肯定行多),加锁会比较慢,容易出现死锁的情况
从这里我们可以发现, InnoDB 与 MyISAM 最大的不同点是:一是支持事务,二是采用了行级锁
定义:
就是仅仅把一条记录锁上,官方的类型名称为: LOCK_REC_NOT_GAP 。
举个栗子:
比如我们把 id 值为 8 的那条记录加一个记录锁(如图所示),仅仅是锁住了id值为 8 的记录,对周围的数据没有影响
BEGIN; #进行开启事务
SELECT * FROM student WHERE id = 8 FOR UPDATE; #插入记录锁
记录锁是有S锁和X锁之分的,称之为 S型记录锁 和 X型记录锁
注意事项:
在进行 UPDATE 语句时,数据库内部引擎会自动加上锁;只有在 SELECT 语句时,才不会自动添加,只能手动添加,如: LOCK IN SHARE MODE 或者 FOR UPDATE 来进行触发共享锁或者排他锁
定义:
所谓间隙锁(Gap Locks),就是在数据之间的间隙处加上一把锁,防止别的事务在当前事务还在操作时,突然间又插入一条数据,从而解决幻读的问题;
举个栗子:
假如现在有事务 A 和 事务 B两个事务。事务 A 先执行,该事务 A 在(3,8)这个范围之间添加了Gap 间隙锁;这时,事务 B 兴高采烈的跑过来,想在(3,8)这个区间内插入一条数据,好巧不巧,事务 A 已经添加了 Gap 锁,所以,事务 B 的 INSERT 操作是失败的,造成阻塞;直到事务 A COMMIT 提交过后,事务 B 才能继续执行 INSERT 语句;
BEGIN; #进行开启事务
SELECT * FROM student WHERE id < 8 AND id > 3 FOR UPDATE; #插入间隙锁
综上所述,Gap锁 仅仅是为了防止插入幻影记录(幻读)而提出的
由来:
有时候我们既想锁住某条记录,又想阻止其他事务在当前事务下的间隙 INSERT 插入新的记录,这时,临键锁(Next-key Locks)横空出世;其中,这是在 InnoDB 引擎、事务级别在可重复读的情况下使用的数据库锁(InnoDB 引擎默认使用的就是临键锁)
思维巧记:
临键锁 Next- key 其实就是一个 记录锁 和 一个 Gap 间隙锁 的合体,它既能保护该条记录,又能阻止别的事务将新的事务插入被保护记录之间的间隙中,避免幻读
举个栗子:
BEGIN; #进行开启事务
#这里 WHERE 条件 既规定了间隙,同时也指定了具体的记录
SELECT * FROM student WHERE id <= 8 AND id > 3 FOR UPDATE; #插入间隙锁与记录锁
举个栗子:
我们说一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了 gap锁 ( next-key锁也包含 gap锁 ),如果有的话,插入操作需要等待,直到拥有 gap锁 的那个事务提交。但是InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个间隙中插入新记录,但是现在在等待,这个锁就叫做插入意向锁
注意事项:
- 插入意向锁是一种 Gap锁 ,不是意向锁,在 INSERT 操作时产生。 插入意向锁是在插入一条记录行前,由 INSERT 操作产生的一种间隙锁
- 事实上 “插入意向锁” 并不会阻止别的事务继续获取该记录上任何类型的锁
定义:
就是在 页的粒度 上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录;当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行
性能分析:
注意事项:
- 每个层级的锁数量是有限制的,因为锁会占用内存空间, 锁空间的大小是有限的 。当某个层级的锁数量超过了这个层级的阈值时,就会进行 锁升级
- 锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如 InnoDB 中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了