MySQL内核月报 2014.11-MySQL· 5.7优化·Metadata Lock子系统的优化

本文详细探讨了MySQL中MDL锁的历史背景及其引入原因,并深入分析了从5.5到5.7版本中MDL锁的优化过程。重点介绍了Facebook和Percona Server提出的解决方案,以及MySQL 5.7中对MDL子系统的重大改进。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

引入MDL锁的目的,最初是为了解决著名的bug#989,在MySQL 5.1及之前的版本,事务执行过程中并不维护涉及到的所有表的Metatdata 锁,极易出现复制中断,例如如下执行序列:

Session 1: BEGIN;
Session 1: INSERT INTO t1 VALUES (1);
Session 2: Drop table t1; --------SQL写入BINLOG
Session 1: COMMIT; -----事务写入BINLOG

在备库重放 binlog时,会先执行DROP TABLE,再INSERT数据,从而导致复制中断。


在MySQL 5.5版本里,引入了MDL, 在事务过程中涉及到的所有表的MDL锁,直到事务结束才释放。这意味着上述序列的DROP TABLE 操作将被Session 1阻塞住直到其提交。


不过用过5.5的人都知道,MDL实在是个让人讨厌的东西,相信不少人肯定遇到过在使用mysqldump做逻辑备份时,由于需要执行FLUSH TABLES WITH READ LOCK (以下用FTWRL缩写代替)来获取全局GLOBAL的MDL锁,因此经常可以看到“wait for global read lock”之类的信息。如果备库存在大查询,或者复制线程正在执行比较漫长的DDL,并且FTWRL被block住,那么随后的QUERY都会被block住,导致业务不可用引发故障。


为了解决这个问题,Facebook为MySQL增加新的接口替换掉FTWRL 只创建一个read view ,并返回与read view一致的binlog位点;另外Percona Server也实现了一种类似的办法来绕过FTWRL,具体点击文档连接以及percona的博客,不展开阐述。


MDL解决了bug#989,却引入了一个新的热点,所有的MDL锁对象被维护在一个hash对象中;对于热点,最正常的想法当然是对其进行分区来分散热点,不过这也是Facebook的大神Mark Callaghan在report了bug#66473后才加入的,当时Mark观察到MDL_map::mutex的锁竞争非常高,进而推动官方改变。因此在MySQL 5.6.8及之后的版本中,引入了新参数metadata_locks_hash_instances来控制对mdl hash的分区数(Rev:4350);


不过故事还没结束,后面的测试又发现哈希函数有问题,类似somedb.someprefix1….somedb.someprefix8的hash key值相同,都被hash到同一个桶下面了,相当于hash分区没生效。这属于hash算法的问题,喜欢考古的同学可以阅读下bug#66473后面Dmitry Lenev的分析。


Mark进一步的测试发现Innodb的hash计算算法比my_hash_sort_bin要更高效, Oracle的开发人员重开了个bug#68487来跟踪该问题,并在MySQL5.6.15对hash key计算函数进行优化,包括fix 上面说的hash计算问题(Rev:5459),使用MurmurHash3算法来计算mdl key的hash值。


MySQL 5.7 对MDL锁的优化

在MySQL 5.7里对MDL子系统做了更为彻底的优化。主要从以下几点出发:


第一,尽管对MDL HASH进行了分区,但由于是以表名+库名的方式作为key值进行分区,如果查询或者DML都集中在同一张表上,就会hash到相同的分区,引起明显的MDL HASH上的锁竞争

针对这一点,引入了LOCK-FREE的HASH来存储MDL_lock,LF_HASH无锁算法基于论文"Split-Ordered Lists: Lock-Free Extensible Hash Tables",实现还比较复杂。 注:实际上LF_HASH很早就被应用于Performance Schema,算是比较成熟的代码模块。

由于引入了LF_HASH,MDL HASH分区特性自然直接被废除了 。

对应WL#7305, PATCH(Rev:7249)


第二,从广泛使用的实际场景来看,DML/SELECT相比DDL等高级别MDL锁类型,是更为普遍的,因此可以针对性的降低DML和SELECT操作的MDL开销。

为了实现对DML/SELECT的快速加锁,使用了类似LOCK-WORD的加锁方式,称之为FAST-PATH,如果FAST-PATH加锁失败,则走SLOW-PATH来进行加锁。

