1.锁的种类
- 按兼容性划分:共享锁(S Lock)、排他锁(X Lock)
- 按锁粒度划分:表锁、行锁
- 按锁模式划分:记录锁、间隙锁、临键锁、意向锁、插入意向锁
- 按加锁机制划分:乐观锁、悲观锁
2.按兼容性划分
(1)共享锁
共享锁(S Lock)也叫读锁(read lock)。共享指读读共享 。也就是说,无论是行级或是表级,如果对某数据上了共享锁 ,允许其他事务也加共享锁,共享锁和共享锁不互斥),但是不能写,也就是读写互斥。
普通的select语句是什么锁都不上的,所谓的读读共享,读写互斥,写写互斥 ,都是对于锁资源来说的,如果没有锁资源竞争,就不存在什么互斥了。
select * from table1 lock in share mode; // 同时还是表锁
select * from table1 where id = 1 lock in share mode; // id为索引,同时还是行锁
(2)排他锁
除了select … for update,InnoDB引擎对修改 、插入、删除(update、insert。delete)都是给相关数据加排他锁。
select * from table1 for update; // 同时还是表锁
select * from table1 where id = 1 for update; // id为索引,同时还是行锁
3. 按锁粒度划分
(1)表锁
并发度低,不会出现死锁。开销小,加锁快。
select * from table1 lock in share mode;
select * from table1 for update;
(2)行锁
并发度高,会出现死锁。开销大,加锁慢。
select * from table1 where id = 1 lock in share mode;
select * from table1 where id = 1 for update;
(3)注意
在InnoDB 下, 不是想用行锁就用行锁的,行锁的触发条件:
4.按锁模式划分
(1)(2)(3)为行锁的三种实现算法。间隙锁 和 临键锁 是用来解决幻读问题的,在已提交读 隔离级别下会失效。
(4)也是行存在的锁。
(5)是表级锁。
(1)记录锁
直接锁定某行数据。
select * from table1 where id = 1 lock in share mode;
select * from table1 where id = 1 for update;
(2)间隙锁
间隙指的是两个记录之间逻辑上尚未填入数据的部分,是一个左开右开空间。
间隙锁就是锁定某些间隙区间的。当我们使用等值查询或者范围查询,并没有命中任何一个记录时,就会将对应的间隙区间锁定。
select * from table1 where id = 1 for update;
select * from table1 where id > 1 and id < 6 for update;
(3)临键锁
临键指的是间隙加上它右边的记录组成的区间。
临键锁 是 记录锁 和 间隙锁 的组合,即除了锁住记录本身,还要再锁住索引之间的间隙。
(4)插入意向锁
插入意向锁是一种间隙锁形式的意向锁,在真正执行 INSERT 操作之前设置。
当执行插入操作时,总会检查当前插入操作的下一条记录(已存在的主索引节点)上是否存在锁对象,判断是否锁住了 gap,如果锁住了,则判定和插入意向锁冲突,当前插入操作就需要等待,也就是配合上面的间隙锁或者临键锁一起防止了幻读操作。
因为插入意向锁是一种意向锁,意向锁只是表示一种意向,所以插入意向锁之间不会互相冲突,多个插入操作同时插入同一个 gap 时,无需互相等待,比如当前索引上有记录 4 和 8,两个并发 session 同时插入记录 6,7。他们会分别为(4,8)加上 GAP 锁,但相互之间并不冲突
INSERT 语句在执行插入之前,会先在 gap 中加入插入意向锁,如果是唯一索引,还会进行 Duplicate Key 判断,如果存在相同 Key 且该 Key 被加了互斥锁,则还会加共享锁,然后等待(因为这个相同的 Key 之后有可能会回滚删除,这里非常容易死锁)。等到成功插入后,会在这条记录上加排他记录锁
(5)意向锁
意向锁的出现是为了支持InnoDB的多粒度锁,它解决的是表锁和行锁共存的问题。
当我们需要给一个表加表锁的时候,我们需要根据去判断表中有没有数据行被锁定,以确定是否能加成功。
假如没有意向锁,那么我们就得遍历表中所有数据行来判断有没有行锁;
有了意向锁这个表级锁之后,则我们直接判断一次就知道表中是否有数据行被锁定了。
有了意向锁之后,要执行的事务A在申请行锁(写锁)之前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的互斥锁时就会失败,因为表上有意向排他锁之后事务B申请表的互斥锁时会被阻塞。
5.按加锁机制划分
(1)悲观锁
悲观锁认为被它保护的数据是极其不安全的,每时每刻都有可能被改动,一个事务拿到悲观锁后,其他任何事务都不能对该数据进行修改,只能等待锁被释放才可以执行。
数据库中的行锁,表锁,读锁,写锁均为悲观锁。
(2)乐观锁
https://blog.csdn.net/a20100997/article/details/123031755
乐观锁认为数据的变动不会太频繁。
乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。
事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本v1与数据中最新的版本v2相对比,如果v1=v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时version会加1,以此来表明数据已被变动。
如果,v1不等于v2,那么说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。不同于悲观锁,乐观锁通常是由开发者实现的。
6.死锁的解决
(1)查看死锁
1、查看正在进行中的事务
SELECT * FROM information_schema.INNODB_TRX
2、查看正在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
3、查看等待锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
4、查询是否锁表
SHOW OPEN TABLES where In_use > 0;
5、查看最近死锁的日志
show engine innodb status
(2)解除死锁
查看当前正在进行中的进程
show processlist;
SELECT * FROM information_schema.INNODB_TRX;
杀掉进程对应的进程id
kill idl
验证
SHOW OPEN TABLES where In_use > 0;