前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Buffer pool 详解

Buffer pool 详解

作者头像
腾讯数据库技术
发布2019-12-03 20:42:17
2.5K0
发布2019-12-03 20:42:17
举报

提示:公众号展示代码会自动折行,建议横屏阅读


1 综述

buffer pool 是 innodb的数据缓存,保存了 data page、index page、undo page、insert buffer page、adaptive hash index、data dictionary、lock info。buffer pool绝大多数page都是 data page(包括index page)。innodb 还有日志缓存 log buffer,保存redo log。

下图可以看出来 innodb buffer pool在mysql中的位置。

所有数据页的读写操作都需要通过buffer pool进行,innodb 写操作,先把数据和日志写入 buffer pool 和 log buffer,再由后台线程将 buffer 中的内容刷到磁盘。读page时,如果buffer pool中没有,则将page从磁盘读取到buffer pool中,这一过程可能伴随着淘汰buffer pool中旧的page。事务持久性由redo log 落盘保证,buffer pool只是为了提高读写效率。为了提高读写效率,在保留mysql正常运行所需内存的情况下,buffer pool最好尽可能大。

buffer pool 代码主要在以下几个文件中:

代码语言:javascript
复制
folder path: storage/innobase/buf

file list:

buf0buddy.cc(880 lines): Binary buddy allocator for compressed pages
buf0buf.cc(7050 lines): The database buffer buf_pool
buf0checksum.cc(159 lines): Buffer pool checksum functions, also linked from /extra/innochecksum.cc
buf0dblwr.cc(1289 lines): Doublwrite buffer module
buf0dump.cc(810 lines): Implements a buffer pool dump/load.
buf0flu.cc(3838 lines): The database buffer buf_pool flush algorithm
buf0lru.cc(2786 lines): The database buffer replacement algorithm
buf0rea.cc(902 lines): The database buffer read

2 数据结构

innodb buffer pool 逻辑上分为三级结构,instance、chunk、block;对应结构体定义 buf_pool_t、buf_chunk_t、buf_block_t。

buf_pool_t

instance 数量由参数 innodb_buffer_pool_instances 控制,每个 instance 分到 innodb_buffer_pool_size/innodb_buffer_pool_instances 大小的内存,在实例启动时分配,实例 shutdown 时释放 每个buffer pool instance中保存的 page 由 page_id 决定:page_id >> 6 后,再对instance个数取模,即得出该 page 具体存放在哪个buffer pool instance(buf_pool_get)。

LRU List

buf_pool_t::LRU 用于从buffer pool中淘汰不常用的页。和普通的 LRU list 有所区别,这里的 LRU 分为 old 和 new 两个部分,参数 innodb_old_blocks_pct 控制 old 部分的比例,参数 innodb_old_blocks_time 控制新加入的old 部分的页,需要等待多久才能被移动到 new 部分。这么设计是为了防止刚刚被访问的页占满 buffer pool,但是这些页可能很久都不会再用了,比如低频全表扫描语句。有些页(adaptive hash、lock info等)虽然通过Free List申请,但并不加入 LRU List。

Hazard Point

加速逆向的逻辑链遍历。

Free List

buf_pool_t::free free page的集合,用于申请page;如果申请 page 时这个链表为空,则需要强制刷脏来加入新的free page。

Flush List

buf_pool_t::flush_list 被修改过的 page(脏页),但是还没来得及被刷新到磁盘上。Flush List 中的 page 一定在 LRU 中,反之不一定。一个数据页可能会在不同的时刻被修改多次,在数据页上记录了最老(也就是第一次)的一次修改的lsn,即oldest_modification。不同数据页有不同的oldest_modification,FLU List 中的节点按照oldest_modification排序,链表尾是最小的,也就是最早被修改的数据页,当需要从FLU List中淘汰页面时候,从链表尾部开始淘汰。加入FLU List,需要使用flush_list_mutex保护,所以能保证FLU List中节点的顺序。

page_hash

可以通过 page id 定位 buffer pool 中的 page,是最常用的查找page的手段。 page_hash 是最朴素的 hash,有大小为table_size的链表数组,数组中每个链表有 rwlock 保护,通过 (key ^ UT_HASH_RANDOM_MASK2) % table_size 取得 hash value,冲突时加入链表末尾(hash0hash.h)。

