什么是内存表

内存表,就是放在内存中的表,所使用内存的大小可通过My.cnf中的max_heap_table_size指定,如max_heap_table_size=1024M 内存表满后,会提示数据满错误。 ERROR 1114 (HY000): The table ‘abc’ is full

内存表的特性

  • 内存表的表定义是存放在磁盘上的,扩展名为.frm, 所以重启不会丢失。
  • 内存表的数据是存放在内存中的,所以重启会丢失数据
  • 内存表支持AUTO_INCREMENT列 (ps, 一些网站资源说不支持)
  1. mysql> show create table heap_test;
  2. | heap_test | CREATE TABLE `heap_test` (
  3. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  4. `a1` char(8) DEFAULT NULL,
  5. `a2` char(8) DEFAULT NULL,
  6. PRIMARY KEY (`id`),
  7. KEY `a1` (`a1`(2))
  8. ) ENGINE=MEMORY AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
  • 内存表表在所有客户端之间共享
  • 在数据库复制时,如果主机当掉,则会在binLog中自动加入delete from [内存表],将slave的数据也删除掉,以保证两边的数据一致性
  • 内存表不支持事务
  • 内存表是表锁,当修改频繁时,性能可能会下降。

内存表使用场景

因为内存表有两个主要的特性

  1. 多线程共享,对所有的用户连接是可见的,这一点和临时表完全不同。
  2. 数据存储在内存中,有很快的访问速度。

所以内存表很适合作为缓存,存储中间结果,和需要频繁访问的数据。

缺点也很明显,数据存在内存中, 服务器重启后数据会丢失。

内存表源码说明

创建内存表的相关参数 internal_tmp_mem_storage_engine

  • set internal_tmp_mem_storage_engine = memory, create_tmp_table 创建出的临时表引擎是 memory (heap 表)
  • set internal_tmp_mem_storage_engine=default , create_tmp_table 创建出的临时表引擎是 TempTable (temporary 表)

以下代码调研 临时表引擎为 memory

创建 heap 表

代码调用栈

  1. Sql_cmd_create_table::execute -> mysql_create_table -> mysql_create_table_no_lock -> create_table_impl -> rea_create_base_table -> ha_create_table -> handler::ha_create -> ha_heap::create

代码接口:ha_heap::create 这个接口做的事情主要是

  1. Prepare HP_CREATE_INFO (HP_CREATE_INFO 是存储了 heap 表的一些表结构定义信息)
  2. 创建 heap 表,把之前 准备好的 表定义信息, 并且赋值给 HP_SHARE 类的指针 (HP_SHARE 是一个描述每一个内存中的存储文件的类) 初始化 HP_KEYDEF, 和 HP_BLOCK,这两个类作为 HP_SHARE 的类成员变量 2.1 HP_KEYDEF 定义了 其索引描述符 2.2 在create_heap 表内,会调用一个 static 的方法 init_block, HP_BLOCK 是 memory 引擎树型存储结构的描述类

PS: MEMORY 引擎会将数据记录在一些定长的内存块中,每个内存块中记录数目存储在 HP_BLOCK 类中的

uint records_in_block{0}; /* Records in one heap-block */

每条记录的长度存储在 HP_BLOCK 中的 uint recbuffer{0}; / * Length of one saved record * / 给每条记录分配空间的内存长度为 recbuffer + 1, 最后一位是标记位,value = 1 为未删除,value = 0 为删除。

查询 heap 表

Memory 有两个全局变量,heap_open_list and heap_share_list.

  • 每一个 HP_SHARE 对应一个物理表
  • 每个表会有一个或者多个表描述类,所以每一个表描述类对应着一个 handler 实例和HP_INFO 实例 (这也是多个线程可以共享 heap 表的原因,每一个线程会有一个自己的表描述类)
  • 每一个handler 实例中有一个 HP_SHARE 的引用
  • hp_share_list 保存所有的 hp_share , hp_open_list 保存所有的 hp_info

