元数据锁定
MySQL使用元数据锁定来管理对数据库对象的并发访问并确保数据一致性。元数据锁定不仅适用于表,而且还适用于模式,存储程序(过程,函数,触发器,计划的事件),表空间,通过GET_LOCK()
函数获取的用户锁(请参见“锁定函数”)和通过函数获取的锁。“锁定服务”中描述的锁定服务。
性能模式metadata_locks
表公开了元数据锁定信息,这对于参见哪些会话持有锁定,被阻止等待锁定等很有用。有关详细信息,请参见“ metadata_locks表”。
元数据锁定确实涉及一些开销,随着查询量的增加而增加。多个查询尝试访问同一对象的次数越多,元数据争用就会增加。
元数据锁定不能代替表定义高速缓存,并且其互斥锁和锁定与互斥锁不同LOCK_open
。以下讨论提供了有关元数据锁定如何工作的一些信息。
- 元数据锁定获取
- 元数据锁定释放
元数据锁定获取
如果给定锁有多个侍者,则首先满足最高优先级的锁请求,但max_write_lock_count
系统变量除外。写锁定请求比读锁定请求具有更高的优先级。但是,如果max_write_lock_count
将其设置为某个较低的值(例如10),则如果已将读取锁定请求传递给了10个写入锁定请求,则读取锁定请求可能比挂起的写入锁定请求优先。通常,不会发生此现象,因为max_write_lock_count
默认情况下它的值非常大。
语句(而不是同时)一个接一个地获取元数据锁,并在该过程中执行死锁检测。
DML语句通常以在语句中提到表的顺序获取锁。
DDL语句LOCK TABLES
和其他类似的语句通过按名称顺序获取对显式命名的表的锁,试图减少并发DDL语句之间可能出现的死锁。对于隐式使用的表(例如,具有外键关系的表也必须被锁定),可以以不同的顺序获取锁。
例如,RENAME TABLE
一条DDL语句按名称顺序获取锁:
该
RENAME TABLE
语句重命名tbl
为其他名称,并重命名tblc
为tbl
:RENAME TABLE tblaTO tbld, tblcTO tbl ;声明获取元数据锁,按顺序上
tbl
,tblc
和tbld
(因为tbld
如下tblc
的名称顺序排列):这个稍有不同的语句还重命名
tbl
为其他名称,并重命名tblc
为tbl
:RENAME TABLE tblaTO tblb, tblcTO tbl ;在这种情况下,该语句获取元数据锁,按顺序上
tbl
,tblb
和tblc
(因为tblb
先于tblc
在名称顺序排列):
这两个语句都按tbl
和tblc
顺序获得对和的锁定,但是在剩余表名上的锁定是在之前还是之后都不同tblc
。
如以下示例所示,当多个事务同时执行时,元数据锁获取顺序可能会在操作结果上产生差异。
与两个表开始x
和x_new
具有相同的结构。三个客户发出涉及这些表的语句:
客户1:
LOCK TABLE xWRITE , x_newWRITE ;
该语句按名称顺序在x
和上请求并获取写锁定x_new
。
客户2:
INSERT INTO xVALUES (1);
该语句请求并阻塞以等待写入锁定x
。
客户3:
RENAME TABLE xTO x_old, x_newTO x;
声明要求独占锁在名字顺序x
,x_new
和x_old
,而是块等待上了锁x
。
客户1:
UNLOCK TABLES ;
该语句释放对x
和的写锁x_new
。x
客户端3 的排他锁定请求比客户端2的写锁定请求具有更高的优先级,因此客户端3在上获取其锁定x
,然后在x_new
和上获取其锁定x_old
,执行重命名并释放其锁定。客户端2然后获取其锁定x
,执行插入操作,然后释放其锁定。
锁定获取顺序导致在RENAME TABLE
之前执行INSERT
。在x
进入时发生的插入是被命名表x_new
时,客户端2日插入,并更名为x
通过客户端3:
mysql>SELECT *FROM x; +------ + | i | +------ + | 1 | +------ + mysql>SELECT *FROM x_old; Empty set (0.01 sec)
现在,而不是首先命名表x
,并new_x
具有相同的结构。同样,三个客户发出涉及这些表的语句:
客户1:
LOCK TABLE xWRITE , new_xWRITE ;
该语句按名称顺序在new_x
和上请求并获取写锁定x
。
客户2:
INSERT INTO xVALUES (1);
该语句请求并阻塞以等待写入锁定x
。
客户3:
RENAME TABLE xTO old_x, new_xTO x;
声明要求独占锁在名字顺序new_x
,old_x
和x
,而是块等待上了锁new_x
。
客户1:
UNLOCK TABLES ;
该语句释放对x
和的写锁new_x
。对于x
,唯一未决的请求是客户端2发出的,因此客户端2获取其锁,执行插入操作,然后释放该锁。对于new_x
,唯一待处理的请求是由客户端3进行的,客户端3被允许获取该锁(以及上的锁old_x
)。x
在Client 2插入完成并释放其锁定之前,重命名操作仍然会阻止锁定。然后,客户端3获取上的锁x
,执行重命名,然后释放其锁。
在这种情况下,锁定获取顺序导致在INSERT
之前执行RENAME TABLE
。将x
其插入到时原来是x
,现在更名为old_x
通过重命名操作:
mysql>SELECT *FROM x; Empty set (0.01 sec) mysql>SELECT *FROM old_x; +------ + | i | +------ + | 1 | +------ +
如前例所示,如果并发语句中锁获取的顺序对应用程序的操作结果有所不同,则可以调整表名以影响锁获取的顺序。
根据需要,将元数据锁扩展到由外键约束关联的表,以防止冲突的DML和DDL操作在相关表上并发执行。在更新父表时,在更新外键元数据的同时对子表进行元数据锁定。外键元数据归子表所有。
元数据锁定释放
为确保事务可序列化,服务器不得允许一个会话对在另一会话中未完成的显式或隐式启动的事务中使用的表执行数据定义语言(DDL)语句。服务器通过获取事务中使用的表上的元数据锁并将这些锁的释放推迟到事务结束之前来实现。表上的元数据锁可防止更改表的结构。这种锁定方法的含义是,在一个事务内的事务正在使用的表不能在事务结束之前由其他会话在DDL语句中使用。
该原理不仅适用于事务表,而且适用于非事务表。假设会话开始使用事务表t
和非事务表的事务nt
,如下所示:
START TRANSACTION ;SELECT *FROM t;SELECT *FROM nt;
服务器在两者上都持有元数据锁t
,nt
直到事务结束。如果另一个会话尝试在任一表上执行DDL或写锁定操作,它将阻塞,直到在事务结束时释放元数据锁定为止。例如,如果第二个会话尝试执行以下任何操作,则它将阻塞:
DROP TABLE t;ALTER TABLE t ...;DROP TABLE nt;ALTER TABLE nt ...;LOCK TABLE t ...WRITE ;
相同的行为适用于LOCK TABLES ... READ
。也就是说,更新该表(事务性或非事务性)的显式或隐式启动的事务将LOCK TABLES ... READ
对该表进行阻塞和阻塞。
如果服务器获取语法上有效但在执行过程中失败的语句的元数据锁,则它不会尽早释放该锁。锁定释放仍被推迟到事务结束,因为失败的语句被写入二进制日志,并且锁定保护日志的一致性。
在自动提交模式下,每个语句实际上是一个完整的事务,因此为该语句获取的元数据锁仅保留到该语句的末尾。
PREPARE
准备好语句后,即使在多语句事务中进行准备,也会释放语句期间获取的元数据锁。
从MySQL 8.0.13开始,对于处于PREPARED
状态的XA事务,将在客户端断开连接和服务器重新启动之间维护元数据锁定,直到执行XA COMMIT
或为止XA ROLLBACK
。