读书笔记:自卑这个词听起来不太好听,我更倾向于是个人对现实的清醒认识以及坦然面对。自卑和自卑情结有着本质区别。比如学历差,如果可以坦然接受面对学历背景不好,并能认识到需要更加努力才能弥补差距,自卑将被划为动力。如果总是以自身条件差为借口和原因,觉得自己做不到或无法实现xx。或者认为如果我学历好,我也可以做到或者实现xx。这样就是自卑情结。
一、前言背景
二、MySQL InnoDB的锁类型
2.1 读锁和写锁(S锁和X锁)
2.2 意向锁
2.3 记录锁
2.4 间隙锁
2.5 临键锁
三、MySQL加锁、释放锁有多少种方式
3.1 读锁-S锁,加锁和释放锁方式
3.2 写锁-X锁
3.2.1 主动加锁
3.2.2 自动加锁
四、事务并发场景实战验证
4.1 并发事务同时读和写,不冲突demo实战
4.2 并发事务加锁读和加锁写,锁冲突demo实战
4.3 事务里的select 查询默认是否加读锁?实战验证
4.4 那么多事务并发、读写加锁和不加锁场景?能否一句话讲透
五、MySQL的锁,锁的是索引?
一、前言背景
说到数据库锁,我们最常见的、最先想到的是行锁、表锁。MySQL 的InnoDB存储引擎,支持行级锁,而MYISAM支持的是表级锁。这个锁粒度的区别,让InnoDB在互联网海量数据高并发时代,得以脱颖而出,成为MySQL默认的存储引擎。而MYISAM,则适合在一次性大批量更新导入,后续日常查多写少的场景使用。
二、MySQL InnoDB的锁类型
MySQL5.7 版本的 InnoDB有8种锁,包括读锁、写锁、意向锁、记录锁、间隙锁、临键锁、写入意向锁、自增主键锁。实际上我们最常见关注的是读锁、写锁。意向锁,是表级别的锁,然后记录锁、间隙锁、临键锁,只是不同锁范围的表现形式。今天重点分享的是并发事务下,读写锁深入实践理解。
2.1 读锁和写锁(S锁和X锁)
读锁-S锁,全称是shared (S) locks ,写锁-又称排它锁,全称是 exclusive (X) locks。
此外也有很多说法,比如共享锁和独占锁。而在MySQL里,共享锁就是读锁。写锁就是排它锁和独占锁。
在MySQL并发事务情况下:如果一行数据被加了读锁,允许其他事务读、以及加读锁,但是不允许其他事务加写锁。
如果一行数据被加了写锁,不允许其他事务继续申请加读锁、以及写锁。
也就是数据的读锁与读锁之间不互斥,而写锁与写锁、读锁互斥。
2.2 意向锁
在MySQL 5.7 意向锁Intention Locks,属于表级别的锁。当表里的某些数据加读锁或者写锁后,会给表也加上意向读、意向写锁标识。当系统需要给这张表添加表锁的时候,就会判断这个标识,是否有数据已经被加锁,而无需全表扫描,提高表加锁效率。
表级意向锁,日常我们用的非常少,除非是DBA,研发对此应用几乎不需要了解。因为意向读锁、意向写锁彼此不互斥。这个意向锁,真正影响的是表锁。但是表锁比如DDL,给表新增修改删除某个字段属性,自动会触发表锁。表加了排他锁后,表里的数据,自然无法直接申请更新数据的排它锁,需要等表锁释放后,才能申请行级别的排它锁。
2.3 记录锁
记录锁全称是Record Locks。这里附带说一下,记录锁、间隙锁、临键锁特指锁的范围。比如id是主键或者是索引,
select * from user_mvcc_demo where id=3 for update;
这里触发的就是行的排它锁-而范围是记录级锁,给id=3这行数据精准加锁。
2.4 间隙锁
间隙锁,全称是Gap Locks。间隙锁的作用是锁住某个范围。比如事务A把id范围【1,4】的数据都加了排它锁,那事务B再尝试给id=4的数据加锁,就会失败。
这里锁的范围就是间隙锁。作用是可以避免该范围被新增或者删除,以及其他事务尝试修改该范围的数据。比如表里有id=1,4,9三条数据。如果对select * from user_mvcc_demo where id>=1 and id<=4 for update;加锁,那么就可以有效阻塞,新增id=2,3的数据。可以有效避免幻读。
总结起来:间隙锁,用于锁住目标范围数据的【间隙】或者说空隙,可以避免幻读。
2.5 临键锁
临键锁全称Next-Key Locks。实际上就是临键锁=记录锁+间隙锁。看起来不太好理解,容易和间隙锁搞混。可以这么理解:临键锁,不仅锁住范围数据的间隙,也锁住了相关行前后间隙。
比如事务A把id范围【1,7)的数据都加了排它锁,实际因为触发了临键锁,事务B尝试给id=7的数据加锁,就会需要等待,无法直接加锁。
三、MySQL加锁、释放锁有多少种方式
3.1 读锁-S锁,加锁和释放锁方式
可以在sql 末尾增加 lock in share mode的方式去加读S锁。比如:
begin;
select * from user_mvcc_demo where id=1 lock in share mode;
释放读锁,当事务结束后会自动释放。比如commit,或者rollback,或者会话超时,也都会自动释放。
3.2 写锁-X锁
3.2.1 主动加锁
可以在sql 末尾增加for update的方式去加写X锁。比如:
begin;
select * from user_mvcc_demo where id=1 for update;
3.2.2 自动加锁
在事务里,insert、update、delete语句都是会自动加写锁。
四、事务并发场景实战验证
4.1 并发事务同时读和写,不冲突demo实战
场景设计:事务A查询id=1的数据,但是不加读锁。另一个事务B尝试加锁更新,是否会被卡住?
事务1- 读id=1数据。
begin;
select * from user_mvcc_demo where id=1;
事务2-直接尝试加独占锁
select * from user_mvcc_demo where id=1 for update;
预期效果:事务B直接加锁成功,并可以执行更新。
具体示例:
首先,事务AB、并发开启并执行查询id=1的数据,结果互不干扰。
然后事务B继续对name值进行更新并查询。
update user_mvcc_demo set name='拉丁解牛说技术002' where id=1;
select * from user_mvcc_demo where id=1;
结论:
在MySQL InnoDB存储引擎下,事务并发读写,两个事务没有受写锁的影响。底层就是系列五,之前说的MVCC机制,undoLog版本链支持严格按照事务隔离级别去读数据、更新数据。MVCC的设计,大幅提升MySQL InnoDB存储引擎的事务并发能力。
4.2 并发事务加锁读和加锁写,锁冲突demo实战
场景设计:事务A查询id=1的数据,并加了读锁,另一个事务B尝试加锁更新,是否会被卡住?
事务1- 读id=1数据,并加读锁。
begin;
select * from user_mvcc_demo where id=1 lock in share mode;
事务2-直接尝试加独占锁
select * from user_mvcc_demo where id=1 for update;
预期效果:事务B会被卡住,直到超时,或者事务1提交释放锁。
具体示例:
demo结果:事务1一直未提交事务释放锁,最后事务B,加锁超时失败。
4.3 事务里的select 查询默认是否加读锁?实战验证
事务A里,查询id=1的数据;事务B对id=1的数据进行加写锁,是否会读事务A的重复读有影响?
如下图:
结论:MySQL的select查询,默认是不加读锁。
而且并发事务下,事务B虽然对id=1加了写锁,事务A里仍然可以继续查id=1的数据。底层就是基于readView机制去读事务隔离级别下对应的数据。如下:事务A可以重复读,没有被写锁阻塞。
4.4 那么多事务并发、读写加锁和不加锁场景?能否一句话讲透
我们很容易看到并理解,“数据被加了读S锁,大家可以共享读,但是不可以被再加写X锁。然后数据如果被加了写X锁,就不能被读。”
这句话其实有一定误导性,末尾数据加了X锁,不可以读。实际应该是:数据加了X锁,数据可以读,但是不能加读锁。
如果数据已经被加了写锁,其他事务要进行加读锁(比如select * from t1 where id=1 lock in share mode),那肯定就是不能读。如下:
首先事务B,加了写锁:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user_mvcc_demo where id=1 for update;
然后事务A尝试加读锁,就会被阻塞卡住。
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user_mvcc_demo where id=1 lock in share mode;
结果如下图:
最后事务A,加读锁超时失败。
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
这个demo,另一个场景,即使事务B里读数据进行加了排它锁,如果事务A不加读锁直接读,是可以直接读到数据。
一句话总结:并发事务,数据被加了写锁,此时不可以再被加读锁。如果被加了读锁,事务可以共享读,但不能被加写锁。
其他的,在非主动加锁(xxx for update,xxx lock in share mode)情况下,事务可以自由并发进行读写,互不干扰,并发效率很高。
五、MySQL的锁,锁的是索引?
日常我们如果给特定id=xx的值进行加锁,不管是update xx 还是通过select xx for update方式,如果id这列不是主键,就会被触发表锁,锁住整张表。如果id是主键,也就是索引。那就是锁定一行数据而已。
比如如下案例demo,表里有3条数据,id分别为1、2、3。
在事务A里给id=1的数据加了排它锁。然后事务B仍然可以给id=2、3的数据加排他锁。
随后,事务A尝试给已经被事务B加锁的id=2的数据加锁,就需要等待排队。
效果如下图:
好了,今天先分享到这,下一篇再见。
推荐阅读拉丁解牛相关专题系列(欢迎交流讨论):
2、JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?