MySQL8.0 - 新特性 - 临时表改进

本文涉及的产品
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: temptable engine 我们知道UNION, DERIVED TABLE, CTE, 子查询或者distinct order by之类的查询都可能用到临时表来存储中间结果,官方文档中列举了几种场景。

update 15/6/2019

最新release的MySQL 8.0.16中包含的临时表改动:

  • WL#11974, 不再支持myisam作为内部临时表转储磁盘时使用,参数internal_tmp_disk_storage_engine被移除掉了, 直接使用innodb作为内部内存表的持久化引擎
  • 新增参数temptable_use_mmap, 默认打开,表示当使用TempTable的临时表的内存占用超过temptable_max_ram之后,就使用memory map的方式去扩展临时文件到内存。如果为off,则使用innodb引擎来存储temptable数据

temptable engine

我们知道UNION, DERIVED TABLE, CTE, 子查询或者distinct order by之类的查询都可能用到临时表来存储中间结果,官方文档中列举了几种场景。内存引擎可以通过参数
internal_tmp_mem_storage_engine来选择: temptable(default) 或者memory引擎。本文只讨论temptable引擎

当内存超出temptable引擎限制( temptable_max_ram, 默认1GB)时,将转换成磁盘数据,这里也可以选择是存储成innodb还是myisam(参数). 但COMMON TABLE EXPRESSION(CTE)不允许使用myisam引擎

Note: 由于innodb有行长度限制,可能报row size too large 或者too many columns之类的错误,可以通过设置internal_tmp_disk_storage_engine来绕过限制。

MySQL8.0.16引入了新的参数temptable_use_mmap,用来控制temptable引擎是否磁盘数据转换成Innodb存储,还是内存映射文件。

temptable引擎和memory引擎本质上类似,但最大的不同时可以支持变长类型(例如blob, text, json, geometry等),例如varchar(100)的数据"abcd"应该只占用4个字节而非100个字节。

在之前的版本中当存在Lob类型时,数据会直接转换成磁盘存储。而WL#11613对此做了修改:在内存中使用数组来维护大字段,每个字段包含数据长度和数据指针。在数组之后连续的存储列值,没有padding(如果使用memory引擎,则会padding)。[官方博客]的评测中由于无需在遇到lob时转换成磁盘存储,相比之前的版本可能获得数倍的性能提升。

从设计上temptable引擎支持hash Index和tree index,允许一个inserter和多个reader, 插入不影响reader的cursor。

笔者的主要关注点在innodb,由于从5.7开始MySQL对Innodb做了大量的优化(cursor优化,无redo log, 去除代码路径上的各种锁),因此默认情况下使用innodb作为内部临时表的磁盘存储.

可以通过查询performance schema表来监控内存和磁盘上的临时表占用空间:

mysql> SELECT * FROM performance_schema.memory_summary_global_by_event_name WHERE event_name like '%temptable%'\G
*************************** 1. row ***************************
                  EVENT_NAME: memory/temptable/physical_disk
                 COUNT_ALLOC: 0
                  COUNT_FREE: 0
   SUM_NUMBER_OF_BYTES_ALLOC: 0
    SUM_NUMBER_OF_BYTES_FREE: 0
              LOW_COUNT_USED: 0
          CURRENT_COUNT_USED: 0
             HIGH_COUNT_USED: 0
    LOW_NUMBER_OF_BYTES_USED: 0
CURRENT_NUMBER_OF_BYTES_USED: 0
   HIGH_NUMBER_OF_BYTES_USED: 0
*************************** 2. row ***************************
                  EVENT_NAME: memory/temptable/physical_ram
                 COUNT_ALLOC: 2
                  COUNT_FREE: 0
   SUM_NUMBER_OF_BYTES_ALLOC: 2097152
    SUM_NUMBER_OF_BYTES_FREE: 0
              LOW_COUNT_USED: 0
          CURRENT_COUNT_USED: 2
             HIGH_COUNT_USED: 2
    LOW_NUMBER_OF_BYTES_USED: 0
