MySQL · 8.0版本更新 · 性能优化篇

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介:

本文主要总结下MySQL在8.0版本和性能相关的一些改动,随着新的小版本的发布,本文将不断进行更新,直到正式GA。

|已更新版本|

| MySQL 8.0.0

MySQL 8.0.0

WL#9387: InnoDB: Group purging of rows by table ID

这个问题最早是facebook的工程师Domas报的一个bug,InnoDB使用多线程来进行Undo Purge操作,但分配undo的策略不太合理,直接轮询分配。这意味着如果从一张表上删除大量数据,这N个purge线程可能产生大量的索引锁冲突(例如索引页合并及重组织)

在WL#9387中,在parse undo log时,通过table_id进行分组存储,在分发时确保同一个table id的记录被分配给同一个线程。(参考函数 trx_purge_attach_undo_recs)

当然这也意味着合理的不会产生冲突的单表操作, 无法利用到多线程purge了,也算是一个弊端。

WL#8423: InnoDB: Remove the buffer pool mutex

这个算是众望所归的改进了,由Percona贡献的补丁(bug#75534),主要是对InnoDB的buffer pool mutex这个大锁进行了拆分,降低锁冲突:

Name Desc
buf_pool_t::LRU_list_mutex 用于保护LRU链表,例如从LRU链表上刷脏或驱逐Page
buf_pool_t::free_list_mutex 保护free list及withdraw list(online resize)
buf_pool_t::zip_free_mutex 保护zip_free数组,该数组用于维护对压缩表产生的非标准page size的内存维护, ref buf/buf0buddy.cc
buf_pool_t::zip_hash_mutex 保护zip_hash, 其中存储压缩页block
buf_pool_t::flush_state_mutex 保护init_flush, n_flush, no_flush等数组

分配空闲block(buf_LRU_get_free_block):

  • 从free list获取: buf_pool_t::free_list_mutex
  • 从unzip_lru/lru上驱逐一个空闲page,需要buf_pool_t::LRU_list_mutex

批量扫描LRU(buf_do_LRU_batch): buf_pool_t::LRU_list_mutex

批量扫描FLUSH_LIST(buf_do_flush_list_batch): buf_pool_t::flush_list_mutex

脏页加入到flush_list(buf_flush_insert_into_flush_list): buf_pool_t::flush_list_mutex

脏页写回磁盘后,从flush list上移除(buf_flush_write_complete): buf_pool_t::flush_state_mutex/flush_list_mutex

从LRU上驱逐Page(buf_LRU_free_page):buf_pool_t::LRU_list_mutex, 及buf_pool_t::free_list_mutex(buf_LRU_block_free_non_file_page)

buf_flush_LRU_list_batch 使用mutex_enter_nowait 来获取block锁,如果获取失败,说明正被其他session占用,忽略该block.

有些变量的修改从通过buf_pool_t::mutex保护,修改成通过memory barrior来保护(os_rmb or os_wmb), 例如下面几个函数中均有体现:

btr_search_enable()
buf_resize_thread()
buf_get_withdraw_depth()
buf_LRU_get_free_block()

通过对锁的拆分,降低了全局大锁的竞争,提升了buffer pool的扩展性,这个特性其实在Percona Server中很多年前就有了, 但直到MySQL8.0版本才合并进来。

WL#7170: InnoDB buffer estimates for tables and indexes

主要是用于为优化器提供更准确的信息,即数据是存在与磁盘还是内存中, 这样优化器可以更准确的做出代价计算。

增加一个全局对象(buf_stat_per_index_t)来管理所有的索引页计数

为了避免引入新的全局锁开销,实现并使用一个lock-free的hash结构("include/ut0lock_free_hash.h)来存储索引信息,key值为索引id。(目前索引id具有唯一性,但不排除未来可能发生改变)。

增加计数:

  1. Page刚从磁盘读入内存 (buf_page_io_complete --> buf_page_monitor)
  2. 创建一个新的page (btr_page_create)

递减计数: Page从LRU上释放时进行递减(buf_LRU_block_remove_hashed)

增加新的information_schema.innodb_cached_indexs 打印每个索引在内存中的page个数,其结构如下:

mysql> show create table INNODB_CACHED_INDEXES\G
*************************** 1. row ***************************
       Table: INNODB_CACHED_INDEXES
Create Table: CREATE TEMPORARY TABLE `INNODB_CACHED_INDEXES` (
  `SPACE_ID` int(11) unsigned NOT NULL DEFAULT '0',
  `INDEX_ID` bigint(21) unsigned NOT NULL DEFAULT '0',
  `N_CACHED_PAGES` bigint(21) unsigned NOT NULL DEFAULT '0'
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

### 和表名/索引名关联

SELECT
    tables.name AS table_name,
    indexes.name AS index_name,
    cached.n_cached_pages AS n_cached_pages
FROM
    information_schema.innodb_cached_indexes AS cached,
    information_schema.innodb_sys_indexes AS indexes,
    information_schema.innodb_sys_tables AS tables
WHERE
    cached.index_id = indexes.index_id
        AND
    indexes.table_id = tables.table_id;

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

WL#9383: INNODB: ADD AN OPTION TO TURN OFF/ON DEADLOCK CHECKER

增加选项,可以动态关闭死锁检测,这对诸如热点更新这样的场景效果显著,之前已专门写了篇博客,感兴趣的自取

Bug#77094

这个优化来自alisql的贡献,主要是优化了InnoDB Redo的扩展性问题,通过双buffer机制,允许在写日志到磁盘的同时,也允许进行mtr commit,具体参阅我写的这篇月报

WL#7093: Optimizer provides InnoDB with a bigger buffer

为了减少对Btree的锁占用,InnoDB在读取数据时实际上是有一个小的缓存buffer。对于连续记录扫描,InnoDB在满足比较严格的条件时采用row cache的方式连续读取8条记录(并将记录格式转换成MySQL Format),存储在线程私有的row_prebuilt_t::fetch_cache中;这样一次寻路就可以获取多条记录,在server层处理完一条记录后,可以直接从cache中取数据而无需再次寻路,直到cache中数据取完,再进行下一轮。

在WL#7093中引入了新的接口,由于优化器可以估算可能读取的行数,因此可以提供给存储引擎一个更合适大小的row buffer来存储需要的数据。大批量的连续数据扫描的性能将受益于更大的record buffer。

Record buffer由优化器来自动决定是否开启,增加新的类Record_buffer进行管理, Record buffer的大小最大不超过128KB, 目前是hard code的,不可以配置。

判断及分配record buffer函数: set_record_buffer, 并通过新的API接口(handler::ha_set_record_buffer)传到引擎层

buffer本身是引擎无关的,在sever层分配,通过handler成员m_record_buffer传递到引擎层。

增加新的接口,判断是否支持Record buffer, 目前仅InnoDB支持,需要满足如下条件 (ref set_record_buffer):

  1. access type 不是 ref, ref_or_null, index_merge, range, index 或者ALL
  2. 不是临时表
  3. 不是loose index scan
  4. 进入InnoDB引擎层判断((row_prebuilt_t::can_prefetch_records))
       return select_lock_type == LOCK_NONE // 只读查询
                && !m_no_prefetch   // 允许prefetch
                && !templ_contains_blob // 没有BLOB, TEXT, JSON, GEOMETRY这些大列
                && !templ_contains_fixed_point // 不是空间数据类型DATA_POINT
                && !clust_index_was_generated   // 需要用户定义的primary key 或者唯一索引(被隐式的用作Pk)
                && !used_in_HANDLER // 不是通过HANDLER访问的
                && !innodb_api // 不是通过类似innodb memcached访问的
                && template_type != ROW_MYSQL_DUMMY_TEMPLATE //不是check table
                && !in_fts_query; // 不是全文索引查询

在InnoDB中,当record buffer被配置时,就使用server层提供的record buffer,而不是row_prebuilt_t::fetch_cache

官方博客对此改进的介绍:http://mysqlserverteam.com/mysql-8-0-faster-batch-record-retrieval/

WL#9250: Split LOCK_thd_list and LOCK_thd_remove mutexes

该Worklog的目的是改进短连接场景下的性能,对thd list的操作可能导致比较高的锁竞争。

解决方案也比较传统,就是进行分区,将链表thd_list划分成多个数组,目前为8个分区,相应的锁LOCK_thd_removeLOCK_thd_list锁也进行了分区。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4月前
|
存储 关系型数据库 MySQL
《MySQL 简易速速上手小册》第3章:性能优化策略(2024 最新版)
《MySQL 简易速速上手小册》第3章:性能优化策略(2024 最新版)
72 2
|
4月前
|
存储 缓存 关系型数据库
16. mysql的性能优化
优化MySQL性能主要从设计、功能和架构三方面入手。选择适合的存储引擎(如MyIsam用于高读写,InnoDB处理事务),选取恰当的字段类型(如使用char for定长字符串,tinyint for有限状态),并遵循数据库范式设计。功能上,利用索引优化查询,使用缓存减少数据库负担,并进行分库分表。在架构上,实施主从复制、读写分离和负载均衡来提升性能。
42 0
|
4月前
|
存储 监控 关系型数据库
轻松入门Mysql:MySQL性能优化与监控,解锁进销存系统的潜力(23)
轻松入门Mysql:MySQL性能优化与监控,解锁进销存系统的潜力(23)
|
3月前
|
存储 关系型数据库 MySQL
MySQL数据库进阶第三篇(MySQL性能优化)
MySQL数据库进阶第三篇(MySQL性能优化)
|
3天前
|
存储 SQL 关系型数据库
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
MySQL调优主要分为三个步骤:监控报警、排查慢SQL、MySQL调优。 排查慢SQL:开启慢查询日志 、找出最慢的几条SQL、分析查询计划 。 MySQL调优: 基础优化:缓存优化、硬件优化、参数优化、定期清理垃圾、使用合适的存储引擎、读写分离、分库分表; 表设计优化:数据类型优化、冷热数据分表等。 索引优化:考虑索引失效的11个场景、遵循索引设计原则、连接查询优化、排序优化、深分页查询优化、覆盖索引、索引下推、用普通索引等。 SQL优化。
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
|
4月前
|
SQL 缓存 关系型数据库
mysql性能优化-慢查询分析、优化索引和配置
mysql性能优化-慢查询分析、优化索引和配置
203 1
|
1月前
|
存储 关系型数据库 MySQL
"深入探索MySQL临时表:性能优化利器,数据处理的灵活之选"
【8月更文挑战第9天】MySQL临时表专为存储临时数据设计,自动创建与删除,仅在当前会话中存在,有助于性能优化。它分为本地临时表和全局临时表(通过特定逻辑模拟)。创建语法类似于普通表,但加TEMPORARY或TEMP关键字。适用于性能优化、数据预处理和复杂查询,需注意内存占用和事务支持问题。合理使用可大幅提升查询效率。
56 2
|
2月前
|
SQL 存储 数据库
MySQL设计规约问题之性能分析工具如Sql explain、show profile和mysqlsla在数据库性能优化中有什么作用
MySQL设计规约问题之性能分析工具如Sql explain、show profile和mysqlsla在数据库性能优化中有什么作用
|
3月前
|
SQL 关系型数据库 MySQL
MySQL性能优化实战:从索引策略到查询优化
MySQL性能优化聚焦索引策略和查询优化。创建索引如`CREATE INDEX idx_user_id ON users(user_id)`可加速检索;复合索引考虑字段顺序,如`idx_name ON users(last_name, first_name)`。使用`EXPLAIN`分析查询效率,避免全表扫描和大量`OFFSET`。通过子查询优化分页,如LIMIT配合内部排序。定期审查和调整策略以提升响应速度和降低资源消耗。【6月更文挑战第22天】
458 2
|
3月前
|
存储 关系型数据库 MySQL
深入探索MySQL:成本模型解析与查询性能优化
深入探索MySQL:成本模型解析与查询性能优化

相关产品

  • 云数据库 RDS MySQL 版