一.概略
通过前面的7篇文章的学习,我们已经学习了msyql的整体架构,读写流程,接下来要进去到msyql中最复杂,最晦涩难懂的知识点--锁.
锁里面的概念太多了,分类也很多,之间的是否兼容也较为复杂,在这块,本人也是不停的挤出来时间来学习这块,感觉自己掌握的也不是非常的清楚,一起加油学习吧
二.mysql的锁
一句话说明锁的本质: mysql的锁是处理写写冲突的
前面学习的MVCC是处理
读写冲突的,针对写写冲突还是需要加锁的
2.1 锁的分类
锁根据划分的角度不同,也会有不同的分类:
1.从加锁机制上来分分为乐观锁和悲观锁
乐观锁:从本质上讲,是无锁的,因为他总是认识在修改数据的时候,没有别人修改这个数据,如果修改失败的话,会进行一定的重试,一般是通过版本号和时间戳来判断是否发生更新冲突;
悲观锁:悲观锁是加锁的,因为他总是悲观的认为自己在修改中途中,一定会有人在修改,所以每次都会加锁
2.从锁粒度上,分为表锁和行锁
表锁:表锁是指对一整张表加锁。表锁由MySQL Server层实现
行锁:锁是对表中的某一行,多行,或者行之间的间隙加锁的,是由引擎层实现的
3.储存引擎,分为innodb和myisam
这两个是我们常用的引擎,MYISAM没有行锁,只有表锁,INNODB不但有表锁,还有行锁,行锁能增加并发能力
4.按照兼容性分为共享锁和排它锁
共享锁(Share Lock,简称S锁):对锁住的数据,允许其他的事务来读取这个数据,但是不允许修改这个事务;
排它锁(Exclusive Lock,简称X锁):对锁住的数据,不允许其他的事务来读取和修改这个数据,具有排他性
下面我会重点介绍一些常用的锁
2.2 行锁
行锁是INNODB的锁,其他的存储引擎像MYISAM和MEMORY等,是没有行锁的; 行锁根据兼容性分为共享锁和排他锁,根据类型分为RECORD,GAP,next-key锁;
共享锁:像我们平时执行的普通的sql,select * from book; 这种是不带锁的,如果需要加共享锁,需要显示的写sql:select * from book lock in share mode,这样就会给这些数据添加共享锁,共享锁能允许其他的共享锁查询,但是不允许增删改操作,会被阻塞到当前的锁释放.
排他锁:select * from book for update; 就会为该数据添加排他锁,排它锁添加后,是不允许其他增删改和共享,排他锁的
兼容性
是否兼容 | 共享锁 | 排它锁 |
|---|---|---|
| 共享锁 | 兼容 | 不兼容 |
| 共享锁 | 不兼容 | 不兼容 |
记录锁(Record Lock):更新数据时候,通过前面学习,首先会把数据从磁盘加载在内存中,在更新的时候,会在内存中创建一个锁结构,来判断该数据上是否有锁;
如果在update时候,过滤条件不走索引的话,会升级为表锁,最终导致效率低下,在日常开发中,应该注意
间隙锁(Gap Lock):间隙锁存在的目的是为了防止幻读,间隙锁是一个索引值的左开右开的区间'
Next-key Lock: 间隙锁是左开右开的区间,不会包含具体的数据,如果要把具体的数据包含在内,也就是 Next-key Lock = Record Lock +GAP lock;
插入意向锁(Insert Intention Lock):插入意图锁是一种间隙锁,在行执行INSERT之前的插入操作设置,如果多个事务INSERT到同一个索引间隙之间,但没有在同一位置上插入,则不会产生任何的冲突
三者的关系如图所示:
三者兼容性如下图:
重点记下面几个就行:
RECORD和RECORD 不兼容,原因:举例,id=1的数据上添加了RECORD数据,另外事务再次修改 id=1的数据,再次添加record锁的时候,肯定是不支持的;
RECORD和 NEXT-KEY不兼容: next-key里面有record锁的,举例 在next-key为(1,10]的区间,修改 id=10的数据, 然后再次修改id=10的数据,这个时候就不允许,所以不兼容
NEXT-KEY NEXT-KEY不兼容,同上,因为NEXT-KEY里面包含record,相当于简化为RECORD和RECORD的不兼容
|| GAP 和GAP/next-key 不兼容:原因, 当GAP或者next-key的时候,说明是对一段间隙进行加锁的,这个时候对这个间隙插入数据,肯定是不允许的,比如 gap 区域是 (1,10),在这个时候查询数据 id=5,肯定是阻塞的
|| GAP 和|| GAP不阻塞的原因:只要插入的位置不是相同的,是不会阻塞的,比如事务1插入数据 id=4,事务2插入数据id=5,这两个数据不在同一位置,肯定是允许添加的;
在之前的理解中,我以为GAP和gap也是不兼容的,通过测试,发现gap和gap是兼容的,如下图所示:
2.3 表锁
2.3.1 读写意向锁
读写意向锁来检测
表锁和行锁的冲突
当数据在行上面添加对应的读锁和写锁的时候,会在表上对应的添加读写意向锁,如果表在执行DDL语句的时候(比如 alter table xxx 或者drop table xxx),会对表添加排它锁,在添加排它锁之前需要判断表中的行是否是锁,如果一行一行遍历所有行来获取是否有锁,效率将会大大降低,这个时候只需要判断表上是否有意向锁,就能快速判断表中的行的锁情况,大大提升效率;
意向锁也是表级锁,分为读意向锁(IS锁)和写意向锁(IX锁)
当事务要在记录上加上行锁时,要首先在表上加上意向锁。这样判断表中是否有记录正在加锁就很简单了,只要看下表上是否有意向锁就行了,从而就能提高效率
意向锁也是表级锁,分为读意向锁(IS锁)和写意向锁(IX锁)
2.3.2 自增锁
AUTOINC 锁又叫自增锁(一般简写成 AI 锁),是一种表锁,当表中有自增列(AUTOINCREMENT)时出现。当插入表中有自增列时,数据库需要自动生成自增值,它会先为该表加 AUTOINC 表锁,阻塞其他事务的插入操作,这样保证生成的自增值肯定是唯一的
三.案例说明锁的作用
建表语句:
CREATE TABLE
book(
idint(11) NOT NULL,
isbnvarchar(255) DEFAULT NULL,
authorvarchar(255) DEFAULT NULL,
scoredouble(2,1) DEFAULT NULL,PRIMARY KEY (
id),UNIQUE KEY
isbn_unique_index(isbn) USING BTREE,KEY
author_index(author) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO book(id, isbn, author, score) VALUES (10, 'N0001', 'Bob', 3.4);
INSERT INTO book(id, isbn, author, score) VALUES (18, 'N0002', 'Alice', 7.7);
INSERT INTO book(id, isbn, author, score) VALUES (25, 'N0003', 'Jim', 5.0);
INSERT INTO book(id, isbn, author, score) VALUES (30, 'N0004', 'Eric', 9.1);
INSERT INTO book(id, isbn, author, score) VALUES (41, 'N0005', 'Tom', 2.2);
INSERT INTO book(id, isbn, author, score) VALUES (49, 'N0006', 'Tom', 8.3);
INSERT INTO book(id, isbn, author, score) VALUES (60, 'N0007', 'Rose', 8.1);
id为主键,isbn是唯一二级索引,author是为二级非唯一索引,score(评分)无索引,数据如下:
3.1 聚簇索引命中情况
UPDATE book SET score = 9.2 WHERE ID = 10;
这个时候会对id=10的这条数据添加RECORD锁,如图:
这个是最简单的,测试
3.2 聚簇索引,未命中情况
事务1: UPDATE book SET score = 9.2 WHERE ID = 16;
事务2: INSERT INTO
book(id,isbn,author,score) VALUES (17, 'N00017', 'Alice', 7.7);
结果:事务2会阻塞掉,直到事务1提交了事务
如图所示:
原因如下: 在 RC 隔离等级下,不需要加锁;而在 RR 隔离级别会在 ID = 16 前后两个索引之间加上间隙锁,而事务2要添加id=17的数据,根据前面的锁的兼容关系,我们得知GAP锁和RECORD是不兼容的,所以事务2会阻塞直到事务1提交事务;
从上图的测试也能得知GAP锁和GAP锁是兼容的,和我们上面的总结是一样的;
3.3 二级唯一索引,查询命中
UPDATE book SET score = 9.2 WHERE ISBN = 'N0003'
因为INNODB的二级索引,叶子节点保存的是主键的值,所以会有一个二次查找的过程,这个时候,对二级索引和主键索引都会添加RECOED锁
3.4 二级唯一索引,查询未命中
UPDATE book SET score = 9.2 WHERE ISBN = 'N0008'
RR 隔离等级下未命中时的加锁情况,RC 隔离等级下该语句未命中不会加锁。
执行结果如下:
原因分析: 事务1执行UPDATE book SET score = 9.2 WHERE ISBN = 'N0008';由于没有命中,并且N008在N007后面,所以会添加一个间隙锁 (N007,正无穷),事务2在执行数据INSERT INTO book(id, isbn, author, score) VALUES (61, 'N0009', 'Rose', 8.1);时候,由于N009也在这个间隙锁内,所以无法插入成功,需要事务1提交后才能执行;
3.5 二级非唯一索引,查询命中
执行语句:UPDATE book SET score = 9.2 WHERE Author = 'Tom'
在 RC 等级下,二级唯一索引和二级非唯一索引的加锁情况是一致的,都是在涉及的二级索引和对应的主键索引上加上排他记录
但是在 RR 隔离等级下,加锁的情况产生了变化,它不仅对涉及的二级索引和主键索引加了排他记录锁,还在非唯一二级索引上加了三个间隙锁,锁住了两个 Tom 索引值相关的三个范围;
那为什么唯一索引不需要加间隙锁呢?间隙锁的作用是为了解决幻读,防止其他事务插入相同索引值的记录,而唯一索引和主键约束都已经保证了该索引值肯定只有一条记录,所以无需加间隙锁。
测试结果如下:
3.6 二级非唯一索引,查询未命中
UPDATE book SET score = 9.2 WHERE Author = 'Sarah' 在 RR 隔离等级下未命中的加锁情况,它会在二级索引 Rose 和 Tom 之间加间隙锁。而 RC 隔离等级下不需要加锁。
3.7 无索引
UPDATE book SET score = 9.2 WHERE score = 22
当 Where 从句的条件并不使用索引时,则会对全表进行扫描,在 RC 隔离等级下对所有的数据加排他记录锁。在RR 隔离等级下,除了给记录加锁,还会对记录和记录之间加间隙锁。和上边一样,间隙锁会和左侧的记录锁合并成 Next-Key 锁
3.8 聚簇索引,范围查询
UPDATE book SET score = 9.2 WHERE ID <= 25
RC 场景下与等值查询类似,只会在涉及的 ID = 10,ID = 18 和 ID = 25 索引上加排他记录锁。
而在 RR 隔离等级下则有所不同,它会加上间隙锁,和对应的记录锁合并称为 Next-Key 锁。除此之外,它还会在(25, 30] 上分别加 Next-Key 锁
3.9二级索引,范围查询
UPDATE book SET ISBN = N0001 WHERE score <= 7.9
四.总结
msyql锁在索引的加锁情况,可以整理如下:
mysql锁的分类很多,细节很多,各种锁之间兼容性也较为复杂,还需要进一步的学习和领悟
太晚了,都23点多了,要睡啦