MySQL8.0 · 优化器新特性 · Cost Model, 直方图及优化器开销优化

MySQL当前已经发布到MySQL8.0版本,在新的版本中,能够看到MySQL以前被人诟病的优化器部分作了不少的改动,因为笔者以前的工做环境是5.6,最近切换到最新的8.0版本,本文涵盖了一些本人感兴趣的和优化器相关的部分,主要包括MySQL5.7的cost model以及MySQL8.0的直方图功能。mysql

本文基于当前最新的MySQL8.0.12版本,主要是讲下cost model 和 histogram的用法和相关代码sql

Cost Model

Configurable cost constants数据库

为何须要配置cost model常量 ? 咱们知道MySQL已经发展了好几十年的历史,可是在优化器中依然使用了hardcode的权重值来衡量io, cpu等资源状况,而这些权重值其实是基于多年前甚至十来年前的经验设定的。想一想看,这么多年硬件的发展多么迅速。几十上百个核心的服务器不在少数甚至在某些大型公司大规模使用,ssd早就成为主流,NVME也在崛起。高速RDMA网络正在走入寻常百姓家。这一切甚至影响到数据库系统的实现和变革。显而易见,那些hardcode的权值已通过时了,咱们须要提供给用户可定义的方式,甚至更进一步的,可以智能的根据硬件环境自动设定。json

MySQL5.7引入两个新的系统表, 经过这两个系统表暴露给用户来进行更新,以下:api

clipboard.png

你能够经过update语句来进行更新, 例如:数组

clipboard.png

能够看到用法也很是简单,上面包含了两张表:server_cost及engine_cost,分别对server层和引擎层进行配置服务器

相关代码:

全局cache Cost_constant_cache网络

全局cache维护了一个当前的cost model信息, 用户线程在lex_start时会去判断其有没有初始化本地指针,若是没有的话就去该cache中将指针拷贝到本地session

初始化全局cache:架构

clipboard.png

从系统表读取配置,适用于初始化和flush optimizer_costs并更新cache:

clipboard.png

因为用户能够动态的更新系统表,执行完flush optimizer_costs后,有可能老的版本还在被某些session使用,所以须要引用计数,老的版本ref counter被降为0后才能被释放

线程cost model初始化

• Cost_model_server

在每一个线程的thd上,挂了一个Cost_model_server的对象THD::m_cost_model, 在lex_start()时,若是发现线程的m_cost_model没有初始化,就会去获取全局的指针,存储到本地:

clipboard.png

可见thd不建立本身的cost model, 只引用cache中的指针

Table Cost Model

struct TABLE::m_cost_model, 类型:Cost_model_table

其值取自上述thd中存储的cost model对象

Cost_estimate

统一的对象类型cost_estimate来存储计算的cost结果,包含四个维度:

clipboard.png

将来

目前来看,除非根据工做负载,通过充分的测试才能得出合理的配置值,但如何配置,什么是合理的值,我的认为应该是能够自动调整配置的。关键是找出配置和硬件条件的对应关系。 这也是咱们将来能够努力的一个方向。

reference:

  1. Cost Model官方文档
  2. 官方博客1:The MySQL Optimizer Cost Model Project
  3. 官方博客2: A new dimension to MySQL query optimizations
  4. Optimizer Cost Model Improvements in MySQL 5.7.5 DMR

5.Slide: MySQL Cost Model

Related Worklog:

WL#7182: Optimizer Cost Model API

WL#7209: Handler interface changes for new cost model

WL#7276: Configuration data base for Optimizer Cost Model

WL#7315 Optimizer cost model: main memory management of cost constants

WL#7316 Optimizer cost model: Command for online updating of cost model constants

Histogram

直方图也是MySQL一个万众期待的功能了,这个功能实际上在其余数据库产品中是很常见的,能够很好的指导优化器选择执行路径。利用直方图存储了指定列的数据分布。MariaDB从很早的10.0.2版本支持这个功能, 而MySQL在最新的8.0版本中也开始支持

使用

MySQL里使用直方图是经过ANALYZE TABLE语法来执行:

clipboard.png

举个简单的例子:

clipboard.png

直方图统计信息存储于InnoDB数据词典中,能够经过information_schema表来获取

clipboard.png

从column_statistics表的定义能够看到,有一个名为mysql.column_statistics系统表,但被隐藏了,没有对外暴露

如下举个简单的例子:

clipboard.png

从输出的json能够看到,在执行了上述语句后产生的直方图,有4个bucket,数据类型为Int, 类型为equi-height,即等高直方图(另一种是等宽直方图,即SINGLETON)。每一个Bucket中,描述的信息包括:数值的上界和下界, 频率以及不一样值的个数。经过这些信息能够得到比较精确的数据分布状况,从而优化器来根据这些统计信息决定更优的执行计划。

若是列上存在大量的重复值,那么MySQL也可能选择等宽直方图,例如上例,咱们将列k上的值更新为一半10一半为20, 那么出来的直方图数据以下:

clipboard.png

如上,对于SINGLETON类型,每一个bucket只包含两个值:列值,及对应的累计频率(即百分之多少的数据比当前Bucket里的值要小或相等)

注意这里的sampling-rate, 这里的值为1,表示读取了表上全部的数据来进行统计,但一般对于大表而言,咱们可能不但愿读太多的数据,由于可能产生过分的内存消耗,所以MySQL还提供了一个参数histogram_generation_max_mem_size来限制内存的使用上限。

若是表上的DML很少,那直方图基本是稳定的,但频繁写入的话,那咱们就可能须要去按期更新直方图,MySQL自己不会去主动更新。

优化器经过histogram来计算列的过滤性,大多数的谓词均可以使用到。具体参阅官方文档

关于直方图影响查询计划,这篇博客 及 这篇博客

相关代码

代码结构:

以MySQL8.0.12为例,主要代码在sql/histogram目录下:

clipboard.png

建立及存储histogram:

处理histogram的相关函数和堆栈以下:

clipboard.png

使用histogram

使用的方式就比较简单了:

首先在表对象TABLE_SHARE中,增长成员m_histograms,其结构为一个unordered map,key值为field index, value为相应的histogram对象

获取列值过滤性的相关堆栈以下:

clipboard.png

MySQL支持多种操做类型对直方图的使用,包括:

clipboard.png

经过直方图,咱们能够根据列上的条件判断出列值的过滤性,来辅助选择更优的执行计划。在没有直方图以前咱们须要经过在列上创建索引来得到相对精确的列值分布。但咱们知道索引是有很大的维护开销的,而直方图则能够灵活的按需建立。

reference

WL#5384 PERFORMANCE_SCHEMA, HISTOGRAMS

WL#8706 Persistent storage of Histogram data

WL#8707 Classes/structures for Histograms

WL#8943 Extend ANALYZE TABLE with histogram support

WL#9223 Using histogram statistics in the optimizer

其余

优化rec_per_key

相关worklog:

WL#7338: Interface for improved records per key estimates

WL#7339 Use improved records per key estimate interface in optimizer

MySQL经过rec_per_key 接口来估算记录的个数(暗示每一个索引Key对应的记录个数),但在早前版本中这个数字是整数,对于小数会取整,不能表示准确的rec_per_key,从而影响到索引的选择,所以在5.7版本中,将其记录的值改为了float类型

引入数据cache状态计算开销

相关worklog:

WL#7168 API for estimates for how much of table and index data that is in memory buffer

WL#7170: InnoDB buffer estimates for tables and indexes

WL#7340 IO aware cost estimate function for data access

在以前的版本中,优化器是没法知道数据的状态,是不是cache在内存中,仍是须要从磁盘读出来的,缺少这部分信息,致使优化器统一认为数据属于磁盘的来计算开销。这可能致使低效的执行计划。

相关代码:

server层新增api,用于获取表或索引上有百分之多少的数据是存储在cache中的

clipboard.png

而在innodb层,增长了一个全局变量buf_stat_per_index (对应类型为buf_stat_per_index_t) 来维护每一个索引在内存中的leaf page个数, 其内部实现了一个lock-free的hash结构,Key值为(m_space_id) << 32 | m_index_id), 在读入page时或者内存中建立新page时, 若是对应的page是leaf page,就递增计数;当从page hash中移除时,则递减计数。

为了减小性能的影响,计数器是经过lock-free hash的结构存储的,对应的结构为ut_lock_free_hash_t。

基本的实现思路是:hash是一个定长的数组,数组元素为(key, val), 根据Key计算一个hash值再模上array size, 找到对应的槽位, 若是槽位被占用了,则向右查找一个空闲的slot。

当数组满了的时候,会建立一个新的更大的数组,在数据还没Move到这个新hash以前,全部的search都须要查询两个数组。当全部的记录到迁移到新数组,而且没有线程访问老的数组时,就能够把老的hash删除掉了。

在hash中存储的counter自己,也考虑到多核和numa架构,避免同时更新引发的cpu cache失效。在大量core的场景下这个问题可能很明显。Innodb封装计数操做到类ut_lock_free_cnt_t中,使用数组维护counter, 按照cpu no做为index更新,须要获取counter值时则累加数组中的值。

这个Lock free hash并非个通用场景的hash结构:例如处理冲突的时候,可能占用其余key的槽位,hash不够用时,须要迁移到新的array中。实际上mysql自己实现了一个lf_hash,在扩展Hash时无需迁移数据,有空单独开篇博客讲一下。

你能够从information_schema.innodb_cached_indexes表中读取到每一个索引cache的page个数。

当定义好接口,而且Innodb提供相应的统计数据后,优化器就能够利用这些信息来计算开销:

• Cost_model_table::page_read_cost

• Cost_model_table::page_read_cost_index

本文做者:zhaiwx_yinfeng

阅读原文

本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索