CURRENT_NUMBER_OF_BYTES_USED: 2097152
   HIGH_NUMBER_OF_BYTES_USED: 2097152
2 rows in set (0.03 sec)

temptable引擎实现了自己的内存分配器来减少对系统的内存分配释放调用,封装从磁盘上通过mmap进行分配的策略。先从系统分配大块的内存,然后通过这些内存块来提供malloc/free请求. 每个block包含一个header以及一系列的chunk:

a. 每个block的结构如下:(quoted from worklog)

- bytes [0, 3]: 4 bytes for the block size (set at block creation and never
  changed later).
- bytes [4, 7]: 4 bytes for the number of used/allocated chunks from this
  block (set to 0 at block creation).
- bytes [8, 11]: 4 bytes for the offset of the first byte from the block
  start that is free and can be used by the next allocation request (set
  to 12 at block creation (3 * 4 bytes)). We call this first pristine offset.
- bytes [12, block size) a sequence of chunks appended to each other.

b. 每个chunk的结构

- bytes [0, 3]: 4 bytes that designate the offset of the chunk from
  the start of the block. This is used in order to be able to deduce
  the block start from a given chunk. The offset of the first chunk is
  12 (appended after the block size (4), number of allocated chunks (4)
  and the first pristine offset (4)).
- bytes [4, chunk size): user data, pointer to this is returned to the
  user after a successfull allocation request.

c. 分配内存:

- if the current block does not have enough space:
    create a new block and make it the current (lose the pointer to the
    previous current block).
- increment the number of allocated chunks by 1.
- in the first pristine location - write its offset from the block
  start (4 bytes).
- increment the first pristine offset with 4 + requested bytes by the user.
- return a pointer to the previous first pristine + 4 to the user.

d. 释放内存:

- read 4 bytes before the provided pointer and derive the block start.
- decrement the number of used chunks by 1.
- if this was the last chunk in the block and this is not the last block:
    destroy the block, returning the memory to the OS.
- keep the last block for reuse even if all chunks from it are removed, it
  will be destroyed when the thread terminates. When the last chunk from
  the last block is removed, instead of destroying the block reset its first
  pristine byte offset to 12.

内存分配器的定义和实现在文件storage/temptable/include/temptable/allocator.h

其他模块的定义都在目录storage/temptable/include/下,如果想深入了解该引擎的实现,可以阅读这些头文件代码,有比较详细的注释

InnoDB临时表

在innodb的代码里有大量使用dict_table_t::is_intrinsic()来判定执行路径,对于内部临时表而言,会去消除不必要的开销,例如表锁和事务开销等等。这里简单介绍下相关的代码。

插入操作

当插入临时表时,直接使用cursor进行操作,跳过事务和锁相关操作:

row_insert_for_mysql 
    |--> row_insert_for_mysql_using_cursor

对于临时表记录:

  • 其row_id取自表上递增计数器dict_table_t::sess_row_id, 事务id取自dict_table_t::sess_trx_id而非全局计数器(trx_sys->max_trx_id). 事务Id写入到记录中。

为什么还需要trx id ? 代码中的解释:

Intrinsic table are not shared so don't need a central trx-id
but just need a increased counter to track consistent view while
proceeding SELECT as part of UPDATE
  • 插入操作无需记录undo log, 因此需要通过插入的记录显式回滚(row_explicit_rollback),实际上就是将插入的记录进行标记删除
  • 索引上dict_index_t::last_ins_cur维护了上次插入位点的cursor, 这样对于顺序插入操作,无需每次都commit mtr,并能快速定位到btre上的插入点(row_ins_sorted_clust_index_entry)

    • delete/update操作会自动把cursor提交掉
    • 当存在blob/text类型时,不能cache cursor

查询操作

函数:

row_search_for_mysql
     |--> row_search_no_mvcc

