概述
相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
MySQL这3种锁的特性可大致归纳如下。
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!仅从锁的角度来说:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web 应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。
MyISAM 表锁
MyISAM 存储引擎只支持表锁,这也是MySQL 开始几个版本中唯一支持的锁类型。随着应用对事务完整性和并发性要求的不断提高,MySQL 才开始开发基于事务的存储引擎,后来慢慢出现了支持页锁的BDB 存储引擎和支持行锁的InnoDB 存储引擎(实际InnoDB 是单独的一个公司,现在已经被Oracle 公司收购)。但是MyISAM 的表锁依然是使用最为广泛的锁类型。
1. 查询表级锁争用情况
可以通过检查table_locks_waited 和table_locks_immediate 状态变量来分析系统上的表锁定争夺:
如果Table_locks_waited 的值比较高,则说明存在着较严重的表级锁争用情况。
2. MySQL 表级锁的锁模式
MySQL 的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
可见,对MyISAM 表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对MyISAM 表的写操作,则会阻塞其他用户对同一表的读和写操作;MyISAM 表的读操作与写操作之间,以及写操作之间是串行的!
当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
3. 如何加表锁
MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE 命令给MyISAM 表显式加锁。
给MyISAM 表显示加锁,一般是为了在一定程度模拟事务操作,实现对某一时间点多个表的一致性读取。
要特别说明以下两点内容:
- 在LOCK TABLES 时加了“local”选项,其作用就是在满足MyISAM 表并发插入条件的情况下,允许其他用户在表尾并发插入记录,有关MyISAM 表的并发插入问题,在后面的章节中还会进一步介绍。
- 在用LOCK TABLES 给表显式加表锁时,必须同时取得所有涉及到表的锁,并且MySQL 不支持锁升级。也就是说,在执行LOCK TABLES 后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。其实,在自动加锁的情况下也基本如此,MyISAM 总是一次获得SQL 语句所需要的全部锁。这也正是MyISAM 表不会出现死锁(Deadlock Free)的原因。
4. 并发插入(Concurrent Inserts)
上文提到过MyISAM 表的读和写是串行的,但这是就总体而言的。在一定条件下,MyISAM表也支持查询和插入操作的并发进行。
MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。
- 当concurrent_insert设置为0时,不允许并发插入。
- 当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
- 当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。
可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。
参考自:《深入浅出MySQL:数据库开发、优化与管理维护(第2版)》