buf_chunk_t

chunk 大小由 innodb_buffer_pool_chunk_size 决定,引入chunk 是为了方便在线修改 innodb_buffer_pool_size,修改时以 chunk 为单位拷贝 buffer pool。

buf_block_t

buf_block_t 的 第一个字段是 buf_page_t,保存了page 元信息,放第一个的目的是为了 buf_block_t 和 buf_page_t 的指针可以相互转换。第二个字段是 frame,指向真正的数据页。buf_page_t 中有 state 字段表名 page 当前状态主要有以下几种:

  • BUF_BLOCK_NOT_USED 此page处于 Free List 中,未被使用
  • BUF_BLOCK_READY_FOR_USE 已从 Free List 中取出,准备使用 (buf_LRU_get_free_block)
  • BUF_BLOCK_FILE_PAGE 正常使用中的page,LRU List 中的页大部分是这种状态,page初始化后变成这个状态(buf_page_init)

内存分配逻辑可以参考函数 buf_pool_init(buf0buf.cc)。

3 一个buffer pool page的生命历程

1. 实例启动后,按照 instance、chunk、block 三级结构进行初始化,所有page都加入 Free List。

2.读取page的操作一般由 Mini transaction 执行,最顶层的读取操作是 buf_page_get_gen。首先通过page_id 在 page_hash中查找,如果找不到,则需要从文件中读取,进行步骤【3】。之后如果page在LRU 的old 部分,则移动至young 部分。再根据 rw_latch 参数对page加锁。最后返回找到的block。

3. buffer pool 中找不到对应page,则需要调用 buf_read_page 从文件中异步读取。buf_read_page_low 先调用 buf_page_init_for_read 在buffer pool 中初始化一个page (步骤【4】),然后调用 fil_io 从文件读入page。进行如果第一次没有从文件中读取到page,会调用 buf_read_ahead_random 预读一部分页进buffer pool,避免频繁调用 buf_read_page。

4. buf_page_init_for_read 调用 buf_LRU_get_free_block(持有 buf_pool->mutex) 先尝试从 Free List 中获得一个 page(函数叫 buf_LRU_get_free_only,并没看出来和 LRU 有什么关系),如果失败,再尝试从 LRU 中获取一个找到一个已经flush的脏页,移入Free List(buf_LRU_scan_and_free_block),第一次只尝试扫描 LRU 尾部,从第二次开始则扫描整条 LRU List。扫描 LRU 尾部如果还没有找到可以free的page,则调用 buf_flush_single_page_from_LRU 从LRU 尾部开始,找到一个脏页强制 flush,这个操作涉及文件IO,对性能影响极大,应该尽量避免。 为了避免多个线程读取同一page,找到 free block 后会进行如下操作: a. 依次持有 buf_pool->mutex 和 page_hash X lock b. 检查page_hash中是否已读入该page,如果已读入,说明其他线程已经完成IO c. 持有 block->mutex 并将 block 加入 page_hash中 d. 设置 IO FIX 为 BUF_IO_READ e. 释放 hash lock f. 将 block 加入 LRU 中的 old 部分 g. 持有一个 BUF_IO_READ pass-type 的 block X lock,可以等待 read page 结束。这个 X 锁会被 IO-handler thread 释放(io_handler thread 调用 buf_page_io_complete 释放 rw_lock)

5. buffer pool 中修改过的页需要标记成脏页,即buffer pool中的page数据和磁盘中不同,刷脏就是将buffer pool 脏页中的数据flush 到磁盘。Mini Transaction commit时,如果block是第一次被修改,则会加入 Flush List,以后的修改无需加入Flush List。buf_page_t 中有字段oldest_modification 来标记最早修改page的lsn,如果字段为0,说明这是第一次修改,需要加入 Flush List。

