关于美团:MySQL自治平台建设的内核原理及实践上

33次阅读

共计 14104 个字符,预计需要花费 36 分钟才能阅读完成。

本文整顿自主题分享《美团数据库自治服务平台建设》,系超大规模数据库集群保稳系列的第四篇文章。本文作者在演讲后依据同学们的反馈,补充了很多技术细节,跟演讲(视频)相比,内容更加丰盛。文章分成上、下两篇,上篇将介绍数据库的异样发现跟诊断方面的内容,下篇将介绍内核可观测性建设、全量 SQL、异样解决以及索引优化倡议与 SQL 治理方面的内容。心愿可能对大家有所帮忙或启发。

1 背景 & 指标

MySQL 的故障与 SQL 的性能,是 DBA 跟研发同学每天都须要关注的两个重要问题,它们间接影响着数据库跟业务应用程序的稳定性。而当故障或者 SQL 性能问题产生时,如何疾速发现、剖析以及解决这些问题,使得数据库或者业务零碎疾速复原,是一项比拟大的挑战。

针对此问题,美团数据库自治平台通过多轮的迭代建设,在多个场景下曾经实现了异样的发现、剖析以及解决的端到端能力。本文将跟大家分享一下咱们平台建设的心路历程,同时提供一些教训、教训供同行参考,心愿可能起到“抛砖引玉”的作用。本文次要介绍以下主题:

  • 异样发现 :基于数理统计形式的动静阀值策略,来发现数据库系统的指标异样。
  • 故障剖析 :丰盛欠缺数据库要害信息,来做准确的数据库异样根因剖析;深刻开掘内核价值,来解决根因诊断方面的疑难杂症。
  • 故障解决 :根据异样根因剖析的不同后果,通过自助化或自动化的形式来进行故障的复原解决。
  • 内核可观测性建设 :如何跟数据库内核团队单干,从内核的角度来剖析 SQL 性能问题,通过内核团队大量的内核代码革新,力求将数据库的可观测性跟诊断做到极致。
  • 单 SQL 优化倡议 :通过革新 MySQL 存储引擎,同时联合查问优化来打造基于 Cost 模式的索引优化倡议。
  • 基于 workload 索引优化倡议 :基于整个 DB 或者实例的 Workload 策略的索引优化倡议,为实现数据库的索引自保护提供前置条件。
  • 基于 SQL 生命周期的治理 :实现从 SQL 上线前、执行过程中、执行结束后几个环节,以期实现端到端的慢 SQL 治理。

2 平台演进策略

美团数据库自治平台从下到上总体分为四层,别离为接口与展现、平台性能层,计算与存储、数据采集层,平台的总体架构以及每一层的作用如下:

  • 数据库采集层 :要进行数据库的诊断与剖析,须要依附要害的指标以及 SQL 文本数据,以后在每个数据库实例上部署一个数据采集程序(rds-agent)对立负责采集、上报要害数值指标以及 SQL 文本数据。
  • 数据计算与存储层 :数据采集层上报上来的数据,依靠 Kafka、Flink&Spark 作为数据缓冲,对要害组件进行相干的数据处理,如 SQL 解析、SQL 模版化、数据聚合等操作,再把解决的后果存入 ES、Blade(美团自研的分布式数据库)、Hive 等分布式数据库或者大数据平台,提供给下层的平台性能层应用。
  • 平台性能层 :此层是整个零碎最为重要的局部,因为平台同时服务于 DBA 运维团队及研发团队,所以平台的建设分成了两条路:1)次要面向 DBA 用户,依照可观测性建设、异样发现、故障根因剖析、故障解决几个阶段来进行建设;2)次要面向研发同学,依照 SQL 优化倡议、危险 SQL 的发现、剖析与 SQL 治理等跟 SQL 相干的几个阶段来建设。当然,两者并没有严格界线,这些性能所有的用户都能够同时应用。
  • 接口与展现 :平台性能层提供的外围性能会通过 Portal 来展现,同时为了让平台提供的性能更好地集成在用户本人的零碎中,咱们也通过 OpenAPI 的形式对外提供服务。

