本节讨论 MySQL 服务器内部锁定,以管理多个会话对表内容的争用。这种锁定是内部的,因为它完全由服务器执行,不涉及其他程序。有关 MySQL 文件的锁定由其他程序执行,请参阅 第 10.11.5 节,“外部锁定”。
MySQL 使用 行级锁定 对 InnoDB
表,以支持多用户、高并发和 OLTP 应用程序中的同时写访问。
为了避免 死锁 在执行多个并发写操作时,对单个 InnoDB
表,需要在事务开始时通过发出 SELECT ... FOR UPDATE
语句来获取必要的锁,即使数据更改语句在事务中较晚出现。如果事务修改或锁定多个表,请在每个事务中以相同的顺序发出适用的语句。死锁影响性能,而不是严重错误,因为 InnoDB
自动 检测 死锁条件,默认情况下回滚一个受影响的事务。
在高并发系统上,死锁检测可能会导致等待同一个锁的多个线程时的性能下降。在某些情况下,禁用死锁检测可能更高效,依靠 innodb_lock_wait_timeout
设置在死锁发生时回滚事务。可以使用 innodb_deadlock_detect
配置选项禁用死锁检测。
行级锁定的优点:
-
不同会话访问不同行时,锁定冲突较少。
-
回滚时需要更少的更改。
-
可以长时间锁定单个行。
MySQL 使用 表级锁定 对 MyISAM
、MEMORY
和 MERGE
表,仅允许一个会话更新这些表。这种锁定级别使这些存储引擎更适合只读、读多写少或单用户应用程序。
这些存储引擎通过始终在查询开始时请求所有需要的锁,并始终以相同的顺序锁定表来避免 死锁。这种策略的权衡是降低了并发性;其他想要修改表的会话必须等待当前数据更改语句完成。
表级锁定的优点:
-
需要相对较少的内存(行锁定需要每行或行组的内存)
-
如果在大部分表上执行
GROUP BY
操作或频繁扫描整个表,速度很快,因为只涉及一个锁。 -
如果经常对大量数据执行
GROUP BY
操作或频繁扫描整个表,速度很快。
MySQL 授予表写锁的方式如下:
-
如果表上没有锁,授予写锁。
-
否则,将锁请求添加到写锁队列中。
MySQL 授予表读锁的方式如下:
-
如果表上没有写锁,授予读锁。
-
否则,将锁请求添加到读锁队列中。
表更新优先于表检索。因此,当锁被释放时,锁将被提供给写锁队列中的请求,然后提供给读锁队列中的请求。这确保了即使存在大量的SELECT
活动,对表的更新也不会被“饿死”。然而,如果有很多表更新,SELECT
语句将等待,直到没有更多的更新。
有关改变读取和写入优先级的信息,请参阅第 10.11.2 节,“表锁定问题”。
您可以通过检查Table_locks_immediate
和Table_locks_waited
状态变量来分析系统上的表锁争用,这些变量分别指示了可以立即授予的表锁请求次数和需要等待的次数。
mysql> SHOW STATUS LIKE 'Table%';
+-----------------------+---------+
| Variable_name | Value |
+-----------------------+---------+
| Table_locks_immediate | 1151552 |
| Table_locks_waited | 15324 |
+-----------------------+---------+
性能模式锁表也提供了锁信息。请参阅第 29.12.13 节,“性能模式锁表”。
MyISAM 存储引擎支持并发插入,以减少读者和写者之间的争用:如果 MyISAM 表在数据文件中间没有空闲块,行总是插入到数据文件的末尾。在这种情况下,您可以自由地混合并发INSERT
和SELECT
语句,用于 MyISAM 表,而不需要锁定。那是说,您可以在其他客户端从表中读取数据时插入行到 MyISAM 表中。孔洞可能来自于从表中删除或更新的行。如果有孔洞,.concurrent 插入将被禁用,但是一旦所有孔洞被填充新的数据,插入将被重新启用。要控制这种行为,请使用concurrent_insert
系统变量。请参阅第 10.11.3 节,“并发插入”。
如果您使用LOCK TABLES
明确地获取表锁,您可以请求READ LOCAL锁,而不是READ锁,以便在您锁定表时启用其他会话的并发插入。
要在表 t1
上执行许多INSERT
和SELECT
操作时,并发插入不可用,您可以将行插入临时表 temp_t1
,然后使用临时表中的行更新实际表:
mysql> LOCK TABLES t1 WRITE, temp_t1 WRITE;
mysql> INSERT INTO t1 SELECT * FROM temp_t1;
mysql> DELETE FROM temp_t1;
mysql> UNLOCK TABLES;
一般来说,表锁优于行级锁在以下情况下:
使用高级锁,可以更容易地调整应用程序,因为锁的开销小于行级锁。
其他锁定选项:
-
版本控制(例如 MySQL 中的并发插入),其中可以在同一时间拥有一个写者和多个读者。这意味着数据库或表支持根据访问开始时间的不同视图。其他常见的术语是“时间旅行”、“复制写入”或“按需复制”。
-
按需复制在许多情况下优于行级锁定。然而,在最坏的情况下,它可能比使用普通锁占用更多的内存。
-
除了使用行级锁,您可以使用应用程序级锁,例如 MySQL 中的
GET_LOCK()
和RELEASE_LOCK()
。这些是咨询锁,只有在应用程序之间合作时才有效。请参阅 第 14.14 节,“锁定函数”。