每个MDL锁对象(MDL_lock)都维持了一个long long类型的状态值来标示当前的加锁状态,变量名为MDL_lock::m_fast_path_state 举个简单的例子:(初始在sbtest1表上对应MDL_lock::m_fast_path_state值为0)

Session 1: BEGIN;
Session 1: SELECT * FROM sbtest1 WHERE id =1; //m_fast_path_state = 1048576, MDL ticket 不加MDL_lock::m_granted队列
Session 2: BEGIN;
Session 2: SELECT * FROM sbtest1 WHERE id =2; //m_fast_path_state=1048576+1048576=2097152,同上,走FAST PATH
Session 3: ALTER TABLE sbtest1 ENGINE = INNODB; //DDL请求加的MDL_SHARED_UPGRADABLE类型锁被视为unobtrusive lock,可以认为这个是比上述SQL的MDL锁级别更高的锁,并且不相容,因此被强制走slow path。而slow path是需要加MDL_lock::m_rwlock的写锁。m_fast_path_state = m_fast_path_state | MDL_lock::HAS_SLOW_PATH | MDL_lock::HAS_OBTRUSIVE
注:DDL还会获得库级别的意向排他MDL锁或者表级别的共享可升级锁,但为了表述方便,这里直接忽略了,只考虑涉及的同一个MDL_lock锁对象。
Session 4: SELECT * FROM sbtest1 WHERE id =3; // 检查m_fast_path_state &HAS_OBTRUSIVE,如果DDL还没跑完,就会走slow path。

从上面的描述可以看出,MDL子系统显式的对锁类型进行了区分(OBTRUSIVE or UNOBTRUSIVE),存储在数组矩阵m_unobtrusive_lock_increment。 因此对于相容类型的MDL锁类型,例如DML/SELECT,加锁操作几乎没有任何读写锁或MUTEX开销。

对应WL#7304WL#7306 , PATCH(Rev:7067,Rev:7129)(Rev:7586)


第三,由于引入了MDL锁,实际上早期版本用于控制Server和引擎层表级并发的THR_LOCK 对于Innodb而言已经有些冗余了,因此Innodb表完全可以忽略这部分的开销。

不过在已有的逻辑中,Innodb依然依赖THR_LOCK来实现LOCK TABLE tbname READ,因此增加了新的MDL锁类型来代替这种实现。

实际上代码的大部分修改都是为了处理新的MDL类型,Innodb的改动只有几行代码。

对应WL#6671,PATCH(Rev:8232)


第四,Server层的用户锁(通过GET_LOCK函数获取)使用MDL来重新实现。

用户可以通过GET_LOCK()来同时获取多个用户锁,同时由于使用MDL来实现,可以借助MDL子系统实现死锁的检测。

注意由于该变化,导致用户锁的命名必须小于64字节,这是受MDL子系统的限制导致。

对应WL#1159, PATCH(Rev:8356)