3 异样发现

数据库产生异样时须要尽早地发现,能力避免异样一提高放大,防止造成真正的故障。异样发现的次要形式是对数据库或者 OS 的要害数值指标进行监控,相干指标包含 seconds_behind_master、slow_queries、thread_running、system load、Threads_connected 等等,也能够是业务端研发关注的要害指标,如“应用程序拜访数据库的报错数量”、“SQL 执行均匀耗时”等指标来进行监控。如果这些指标短时间内产生比拟大的稳定,那么数据库很可能呈现了一些异样,这就须要及时进行解决。

这些异样如何能力被发现呢?业界个别有基于动态阀值以及动静阀值的两种异样发现策略。前者很简略,如依据专家教训,人工设定 seconds_behind_master 或者 Threads_connected 的告警阀值,超过阀值就认为产生了异样。此形式尽管简略易用,但 OLTP、OLAP 等不同的业务场景,对于雷同指标的敏感度是不一样的,如果所有场景都应用对立的动态阀值来做异样发现,难免会有很多误告。而如果每个场景都去手工去调整,既不灵便,老本又太高,解决方案是基于不同场景的历史时序数据,应用数理统计的形式来别离建模,通过拟合出各自场景的模型来作为异样发现的策略。

3.1 数据分布法则与算法抉择

基于数理统计办法的异样发现,须要依据具体的场景应用特定的模型。一般来说,模型的抉择跟时序数据的散布状态有很大的关系,时序数据的散布并不总是都像正态分布一样都是对称的,而是有些是左偏的,有些是右偏的,当然也有对称散布的。下图就展现典型的三种不同的时序数据分布状态。

针对下面的三种不同时序数据的散布状态,以及每种异样检测算法本身的个性,咱们别离采纳不同的异样检测算法。

对于低偏态高对称散布抉择“相对中位差(MAD)”,中等偏态散布抉择“箱形图(Boxplot)”,高偏态散布抉择“极值实践(EVT)”。没有抉择 3Sigma 的次要起因是:它对异样容忍度较低(建模的时候,如果有乐音等异样点也不会对模型的状态产生很大的影响,则阐明异样容忍度很高),而相对中位差(MAD)从实践上而言具备更好的异样容忍度,所以在数据出现高对称散布时,通过相对中位差代替 3Sigma 来进行检测。

3.2 模型抉择

数据分布跟算法实用场景的剖析之后,对外部的时序数据进行查看,发现数据的法则次要出现漂移、周期和安稳三种状态,对样本先进行时序的漂移(如果检测存在漂移的场景,则须要依据检测取得的漂移点 t 来切割输出时序,应用漂移点后的时序样本作为后续建模流程的输出)。

之后同时进行平稳性剖析(如果输出时序 S 满足平稳性测验,则间接通过箱形图或相对中位差的形式来进行建模)以及周期剖析(存在周期性的状况下,将周期跨度记为 T,将输出时序 S 依据跨度 T 进行切割,针对各个工夫索引 j∈{0,1,⋯,T−1} 所组成的数据桶进行建模流程。不存在周期性的状况下,针对全副输出时序 S 作为数据桶进行建模流程),再对时序数据分布个性进行偏度的计算,最初再依据不同的偏度个性抉择不同的算法模型,具体如下:

在算法确定之后,先在离线环境针对不同的场景应用历史指标来训练模型,模型训练结束之后会存入数据库,在生产环境运行过程中,对于不同场景下的数值指标依据其个性来加载不同的模型,并且联合 Flink 实时计算框架来实时的发现指标的异样并进行告警。

4 异样诊断

发现指标异样后,须要疾速的给出异样的根因,咱们能够依据具体的根因来抉择不同的解决策略,而后进行主动或者手动的复原工作。根因剖析能够基于专家教训,也能够严格地依照内核代码的逻辑来进行剖析。

