[WorkLog] InnoDB Faster truncate/drop table space

简介: 这个系列, 介绍upstream 一些有意思的worklog **问题** 在InnoDB 现有的版本里面, 如果一个table space 被truncated 或者 drop 的时候, 比如有一个连接创建了临时表, 连接断开以后, 对应的临时表都需要进行drop 操作. InnoDB 是需要将该tablespace 对应的所有的page 从LRU/FLUSH li

这个系列, 介绍upstream 一些有意思的worklog

问题

在InnoDB 现有的版本里面, 如果一个table space 被truncated 或者 drop 的时候, 比如有一个连接创建了临时表, 连接断开以后, 对应的临时表都需要进行drop 操作.

InnoDB 是需要将该tablespace 对应的所有的page 从LRU/FLUSH list 中删除, 如果没有这个操作, 新的table 的table spaceid 如果重复的话, 那么就可能访问到脏数据.

为了将这些page 删除, 那么就需要全部遍历LRU/FLUSH list, 当bp 特别大的时候, 这样遍历的开销是很大的, 并且无论这个要删除的table 有多大, 都需要将这些LRU/FLUSH list 全部遍历..

解决方法

解决方法和之前解决undo ACID DDL 的方法类似, 核心思想就是通过引用计数的方法, 对table_space 加reference, 然后后续lazy delete

bp 上的每一个page 都有自己对应的version, 当table space 被drop/rename 的时候, 只需要对fil_space 的version + 1, 那么bp 中该fil_space 对应的page 就因为version < fil_space.current_version 而变得无效.

原先由drop/rename tablespace 触发的space_delete 操作就变的非常的轻量. 后续定期的将这些stable page 删除或者复用即可

不过带来的额外开销就是, 每一次访问bp 中的一个page 就需要确认当前page 是否过期.

具体实现

buf_page_t 增加 m_space, m_version.

Additions to buf_page_t {
 ...
 // 指向对应的fil_space_t
 fil_space_t *m_space{};

 // Version number of the page to check for stale pages. This value is
 // "inherited" from the m_space->m_version when we init a page.
 // page 的version number, 在page_init 的时候设置成m_space->m_version
 uint32_t m_version{};
};

fil_space_t 增加m_version, m_n_ref_count.

m_version 就是当前fil_space_t 的版本号, 每次delete/truncate 就会 + 1

m_n_ref_count: bp 每增加一个page , m_n_ref_count + 1, 只能等到m_n_ref_count == 0 的时候, 改fil_space 才能被删除, 否则bp 里面的m_space 指针就会指向空

Additions to  fil_space_t {
 ...
 // Version number of the instance, not persistent. Every time we truncate
 // or delete we bump up the version number.
 lsn_t m_version{};

 // Reference count of how many pages point to this instance. An instance cannot
 // be deleted if the reference count is greater than zero. The only exception
 // is shutdown.
 std::atomic_int m_n_ref_count{};
};

增加了lazy delete fil_space 以后, 那么什么时候将内存中的fil_space_t 删除呢?

最后的删除操作在 master_thread 会定期执行, 将之前已经标记删除, 放入到m_deleted_spaces 中的space 一起删除