由于表只对当前session可见,因此无需走mvcc判断。 查询在满足一定条件时也使用了缓存策略cursor的策略, 上次查询的cursor存储在dict_index_t::last_sel_cur中,无需频繁提交mini transaction, 该特性仅限于auto-generated clust index

临时表空间

在当前版本(8.0.15)的MySQL中,有两类临时表空间:

ibtmp1

在data目录下,具有固定的space id(s_temp_space_id = 0xFFFFFFFD)

Note: 在之前的版本中(例如5.7),使用ibtmp1来存储临时表数据和undo信息等,在每次重启时重新创建并使用新的space id.

在内存中对应的对象为srv_tmp_space,目前用于存储临时表的Undo log:

  • 正常shutdown(innodb_space_shutdown())或者重启时(srv_open_tmp_tablespace())重建文件
  • 回滚段初始化(trx_rseg_adjust_rollback_segments())
  • 回滚段内存对象在trx_sys_t::tmp_rsegs中,默认128个回滚段,与正常回滚段在事务开始时分配不同,临时表回滚段是在使用时才分配(trx_undo_report_row_operation() --> trx_assign_rseg_temp() -->get_next_temp_rseg)
Note: 通常查询产生的内部中间表只有插入和查询,因此无需记录undo log。但对于用户显式创建的临时表依然需要
innodb_temp目录下的临时表空间文件

这些文件以temp_{id}.ibt命名,主要是避免所有文件都存储在ibtmp1中,而ibtmp1是在重启时才会重置,就算表被删除了也不会缩减空间。

  • 在实例启动时,这些文件在目录innodb_temp_tablespaces_dir或者#innodb_temp(如果未显式指定)下被创建(ibt::open_or_create), 初始化创建10个文件.
  • 每个session在第一次请求创建临时表时,会从池中分配一个tablespace. 当这个tablespace被attach到该session时,所有临时表都创建在其中. 每个session最多可以有两个独立的tablespace,一个用于显式创建临时表,一个用于优化器创建的临时表。需要两个独立表空间的原因是未来可以在链接断开之前就单独回收优化器表的空间
dict_build_tablespace_for_table 
    |--> innodb_session->get_instrinsic_temp_tblsp()
    |--> innodb_session->get_usr_temp_tblsp()
  • 当pool中space不够用时,会自动进行扩展,每次扩展单位为10个文件
  • 在session断开时,将tablespace truncate并放回到pool中。所以如果临时表空间占用过大,可以通过中断链接的方式来释放
  • 可以通过is表查询tablespace占用的session id
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_SESSION_TEMP_TABLESPACES;
+----+------------+----------------------------+-------+----------+-----------+
| ID | SPACE      | PATH                       | SIZE  | STATE    | PURPOSE   |
+----+------------+----------------------------+-------+----------+-----------+
| 72 | 4294566162 | ./#innodb_temp/temp_10.ibt | 81920 | ACTIVE   | INTRINSIC |
|  0 | 4294566153 | ./#innodb_temp/temp_1.ibt  | 81920 | INACTIVE | NONE      |
|  0 | 4294566154 | ./#innodb_temp/temp_2.ibt  | 81920 | INACTIVE | NONE      |
|  0 | 4294566155 | ./#innodb_temp/temp_3.ibt  | 81920 | INACTIVE | NONE      |
|  0 | 4294566156 | ./#innodb_temp/temp_4.ibt  | 81920 | INACTIVE | NONE      |
|  0 | 4294566157 | ./#innodb_temp/temp_5.ibt  | 81920 | INACTIVE | NONE      |
|  0 | 4294566158 | ./#innodb_temp/temp_6.ibt  | 81920 | INACTIVE | NONE      |
|  0 | 4294566159 | ./#innodb_temp/temp_7.ibt  | 81920 | INACTIVE | NONE      |
|  0 | 4294566160 | ./#innodb_temp/temp_8.ibt  | 81920 | INACTIVE | NONE      |
|  0 | 4294566161 | ./#innodb_temp/temp_9.ibt  | 81920 | INACTIVE | NONE      |
+----+------------+----------------------------+-------+----------+-----------+
10 rows in set (0.00 sec)
  • temp tablespace有单独space id 段,内部预留了400k的space id 给temporary tablespace (s_min_temp_space_id , s_max_temp_space_id),足够使用.
  • space pool中大小不会缩小,也就是说只会扩展,不会收缩!