本文重点讲述后者,强调如何应用“内核思维”来解决专家教训很难或者无奈解决的诊断问题。本文将列举“内核代码路径分析”、”内核日志剖析”、“内核性能加强“、“内核 Core Dump 剖析”以及“内核埋点”等几种不同的范式,来阐明数据库根因诊断的思路。

4.1 主从提早(内核代码路径分析)

这里先介绍“内核代码路径分析”这个形式来诊断根因。对于数据一致性要求比拟高的应用程序,seconds_behind_master 是一个非常重要的指标,如果其值过大就须要诊断其根因,避免应用程序读取到不统一的数据。依据专家教训,其值过大可能由“QPS 突增”、“大事务”、“大表 DDL”、“锁阻塞”、“表短少主键或者惟一健”、“低效执行打算”、“硬件资源有余”等因数造成,把这些专家经验总结成规定列表,当异样产生时一一迭代去验证是不是合乎某个规定,据此来诊断根因,然而此形式存在如下两大问题:

  1. 无奈枚举所有根因 :教训因为其固有的局限性不可能思考到所有的故障场景,如何残缺的给出造成 seconds_behind_master 值异样的所有规定是一个挑战;另外,如果须要对一个全新的指标进行诊断,而在没有任何的专家教训状况下,如何能疾速地整顿出残缺的规定列表?
  2. 不足对根因的深层次了解 :“QPS 突增”、“大事务”、“大表 DDL”、“锁阻塞”、“低效执行打算”、“硬件资源有余”等因素会造成 seconds_behind_master 指标值的异样,然而为什么这些因数会造成指标值的异样呢?如果不从内核源码角度来理解这些因素跟 seconds_behind_master 之间的逻辑计算关系,那么是无奈理解真正起因的。

4.1.1 内核代码路径分析

针对下面两个问题,具体策略是间接看 seconds_behind_master 这个变量在内核层面是如何计算的,只有这样能力残缺的枚举出所有影响 seconds_behind_master 值计算的因数。

从源码角度看,seconds_behind_master 的值由①time(0)、②mi->rli->last_master_timestamp 和③mi->clock_diff_with_master 这三个变量来决定(代码层面 seconds_behind_master 的计算逻辑是:seconds_behind_master=((long)(time(0) – mi->rli->last_master_timestamp)- mi->clock_diff_with_master),其中 time(0) 是零碎以后工夫(用秒示意),clock_diff_with_master 这个值的计算很简单、又很要害,会放到下一节具体进行阐明。

而针对 mi->clock_diff_with_master 的计算,这个变量从源码层面看就是主、从实例之间的时间差;依据以后的信息就可以看进去,从库的以后工夫以及主从库之间的时间差都会影响 seconds_behind_master 值的计算。seconds_behind_master 的计算和事务在主从库执行的状况如下:

last_master_timestamp 计算逻辑

从下面剖析能够晓得,last_master_timestamp 值是影响 seconds_behind_master 值计算的要害变量,所以很有必要从源码角度剖析影响 last_master_timestamp 值的因数有哪些(从而间接获取了影响 seconds_behind_master 值的因素)。

last_master_timestamp 的值来自于一个叫 rli->gaq->head_queue() 的成员变量 new_ts(此处的 rli->gaq->head_queue() 是指代某个最新的曾经实现 replay 的事务对应的 event group,event group 是指一个事务在 binlog 文件里生成一组 event 来示意某个事务,这个 event group 里的 event 从主库传输到从库后进行 replay 操作来还原主库的事务)。new_ts 值来源于 rli->gaq->head_queue())->ts,而 rli->gaq->head_queue())->ts 的值是通过 ptr_group->ts= common_header->when.tv_sec + (time_t) exec_time 计算获取的。

再看一下 when.tv_sec 跟 exec_time 的含意,前者指代 SQL 在主库上的 SQL 执行的开始工夫,后者指代 SQL 在主库上的“执行时长”,“执行时长”又跟“锁阻塞”、“低效执行打算”、“硬件资源有余”等因素非亲非故。

