来自于mysql 官方手册的介绍:innodb locking
广义来讲,就行锁和表锁,行锁锁的是索引,锁不到索引则会变成表锁。但是其实innodb为了高并发,是有很多类型的锁:
1、共享锁,排他锁,2、意向共享锁,意向排他锁
3、记录锁,4、间隙锁,5、next-key临键锁6、自增锁。7、空间索引谓词锁
大概按类型划分,锁住数据的范围:记录锁,临键锁。锁的类型:共享锁,排他锁。以及其他的锁。
当然说的都是RR这个默认隔离级别。
1、共享锁、排他锁。
共享锁:读读不互斥,读写互斥。
排他锁:和读写互斥,写写互斥。
设置共享锁:SELECT ... LOCK IN SHARE MODE;
设置排他锁:SELECT ... FOR UPDATE;delete,insert,update。
2、意向共享锁、意向排他锁。
意向锁:通知数据库将有加什么锁的意向并加锁。
意向共享锁:需要对表增加共享锁的时候,先告诉数据库加意向共享锁,再加共享送。
意向排他锁:同理。
意向锁是系统控制的,人工无法干预页无需干预,主要目的是显示某人正在锁定行
3、记录锁。
记录锁:更像我们说的行锁。锁住索引记录。
update table set id = 3 where id = 2;
锁住id为2这条记录。
4、间隙锁。(重点)
间隙锁:锁住索引的上下间隙。
间隙锁的目的:防止幻读,保证可重复读。所以,只有在RR隔离级别才有。
没有间隙锁产生幻读的例子:因为事务在没提交前的操作都是在新版本上操作(原有的记录),快照读读的是旧版本数据(undo日志),另一个事务更新的也是原有记录,故写写同一条记录互斥。如果没有间隙锁,则事务1:update table set name = 'abc' where name = 'a';事务2:insert into table(name) values ('abc');然后事务1读到了name='abc'的数据,产生了幻读现象。
比如有表:
6 a
3 abc
7 b
执行select * from test where name = 'a' for update;
会锁住a上面无穷的间隙,第一条记录和第二条记录的间隙。
故执行:insert into test values(4, 'a');会堵塞。
执行insert into test values(7, 'ab')也会堵塞;
在比如:select * from test where name > 'a' and name < 'b' for update;
则a和b之间的区间也会被锁住,
故执行:insert into test values(4, 'a');不会堵塞,因为这条记录是插入到(6,a)这条记录的上面。
执行insert into test values(7, 'ab')会堵塞,因为这条记录会插入到(6,a)和(3,abc)记录之间;
update、insert、delete语句也会产生间隙锁。
比如事务1:update table set name = 'b' where name = 'a'
事务2:insert into table (name) values ('a')等待。
注意:唯一索引不会的非范围过滤不会产生间隙锁。
比如:select * from table where id = 3 for update;不会锁住间隙,只会锁住3这条索引记录。原因是,唯一索引其他事务不可能再插入一条id为3的记录进来,不会产生幻读现象。
因为有间隙锁的存在:一不小心就会导致死锁的产生。
比如:
事务1:执行delete,delete 语句产生一个共享间隙锁。
事务2:执行delete,delete语句产生一个事务一个共享间隙锁
事务1:执行insert, 语句插入的位置刚好在事务2锁住的区间,需要获取排他间隙锁。
事务2:执行,insert语句插入的位置刚好在事务1锁住的区间。需要获取排他间隙锁。
导致死锁。
事务1: insert into testinnodb values (4, 'ab');
事务2: insert into testinnodb values (4, 'ab');
事务3: insert into testinnodb values (4, 'ab');
说明
事务1,获取排他锁。
事务2:需要先进行主键校验,先获取共享锁,堵塞。
事务3:需要先进行主键校验,先获取共享锁,堵塞
事务1:释放锁。
事务2:获取共享锁。再取获取排他锁,跟事务3的共享锁互斥。
事务3:获取共享锁。再取获取排他锁,跟事务2的共享锁互斥。
导致死锁
还有一种情况和其类似:
事务1:delete from testinnodb where id = 2;
事务2:delete from testinnodb where id = 2;
事务2:insert into testinnodb (id, a) values (2, 3);
说明
事务1:获取id=2的排他锁。
事务2:获取id=2的排他锁,堵塞。
事务2:需要先进行主键校验,先获取共享锁,因为事务1已经在等待这条记录的排他锁了,所以共享锁排在事务2的后面,而事务2又要等待事务1,如此,购车给一个循环,死锁。
show engine innodb status;
这条命令可以查看最新一条死锁的信息。包括:ip,sql,锁详细信息。
死锁的解决方案:很难保证绝对不产生死锁。解决方案:1、innodb有死锁检测机制,发生死锁,一个事务会自动放弃。2、锁都有超时机制,超时自然会释放锁。3、修改程序执行顺序,尽量避免死锁。4、需要获取的锁一次性全获取。(很难办到,毕竟总有个先后顺序)。5、修改事务隔离级别,如果对幻读不介意的话可以考虑。6、修改配置文件,取消间隙锁。
5、next-key临键锁
记录锁和间隙锁都锁住就是临界锁。InnoDB默认加锁方式是next-key 锁。
6、自增锁。
自增锁:默认不开启。保证插入的连续性,事务自增插入会产生表级锁,其他事务不能插入。
比如:
事务A:insert into table (name) values ('a');插入的记录id=1。
事务B:insert into table (name) values ('b');插入的记录id=2。
事务A:insert into table (name) values ('c');插入的记录id=3。事务A会觉得很奇怪。id直接从1跳到了3。
所以:如果开启了自增锁,事务B堵塞。
我觉得没什么用。
网友评论