首页 > 数据库 >mysql 表级锁之一元数据锁(meta data lock,MDL)

mysql 表级锁之一元数据锁(meta data lock,MDL)

时间:2023-11-07 15:33:24浏览次数:32  
标签:MDL lock meta DDL schema id metadata

什么是元数据锁(meta data lock,MDL)


在MySQL5.5.3之前,有一个著名的bug#989(bug链接: MySQL Bugs: #989: If DROP TABLE while there’s an active transaction, wrong binlog order),大致如下:

# session1:
BEGIN;
INSERT INTO t ... ;
COMMIT;
 
# session2:
DROP TABLE t;
 
# 如果user1在一个表上有一个活动的事务,然后user2删除这个表,那么user1会提交,那么在binlog中我们有如下内容(binlog记录的操作顺序顺序):
1. 
DROP TABLE t;
 
2.
BEGIN;
INSERT INTO t ... ;
COMMIT;


很显然mysql执行时会先删除表t,然后执行insert 会报1032 error。

再举一个简单的例子,如果你在查询一个表的过程中,另外一个session对该表删除了一个列,那前面的查询到底该显示什么呢?如果在RR隔离级别下,事物中再次执行相同的语句还会和之前结果一致吗?为了防止这种情况,表查询开始MySQL会在表上加一个锁,来防止被别的session修改了表定义,这个锁就叫‘metadata lock’,简称MDL,翻译成中文也叫‘元数据锁’,它是一个表级锁。

什么情况下会添加元数据锁


首先给出答案:mysql所有的增删改查以及ddl都会加mdl锁。

什么是DDL(Data Definition Languages)语句:数据定义语言,这些语句定义了不同的数据段,数据库,表,列,索引等数据库对象。常用的语句关键字主要包括create,drop,alter等。

也就是说,增删改查、数据库的创建、销毁、索引的修改、字段的调整等等操作,都会加一个表级锁——元数据锁(meta data lock,MDL)。

这是不是听起来很恐怖的事情?所有的操作都会加一个表级锁。

但是!不要慌张,请继续往下看。

元数据锁工作原理

在 MySQL 5.5 版本中引入了 MDL(元数据锁),当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。

读写锁大家应该都很清楚吧。

读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。
读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
MDL写锁只有在执行DDL语句的时候才会加,平时我们的增删改查只是加了MDL读锁,MDL读锁之间是可以共享的,并不会出现锁等待的情况。

创建索引导致数据库崩溃


线上某数据库意外发现缺少索引,并且该表的数据量很少,只有几万条记录而已,因此很随意地尝试给该表添加索引。原本预期该表的记录很少,添加索引的耗时应该很短,结果却直接导致该表被锁,所有该表的增删改查操作全部阻塞,继而影响到了线上业务。

发现锁表后,执行show processlist发现大量线程阻塞,状态显示Waiting for table metadata lock。通过命令终止了DDL线程,该表恢复正常。mysql从5.6版本起支持Online DDL,理论上执行DDL语句不会阻塞诸如INSERT、UPDATE、DELETE这类DML操作。

事后排查发现,该表有个持续了很久未提交的事务,正是该事务导致DDL语句执行时锁表。

复现


1、新建表

CREATE TABLE `lock_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` varchar(32) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


2、新建一个会话1,开启事务执行以下命令后不要提交事务

begin;
select * from lock_table limit 1;


3、新建另一个会话2,执行DDL命令,发现DDL语句执行被阻塞

ALTER TABLE `lock_table` ADD INDEX content_index(content);


4、此时表被锁定,再新建一个会话3,执行查询语句发现该操作同样被阻塞住

select id from lock_table limit 1;


5、我们使用SHOW PROCESSLIST; 命令,发现Waiting for table metadata lock,就是元数据锁。

 

6、我们使用以下语句,也会发现metadata_locks

select * from performance_schema.metadata_locks ;


metadata_locks是5.7中被引入,记录了metadata lock的相关信息,包括持有对象、类型、状态等信息。但5.7默认设置是关闭的(8.0默认打开),需要通过下面命令打开设置:

UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES'WHERE NAME = 'wait/lock/metadata/sql/mdl';


如果要永久生效,需要在配置文件中加入如下内容:

[mysqld]performance-schema-instrument='wait/lock/metadata/sql/mdl=ON'


单纯查询这个表无法得出具体的阻塞关系,也无法得知什么语句造成的阻塞,这里要关联另外两个表performance_schema.thread和performance_schema.events_statements_history,thread表可以将线程id和show processlist中id关联,events_statements_history表可以得到事务的历史sql,关联得到一条监控元数据锁的sql语句。

关联后的完整sql如下:

