锁
作为一个程序员我相信锁的概念对你来说一定不会很陌生,在开发的过程中都或多或少的接触过,我们都知道锁的种类一般分为乐观锁和悲观锁两种,不管是悲观锁还是乐观锁都是为了解决并发问题的.
乐观锁
乐观锁的实现用的是一种冲突检验的思想,当线程对一条数据进行写操作时,先判断他是否被修改过,如果没有那么可以执行写操作,否则丢弃该操作或重试操作,在整个的执行过程中其实都没有对数据进行加锁;
悲观锁
悲观锁的实现是在冲突未产生之前事先对资源进行加锁,确保同一时刻只有有限的线程能够访问该资源,其他想要尝试获取资源的操作都会进入等待状态,直到该线程完成了对资源的操作并且释放了锁后,其他线程才能重新操作资源;
乐观锁和悲观锁的使用
当我们使用乐观锁的时候不用考虑死锁的问题,但是由于乐观锁的实现基于冲突检验,所以当冲突频率和重试成本较高时更推荐使用悲观锁,而需要非常高的响应速度并且并发量非常大的时候使用乐观锁就能较好的解决问题,在这时使用悲观锁就可能出现严重的性能问题;在选择并发控制机制时,需要综合考虑上面的四个方面(冲突频率、重试成本、响应速度和并发量)进行选择。
锁的种类
我们都知道对于数据的操作无非是读和写两种,所以innodb在实现锁的时候对这两种操作使用不同的锁,他们分别是共享锁(Shared Lock)和互斥锁(Exclusive Lock);
共享锁(读锁):允许事务对一条行数据进行读取;
互斥锁(写锁):允许事务对一条行数据进行删除或更新;
读默认共享,写默认排他.
锁的粒度
innodb支持表锁和行锁两种不同粒度的锁.
行锁
innodb实现了标准的行锁,也就是行共享锁(Shared Lock)和行互斥锁(Exclusive Lock)
行共享锁:当innodb搜索表索引时,它会在遇到的索引记录上设置共享锁.
行排他锁:当innodb扫描表索引时,它会在遇到的索引记录上设置排他锁.
无论是共享锁还是互斥锁其实都只是对某一个数据行进行加锁;为了支持多粒度锁定,InnoDB 存储引擎引入了意向锁(Intention Lock),意向锁就是一种表级锁。
表锁
与行锁的种类相似的是,意向锁也分为两种:
意向共享锁:事务想要在获得表中某些记录的共享锁,需要在表上先加意向共享锁;
意向互斥锁:事务想要在获得表中某些记录的互斥锁,需要在表上先加意向互斥锁;
随着意向锁的加入,锁类型之间的兼容矩阵也变得愈加复杂:
意向锁的意义
意向锁其实不会阻塞全表扫描之外的任何请求,它们的主要目的是为了标识表中的某一行数据是否已经被请求锁定了。
看到这里可能你会有一些疑惑?为什么意向锁是这样设计的?
你可以设想一下这样的情景
如果没有意向锁,当已经有人使用行锁对表中的某一行进行修改时,如果另外一个请求要对全表进行修改,那么就需要对所有的行是否被锁定进行扫描,在这种情况下,效率是非常低的;不过,在引入意向锁之后,当有人使用行锁对表中的某一行进行修改之前,会先为表添加意向互斥锁(IX),再为行记录添加互斥锁(X),在这时如果有人尝试对全表进行修改就不需要判断表中的每一行数据是否被加锁了,只需要通过等待意向互斥锁被释放就可以了。
锁的实现
前面简单的介绍了一些锁的概念,了解了在对数据库进行读写时会获取不同的锁,接下来我将介绍innodb是如何将锁添加到对应的数据行上的,我们会分别介绍几种锁的算法:Record Lock、Gap Lock 和 Next-Key Lock等。官方文档
Record Lock(记录锁)
记录锁是对索引记录的锁定,刚刚介绍过innodb在搜索或扫描表索引时,它会在遇到的索引记录上设置共享锁或排他锁。因此,记录锁可以理解为行锁的实现.
记录锁例子描述
CREATE TABLE users (id INT NOT NULL AUTO_INCREMENT, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255), age INT , PRIMARYKEY(id) , KEY(last_name) , KEY(age))engine=innodb;
当我们使用 索引键(id 或者 last_name )作为 SQL 中 WHERE 语句的过滤条件时,InnoDB 会通过索引建立的 B+ 树找到行记录并添加记录锁,但是如果使用 first_name 作为过滤条件时,由于 InnoDB 不知道待操作的记录具体存放的位置,也无法对将要操作哪条记录提前做出判断就会锁定整个表。
Gap Lock(间隙锁)
间隙锁是对索引记录中的一段连续区域的锁定;InnoDB实现间隙锁是用来防止其他事务对间隙进行操作的。间隙锁是可以共存的。意思是当一个事务占用的间隙锁时不会阻止另一个事务在同一个间隙上进行间隙锁定。共享和排他间隙锁之间没有区别。它们彼此不冲突,它们执行相同的功能。
间隙锁例子描述
当使用类似 SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;的 SQL 语句时,innodb就会在[10,20]之间加上间隙锁用来阻止其他事务对 10<=id<=20 记录的操作,间隙可以跨越单个索引值,多个索引值,甚至可能为空。
Next-Key Lock
Next-Key 锁相比前两者就稍微有一些复杂,是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。
Next-Key Lock例子描述
假设索引包含值10,11,13和20.此索引的可能的下一个键锁定包括以下间隔,其中圆括号表示排除间隔端点,方括号表示包含端点:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
默认情况下,InnoDB以 REPEATABLE READ事务隔离级别运行。在这种情况下,InnoDB使用下一键锁进行搜索和索引扫描,这会阻止幻影行(请参见第14.7.4节“幻影行”)。
插入意向锁
插入意向锁是一种专门针对insert操作的锁,假设在插入前,该间隙已经有间隙锁,那么Insert会申请插入意向锁。插入意向锁是这样设计的:如果插入到相同索引间隙中的多个事务不插入间隙内的相同位置,则不需要等待彼此。假设存在值为4和7的索引记录。两个事物分别尝试插入值5和6,在获取插入行上的排它锁之前,每个事物都可以对4和7之间的间隙加插入意向锁,并且不要互相阻塞因为行是非冲突的。
插入意向锁例子描述
客户端A创建一个包含两个索引记录(90和102)的表,然后启动一个事务,该事务对ID大于100的索引记录放置排他锁。排他锁包括记录102之前的间隙锁:
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
|+-----+|
id
|+-----+|
102
|+-----+|
客户端B开始事务以将记录插入间隙。该事务在等待获取独占锁时采用插入意向锁。
mysql>STARTTRANSACTION;
mysql>INSERTINTOchild(id)VALUES(101);
AUTO-INC Locks
AUTO-INC锁是当向使用含有AUTO_INCREMENT列的表中插入数据时需要获取的一种特殊的表级锁
在最简单的情况下,如果一个事务正在向表中插入值,则任何其他事务必须等待对该表执行自己的插入操作,以便第一个事务插入的行的值是连续的。
innodb_autoinc_lock_mode配置选项控制用于自动增量锁定的算法。 它允许您选择如何在可预测的自动递增值序列和插入操作的最大并发性之间进行权衡。
有关更多信息,请参见 第14.6.1.4节“InnoDB中的AUTO_INCREMENT处理”。
谓词锁
InnoDB支持SPATIAL 对包含空间列的列进行索引(请参见 第11.5.8节“优化空间分析”)。
要处理涉及SPATIAL索引的操作的锁定 ,Next-Key Lock不能很好地支持REPEATABLE READ或 SERIALIZABLE事务隔离级别。多维数据中没有绝对排序概念,因此不清楚哪个是 “ 下一个”密钥。
要为具有SPATIAL索引的表启用隔离级别 ,请InnoDB 使用谓词锁。
声明:文章图片以及相关资料来自https://draveness.me/mysql-innodb.html
如有侵犯您的知识产权和版权问题,请通知本人,本人会即时做出处理删除文章。
如果本博客的文章在知识点上有错误,欢迎指出错误所在,欢迎多多交流。谢谢!
参考资料:https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxcheckurl?requrl=https%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F5.7%2Fen%2Finnodb-locking.html&skey=%40crypt_f43a8450_c582350c5419370ab04199879640e46e&deviceid=e041497283477858&pass_ticket=undefined&opcode=2&scene=1&username=@4aec4a8ea53f992132048ffa91dbdf31aa74fc618da5fb4fd03902c20da5a7b4