写点什么

如何迅速并识别处理 MDL 锁阻塞问题

  • 2025-01-22
    广东
  • 本文字数:4243 字

    阅读完需:约 14 分钟

如何迅速并识别处理MDL锁阻塞问题

摘要:TaurusDB 推出 MDL 锁视图功能,帮助用户迅速识别并处理 MDL 锁阻塞问题,从而有效减少对业务的负面影响,提升数据库管理效率。

 

本文分享自华为云社区《【华为云MySQL技术专栏】TaurusDB MDL锁视图》,作者:GaussDB 数据库。


一、背景

数据库中的元数据锁(MDL ,Metadata Lock),用来保护表元数据信息的一致性。用户对表进行读写操作或结构变更时,系统会添加不同类型的 MDL 锁。当客户业务设计不合理,有部分事务长时间持有 MDL 锁时,可能会阻塞其他会话获取相应的 MDL 锁。此时,用户使用执行‘SHOW PROCESSLIST’命令,通常会看到多个会话处于 “Waiting for metadata lock” 状态。但由于无法明确各个会话 ID 之间的关联,往往无法快速找到导致大量 MDL 锁等待的根源,使得用户不得不盲目地 Kill 大量可疑的会话,甚至直接重启实例来快速恢复业务,这种做法无疑增加了解决问题的成本,对业务产生较大影响。


因此,自社区 MySQL 5.7 版本之后,在 PERFORMANCE_SCHEMA 库中新增了 METADATA_LOCKS 表,用于记录系统中 MDL 锁的状态信息,但是需要客户启用 Performance Schema 性能分析监控插件。启用后,Performance Schema 会收集大量的性能数据,包括 SQL 语句执行情况和实例内的锁状态信息等,这会对 MySQL 实例的性能产生一定的负担,尤其在高并发的生产环境中,性能开销更为明显。


鉴于此,TaurusDB 推出 MDL 锁视图功能,帮助用户迅速识别并处理 MDL 锁阻塞问题,从而有效减少对业务的负面影响,提升数据库管理效率。


二、MDL 锁阻塞场景分析

我们以表 1 中的 MDL 锁阻塞案例来介绍 MDL 锁视图的使用场景


表 1 MDL 锁阻塞案例


由于 SESSION 3 存在一个长事务未提交,会一直持有 t2 表的 MDL_SHARED_READ(SR)类型锁。当 SESSION 4 对表 t2 执行 TRUNCATE 操作时,需要获取 MDL-EXCLUSIVE(X)锁,但由于 MDL 锁类型和 SR 锁不兼容而被阻塞。


随后,SESSION 5 的 DML 操作在添加 SR 类型的锁时,发现 MDL 锁等待队列中有比 SR 类型的锁优先级更高的 X 锁在等待,所以 SESSION 5 的 SR 锁请求也会处于等待状态,详细原因可参考《【华为云MySQL技术专栏】TaurusDB新特性解读:非阻塞DDL》。


用户发现 DDL 和 DML 操作都被阻塞后,执行 SHOW PROCESSLIST 查看原因。在 SHOW PROCESSLIST 的信息中,只能看到如图 1 中的结果:


图 1 SHOW PROCESSLIST 结果


  • SESSION 4 执行 TRUNCATE 操作时,被其他 SESSION 持有的 table metadata lock 阻塞;

  • SESSION 5 执行 SELECT 操作时,也同样被阻塞;

  • 无法确定哪个会话(2 或 3?)阻塞了 SESSION 4 和 SESSION 5;


此时,如果业务盲目地去 kill 其他会话(2 或 3),可能会影响其他不相关的业务,从而加大问题处理的成本。在实际的生产业务中,可能有更多的会话,用户从成百上千的会话信息中几乎无法找到导致 MDL 锁等待的根源,只能盲目地 Kill 大量的会话或者重启实例来快速恢复。而且用户在事后也无法定位到根因,从源头杜绝此类问题的再次发生。刚好,TaurusDB 的 MDL 锁视图功能在这个时候就可以发挥作用。


三、MDL 锁视图介绍

TaurusDB 的 MDL 锁视图以系统表的形式呈现,该表位于 INFORMATION_SCHEMA 库下,表名为 METADATA_LOCK_INFO。其中每一行的信息表示一个会话持有或正在等待的 MDL 锁信息。