值得注意的是,后面提到的 rli->gaq->head_queue())->ts 的计算跟 slave_checkpoint_period 以及 sql_delay 两个变量也有关系,依照这个思路层层迭代上来找出所有影响 seconds_behind_master 值的因素,这些因素都是潜在的主从提早异样的本源,这样也解决了后面说的“无奈枚举所有根因”跟“不足对根因的深层次了解”两大问题。

为了便于了解上诉的逻辑,放出要害的源代码:获取 last_master_timestamp 值的起源 rli->gaq->head_queue() 的成员变量 new_ts。

bool mts_checkpoint_routine(Relay_log_info *rli, ulonglong period,
                            bool force, bool need_data_lock)
{
 do
  {cnt= rli->gaq->move_queue_head(&rli->workers);
  } 
 .......................
  ts= rli->gaq->empty()
    ? 0
    : reinterpret_cast<Slave_job_group*>(rli->gaq->head_queue())->ts; // 其中的 ts 来自上面的 get_slave_worker 函数;rli->reset_notified_checkpoint(cnt, ts, need_data_lock, true);
  //  社区版本的代码 rli->reset_notified_checkpoint(cnt, rli->gaq->lwm.ts, need_data_lock);
  /* end-of "Coordinator::"commit_positions" */
 ......................
}

获取 Master 实例上执行的 SQL 的开始跟执行时长信息 tv_sec 跟 exec_time。

Slave_worker *Log_event::get_slave_worker(Relay_log_info *rli)
{if (ends_group() || (!rli->curr_group_seen_begin && (get_type_code() == binary_log::QUERY_EVENT || !rli->curr_group_seen_gtid)))
  {
  ..............
    ptr_group->checkpoint_seqno= rli->checkpoint_seqno;
    ptr_group->ts= common_header->when.tv_sec + (time_t) exec_time; // Seconds_behind_master related
    rli->checkpoint_seqno++;
  }
} 

根因层叠图

如果进一步剖析内核代码,能够发现影响 seconds_behind_master 变量计算的因素还有很多,然而找出这些因素的思路是雷同的。这个思路的益处是:无论之前有没有相干专家教训,实践上这种剖析形式都能尽可能地枚举出所有的根因。

除了 seconds_behind_master,其余的像 thread_running、Threads_connected,slow_queries 等指标异样的剖析也都能够套用这种思路。上面为依照上述思路整理出来的影响 seconds_behind_master 值的局部因素的层次结构图:

4.1.2 流程剖析

把影响 seconds_behind_master 值的相干因素确认后,能够画一个流程图把这些因素都体现在流程图中的具体位置。这样既能比拟形象地了解影响 seconds_behind_master 的因素在整个主从复制流程中的所处的地位,又便于对整体规定进行查漏补缺。

上面咱们应用一个具体的例子,来阐明一下下面剖析的因素是如何影响 seconds_behind_master 的。从下图能够看出在执行 SQL 的过程中影响 seconds_behind_master 计算的两个变量 thd->start_time 跟 exec_time 的计算在 master 实例。假如 start_time 的值为 2023-07-03 00:00:00,SQL 执行了 60 秒,所以 exec_time 为 60,2023-07-03 00:01:00,SQL 在主库上执行结束,在从库上 replay 这个 SQL,能够看到 seconds_behind_master 值会从 0 开始并且逐步减少 60 秒,而后再返回 0。

具体起因是:假如咱们疏忽 binlog 日志的传输工夫,那么从库开始执行 replay 这个 SQL 的开始工夫也是 2023-07-03 00:01:00,所以依据 seconds_behind_master=((long)(time(0) – mi->rli->last_master_timestamp)- mi->clock_diff_with_master)=2023-07-03 00:01:00 – 2023-07-03 00:00:00-60s,后果就是 0,而后 SQL 的执行工夫是 60 秒,并且 (long)(time(0)(以后工夫)的工夫一秒一秒的在减少,所以 seconds_behind_master 值会从 0 开始逐步减少至 60 秒。

再看一下其余的因数,协调器(Coordinator)会把 Group 放入一个叫做 GAP Group 的队列中,Coordinator 会以 slave_checkpoint_period 值为周期来扫描 GAP Group 中的元素并且更新 rli->gaq->head_queue())->ts 值,所以果 slave_checkpoint_period 的值被设置的很大,那么 rli->gaq->head_queue())->ts 的值因为没有及时更新而变得比拟旧,从而引起 seconds_behind_master 值变大。

