本章节讨论内部锁;内部锁是MySQL服务器内部用来管理不同会话对表内容的竞争访问的锁。这种类型的锁之所以是内部的,是因为它完全在数据库服务器内部进行,不涉及其它程序。像了解其它程序在MySQL文件上添加的锁,请查阅8.11.5 “External Locking”。
- 行级别锁。
- 表级别锁。
- 选择锁类型。
行级锁 #
对于InnoDB表,MySQL使用行级锁来支持多个会话同时进行写操作,这使得它们可以支持多用户、高并发、以及OLTP型应用。
在对一张InnoDB表进行并发写操作时,为了避免死锁,可以在事务一开始就使用SELECT … FOR UPDATE语句来锁定要修改的每一个分组的记录,即使不是在事务 一开始就修改这些记录。 如果这些事务修改或锁定多张表,那么应该在每个事务中按照相同的顺序来执行语句。死锁影响性能,但不代表一个严重错误,因为InnoDB默认会自动检测死锁,并且回滚其中一个事务。
在高并发系统中,如果有多个线程在等待同一个锁,死锁检测可能会导致性能下降。有时,禁用死锁检测,依赖innodb_lock_wait_timeout配置项来在发生死锁 时进行事务回滚可能会更高效。可以使用innodb_deadlock_detect配置项来禁用死锁检测。
使用行级锁的优点:
- 当多个会话访问不通的行时,可以减少所冲突。
- 回滚时要修改的数据更少。
- 使得长时间锁定一个特定行变得可能。
表级锁 #
对于MyISAM、MEMORY、以及MERGE表,MySQL使用表级锁,同一时刻只允许一个会话对表进行更新。这种级别的锁使这些引擎更适合只读,多占多数,或单用户场景。
这些存储引擎,在查询一开始的时候就获取所有需要的锁,并使用相同的顺序来锁定多张表,以此来避免死锁。这种策略的代价是降低了并发性能;其它会话要修改相关表的话,就必须等待当前语句执行结束。
使用表级锁的优点:
- 使用的内存相对较少。(行级锁锁定每一行或每一组记录都需要内存)
- 锁定表中的一大部分记录时更快,因为只涉及一个锁。
- 在你经常在大量数据上使用GROUP BY 操作或扫描全表时,更快。
MySQL添加表级写锁的规则如下:
- 如果表上没有锁,则在上面添加写锁。
- 否则,将锁请求放入写锁队列中。
MySQL添加表级读锁的规则如下:
- 如果表上没有写锁,则在上面添加读锁。
- 否则,将锁请求放入读锁队列中。
更新表比检索表的优先级更高。因此,当一个锁被释放时,MySQL会优先将锁分配给写锁等待队列中的锁请求,然后才是读锁等待队列中的锁请求。这保证了在SELECT请求较多时,对表的更新操作不会产生 ”饥饿“。然而,如果一张表有很多更新,SELECT操作要等到更新完成才能执行。
要想了解修改读写优先级的信息,请查阅8.11.1,“Table Locking Issues”.
你可以通过查看Table_locks_immediate和Table_locks_waited两个状态变量来查看系统中的表锁竞争情况,这两个状态变量立即获取到表锁和等待表锁的请求次数:

Performance Schema数据库中的锁定表也提供了相关锁信息。见27.12.13 “Performance Schema Lock Tables”。
针对特定表,MyISAM存储引擎支持并发插入以减少读操作和写操作的竞争:如果一张MyISAM表在data文件中间没有空闲区块,新插入的记录都会被添加到data文件 的尾部。在这种场景下,读操作和写操作可以同时进行,而不会产生锁。就是说,在你向一张MyISAM表中插入数据的同时,其它客户端可以从其中读取数据。删除操作和更新操作可能会在表文件中间造成间隙。如果有间隙,并发插入就会被禁止,但是一旦这些间隙被新的数据填满,就可以自动恢复。可以使用系统变量concurrent_insert来控制这种操作。见8.11.3,”Concurrent Inserts”。
如果你使用lock tables来显式的获取表锁,你可以请求一个READ LOCAL锁,而不是一个READ锁,这使得在你获取到表锁的同时,其它会话可以进行并发插入。(?)
如果在一张表t1上有多很多insert和select操作却无法进行并发插入,你可以将输入写入临时表temp_t1,然后将临时表中的数据更新到原表中。
选择锁类型 #
通常来讲,在以下场景中,表锁会优于行锁:
- 大多数语句都是读。
- 对表的操作有读操作也有写操作,其中写操作时对特定一行进行,或可以通过一个特定的key来读取。(?)
- SELECT操作及并发写操作,UPDATE操作或DELETE操作极少。
- 没有写操作,全表扫描机GROUP BY操作很多。 有了更高级别的锁,你可以更容易的对应用进行调优,因为表锁的压力会比行锁更小。
行级锁以外的其它选项(?):
- 版本管理(比如在MySQL在并发插入中使用的),有一个写操作的同时可以有很多读操作。这意味着根据访问开始时间的不同,数据库和表支持不同的数据视图。一些其他常见的术语是“时间履行”,“copy on write",或”copy on demand"。
- 在很多场景中 copy one demand优于行级锁。然而,在最坏的情况下,这会比使用普通锁消耗更多的内存。
- 你可以使用应用级别的锁来替代行级锁,比如MySQL提供的GET_LOCK()和RELEASE_LOCK()。这些都是公共锁,所以他们只能用于相互协作的程序。见12.15,“Locking Functions”。