每个字段的具体含义,如表 2 所示:


表 2 MDL 锁视图表字段含义


此表的查询结果中,同一个会话可能持有多行 MDL 锁的相关信息。主要有以下几方面的原因:


1)当执行涉及多张表的连表查询时,会给每一个表添加 MDL_SHARED_READ 模式的 MDL 锁。

2)事务级别的 MDL 锁,只有在事务结束时才会释放。因此,当一个事务涉及多张表的 DML 操作时,这个会话会同时持有多个 MDL 锁,直到事务结束。

3)在 DDL 语句的执行过程中,需要添加多种类型的 MDL 锁。例如,在添加列的 DDL 语句中,可能会添加 Backup lock,Global read lock,Schema metadata lock,Table metadata lock,并且在不同阶段对锁的模式进行升/降级。


四、MDL 锁视图使用方法

针对表格 1 中的 MDL 锁阻塞场景,用户可以执行以下 SQL 语句:SELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;


再结合 SHOW PROCESSLIST 的输出结果,快速定位到问题根因。


在图 2 元数据锁视图的结果信息中,我们应该从 PENDING 状态的会话开始入手。


图 2 元数据锁视图结果


根据 MDL 锁等待的 TABLE_SCHEMA 和 TABLE_NAME 信息,找到其他 THREAD ID 下,具有相同库名和表名且状态是 GRANTED 的 MDL 锁信息。这个 THREAD ID 即是造成锁等待的会话。


基于以上原理,我们可以看出:

  • 会话 4 在等待获取 test 库 t2 表的 MDL_EXCLUSIVE 模式的元数据锁;

  • 会话 5 在等待获取 test 库 t2 表的 MDL_SHARED_READ 模式的元数据锁;

  • 会话 3 持有 test 库 t2 表 t2 的 MDL 锁,该 MDL 锁为事务级别,只要 session 3 的事务不提交,session 4 和 5 便会一直阻塞。


所以,通过 MDL 锁视图,我们只需要在会话 3 中执行 Rollback 或者 Commit,便可以让业务继续运行。虽然 MDL 锁视图可以帮助定位到导致大量 MDL 锁等待的根源,但是当会话较多时,表中很多不相关的 MDL 锁信息查看起来也会耗费大量时间,这里我们提供一个可以快速查找到阻塞会话的 SQL。在发生问题时,只要执行一下这个语句,就可以迅速找到需要 kill 的会话。

SELECT f.processlist_id, p.Info AS sql_infoFROM (    SELECT DISTINCT c.blocking_processlist_id AS processlist_id    FROM (        SELECT DISTINCT b.THREAD_ID AS blocking_processlist_id        FROM information_schema.metadata_lock_info a            JOIN information_schema.metadata_lock_info b            ON a.TABLE_SCHEMA = b.TABLE_SCHEMA                AND a.TABLE_NAME = b.TABLE_NAME                AND a.lock_status = 'PENDING'                AND b.lock_status = 'GRANTED'                AND a.THREAD_ID <> b.THREAD_ID    ) c    WHERE c.blocking_processlist_id NOT IN (        SELECT DISTINCT d.THREAD_ID AS blocked_processlist_id        FROM information_schema.metadata_lock_info d            JOIN information_schema.metadata_lock_info e            ON d.TABLE_SCHEMA = e.TABLE_SCHEMA                AND d.TABLE_NAME = e.TABLE_NAME                AND d.lock_status = 'PENDING'                AND e.lock_status = 'GRANTED'                AND d.THREAD_ID <> e.THREAD_ID    )) f    JOIN information_schema.processlist p ON processlist_id = p.Id;
复制代码

五、原理解析

基于对《【华为云MySQL技术专栏】TaurusDB MDL实现机制解析》中 MDL 锁相关数据结构和 MDL 锁添加、释放流程的分析,TaurusDB 在 INFORMATION_SCHEMA 库下添加了 MDL 锁视图。


在 TaurusDB 的内部架构中,每一个用户连接有一个 THD(Thread Handler,线程处理器)对象,这些 THD 对象统一由 Global_THD_manager 结构体进行管理。如图 3 所示,每个 THD 对象关联了一个 MDL_context 实例,这个实例提供了线程级的 MDL 锁操作接口,包括申请、释放 MDL 锁以及锁的升降级。


