锁的分类
根据锁的粒度,锁可以分为以下三大类:
- 行级锁(InnoDB存储引擎)
- 表级锁(MyISAM存储引擎)
- 页级锁(BDB存储引擎)
- 行级锁
行级锁是粒度最小的锁,它能锁定到具体某一行,加锁开销大,相应的支持并发也最高的。但可能会出现死锁的现象。它分为共享锁、排他锁等。 - 表级锁
表级锁锁定的是这个表,一个用户对表进行写操作(插入、更新、删除等)要先获得写锁,进行读操作要先获取读锁。它的粒度相对行级锁而言是很大的。加锁消耗资源少,MyISAM和InnoDB都支持。它分为表共享(读)锁、表排他(独占写)锁。加锁消耗资源少,但访问冲突多,并发支持弱。 - 页级锁
页级锁介于上述两者之间,它一次只锁定相邻的一组数据。也会出现死锁,并发一般。
读写锁
前面提到共享锁和排他锁两种模式,其实不管是在行级锁或者表级锁中,它们的含义是一样的。读的时候是共享,互不塞的;写的时候,会阻塞其他的写锁或者读锁访问资源,这都是为了保持数据安全。
意向锁
在InnoDB中还有两种意向锁。
- 意向共享锁(IS),在对行数据进行加共享锁前,要先获取这张数据表的意向共享锁。
- 意向排他锁(IX),同理,在对行数据进行加排他锁前,要先获取这张数据表的意向排他锁。
间隙锁
在InnoDB可重复读隔离级别下,会出现幻读的情况。间隙锁就是为了解决这个问题而引入的。所谓间隙,就是锁定行之外的表空间,比如一个表中有五行数据,执行select语句时,读锁定两行,那么其他三行都是在这两行外的间隙中。给间隙加锁,就可以保证表中的其他数据不会被修改。从而保证不会出现幻读的现象。
另外间隙锁都是左开右闭的原则对范围加锁的,注意是范围,而不是某一行。
由于间隙锁之间不是互斥的,所以可能会出现死锁的现象。
举一个例子
步骤 | A事务 | B事务 |
---|---|---|
1 | select * from Tab where id = 5 for update; | |
2 | select * from Tab where id = 7 for update; | |
3 | insert into Tab value(1) 阻塞 | |
4 | insert into Tab value(1) 阻塞 |
假设表Tab中已经有10条数据,分别是1-10,进行完步骤2之后,有三个间隙锁分别是,(1,5], (5,7], (7,10]。 间隙锁之间是不互斥的,那么
当A事务拿到(1,5], (5,7]并保持,去请求 (7,10];
这时B事务拿到(7,10]并保持,去请求(1,5], (5,7]。
就出现了死锁。
MySql中死锁的排查,可以通过show engine innodb status 命令查看死锁日志来分析。