SELECT locked_schema,
locked_table,
locked_type,
waiting_processlist_id,
waiting_age,
waiting_query,
waiting_state,
blocking_processlist_id,
blocking_age,
substring_index(sql_text,"transaction_begin;" ,-1) AS blocking_query,
sql_kill_blocking_connection
FROM 
( 
SELECT 
b.OWNER_THREAD_ID AS granted_thread_id,
a.OBJECT_SCHEMA AS locked_schema,
a.OBJECT_NAME AS locked_table,
"Metadata Lock" AS locked_type,
c.PROCESSLIST_ID AS waiting_processlist_id,
c.PROCESSLIST_TIME AS waiting_age,
c.PROCESSLIST_INFO AS waiting_query,
c.PROCESSLIST_STATE AS waiting_state,
d.PROCESSLIST_ID AS blocking_processlist_id,
d.PROCESSLIST_TIME AS blocking_age,
d.PROCESSLIST_INFO AS blocking_query,
concat('KILL ', d.PROCESSLIST_ID) AS sql_kill_blocking_connection
FROM performance_schema.metadata_locks a JOIN performance_schema.metadata_locks b ON a.OBJECT_SCHEMA = b.OBJECT_SCHEMA AND a.OBJECT_NAME = b.OBJECT_NAME
AND a.lock_status = 'PENDING'
AND b.lock_status = 'GRANTED'
AND a.OWNER_THREAD_ID <> b.OWNER_THREAD_ID
AND a.lock_type = 'EXCLUSIVE'
JOIN performance_schema.threads c ON a.OWNER_THREAD_ID = c.THREAD_ID JOIN performance_schema.threads d ON b.OWNER_THREAD_ID = d.THREAD_ID
) t1,
(
SELECT thread_id, group_concat( CASE WHEN EVENT_NAME = 'statement/sql/begin' THEN "transaction_begin" ELSE sql_text END ORDER BY event_id SEPARATOR ";" ) AS sql_text
FROM
performance_schema.events_statements_history
GROUP BY thread_id
) t2
WHERE t1.granted_thread_id = t2.thread_id \G
 
*************************** 1. row ***************************
               locked_schema: report-vision
                locked_table: students
                 locked_type: Metadata Lock
      waiting_processlist_id: 13
                 waiting_age: 938
               waiting_query: alter table students add column col1 int
               waiting_state: Waiting for table metadata lock
     blocking_processlist_id: 12
                blocking_age: 947
              blocking_query: SELECT * from students where id=1
sql_kill_blocking_connection: KILL 12
1 row in set, 2 warnings (0.01 sec)


根据显示结果,processlist_id为12的线程阻塞了13的线程,我们需要kill 12即可解锁。

实际上,MySQL也提供了一个类似的视图来解决metadata lock问题,视图名称为sys.schema_table_lock_waits,但此视图查询结果有bug,不是很准确,建议大家还是参考上面sql。

7、我们使用语句查看一下当前事务,发现会话1长事务一直没有关闭,导致了元数据锁,导致后续所有的增删改查请求等待,数据库崩溃。

SELECT * FROM information_schema.INNODB_TRX;


1.
DDL导致锁表的原因
To ensure transaction serializability, the server must not permit one session to perform a data definition language (DDL) statement on a table that is used in an uncompleted explicitly or implicitly started transaction in another session. The server achieves this by acquiring metadata locks on tables used within a transaction and deferring release of those locks until the transaction ends. A metadata lock on a table prevents changes to the table’s structure. This locking approach has the implication that a table that is being used by a transaction within one session cannot be used in DDL statements by other sessions until the transaction ends.

mysql官方文档metadata-locking一节中指出,为了确保事务可序列化,mysql不允许一个会话对在另一会话中未完成的显式或隐式启动的事务中使用的表执行DDL语句。服务器通过获取事务中使用的表上的元数据锁并将这些锁的释放推迟到事务结束之前来实现。表上的元数据锁可防止更改表的结构。这种锁定方法的含义是,一个会话中事务正在使用的表在事务结束之前不能被其他会话在DDL语句中使用。

mysql对申请MDL锁的操作会形成一个队列,队列中写锁获取优先级高于读锁。一旦出现写锁等待,不但当前操作会被阻塞,同时还会阻塞后续该表的所有操作。由上可知当事务一旦申请到MDL锁后,直到事务执行完才会将锁释放,当长事物或未提交的事务未提交完成时,执行DDL语句会等待MDL排他锁而阻塞,继而阻塞该表的后续其他操作。

MySQL Online DDL的改进与应用


prepare阶段:尝试获取MDL排他锁,禁止其他线程读写;
ddl执行阶段:降级成MDL共享锁,允许其他线程读取;
commit阶段:升级成MDL排他锁,禁止其他线程读写;
finish阶段:释放MDL锁;
1、3、4如果没有锁冲突,执行时间非常短。第2步占用了DDL绝大部分时间,这期间这个表可以正常读写数据,是因此称为“online ”。
如果第3步升级为MDL写锁的时候,这个表的MDL锁有其他事务占着,那么这个事务会阻塞,等到可以拿到MDL写锁,而且如果不幸一直拿不到,最后锁超时了,就只好回滚这个DDL操作。
所以,DDL语句只有才开始和结束的时候,才会禁止读和写,在语句执行的时候是可以进行读的。

如何安全地给表添加字段、添加索引


1、生产环境的任何大表或频繁操作的小表,ddl都要非常慎重,最好在业务低峰期执行。