图 3. MDL 锁基本概念图


其中,THD 中的 MDL_context 也有两个变量m_ticket_storem_waiting_for 用于维护会话持有和等待的 MDL 锁的信息。


  1. m_ticket_store:用来存储当前线程获取的所有MDL_ticket。为了提升搜索效率,根据MDL锁的持续时间(语句执行时间段,事务执行时间段和显示指定时间段)将其划分为三个链表,在需要获取MDL锁前,会先在这些链表内查询是否已经获取到了相同的或这是更强类型的MDL锁,如果搜索不到继续获取MDL锁。

  2. m_waiting_for:用来存储当前线程正在等待的 MDL 锁,一个线程同一时刻只能等待一种类型的 MDL 锁。


每一个链表中的 MDL_ticket 对象是每个线程已经获取到的 MDL 锁或者是要请求的 MDL 锁的详细信息,其结构体内包含 MDL 锁的模式(enum_mdl_type)、MDL 锁的持续时间(enum_mdl_duration)和 MDL 锁对象(MDL_lock)。其中,MDL_lock 由 MDL_key 唯一标识。MDL_key 是一个三元组,由命名空间(enum_mdl_namespace)、库名和对象名组成。


因此,在用户查询 MDL 锁视图时,实现流程如图 4 所示,只需要遍历所有会话 THD 的 MDL_context 对象,根据其 m_ticket_store 链表中的每一个 MDL_ticket 对象构造出处于 GRANTED 状态的 MDL 锁信息。同理,通过 m_waiting_for 对象获取到处于 PENDING 状态的 MDL 锁信息。最后,将结果集返回给客户端展示即可。


图 4 MDL 锁视图实现流程

 

其中,i_s_metadata_lock_info_fill_table 和 List_THD_MDL_tickets 为核心函数,用来实现遍历 Global_THD_manager 中的 THD 并从其 MDL_context 中构造当前会话的持有和等待的 MDL 锁信息。

i_s_metadata_lock_info_fill_table() {    //对系统中每一个THD执行List_THD_MDL_tickets函数找到持有和等待的MDL锁    Global_THD_manager::get_instance()->do_for_all_thd_copy(List_THD_MDL_tickets)}
List_THD_MDL_tickets() { // 获取当前THD的MDL_context: MDL_context &mdl_ctx = inspect_thd->mdl_context; // 获取当前THD持有的MDL锁 const MDL_ticket_store &m_ticket_store = mdl_ctx.get_mdl_ticket_store(); // 遍历每个m_ticket_store的三个作用范围内的MDL_ticket for (int i = 0; i < MDL_DURATION_END; i++) { MDL_ticket_store::List_iterator it = m_ticket_store.list_iterator(duration); lock_extras.duration = duration; while ((ticket = it++)) { enum_mdl_duration duration = (enum_mdl_duration)(i); // 根据MDL_ticket中的信息填充到MDL锁视图中 fill_row_callback(ticket, &lock_extras, args); } } // 获取当前THD等待的MDL锁 ticket = dynamic_cast<const MDL_ticket *>(mdl_ctx.get_m_waiting_for()); if (ticket != nullptr) { // 填充MDL锁的额外信息,PENDING状态和作用范围 lock_extras.lock_status = MDL_ticket::PENDING; lock_extras.duration = ticket->get_duration(); // 根据MDL_ticket中的信息填充到MDL锁视图中 fill_row_callback(ticket, &lock_extras, args); }}
复制代码


六、总结

TaurusDB 的 MDL 锁视图 INFORMATION_SCHEMA.METADATA_LOCK_INFO,可以在不开启 Performance Schema 性能监控插件时,获取到实例系统中所有 MDL 锁的持有和等待状态。熟练的使用 MDL 锁视图,可以帮助用户快速地定位和分析导致大量 MDL 锁等待的根本原因,还能够根据分析结果进行迅速有效的处理,解决 MDL 长时间锁等待问题,并且不会因为依赖 Performance Schema 性能监控插件而对系统性能产生任何影响。

 

关注“GaussDB 数据库”公众号,了解更多动态


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
如何迅速并识别处理MDL锁阻塞问题_MySQL_华为云开发者联盟_InfoQ写作社区