MySQL使用元数据锁定来管理对数据库对象的并发访问,并确保数据一致性。元数据锁定不仅适用于表,还适用于模式、存储程序(过程、函数、触发器、计划事件)、表空间、使用GET_LOCK ()
函数(见第12.15节,“锁定功能”中描述的锁定服务获取的锁第5.6.9.1节“锁定服务”.
性能模式metadata_locks
表公开元数据锁信息,这对于查看哪些会话持有锁、哪些会话阻塞等待锁等等非常有用。有关详细信息,请参见第27.12.13.3节,“metadata_locks表”.
元数据锁定确实涉及一些开销,这些开销会随着查询量的增加而增加。当多个查询试图访问相同的对象时,元数据争用就会增加。
元数据锁定不是表定义缓存的替代品,它的互斥锁和锁与表定义缓存不同LOCK_open
互斥锁。下面的讨论提供了一些关于元数据锁定如何工作的信息。
如果一个给定的锁有多个等待者,那么优先级最高的锁请求将首先得到满足,与max_write_lock_count
系统变量。写锁请求的优先级高于读锁请求。然而,如果max_write_lock_count
如果将读锁请求设置为某个较低的值(例如10),则读锁请求可能优先于挂起的写锁请求,如果已经传递了读锁请求,而优先于10个写锁请求。通常这种行为不会发生,因为max_write_lock_count
默认值非常大。
语句逐个获取元数据锁,而不是同时获取,并在此过程中执行死锁检测。
DML语句通常按照语句中提到表的顺序获取锁。
DDL语句,锁表
,以及其他类似的语句试图减少并发DDL语句之间可能的死锁数量,方法是按照名称顺序获取显式命名表上的锁。对于隐式使用的表(例如外键关系中的表,也必须被锁定),可能以不同的顺序获得锁。
例如,重命名表
是一个DDL语句,它按名称顺序获取锁:
这
重命名表
声明中重命名tbla
转到别的东西,并重命名tblc
来tbla
:重命名表tbla TO tbld, tblc TO tbla
该语句以on的顺序获取元数据锁
tbla
,tblc
,tbld
(因为tbld
遵循tblc
名字顺序):这个稍有不同的语句也会重命名
tbla
转到别的东西,并重命名tblc
来tbla
:重命名表tbla TO tblb, tblc TO tbla
在本例中,该语句按照on的顺序获取元数据锁
tbla
,tblb
,tblc
(因为tblb
之前tblc
名字顺序):
两个语句都获得锁tbla
而且tblc
,但不同的是,其余表名上的锁是在之前还是之后获得的tblc
.
当多个事务并发执行时,元数据锁获取顺序会影响操作结果,如下面的示例所示。
从两个表开始x
而且x_new
它们有相同的结构。三个客户端发出包含以下表的语句:
客户端1:
锁表x写,x_new写
该语句按名称顺序请求和获取写锁x
而且x_new
.
客户2:
INSERT INTO x VALUES(1);
语句请求和阻塞等待写锁x
.
客户端3:
重命名表为x_old, x_new TO x
该语句按名称顺序请求排它锁x
,x_new
,x_old
,但阻塞等待锁上x
.
客户端1:
打开表;
该语句释放上的写锁x
而且x_new
.的独占锁请求x
由于Client 3的写锁请求的优先级高于Client 2的写锁请求,所以Client 3获得自己的锁x
,然后也在x_new
而且x_old
,执行重命名,并释放其锁。然后客户机2获得它的锁x
,执行插入,并释放锁。
锁获取顺序导致重命名表
执行前插入
.的x
插入发生在其中的是已命名的表x_new
客户端2发出插入并被重命名为x
通过客户端3:
SELECT * FROM x;+------+ | 我 | +------+ | 1 | +------+ 从x_old mysql > SELECT *;空集(0.01秒)
现在从命名表开始x
而且new_x
它们有相同的结构。同样,有三个客户端发出涉及以下表的语句:
客户端1:
锁表x写,new_x写;
该语句按名称顺序请求和获取写锁new_x
而且x
.
客户2:
INSERT INTO x VALUES(1);
语句请求和阻塞等待写锁x
.
客户端3:
重命名表x为old_x, new_x为x
该语句按名称顺序请求排它锁new_x
,old_x
,x
,但阻塞等待锁上new_x
.
客户端1:
打开表;
该语句释放上的写锁x
而且new_x
.为x
在这种情况下,唯一挂起的请求是客户机2,因此客户机2获得它的锁,执行插入,然后释放锁。为new_x
,唯一待处理的请求是客户端3,它被允许获得该锁(以及锁上的锁)old_x
).对于打开的锁,重命名操作仍然阻塞x
直到Client 2插入完成并释放锁。然后客户端3获得锁x
,执行重命名,并释放锁。
在这种情况下,锁定获取顺序将导致插入
执行前重命名表
.的x
插入发生的是原始的x
,现已改名为old_x
通过rename操作:
SELECT * FROM x;SELECT * FROM old_x;+------+ |我| +------+ | 1 | +------+
如果并发语句中的锁获取顺序对应用程序的操作结果有影响,就像前面的例子中那样,您可以调整表名来影响锁获取的顺序。
元数据锁在必要时扩展到由外键约束相关的表,以防止冲突的DML和DDL操作在相关表上并发执行。在更新父表时,在更新外键元数据时在子表上执行元数据锁。外键元数据由子表拥有。
为了确保事务的可序列化性,服务器必须不允许一个会话在另一个会话中未完成的显式或隐式启动的事务中使用的表上执行数据定义语言(DDL)语句。服务器通过获取事务中使用的表上的元数据锁并将这些锁的释放推迟到事务结束来实现这一点。表上的元数据锁可以防止对表结构的更改。这种锁定方法意味着,在事务结束之前,由一个会话中的事务使用的表不能被其他会话在DDL语句中使用。
这个原则不仅适用于事务性表,也适用于非事务性表。假设一个会话开始一个使用事务表的事务t
和nontransactional表nt
如下:
开始事务;SELECT * FROM t;SELECT * FROM nt;
服务器在两者上都持有元数据锁t
而且nt
直到事务结束。如果另一个会话试图在任何一个表上执行DDL或写锁操作,它将阻塞,直到事务端释放元数据锁。例如,如果第二个会话尝试以下任何操作,它就会阻塞:
删除表t;修改表t…删除表元;修改表锁表t…写;
同样的行为也适用于The锁表……读
.也就是说,显式或隐式启动的事务更新任何表(事务性或非事务性)块,并被阻塞锁表……读
那张桌子。
如果服务器为语法有效但在执行过程中失败的语句获取元数据锁,则不会提前释放锁。锁释放仍然延迟到事务结束,因为失败的语句被写入二进制日志,而锁保护日志一致性。
在自动提交模式中,每条语句实际上都是一个完整的事务,因此为语句获取的元数据锁只保存到语句的末尾。
操作期间获取的元数据锁准备
语句一旦准备好,就会被释放,即使准备是在多语句事务中进行的。
在MySQL 8.0.13中,对于XA事务准备
状态时,跨客户端断开连接和服务器重新启动维护元数据锁,直到XA提交
或XA回滚
是执行。