另外,每个 Worker 读取本人队列的 Group 元素,进行 repaly 操作,须要留神的是 sql_delay 这个变量,如果以后工夫还没有达到 sql_delay 规定的工夫(假如 sql_delay 被设置为 100 秒,那么 SQL 对应的 binlog 日志达到从库后须要期待 100 秒再执行),那么 worker 就不会进行 repaly 工作,这间接导致影响计算 seconds_behind_master 变量 thd->start_time 值比失常状况下小了 100 秒,所以当 worker 进行 replay 的时候,seconds_behind_master 的值就会相应的减少 100 秒。

4.1.3 产品展现

上面的产品展现了因为流量突增跟 MDL 锁造成的主从提早的诊断剖析报告的产品页面。咱们能够看到,流量突增的具体 SQL 以及 MDL 锁的持有者,不便用户进行限流或者 Kill 掉阻塞者 SQL。

4.2 大事务诊断剖析(内核性能加强)

大事务的存在,对整个数据库系统的稳定性与总体 SQL 的性能都会产生很大的挑战,如大事务长时间持有某个锁会造成大面积阻塞,或者更改过多的行数造成整个实例硬件资源的有余。咱们须要及时发现这些场景,并且将其信息发送给用户治理,但在实际过程中,往往面临如下的两大挑战:

第一个挑战 :无奈失去大事务所蕴含的残缺的 SQL 列表,造成用户不分明大事务的全貌是什么,用户也就无奈辨认须要优化的大事务。

  • 解决方案 :每个事务来 MySQL 会在内核层面生成一个惟一的事务 ID:trx_id,如果事务蕴含的每条 SQL,都给其附加一个事务 ID trx_id 字段,并且把这些 SQL 连同 trx_id 一起输入(通过全量 SQL 输入),问题就能够解决;不过这里还有一个挑战,这个事务 ID 到底是何时产生的呢?如何大家相熟内核内部事务的执行过程,就会晓得事务 ID 的只有在事务批改数据的时候才会通过 trx_assign_id_for_rw 这个办法被获取,这意味着就上面这个图上展现的事务而言,是无奈获取 SQL4 之前执行的读 SQL 语句列表,所以获取到的事务的 SQL 列表还是完好的,那么如何获取到残缺的 SQL 列表呢?解决方案也很简略,能够把事务 ID 的生成逻辑提前到在事务刚开始执行的时候生成就能够了。

第二个挑战 :大事务的耗时组成不明确。数据库规定执行时长大于某个阀值的事务被定义为大事务,然而不分明耗时到底是 SQL 自身的执行工夫还是 SQL 执行之外的耗时,SQL 执行之外的耗时可能是在执行高低两个 SQL 之间,业务端在解决一些跟数据库无关的业务逻辑,也可能是网络提早造成的。

  • 解决方案 :上述问题能够通过在数据库内核内对 SQL 执行开始时、完结时别离埋点工夫戳来解决,这样整个大事务执行总工夫中有多少工夫是在执行 SQL,有多少工夫是在 Sleep 就高深莫测;当然,这一块还能够做的更加的粗疏,比方两条 SQL 之间的 Sleep 工夫到底是网络提早还是应用程序端的提早等,能够进一步细分大事务造成的起因到底是在 MySQL 端、网络端还是用户本人的应用程序造成的期待;对于计算网络端的提早计算,能够参考 MySQL 外部的 mysql_socket_send_time 跟 vio_socket_io_wait_time 这 2 个要害指标的实现思路,下图是一个大事务的 SQL 列表以及耗时组成列表。

4.2.1 产品展现