6. Flush List 中的 page 一般由 page cleaner 线程刷到磁盘,特殊情况是用户线程调用buf_flush_single_page_from_LRU 刷一个脏页。参数 innodb_page_cleaners 控制有几个 page cleaner 线程,默认4个。其中一个 buf_flush_page_cleaner_coordinator 线程,其他都是 buf_flush_page_cleaner_worker 线程。 coordinator 在某些情况下sleep:目前数据库有活跃的操作或者Buffer Pool的读任务, 睡眠1s。 当前的时间超过了上一次Flush设置的下一次刷盘时间(ut_time_ms() > next_loop_time), 则设置OS_SYNC_TIME_EXCEEDED状态。 sleep 结束后调用 page_cleaner_flush_pages_recommendation 建议每个 buffer pool instance 需要刷多少脏页,再通过 pc_request 调用 os_event_set(page_cleaner->is_requested) 唤醒 worker 线程调用 pc_flush_slot 刷脏。之后协调线程也执行 pc_flush_slot 刷脏。 所有 page cleaner 线程共同通过一个 page_cleaner_t 对象保存刷脏信息,page_cleaner_t 中还有 page_cleaner_slot_t 字段保存每个 buffer pool instance 的刷脏状态,一个 instance 对应一个 slot。 每个线程先刷新 LRU List(buf_do_LRU_batch)【7】再刷新 Flush List(buf_do_flush_list_batch)【8】 每个Flush List/LRU List 最多只能容许一个线程批量刷脏,如果已经有线程在这个 List 上刷脏,buf_flush_start 会返回失败

7. buf_do_LRU_batch 从倒序遍历 LRU,先判断 buf_flush_ready_for_replace,如果满足条件则调用 buf_LRU_free_page free 该 page

代码语言:javascript
复制
return (bpage->oldest_modification == 0 &&
    bpage->buf_fix_count == 0 &&
    buf_page_get_io_fix(bpage) == BUF_IO_NONE)

oldest_modification == 0: 表示这个Block没有被修改. bpage->buf_fix_count == 0: buf_fix_count表示有多少操作在fix该页. buf_page_get_io_fix(bpage) == BUF_IO_NONE: 表示该页目前没有任何操作. 如果page不能直接free,调用 buf_flush_ready_for_flush 判断能否 flush,可以的话则调用 buf_flush_page_and_try_neighbors flush page 并且尝试flush 旁边的页

代码语言:javascript
复制
if (bpage->oldest_modification == 0
    || buf_page_get_io_fix(bpage) != BUF_IO_NONE {
    return (false);
}

oldest_modification == 0: 表示这个Block没有被修改,即无须Flush buf_page_get_io_fix(bpage) != BUF_IO_NONE: 表示目前该Page存在操作,不允许进行Flush.

8.buf_do_flush_list_batch 倒序遍历 Flush List,直接调用 buf_flush_page_and_try_neighbors

4 官方刷脏方式的局限和 Percona 的改进

官方的 page cleaner 线程 存在以下问题:

1. LRU List刷脏在先,Flush list的刷脏在后,但是是互斥的。也就是说在进Flush list刷脏的时候,LRU list不能继续去刷脏,必须等到下一个循环周期才能进行

2. 刷脏的时候,page cleaner coodinator会等待所有的page cleaner线程完成之后才会继续响应刷脏请求。这带来的问题就是如果某个buffer pool instance比较热的话,page cleaner就不能及时进行响应

Percona 的改进:

1. 将 LRU List 刷脏和 Flush List 刷脏解耦,两个链表可以并行刷脏

2. 解除各个 buffer pool instance 刷脏线程直接的同步限制,各个 instance 独立选择刷脏时机


腾讯数据库技术团队对内支持QQ空间、微信红包、腾讯广告、腾讯音乐、腾讯新闻等公司自研业务,对外在腾讯云上支持TencentDB相关产品,如CynosDB、CDB、CTSDB、CMongo等。腾讯数据库技术团队专注于持续优化数据库内核和架构能力,提升数据库性能和稳定性,为腾讯自研业务和腾讯云客户提供“省心、放心”的数据库服务。此公众号和广大数据库技术爱好者一起,推广和分享数据库领域专业知识,希望对大家有所帮助。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-28,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 腾讯数据库技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 提示:公众号展示代码会自动折行,建议横屏阅读
    • 2 数据结构
      • buf_pool_t
    • 4 官方刷脏方式的局限和 Percona 的改进
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    http://www.vxiaotou.com