/ Purge any deleted tablespace pages. /
fil_purge(); => fil_shard.purge()

  void purge() {
    mutex_acquire();
    for (auto it = m_deleted_spaces.begin(); it != m_deleted_spaces.end();) {
      auto space = it->second;
      // has_no_references() 说明该fil_space 对应的bp 已经都删除了, 那么该space 就可以删除
      if (space->has_no_references()) {
        ut_a(space->files.front().n_pending == 0);
        space_free_low(space);
        it = m_deleted_spaces.erase(it);
        ... }
    mutex_release();
  }

drop/rename tablespace

执行drop/rename tablespace 的时候需要执行 row_drop_tablespace => fil_delete_tablespace => space_delete(space_id, buf_remove)

新增加 buf_remove_t 类型: BUF_REMOVE_NONE. 不需要移除该tablespace 的所有bp.

8.0.23 drop table 的时候, 执行 row_drop_tablespace => fil_delete_tablespace, 之前delete tablespace 的时候, 传入的是 BUF_REMOVE_ALL_NO_WRITE, 需要将该space 对应的bp 都清理才可以完成操作.

传入 BUF_REMOVE_NONE 就只需要将tablespace 标记删除, 放入到 m_deleted_spaces 中, 不需要清理bp, 然后将对应的物理文件删除即可. 该tablespace 对应bp 中的数据就变成 stale page, 后续会有操作将这些stale page 删除或者复用.

enum buf_remove_t {
  /** Don't remove any pages. */
  BUF_REMOVE_NONE,
  /** Remove all pages from the buffer pool, don't write or sync to disk */
  BUF_REMOVE_ALL_NO_WRITE,
  /** Remove only from the flush list, don't write or sync to disk */
  BUF_REMOVE_FLUSH_NO_WRITE,
  /** Flush dirty pages to disk only don't remove from the buffer pool */
  BUF_REMOVE_FLUSH_WRITE
};

BUF_REMOVE_ALL_NO_WRITE:

从flush list 和 LRU list 上面都删除, 数据不需要, 并且也不需要刷盘. 从LRU list 上面也都删除开销是比较大的, 因此更多的时候是使用BUF_REMOVE_FLUSH_NO_WRITE, 只删flush list, 不删LRU

一般来说truncate table 的时候是执行这个. 在5.6/5.7 里面, 由于truncate table 了以后, space id 是不会变的, 那么就必须把这些space 对应的page 都删除, 否则如果新的table 的space id 和老的space id 一致, 那就访问到脏数据了.

BUF_REMOVE_FLUSH_NO_WRITE:

从flush list 删除删除, 并且不需要刷盘, 直接丢弃掉. 和BUF_REMOVE_ALL_NO_WRITE 相比, 把从LRU list 上面删除的操作放到了后台来做, 因为lru list 的大小是远远大于flush list, 删除lru list 的成本是很大的, 因此放在后来执行

一般drop table 是执行这个操作, 让后台慢慢从lru list 里面把要drop 的tablespace 删除

BUF_REMOVE_FLUSH_WRITE:

从flush list 上删除, 并且刷脏, 那么就不需要从LRU list 上删除, 因为LRU list 上也是最新的

常用场景, 执行DDL 以后, DDL 只需要确保这个DDL 产生的page 必须进行刷脏. 执行刷脏逻辑

BUF_REMOVE_NONE:

只需要将tablespace 标记删除, 不需要清理bp, 该tablespace 对应bp 中的数据就变成 stale page, 后续会有操作将这些stale page 删除或者复用.

<那么什么时候会将这些 stale page 删除呢?

总共有多个场景:

  1. 在正常从bp 中读取page 的时候, 如果读取到的page 是 stale, 那么通过执行 buf_page_free_stale() 将该page 进行删除操作
  2. 在从double write buffer Double_write::write_pages() 到磁盘的时候, 如果这个时候改page 的space file 已经被删除, 那么这个时候通过 buf_page_free_stale_during_write() 进行删除
  3. 在刷脏操作buf_flush_batch()的时候, 从LRU_list 或者 flush_list 拿取page, 如果发现该page 是stale, 并且没有io 操作在这个page 上面, 那么通过 buf_page_free_stale() 进行删除操作
  4. 在single page flush 的时候, 同样判断该page 是stale, 那么通过buf_page_free_stale() 进行删除
目录
相关文章
|
3月前
|
存储 网络协议 关系型数据库
MySQL8.4创建keyring给InnoDB表进行静态数据加密
MySQL8.4创建keyring给InnoDB表进行静态数据加密
101 1
|
5月前
|
存储 SQL 关系型数据库
MySQL底层概述—2.InnoDB磁盘结构
InnoDB磁盘结构主要包括表空间(Tablespaces)、数据字典(Data Dictionary)、双写缓冲区(Double Write Buffer)、重做日志(redo log)和撤销日志(undo log)。其中,表空间分为系统、独立、通用、Undo及临时表空间,分别用于存储不同类型的数据。数据字典从MySQL 8.0起不再依赖.frm文件,转而使用InnoDB引擎存储,支持事务原子性DDL操作。
384 100
MySQL底层概述—2.InnoDB磁盘结构
|
5月前
|
缓存 算法 关系型数据库
MySQL底层概述—1.InnoDB内存结构
本文介绍了InnoDB引擎的关键组件和机制,包括引擎架构、Buffer Pool、Page管理机制、Change Buffer、Log Buffer及Adaptive Hash Index。
379 97
MySQL底层概述—1.InnoDB内存结构
|
5月前
|
SQL 关系型数据库 MySQL
MySQL底层概述—10.InnoDB锁机制
本文介绍了:锁概述、锁分类、全局锁实战、表级锁(偏读)实战、行级锁升级表级锁实战、间隙锁实战、临键锁实战、幻读演示和解决、行级锁(偏写)优化建议、乐观锁实战、行锁原理分析、死锁与解决方案
272 24
MySQL底层概述—10.InnoDB锁机制
|
3月前
|
SQL 缓存 关系型数据库
使用温InnoDB缓冲池启动MySQL测试
使用温InnoDB缓冲池启动MySQL测试
71 0
|
5月前
|
存储 缓存 关系型数据库
MySQL底层概述—5.InnoDB参数优化
本文介绍了MySQL数据库中与内存、日志和IO线程相关的参数优化,旨在提升数据库性能。主要内容包括: 1. 内存相关参数优化:缓冲池内存大小配置、配置多个Buffer Pool实例、Chunk大小配置、InnoDB缓存性能评估、Page管理相关参数、Change Buffer相关参数优化。 2. 日志相关参数优化:日志缓冲区配置、日志文件参数优化。 3. IO线程相关参数优化: 查询缓存参数、脏页刷盘参数、LRU链表参数、脏页刷盘相关参数。
218 12
MySQL底层概述—5.InnoDB参数优化
|
5月前
|
存储 SQL 关系型数据库
MySQL底层概述—4.InnoDB数据文件
本文介绍了InnoDB表空间文件结构及其组成部分,包括表空间、段、区、页和行。表空间是最高逻辑层,包含多个段;段由若干个区组成,每个区包含64个连续的页,页用于存储多条行记录。文章还详细解析了Page结构,分为通用部分(文件头与文件尾)、数据记录部分和页目录部分。此外,文中探讨了行记录格式,包括四种行格式(Redundant、Compact、Dynamic和Compressed),重点介绍了Compact行记录格式及其溢出机制。最后,文章解释了不同行格式的特点及应用场景,帮助理解InnoDB存储引擎的工作原理。
MySQL底层概述—4.InnoDB数据文件
|
5月前
|
存储 缓存 关系型数据库
MySQL底层概述—3.InnoDB线程模型
InnoDB存储引擎采用多线程模型,包含多个后台线程以处理不同任务。主要线程包括:IO Thread负责读写数据页和日志;Purge Thread回收已提交事务的undo日志;Page Cleaner Thread刷新脏页并清理redo日志;Master Thread调度其他线程,定时刷新脏页、回收undo日志、写入redo日志和合并写缓冲。各线程协同工作,确保数据一致性和高效性能。
MySQL底层概述—3.InnoDB线程模型
|
5月前
|
存储 SQL 缓存
MySQL原理简介—2.InnoDB架构原理和执行流程
本文介绍了MySQL中更新语句的执行流程及其背后的机制,主要包括: 1. **更新语句的执行流程**:从SQL解析到执行器调用InnoDB存储引擎接口。 2. **Buffer Pool缓冲池**:缓存磁盘数据,减少磁盘I/O。 3. **Undo日志**:记录更新前的数据,支持事务回滚。 4. **Redo日志**:确保事务持久性,防止宕机导致的数据丢失。 5. **Binlog日志**:记录逻辑操作,用于数据恢复和主从复制。 6. **事务提交机制**:包括redo日志和binlog日志的刷盘策略,确保数据一致性。 7. **后台IO线程**:将内存中的脏数据异步刷入磁盘。
224 12
|
7月前
|
存储 缓存 关系型数据库
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)
MySQL的存储引擎是其核心组件之一,负责数据的存储、索引和检索。不同的存储引擎具有不同的功能和特性,可以根据业务需求 选择合适的引擎。本文详细介绍了MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案。
1184 2
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)