内核团队通过内核革新,对事务中所蕴含的 SQL 都提供了 trx_id 后就能够依据 trx_id 把整个事务所有的 SQL 串起来。依据 SQL 执行的开始跟完结工夫,咱们也提供了所有 SQL 之间的 Sleep 工夫,胜利解决了下面的两个挑战,产品效果图如下:

4.3 MySQL Crash 剖析(内核 Core Dump 剖析)

MySQL 实例忽然 Crash 了怎么进行根因诊断?过程 Crash 的根因剖析,也是数据库故障中最难剖析的问题之一。本节提供一些思路尝试去剖析各种场景下的 MySQL Crash 的根因。

4.3.1 Crash 的触发形式

在剖析 Crash 的根因之前,咱们能够先理解一下 MySQL 过程是如何被 Crash 的整个过程非常有必要。一般来说,触发 Crash 的起因分成两类:①MySQL 过程本人触发了 Crash(这里称之为 MySQL 他杀);②MySQL 过程被 OS 杀死。

针对前者,比方 MySQL 发现某个要害数据产生了 Data Corruption、磁盘空间有余、硬件谬误、期待内核锁工夫过长、MySQL 内核 Bug 等场景,都可能导致 MySQL 他杀。尤其是查看到 MySQL 内核里有些数据的状态不合乎预期时,是必要要让那个实例 Crash 也不能继续执行,否则可能会产生更加重大的数据不一致性问题;而后者,OS 如果发现零碎内存严重不足或者空指针援用等状况,就会把包含 MySQL 在内相干的过程杀掉。

