MySQL支持三种层级的锁定
我们知道,MySQL支持三种层级的锁定,分别为:
- 表级锁定
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定
- 页级锁定
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁
- 行级锁定
行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大
从上到下,锁的粒度逐渐细粒化, 但实现开销逐渐增大。 同时我们也要须知,表锁,页锁,行锁并不是一个具体的锁,仅代表将数据库某个层级上的数据进行锁定。具体怎么去锁这个数据,还要看具体的锁实现是什么
MyISAM引擎的表锁
MyISAM只支持表锁,不支持行锁和页面锁。
-
据我查到的资料中,MyISAM引擎支持的锁不多,也就是表级的排他锁和共享锁而已,官方资料上也没有专门介绍MyISAM锁的章节
-
表锁并不是一个真正的锁,只是代表对数据库表层级的数据进行锁定。具体以什么形式锁定,则要看具体的具体锁实现。
MyISAM下的共享锁和排他锁
我们知道MyISAM存储引擎只支持在表层级对数据进行锁定,既只支持表锁。那么表层级具体的加锁实现有什么?既表级共享锁和表级排他锁
- 表共享读锁(表级共享锁)
获得表共享读锁的事务可以读取该表的任意数据。MyISAM引擎在执行select
语句前,会自动给涉及的表加表共享读锁
- 表独占写锁(表级排他锁)
获得表独占写锁的事务可以更新或删除该表中任意数据。在执行update,delete,insert
等语句前,会自动给涉及的表加表独占写锁
只是很多场合更喜欢把表级共享锁
和表级排他锁
叫做表共享读锁
和表独占写锁
,实际上它们的意思是一样的
既MyISAM的表锁可以做到,读读共享,读写互斥,写写互斥的功能
-
读读共享:事务A获得表读锁,事务B依然可以获得表读锁
-
读写互斥:事务A获得表读锁,事务B申请该表写锁则会阻塞,因为事务A获得了该表读锁,有互斥性
-
写写互斥:事务A获得表写锁,事务B申请该表写锁则会阻塞,因为事务A获得了该表写锁,也有互斥性
值得注意的是
The LOCAL modifier enables nonconflicting INSERT statements (concurrent inserts) by other sessions to execute while the lock is held. (See Section 8.11.3, "Concurrent Inserts".) However, READ LOCAL cannot be used if you are going to manipulate the database using processes external to the server while you hold the lock. For InnoDB tables, READ LOCAL is the same as READ
MyISAM在一定程度上还是可以支持并发的进行查询和插入操作的。既如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个线程读表的同时,另一个线程从表尾插入记录。这属于MyISAM的特性,所以InnoDB存储引擎是不支持的!
MyISAM引擎的行级锁
InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。
前面也提到,普通的 select 语句是不会对记录加锁的,因为它属于快照读。如果要在查询时对记录加行锁,可以使用下面这两个方式,这种查询会加锁的语句称为锁定读。
//对读取的记录加共享锁
select ... lock in share mode;
//对读取的记录加独占锁
select ... for update;
上面这两条语句必须在一个事务中,因为当事务提交了,锁就会被释放,所以在使用这两条语句的时候,要加上 begin、start transaction 或者 set autocommit = 0。
共享锁(S锁)满足读读共享,读写互斥。独占锁(X锁)满足写写互斥、读写互斥。
行级锁的类型主要有三类:
-
Record Lock,记录锁,也就是仅仅把一条记录锁上;
-
Gap Lock,间隙锁,锁定一个范围,但是不包含记录本身;
-
Next-Key Lock:Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。
Record Lock
Record Lock 称为记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的:
-
当一个事务对一条记录加了 S 型记录锁后,其他事务也可以继续对该记录加 S 型记录锁(S 型与 S 锁兼容),但是不可以对该记录加 X 型记录锁(S 型与 X 锁不兼容);
-
当一个事务对一条记录加了 X 型记录锁后,其他事务既不可以对该记录加 S 型记录锁(S 型与 X 锁不兼容),也不可以对该记录加 X 型记录锁(X 型与 X 锁不兼容)。
举个例子,当一个事务执行了下面这条语句:
mysql > begin;
mysql > select * from t_test where id = 1 for update;
就是对 t_test 表中主键 id 为 1 的这条记录加上 X 型的记录锁,这样其他事务就无法对这条记录进行修改了。
当事务执行 commit 后,事务过程中生成的锁都会被释放。
Gap Lock
Gap Lock 称为间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。
假设,表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生。
间隙锁虽然存在 X 型间隙锁和 S 型间隙锁,但是并没有什么区别,间隙锁之间是兼容的,即两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻影记录而提出的。
Next-Key Lock
Next-Key Lock 称为临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。
假设,表中有一个范围 id 为(3,5] 的 next-key lock,那么其他事务即不能插入 id = 4 记录,也不能修改 id = 5 这条记录。
所以,next-key lock 即能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。
next-key lock 是包含间隙锁+记录锁的,如果一个事务获取了 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,是会被阻塞的。
比如,一个事务持有了范围为 (1, 10] 的 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,就会被阻塞。
虽然相同范围的间隙锁是多个事务相互兼容的,但对于记录锁,我们是要考虑 X 型与 S 型关系,X 型的记录锁与 X 型的记录锁是冲突的。
总结
虽然MySQL支持表,页,行三级锁定,但MyISAM存储引擎只支持表级的数据锁定,既表锁。所以MyISAM的加锁相对比较开销低,但数据操作的并发性能相对就不高。但如果写操作都是尾插入,那么还是可以支持一定程度的读写并发
既MyISAM的数据操作如果导致加锁行为,那么该锁肯定是一个表锁,会对全表数据进行加锁。但也要看具体什么形式的表锁,不同形式的表锁有着不同的特性
同时从MyISAM所支持的锁中也可以看出,MyISAM是一个支持读读并发,但不支持通用读写并发,写写并发的数据库引擎,所以它更适合用于读多写少的应用场合