一文读懂MySQL 8.0直方图(2)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 一文读懂MySQL 8.0直方图
  • 无索引时,走直方图
"considered_execution_plans": [
  {
    "table": "`t1`",
    "chosen": true,
    "plan_prefix": [],
    "cost_for_plan": 35080,
    "rows_for_plan": 1,
    "best_access_path": {
      "considered_access_paths": [
        {
          "cost": 35080,   --cost看起来好高
          "chosen": true,  --但还是被选中了
          "access_type": "scan",   --全表扫描
          "rows_to_scan": 347352,  --预计需要扫描行数
          "resulting_rows": 1,   --预计返回行数
          "filtering_effect": [   
            {
              "condition": "(`t1`.`seq` = 1234)",
              "histogram_selectivity": 0.000001   --直方图过滤效果
            }
          ],
          "final_filtering_effect": 0.0000029
        }
      ]
    },
    "condition_filtering_pct": 100
  }

虽然看起来是要走全表扫描,但因为有了直方图,实际上还是很快就能返回结果的。

  • 有索引时,优先走索引
"considered_execution_plans": [
 {
   "table": "`t1`",
   "chosen": true,
   "plan_prefix": [],
   "cost_for_plan": 0.35,
   "rows_for_plan": 1,
   "best_access_path": {
     "considered_access_paths": [
       {
         "cost": 0.35,   --相当上面直方图的cost,这个就非常低了,所以毫无悬念的选择索引
         "rows": 1,
         "index": "k1",
         "chosen": true,
         "access_type": "ref"
       },
       {
         "cause": "heuristic_index_cheaper",
         "chosen": false,
         "access_type": "range",
         "range_details": {
           "used_index": "k1"
         }
       }
     ]
   },
   "condition_filtering_pct": 100
}

如果有源码大佬,也请帮忙确认下是不是这样吧。

3. 如何提高直方图的统计精确度

前文我们提到过参数 histogram_generation_max_mem_size,其作用是控制在创建/更新直方图时所需的内存大小。

The maximum amount of memory available for generating 
histogram statistics.

该参数默认值是 20000000(不到20MB),最小值 1000000(约976KB),这是个会话级(session)分配的内存,而且是每次创建/更新直方图都需要分配,执行结束后就释放。

介绍完前置信息,该说重点了,在直方图里如何提高统计精确度。

在扫描InnoDB data page进行直方图数据统计时,大致是这样的步骤:

  1. 估算要统计的列数据类型长度,记为 row_size_bytes。
  2. 可用内存除以每条记录长度,得到预计可以采样的数据量 rows_in_memory = histogram_generation_max_mem_size / row_size_bytes。
  3. 计算得到采样比例 sample_percentage = rows_in_memory / rows_in_table。其中 rows_in_table 是表预估总记录数。
  4. 依照采样比例,扫描data page,得到采样结果。例如,采样比例是10%,那么就是扫描1个page后,跳过9个page,然后继续采样。
    这几个步骤是以我的三脚猫源码阅读水平得到的结果,若有出入,还请留言指正。
    上述步骤所对应的代码是 sql/histograms/histogram.cc,约868行附近的 update_histogram 函数。

MySQL目前对数据长度处理的非常粗粒度,只区分了下面几种情况,这就导致了直方图列实际所需要的内存可能要比它定义的类型长度要更大,也可以看下源码中的定义:

vim sql/histograms/histogram.cc +113
/**
  Convert from enum_field_types to Value_map_type.
  @param field_type the field type
  @param is_unsigned whether the field type is unsigned or not. This is only
                     considered if the field type is LONGLONG
  @return A Value_map_type. May be INVALID if the Value_map does not support
          the field type.
*/
static Value_map_type field_type_to_value_map_type(
    const enum_field_types field_type, const bool is_unsigned) {
  switch (field_type) {
    case MYSQL_TYPE_DECIMAL:
    case MYSQL_TYPE_NEWDECIMAL:
      return Value_map_type::DECIMAL;
    case MYSQL_TYPE_TINY:
    case MYSQL_TYPE_SHORT:
    case MYSQL_TYPE_LONG:
    case MYSQL_TYPE_INT24:
    case MYSQL_TYPE_YEAR:
    case MYSQL_TYPE_BIT:
      return Value_map_type::INT;
    case MYSQL_TYPE_ENUM:
      return Value_map_type::ENUM;
    case MYSQL_TYPE_SET:
      return Value_map_type::SET;
    case MYSQL_TYPE_LONGLONG:
      return is_unsigned ? Value_map_type::UINT : Value_map_type::INT;
    case MYSQL_TYPE_FLOAT:
    case MYSQL_TYPE_DOUBLE:
      return Value_map_type::DOUBLE;
    case MYSQL_TYPE_TIME:
    case MYSQL_TYPE_TIME2:
      return Value_map_type::TIME;
    case MYSQL_TYPE_DATE:
    case MYSQL_TYPE_NEWDATE:
      return Value_map_type::DATE;
    case MYSQL_TYPE_DATETIME:
    case MYSQL_TYPE_TIMESTAMP:
    case MYSQL_TYPE_TIMESTAMP2:
    case MYSQL_TYPE_DATETIME2:
      return Value_map_type::DATETIME;
    case MYSQL_TYPE_TINY_BLOB:
    case MYSQL_TYPE_MEDIUM_BLOB:
    case MYSQL_TYPE_LONG_BLOB:
    case MYSQL_TYPE_BLOB:
    case MYSQL_TYPE_VAR_STRING:
    case MYSQL_TYPE_STRING:
    case MYSQL_TYPE_VARCHAR:
      return Value_map_type::STRING;
    case MYSQL_TYPE_JSON:
    case MYSQL_TYPE_GEOMETRY:
    case MYSQL_TYPE_NULL:
    default:
      return Value_map_type::INVALID;
  }

可以看到,把TINYINT(1字节)、SMALLINT(2字节)等都统统按 Value_map_type::INT 来处理了,而这个类型实际上是 longlong 的,也就是 16字节。

另外,每条记录还需要约42字节的额外开销(比大多数据类型长度还要大,尴尬)。

vim sql/histograms/value_map.h +262
  /// @return the overhead in bytes for each distinct value stored in the
  ///         Value_map. The value 32 is obtained from both GCC 8.2 and
  ///         Clang 8.0 (same as sizeof(value_map_type::node_type) in C++17).
  size_t element_overhead() const override {
    // TODO: Replace this with sizeof(value_map_type::node_type) when we have
    // full C++17 support.
    return sizeof(typename value_map_type::value_type) +
           sizeof(typename value_map_type::key_type) + 32;
  }

其他的几个数据类型也是很粗犷的处理了,在以后的版本应该会改进吧。

如果 histogram_generation_max_mem_size 不够大,则采样比例比较低,就会影响准确度,那么应该设置多少合理呢,可以按照下面这个方法:

  1. 先设置最小值。
  2. 执行一次采样。
  3. 查看采样比例。
  4. 反推出要全部采样所需的内存。
    当然了,如果表数据量特别大,也没必要全部采样,毕竟消耗的内存比较多,而且也需要更多的物理读。
    来个实例演示。
# 设置 histogram_generation_max_mem_size 为最小值
[root@yejr.run]> set session histogram_generation_max_mem_size = 1000000;
# 重置InnoDB Metric计数器
[root@yejr.run]> SET GLOBAL innodb_monitor_enable = 'sampled%';
# 来一发,创建个直方图
[root@yejr.run]> analyze table t1 update histogram on seq;
1 row in set (0.16 sec)   --耗时0.16秒
# 查看采样统计比例
[root@yejr.run]> SELECT HISTOGRAM->>'$."sampling-rate"' FROM INFORMATION_SCHEMA.COLUMN_STATISTICS;
+---------------------------------+
| HISTOGRAM->>'$."sampling-rate"' |
+---------------------------------+
| 0.059548422871929935            |
+---------------------------------+
# 查看innodb data page扫描统计
[root@yejr.run]> SELECT NAME, COUNT FROM information_schema.INNODB_METRICS WHERE NAME LIKE 'sampled%';
+-----------------------+-------+
| NAME                  | COUNT |
+-----------------------+-------+
| sampled_pages_read    |    51 |  --扫描了51个page
| sampled_pages_skipped |   760 |  --跳过了760个page
+-----------------------+-------+

与此同时,我在另一个session中,分别在之前和之后查询上面创建直方图的线程内存消耗情况。

# 第一次查询
[root@yejr.run]> select * from sys.x$memory_by_thread_by_current_bytes where thread_id = 286;
+-----------+-------------------------------+--------------------+-------------------+-------------------+-------------------+-----------------+
| thread_id | user                          | current_count_used | current_allocated | current_avg_alloc | current_max_alloc | total_allocated |
+-----------+-------------------------------+--------------------+-------------------+-------------------+-------------------+-----------------+
|       286 | thread_pool/tp_one_connection |                163 |            345036 |         2116.7853 |            260280 |       212710814 |
+-----------+-------------------------------+--------------------+-------------------+-------------------+-------------------+-----------------+
1 row in set (0.07 sec)
#第二次查询
[root@yejr.run]> select * from sys.x$memory_by_thread_by_current_bytes where thread_id = 286;
+-----------+-------------------------------+--------------------+-------------------+-------------------+-------------------+-----------------+
| thread_id | user                          | current_count_used | current_allocated | current_avg_alloc | current_max_alloc | total_allocated |
+-----------+-------------------------------+--------------------+-------------------+-------------------+-------------------+-----------------+
|       286 | thread_pool/tp_one_connection |                165 |            346502 |         2100.0121 |            261712 |       213946226 |
+-----------+-------------------------------+--------------------+-------------------+-------------------+-------------------+-----------------+

两次查询 total_allocated 的差值 1235412(1.17MB) 主要就是由于创建直方图所需的内存。

现在,可以推算所需要的内存大约是

[root@yejr.run]> select 1235412 / 0.059548422871929935;
+--------------------------------+
| 1235412 / 0.059548422871929935 |
+--------------------------------+
|                  20746342.8991 |
+--------------------------------+

现在加大内存设置后,再做一次看看

[root@yejr.run]> set session histogram_generation_max_mem_size = 1000000;
[root@yejr.run]> SET GLOBAL innodb_monitor_enable = 'sampled%';
[root@yejr.run]> analyze table t1 update histogram on seq;
1 row in set (3.78 sec)   --耗时3.78秒,是上面的23.6倍
[root@yejr.run]> SELECT HISTOGRAM->>'$."sampling-rate"' FROM INFORMATION_SCHEMA.COLUMN_STATISTICS;
+---------------------------------+
| HISTOGRAM->>'$."sampling-rate"' |
+---------------------------------+
| 1.0                             |
+---------------------------------+
[root@yejr.run]> SELECT NAME, COUNT FROM information_schema.INNODB_METRICS WHERE NAME LIKE 'sampled%';
+-----------------------+-------+
| NAME                  | COUNT |
+-----------------------+-------+
| sampled_pages_read    |   811 |
| sampled_pages_skipped |     0 |   --的确,没有被跳过的page了
+-----------------------+-------+

再次提醒,并不是非得所有page都要被采集到,否则代价可能无法承受。。。

全文完。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
SQL 关系型数据库 MySQL
MySQL直方图统计信息使用场景
当需要过滤的字段上既没有索引也没有直方图时,优化器会根据MySQL代码中内置的默认规则估计过滤的比率,实际很大程度上是瞎猜,部分常用的默认规则如下
|
SQL 关系型数据库 MySQL
一文读懂MySQL 8.0直方图(2)
一文读懂MySQL 8.0直方图
179 0
|
存储 SQL JSON
一文读懂MySQL 8.0直方图(1)
一文读懂MySQL 8.0直方图
125 0
|
存储 SQL JSON
一文读懂MySQL 8.0直方图(1)
一文读懂MySQL 8.0直方图
|
存储 SQL 关系型数据库
MySQL · 引擎特性 · Cost Model,直方图及优化器开销优化
MySQL当前已经发布到MySQL8.0版本,在新的版本中,可以看到MySQL之前被人诟病的优化器部分做了很多的改动,由于笔者之前的工作环境是5.6,最近切换到最新的8.0版本,本文涵盖了一些本人感兴趣的和优化器相关的部分,主要包括MySQL5.7的cost model以及MySQL8.0的直方图功能。
1473 0
|
JSON 关系型数据库 MySQL
MySQL · 特性分析 · 直方图的实现与分析
直方图(Histogram)是 RDBMS 中提供的一种基础的统计信息,最典型的用途是估计查询谓词的选择率,以便选择优化的查询执行计划。常见的直方图种类有:等宽直方图、等高直方图、V-优化的直方图,MaxDiff 直方图等等。RDBMS 产品最初使用的直方图非常简单(只有一个桶),后来逐步演化到等宽直方图、等高直方图等。MariaDB 10.0.2 就已在 server 层实现了直方图功能,参考T
2532 0
|
18天前
|
存储 SQL 关系型数据库
Mysql学习笔记(二):数据库命令行代码总结
这篇文章是关于MySQL数据库命令行操作的总结,包括登录、退出、查看时间与版本、数据库和数据表的基本操作(如创建、删除、查看)、数据的增删改查等。它还涉及了如何通过SQL语句进行条件查询、模糊查询、范围查询和限制查询,以及如何进行表结构的修改。这些内容对于初学者来说非常实用,是学习MySQL数据库管理的基础。
74 6
|
15天前
|
存储 关系型数据库 MySQL
Mysql(4)—数据库索引
数据库索引是用于提高数据检索效率的数据结构,类似于书籍中的索引。它允许用户快速找到数据,而无需扫描整个表。MySQL中的索引可以显著提升查询速度,使数据库操作更加高效。索引的发展经历了从无索引、简单索引到B-树、哈希索引、位图索引、全文索引等多个阶段。
50 3
Mysql(4)—数据库索引
|
1天前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。
本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。同时,文章还对比了编译源码安装与使用 RPM 包安装的优缺点,帮助读者根据需求选择最合适的方法。通过具体案例,展示了编译源码安装的灵活性和定制性。
10 2