剖析一下 MySQL 本身触发 Crash 这个场景,在 MySQL 外部有很多中央通过 ut_a(如果是 ut_error 的话,则间接触发 Crash 断言对程序的外部数据状态进行异样查看,如果发现数据状态不合乎预期,那么势必产生了 Data Corruption,这个时候程序会调用 ut_dbg_assertion_failed 在过程 Crash 之前做一些要害信息(如 thread id、产生 Crash 的文件名字跟 code line 等)的记录后,会持续调用 abort() 向过程发送 SIGABRT 信号量让过程 Crash 掉。

须要留神的是,从 abort() 的源码可知,这里调用了两次 raise (SIGABRT),第一次调用 raise (SIGABRT) 触发处理函数 handle_fatal_signal(此函数在 MySQL 实例初始化时通过 sigaction 的 sa_handler 注册)被调用,这个函数次要是打印一些重要的调试信息,便于做 Core Dump 剖析;第二次调用 raise (SIGABRT) 的目标,是为了让 OS 生成 Core Dump 文件(core Dump 文件十分重要,因为所有引起 MySQL Crash 的现场信息都蕴含在 Core Dump 外面);如果是在 MySQL 他杀的状况下产生了 Crash,个别会在 errorlog 里会产生如下的一段跟 Crash 相干的现场信息,其中的“signal”、“触发 Crash 的线程堆栈”、“正在执行的 SQL”等信息都是剖析 Crash 根因的要害信息。

下图为 MySQL 通过 ut_a 断言查看异样问题后再到 OS 触发过程 Crash 的整体流程图。总体来说,MySQL 通过 raise 来发送 SIGABRT 信号到一个队列,OS 内核解决这个队列的信号并调用 MySQL 的处理程序 handle_fatal_signal 来保留一些要害的现场信息。这里须要留神到的是,OS 内核在__setup_rt_frame 中执行“regs->ip = (unsigned long) ka->sa.sa_handler;”,这个步骤正是让 MySQL 的 handle_fatal_signal 办法被顺利的调用的起因。

4.3.2 依据 Signal 类型做根因剖析

剖析 Crash 根因的第一步就是看触发了什么类型的 signal,常见类型有“signal 6”、“signal 7”、“signal 11”几种类型,当晓得了 Signal 类型后就有一个根因剖析的大方向。依据教训,咱们将常见的 signal 类型以及可能引起的起因大抵如下图所示,上面对于常见的 signal 类型以及引起的根因做一个简略的剖析。

(1) 如果是 signal 6,个别是实例的磁盘空间有余或者磁盘只读,MySQL 的数据产生了 data corruption,内核层面 latch 锁的长时间的锁期待造成。不过这里的 data corruption、长时间的锁期待可能是硬盘损坏或者 MySQL 的 Bug 造成的,判断逻辑如下:

  1. 磁盘空间有余或者磁盘只读

    • 磁盘写数据时,如果磁盘没有残余空间或者数据库被设置为 read only 就会造成实例的 Crash,日志中有“Either disk is full or file system is read only while opening the binlog”的字样。
  2. data corruption

    • MySQL 在运行过程中如果断言(比方这里 ut_a(table != NULL) 返回 false,很可能是数据产生了 corruption 并且 MySQL 就会自行 Crash 掉;产生 data corruption 时个别在 MySQL 的 error log 中有“Database page corruption on disk or a failed file read of tablespace”的字样,所以查看日志来判断否有硬盘故障问题,如果没有硬件故障信息,则可能是 MySQL Bug 造成的 data corruption,具体分析看上面“MySQL Bug”那局部。
  3. 长时间无奈获取 Latch 锁

    • 如果 MySQL 长时间没有方法获取到 Latch 锁,那么 MySQL 认为零碎可能是 Hang 住了也会引起实例的 Crash,并且日志中打印“We intentionally crash the server because it appears to be hung”字样,个别是硬件故障造成的性能问题或者 MySQL 本身的设计缺点造成的性能问题造成的,这次场景根因剖析比拟有挑战。
  4. MySQL Bug

    • 如果不属于下面任何一种状况,那么有可能是 MySQL 本身的 Bug 造成的 Crash,比方 MySQL 对一些 SQL 解析或者执行时会产生 Crash;这种场景个别先看一下 Crash 产生时正在执行的 SQL 是什么,这个 SQL 可能存在于 Crash 日志中(这个 Crash 日志中有个例子),能够先把 SQL 提取进去再次执行查看是否复现问题;如果在 Crash 日志中看不到 SQL 语句,就须要从 core dump 文件中提取 SQL 了,提取形式是 MySQL 每个链接对应的 THD 的成员变量 m_query_string 就蕴含了 SQL 文本,只须要关上 Core Dump 文件切换到某个蕴含 THD 实例的办法内,通过命令“p this->thd->m_query_string.str”来打印,这里有个例子。
    • 再举个 MySQL 的 Bug 造成 data corruption 的例子,从 Crash 日志里“InnoDB: Assertion failure in thread 139605476095744 in file rem0rec.cc line 578”看出,从 rec_get_offsets_func 函数中触发 ut_error 而导致的 Crash,之所以触发这个 Crash 是因为 rec_get_offsets_func 中的 rec_get_status(rec) 获取到的 MySQL 的记录类型不合乎预期(因为记录类型只有固定的 REC_STATUS_ORDINARY、REC_STATUS_NODE_PTR,REC_STATUS_INFIMUM,REC_STATUS_SUPREMUM 这 4 种类型),如果内核发现一个记录类型不属于这 4 种类型的任何一种,那么就是产生了 data corruption,所以必须要把本人 Crash 掉。为了验证方才的论断,看一下 Crash 产生时的 rec 的类型是什么,从源码可知 rec 的类型是通过 rec_get_status 获取,并且其通过调用的 rec_get_bit_field_1 跟 mach_read_from_1 两个函数能够晓得 rec 的类型其实就是 rec 这个指针往前三个 byte(通过 #define REC_NEW_STATUS_MASK 0x7UL 可知)代表的值。
    • 通过 gdb 加载 core dump 文件后,切换到抛出 exception 的线程,因为异样是在 rec_get_offsets_func 里抛出的,切换到 rec_get_offsets_func 对应的 frame 7 来验证 rec 的类型,看到 rec 的指针地址为 0x7f14b7f5685d(相干剖析数据能够看此链接)。后面说过,rec 的类型值在 rec 指针往前三个 byte 里,也就是指针 0x7f14b7f5685a(0x7f14b7f5685d-3)那个地位的值,发现是 0x1f,执行与计算(11111(1f)&00111(0x7UL)=00111=7)失去的类型是 7,而记录类型的范畴是(0~3),很显著这个指针指向的记录类型值信息产生了 data corruption(剖析过程查看此链接),这里做了一个 rec 的类型在失常状况下跟本例异常情况下的类型值计算的比照表,发现失常状况下,rec 的类型值就应该是 3。
  • 这里有个重要问题是,为什么 rec 的类型是有效的呢?很可能是 MySQL 搜寻满足条件的记录的时候,rec 指向的记录很可能被 page_cleaner 在后盾被清理掉了,所以 rec 指针指向的记录就是有效了。官网有个 bugfix,解决方案就是把 prev_rec 设置为 NULL(这里的 prev_rec 是 persistent cursor 指向的记录,这里说一下 persistent cursor,它是 MySQL 从 InnoDB 层取得记录后进入 SQL 层前在 B -tree 上的 cursor 会被临时存储到 row_prebuilt_t::pcur 中,当再次从 InnoDB 层拿数据时,如果对应的 buf_block_t 没有产生批改,则能够持续沿用之前存储的 cursor,否则须要从新定位,如果没有 persistent cursor 则每次都须要从新定位则对性能有影响),这样 prev_rec != NULL 这个条件不满足,也就没有机会进入 rec_get_offsets_g_func 里去查看 rec 的类型值而引发 Crash 了。

(2) 如果为 signal 7,那么大概率是内存硬件谬误,并且日志里个别有“mce: [Hardware Error]: Machine check events logged,mce: Uncorrected hardware memory error in user-access at xxx MCE xxx: Killing mysqld:xxx due to hardware memory corruption”等字样。

(3) 如果为 signal 9,示意这个过程被 Kill 命令杀掉了。

(4) 如果为 signal 11,示意是由 MySQL 的 Bug 造成的,这类问题较难剖析特地是 MySQL Crash 现场(通过 core dump 打印进去的堆栈信息)往往还不是第一现场,因为篇幅关系具体的例子剖析不在本文中给出,然而剖析的思路跟下面的“MySQL Bug”是相似的。

5 本文作者

裕锋,来自美团根底研发平台 - 根底技术部,负责美团数据库自治平台的相干工作。

6 参考

  • https://github.com/shenyufengdb/sql
  • https://github.com/percona/percona-server/blob/release-5.7.41-44
  • An Efficient, Cost-Driven Index Selection Tool for Microsoft SQL Server
  • plan-stitch-harnessing-the-best-of-many-plans-2
  • Random Sampling for Histogram Construction: How Much is Enough?
  • AutoAdmin“what-if”index analysis utility
  • What is a Self-Driving Database Management System?
  • https://www.microsoft.com/en-us/research/publication/self-tuning-database-systems-a-decade-of-progress/
  • Automatic Database Management System Tuning Through Large-scale Machine Learning
  • Query-based Workload Forecasting for Self-Driving Database Management Systems
  • The TSA Method
  • https://blog.langchain.dev/langchain-chat/
  • https://github.com/hwchase17/langchain
  • REAC T: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS
  • Evaluating the Text-to-SQL Capabilities of Large Language Models
  • SQL-PALM: IMPROVED LARGE LANGUAGE MODEL ADAPTATION FOR TEXT-TO-SQL

浏览更多

| 在美团公众号菜单栏对话框回复【2022 年货】、【2021 年货】、【2020 年货】、【2019 年货】、【2018 年货】、【2017 年货】等关键词,可查看美团技术团队历年技术文章合集。

| 本文系美团技术团队出品,著作权归属美团。欢送出于分享和交换等非商业目标转载或应用本文内容,敬请注明“内容转载自美团技术团队”。本文未经许可,不得进行商业性转载或者应用。任何商用行为,请发送邮件至 tech@meituan.com 申请受权。

正文完
 0