查询时内存表主要会做三个步骤 开表 -> 预算有多少记录->读记录 代码接口: ha_heap::open -> ha_heap::info ->ha_heap::rnd_init

开表代码调用栈

  1. open_tables_for_query -> open_tables -> open_and_process_table -> open_table -> open_table_from_share -> handler::ha_open -> ha_heap::open

在 ha_heap::open 这个接口里主要做的事情是

  1. 开表,调用 HP_INFO* heap_open(const char* name, int mode) 根据表名打开对应的 heap 表 这里主要是检查 hp_share_list 中是否有要打开表的 hp_share 信息,如果找到了,则根据找到的 hp_share 信息初始化 新的 hp_info 信息,并将其加入到 hp_open_list 中
  2. 如果没有找到heap 表,create one 通过调用 heap_create

释放 heap 表

代码接口: ha_heap::close

这是一个释放临时表的接口,主要做的事情是从hp_open_list 中删除相应的HP_INFO, 然后–info->s->open_count, 将于 hp_info 关联的 hp_share 中的技术变量 open_count 减1 并且调用 my_free 释放 hp_info 当这个值减为0时候并且 hp_share 的 delete_on_close 为 true, 则调用 hp_free 释放 hp_share

内存表多实例共享

我们已知内存表的一个特性就是多实例共享,以下从三个方面描述多实例共享。 1.MySQL 建立连接 connect_a,连接的数据库中有 heap 表, MySQL 建立连接 connect_b, 选择与 connect_a 同一个库

这种场景下,当connect_a 建立时,系统会调用

  1. open_table_from_share -> handler::ha_open -> ha_heap::open

打开内存表,同时给当前的线程创建一个属于自己的表描述类,这个表描述类,对应着一个handler 和 HP_INFO 实例。 当 connect_b 建立时,系统的做法同 connect_a 建立时一样,会为当前的线程创建一个属于自己的表描述类,用于操做内存表

2.MySQL 建立连接 connect_a,连接的数据库中没有heap表, MySQL 建立连接 connect_b,选择与 connect_a 同一个库,创建内存表, connect_a, 查询该内存表

这种场景下,当connect_a 建立,系统调用open_table_from_share, 因为库中没有内存表,所以不会调用开启内存表接口的方法,connect_b 建立,也不会调用开启内存表接口的方法,当 connect_b 创建内存表后,系统会调用 创建内存表的接口 ha_heap::create。 表创建好后,connect_a 查询内存表,这个时候系统会为connect_a 的线程创建一个属于自己的表描述类,代码路径是 open_table_from_share -> handler::ha_open -> ha_heap::open

3.MYSQL 建立连接 connect_a, 连接的数据库中有heap表,MySQL 建立连接 connect_b, 选择与 connect_a 同一个表,connect_b 断开连接,MySQL 建立连接 connect_c, 选择与 connect_a 同一个库,并drop 内存表。connect_a 查询内存表 这种场景下,connect_a 和 connect_b,connect_c 建立后,系统都会为他们创建属于自己线程的表描述类,用于操作内存表。 connect_b 断开连接后,不会对内存表有任何影响。connect_c 调用 drop table 后,系统会调用 ha_heap::close 方法,这个方法具体的描述在上文已经阐述,connect_a 再去查内存表会抛出 ERROR 1146, table doesn’t exist 错误。

内存表设计思考

MySQL 数据库内部的多线程机制,提高了系统的吞吐量,并且提供的是插件式的存储引擎结构,这样就使得每一个表都可以设置自己的存储引擎。内存表也作为一种存储引擎可以供系统的表做选择,每一个表都有自己的一个表描述类(TABLE),多个线程中每个线程在处理请求的时候都会有自己的表描述类,每个表描述类都会分配一个自己的handler类实例,这不仅仅适用与内存表,也适用了其他的存储引擎。这样设计内存表,就是提供一种快速访问的存储引擎。大大提供数据的访问速率。

参考

  1. https://dev.mysql.com/doc/refman/8.0/en/memory-storage-engine.html
  2. https://www.cnblogs.com/lihaozy/p/3226962.html