innodb 锁介绍
Posted xiaoliuliu2050
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了innodb 锁介绍相关的知识,希望对你有一定的参考价值。
一. 为什么要引入锁
多个用户同时对数据库的并发操作时会带来以下数据不一致的问题:
丢失更新
A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统
脏读
A用户修改了数据,随后B用户又读出该数据,但A用户因为某些原因取消了对数据的修改,数据恢复原值,此时B得到的数据就与数据库内的数据产生了不一致
不可重复读
A用户读取数据,随后B用户读出该数据并修改,此时A用户再读取数据时发现前后两次的值不一致
并发控制的主要方法是封锁,锁就是在一段时间内禁止用户做某些操作以避免产生数据不一致
可重复读是读取了缓存的快照,和加锁没有关系。
普通select 不加锁 ,只有显示的 lock in share mode 才会加共享锁,update delete insert select for update 加 排他锁。
二 锁分类介绍
共享锁(share lock)
又称读锁,读取操作创建的锁。
一旦上锁,任何事务(包括当前事务)无法对其修改,其他事务可以并发读取数据,也可在对此数据再加共享锁
语法:SELECT ... LOCK IN SHARE MODE;
排他锁(exclusive lock)
又称写锁,如果事务对数据A加上排他锁后,则其他事务不可并发读取数据,也不能再对A加任何类型的锁。获准排他锁的事务既能读数据,又能修改数据。
语法:SELECT ... FOR UPDATE
意向锁
共享锁、排它锁按照其作用的粒度,可以锁到行级,也可以锁到页级或表级。不过意向锁只作用于表级,主要是用来标记一个事务对于这张表操作的一个意向。
例如:我有一个事务需要使用表锁,那我就需要知道,这个表是否存在其他的锁,如果有,可能我就需要等待。但是,我如果要排除其他锁,我就需要一个一个记录的遍历,才知道是不是存在行锁。因此,数据库对于行锁就提出另一个机制,就是意向锁,如果你要对这个表进行行锁时,那么先在表上加一个意向锁,方便其他事务查询。
因此,意向锁就有了以下协议:
- 一个事务获得表t中某行的S锁之前,必须先获得t表上的IS锁或者更强类型的锁。
- 一个事务获得表t中某行的X锁之前, 必须先获得t表上的IX锁。
这些结果可以方便地用一个锁类型兼容矩阵来总结:
X | IX | S | IS | |
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
意向锁不会阻止除了全表锁定请求之外的任何锁请求。 意向锁的主要目的是显示事务正在锁定某行或者正意图锁定某行。
三 锁级别介绍:
根据锁的颗粒或者级别不同,我们又把所分为了三个级别:表锁(table-level locking)、页锁(page-level locking)、行锁(row-level locking)。
MyISAM和MEMORY存储引擎采用的是表锁(table-level locking);BDB存储引擎采用的是页锁(page-level locking),但也支持表锁;InnoDB存储引擎既支持行锁(row-level locking),也支持表锁,但默认情况下是采用行锁。
而行锁又包括了三种行锁的算法,分别是:
- 鸿蒙官方战略合作共建——HarmonyOS技术社区
- 记录锁(Record Lock) 锁住1条记录
- 间隙锁(Gap Lock) 锁住多条数据
- 临键锁(Next-Key Lock) 看情况锁1条还是多条。
这里有个小知识点:InnoDB的行锁只针对索引项使用,也就是说,只有在通过索引检索数据时,InnoDB才使用行锁,其他时候都是使用的表锁。
记录锁(Record Locks)
记录锁,顾名思义,就是锁住一条记录。这是Read Committed(读提交)事务级别的默认锁级别。
记录锁是作用于索引的,所以,当查询不是作用于索引上时,系统会创建一个隐式的聚集索引,然后作用在索引上。
例如:
- select * from table where id = 1 lock in share mode;
就是一个共享记录锁,
- select * from table for update where id = 1 ;
就是一个排他记录锁。
间隙锁(Gap Lock)
间隙锁,它不会去锁住索引本身,但是会锁住的是一个索引的范围。启用它有一个前置条件,就是数据库隔离级别必须是Repeatable Read(可重复读),这也是InnoDB的默认隔离级别,假设我们将隔离级别降到Read Committed(读提交),间隙锁将会自动失效。
间隙锁的使用,能够有效的防止幻读。
例如:
如果事务A执行了
- select * from table where id between 8 and 15 for update;
这时,事务B想在事务A执行期间执行插入一条id是10的记录,就会被阻止。因为这会导致事务A中的多次查询数据不一致。
临键锁(Next-Key Lock)
临键锁就是记录锁+间隙锁的组合方式。这是Repeatable Read(可重复读)隔离级别的默认锁级别。使用临键锁有一个好处,就是,假设我们执行执行一个查询
- select * from table where id = 100;
如果id是***索引,那么临键锁就会降级为记录锁,锁住这条记录,而不是去锁住一个范围。
四 死锁 和 锁升级
我们讲完了这些锁,那么就不禁要问了,死锁是怎么产生的呢?
这就要说到另一个情况,就是锁的升级
锁的升级
假设,我们先进行了一个查询,找到了目标数据,然后进行修改,在这个事务中,其实不同的阶段,锁的类型是不同的。
当我们进行查询的时候,我们想数据库先获得了一个共享锁,当我们要对这条数据进行更新的时候,并不是释放共享锁,然后再获取排它锁,而是进行了一个锁的升级操作,直接将共享锁升级成为了排它锁。
而就是因为这个操作,可能导致了死锁。
死锁
假设:事务A中有一个锁升级操作,也就是先执行
- select * from table where id =1
再执行
- update table set Status = 1 where id =1
事务B中,同样存在这样的情况,先执行
- select * from table where id =1
再执行
- update table set Name = '牛' where id =1
而执行的顺序恰好是:
- 鸿蒙官方战略合作共建——HarmonyOS技术社区
- 事务A获得了共享锁,执行查询;
- 事务B获得了共享锁,执行查询;
- 事务A需要升级排它锁,执行修改;
- 事务B也需要升级排它锁,不能释放共享锁。
于是,死锁就发生了。当然,还有一种交叉死锁的情况,更为常见,大家可以自己百度看看了。
死锁发生时,数据库并不会直接检查到死锁的存在,只有在锁等待超时的时候被发现,然后杀死其中一个请求。如果并发量高时,死锁就会引起大量的线程挂起,占用大量资源。
怎么预防死锁呢?
最直接的办法就是,别在update前先select一次。
但是,这种情况在所难免,很多时候我们update时,都是需要先select一次的,如果所有地方要求不能select,那代码难度势必就几何级的上升。
还有一种办法就是,加硬件,提高并发能力,这样,出现两次事务同时请求的概率就下降了。不过,这个方式成本太高。
当然,我们还可以直接在查询时,就申请到最终事务需要的锁级别,避免升级锁的出现,也可以预防死锁,例如查询时就直接写
- select * from table for update;
更多信息参考
以上是关于innodb 锁介绍的主要内容,如果未能解决你的问题,请参考以下文章