2、设计上要尽可能避免大事务,大事务不仅仅会带来各种锁问题,还会引起复制延迟/回滚空间爆满等各类问题。

3、设置参数 lock_wait_timeout 为较小值,使被阻塞端主动停止。

4、增强监控告警,及时发现 MDL 锁。

5、或许这样操作也是一种好办法:按新结构创建新表 -> 将旧表数据迁移至新表 -> 重命名两个表(三步都通过编写sql语句完成,比手动操作快,第二步的数据迁移操作视情况而定)。过程中最好在没人用的时候操作

6、操作ddl之前,先用以下语句查一下有没有长事务:

SELECT * FROM information_schema.INNODB_TRX;


7、多副本(主从、集群)下可以做热更新。

8、MariaDB 已经合并了 AliSQL 的这个功能,所以这两个开源分支目前都支持 DDL NOWAIT/WAIT n 这个语法:

ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ...
-----------------------------------
©著作权归作者所有:来自51CTO博客作者秃了也弱了的原创作品,请联系作者获取转载授权,否则将追究法律责任
mysql创建索引导致死锁,数据库崩溃,mysql的表级锁之【元数据锁(meta data lock,MDL)】全解
https://blog.51cto.com/u_13540373/5847399

标签:MDL,lock,meta,DDL,schema,id,metadata
From: https://www.cnblogs.com/zhanchenjin/p/17815099.html

相关文章

  • Grafana Error database is locked
    目录......
  • ReentrantLock源码笔记 - 获取锁(JDK 1.8)
    ReentrantLock学习-获取锁(JDK1.8)ReentrantLock提供非公平锁与公平锁两种加锁方式,默认加锁方式为非公平锁。ReentrantLock类的结构为:从图中可以看出,ReentrantLock类包含三个静态内部类:SyncNonfairSyncFairSync其中Sync类继承AbstractQueuedSynchronize(AQS),Nonf......
  • ReentrantLock源码笔记 - 释放锁(JDK 1.8)
    ReentrantLock源码学习-释放锁(unlock)上次谈到了利用ReentrantLock的非公平和公平加锁方式,那么接下来看看释放锁的流程首先调用ReentrantLock的unlock方法publicvoidunlock(){sync.release(1);}然后会调用AbstractQueuedSynchronizer(AQS)的release方法,在这个方法......
  • SQL Server,Could not obtain exclusive lock on database 'model'
    创建SQLServer数据库时出现错误“Couldnotobtainexclusivelockondatabase'model'”尝试以下方法:1.totryreconnectingtothedatabase.2.Restartingtheservice.3.killingthespidholdingthelock. 执行以下SQL语句来查询:select d.name,resource_type,resour......
  • Metasploit windows 调试环境搭建
    Metasploitwindows调试环境搭建安装ruby首先确定metasploit的ruby版本metasploit-framework/.ruby-version3.0.5在https://rubyinstaller.org/downloads/archives/下载对应版本的Ruby+DevkitInstallers(x64),默认配置安装即可。输入ruby-v查看是否安装成功安装gem......
  • Metasploit渗透测试框架的基本使用
    一、Metasploit渗透测试框架介绍(1)基础库metasploit基础库文件位于源码根目录路径下的libraries目录中,包括Rex,framework-core和framework-base三部分。Rex是整个框架所依赖的最基础的一些组件,如包装的网络套接字、网络应用协议客户端与服务端实现、日志子系统、渗透攻击支持例......
  • docker容器内需要执行sudo hwclock --systohc吗
     在Docker容器内部,你通常不需要(也不应该)执行与硬件时钟相关的操作。这有几个原因:1.**隔离性**:Docker容器设计为与宿主机隔离。容器不会(也不应该)直接与硬件交互,包括硬件时钟。2.**依赖宿主机**:容器通常会从宿主机继承时间。如果宿主机的时间设置正确,那么容器的时间也应该是正确......
  • java IO模型:BIO(Blocking I/O)
    BIO属于同步阻塞IO模型。同步阻塞IO模型中,应用程序发起read调用后,会一直阻塞,直到内核把数据拷贝到用户空间。在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。因此,我们需要一种更高效的I/O处理模型来应对更高......
  • 11月3日前端需要学习的知识、自闭合标签、meta标签、div标签
    目录前端需要学习的知识生成的网页类型静态网页动态网页网页的架构c/s架构b/s架构浏览器的特别用法第一种结合python来使用第二种将文件拖入浏览器里面(这就符合渲染了)重点HTML首先!DOCTYPEhtml其次就是html到/html还有就是head到/head的内部最后就是body到/body总结其它的标签......
  • 一文读懂ReentrantLock在多线程编程中的作用和优点
    引言在当今这个数字化时代,软件开发已经离不开多线程编程。但是,多线程编程也带来了一系列复杂性和挑战,其中最关键的一个问题就是线程同步和互斥。为了应对这个问题,Java语言提供了一些工具,其中最强大的工具之一就是ReentrantLock。本文将对ReentrantLock进行深入探讨,介绍它在多线程编......