ql-community-server-5.7.44-1.el7.x86_64 需要 --> 正在处理依赖关系 mysql-community-client(x86-64) >= 5.7.9,它被软件包 mysql-community-server-5.7.44-1.el7.x86_64 需要 --> 正在检查事务 ---> 软件包 mysql-community-client.x86_64.0.5.7.44-1.el7 将被 安装 --> 正在处理依赖关系 mysql-community-libs(x86-64) >= 5.7.9,它被软件包 mysql-community-client-5.7.44-1.el7.x86_64 需要 ---> 软件包 mysql-community-common.x86_64.0.5.7.44-1.el7 将被 安装 --> 正在检查事务 ---> 软件包 mariadb-libs.x86_64.1.5.5.60-1.el7_5 将被 取代 --> 正在处理依赖关系 libmysqlclient.so.18()(64bit),它被软件包 2:postfix-2.10.1-7.el7.x86_64 需要 --> 正在处理依赖关系 libmysqlclient.so.18(libmysqlclient_18)(64bit),它被软件包 2:postfix-2.10.1-7.el7.x86_64 需要 ---> 软件包 mysql-community-libs.x86_64.0.5.7.44-1.el7 将被 舍弃 --> 正在检查事务 ---> 软件包 mysql-community-libs-compat.x86_64.0.5.7.44-1.el7 将被 舍弃 ---> 软件包 postfix.x86_64.2.2.10.1-7.el7 将被 升级 ---> 软件包 postfix.x86_64.2.2.10.1-9.el7 将被 更新 --> 解决依赖关系完成 依赖关系解决 ================================================================================== Package 架构 版本 源 大小 ================================================================================== 正在安装: mysql-community-libs x86_64 5.7.44-1.el7 mysql57-community 3.0 M 替换 mariadb-libs.x86_64 1:5.5.60-1.el7_5 mysql-community-libs-compat x86_64 5.7.44-1.el7 mysql57-community 1.2 M 替换 mariadb-libs.x86_64 1:5.5.60-1.el7_5 mysql-community-server x86_64 5.7.44-1.el7 mysql57-community 184 M 为依赖而安装: mysql-community-client x86_64 5.7.44-1.el7 mysql57-community 31 M mysql-community-common x86_64 5.7.44-1.el7 mysql57-community 313 k 为依赖而更新: postfix x86_64 2:2.10.1-9.el7 base 2.4 M 事务概要 ================================================================================== 安装 3 软件包 (+2 依赖软件包) 升级 ( 1 依赖软件包) 总下载量:222 M Is this ok [y/d/N]: y Downloading packages: No Presto metadata available for base 警告:/var/cache/yum/x86_64/7/mysql57-community/packag
最新发布
03-27
<think>好的,我需要解决用户在安装MySQL社区服务器5.7.44及其依赖项时遇到的公钥问题。用户提到安装过程中出现“公钥尚未安装”的错误,特别是针对mysql-community-libs-compat-5.7.41-1.el7.x86_64.rpm和mysql-community-libs-5.7.44-1.el7.x86_64.rpm的问题。根据引用[2]、[3]、[4],解决方案是重新导入MySQL的GPG密钥,使用rpm --import命令导入2022年的密钥,然后再次尝试安装。 首先,用户可能需要确认系统是否已经正确配置了MySQL的YUM仓库。如果没有,需要先添加MySQL的官方仓库。然后,公钥错误通常是因为本地没有正确的GPG密钥来验证软件包,所以导入新的密钥是关键步骤。根据引用[4],即使是在2023年,导入2022年的密钥仍然有效,因此需要执行rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022。 接下来,用户需要运行yum install命令来安装mysql-community-server,这会自动处理依赖项,包括client和libs。不过,可能需要确保所有依赖包都来自同一仓库,并且版本兼容。如果还有其他依赖问题,可能需要手动安装或更新仓库配置。 另外,用户可能需要注意CentOS7的兼容性问题,确认MySQL 5.7是否仍支持该版本,以及是否存在其他系统依赖需要满足。最后,安装完成后,建议进行基本的安全配置,如运行mysql_secure_installation,确保MySQL实例的安全性。 总结步骤应该是:添加仓库(如有必要)、导入GPG密钥、安装服务器包并处理依赖,最后进行配置。需要确保所有步骤按照顺序执行,并且命令正确无误,以避免常见的安装错误。</think>### 逐步解决方案 #### 1. 更新系统并清理缓存(可选但建议) ```bash yum clean all yum makecache ``` #### 2. 添加MySQL官方YUM仓库 *若已配置可跳过此步* ```bash # 下载MySQL官方仓库配置 wget https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm # 安装仓库 rpm -ivh mysql57-community-release-el7-11.noarch.rpm ``` #### 3. 导入最新GPG密钥(关键步骤) ```bash rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 # 必须使用2022年密钥[^4] ``` #### 4. 安装mysql-community-server(自动解决依赖) ```bash yum install -y mysql-community-server-5.7.44 ``` 此命令会自动安装: - mysql-community-client-5.7.44 - mysql-community-libs-5.7.44 - mysql-community-common-5.7.44 - 其他依赖组件 #### 5. 验证安装 ```bash mysqld --version # 应显示5.7.44版本 systemctl start mysqld systemctl status mysqld ``` #### 6. 获取初始密码 ```bash grep 'temporary password' /var/log/mysqld.log ``` #### 7. 安全配置(可选) ```bash mysql_secure_installation ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值