Reference

WL#8117: Compact In-Memory Temporary Tables
WL#11452 Support for BLOBs in temptable engine
WL#11613: InnoDB: Reclaim disk space occupied by temporary tables online
Internal Temporary Table Use in MySQL
InnoDB Temporary Tablespaces

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
4月前
|
SQL 监控 关系型数据库
MySQL事务处理:ACID特性与实战应用
本文深入解析了MySQL事务处理机制及ACID特性,通过银行转账、批量操作等实际案例展示了事务的应用技巧,并提供了性能优化方案。内容涵盖事务操作、一致性保障、并发控制、持久性机制、分布式事务及最佳实践,助力开发者构建高可靠数据库系统。
|
4月前
|
存储 关系型数据库 MySQL
介绍MySQL的InnoDB引擎特性
总结而言 , Inno DB 引搞 是 MySQL 中 高 性 能 , 高 可靠 的 存 储选项 , 宽泛 应用于要求强 复杂交易处理场景 。
202 15
|
4月前
|
关系型数据库 MySQL 数据库
MySql事务以及事务的四大特性
事务是数据库操作的基本单元,具有ACID四大特性:原子性、一致性、隔离性、持久性。它确保数据的正确性与完整性。并发事务可能引发脏读、不可重复读、幻读等问题,数据库通过不同隔离级别(如读未提交、读已提交、可重复读、串行化)加以解决。MySQL默认使用可重复读级别。高隔离级别虽能更好处理并发问题,但会降低性能。
207 0
|
SQL 安全 关系型数据库
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
事务是MySQL中一组不可分割的操作集合,确保所有操作要么全部成功,要么全部失败。本文利用SQL演示并总结了事务操作、事务四大特性、并发事务问题、事务隔离级别。
5258 56
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
|
存储 Oracle 关系型数据库
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
从基本特性、技术选型、字段类型、事务提交方式、SQL语句、分页方法等方面对比Oracle和MySQL的区别。
2763 18
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
|
JSON 关系型数据库 MySQL
MySQL 8.0 新特性
MySQL 8.0 新特性
490 10
MySQL 8.0 新特性
|
关系型数据库 MySQL
mysql事务特性
原子性:一个事务内的操作统一成功或失败 一致性:事务前后的数据总量不变 隔离性:事务与事务之间相互不影响 持久性:事务一旦提交发生的改变不可逆
|
存储 关系型数据库 MySQL
MySQL 8.0特性-自增变量的持久化
【11月更文挑战第8天】在 MySQL 8.0 之前,自增变量(`AUTO_INCREMENT`)的行为在服务器重启后可能会发生变化,导致意外结果。MySQL 8.0 引入了自增变量的持久化特性,将其信息存储在数据字典中,确保重启后的一致性。这提高了开发和管理的稳定性,减少了主键冲突和数据不一致的风险。默认情况下,MySQL 8.0 启用了这一特性,但在升级时需注意行为变化。
289 1
|
SQL 安全 关系型数据库
MySQL8.2有哪些新特性?
【10月更文挑战第3天】MySQL8.2有哪些新特性?
403 2
|
算法 关系型数据库 MySQL
一天五道Java面试题----第七天(mysql索引结构,各自的优劣--------->事务的基本特性和隔离级别)
这篇文章是关于MySQL的面试题总结,包括索引结构的优劣、索引设计原则、MySQL锁的类型、执行计划的解读以及事务的基本特性和隔离级别。

相关产品

  • 云数据库 RDS MySQL 版
  • 推荐镜像

    更多