文章目录
- 05 Buffer Pools
- database storage
- buffer pool manager
- buffer replacement policies
- Least-Recently Used(LRU)
- Clock
- LRU&Clock Q
- other memory pools
- conclusion
05 Buffer Pools
缓存池、内存池,缓存磁盘上的数据库文的流动
- Problem #2:How the DBMS manages its memory and move data back-and-forth from disk.
数据库管理系统是怎样管理内存和移入移出磁盘的数据的?
内存中的内存池是使用缓存磁盘数据库文件的一部分,游戏例子就是打到第四关了需要提前缓存下一关的数据
用户如果修改了数据,首先是放在缓存池的(马上写回硬盘,频繁读写性能差)可能等缓存了一些数据了,或者使用一些方法异步的将这些数据写回去,缓存池一方面缓存数据,一方面缓存数据的更新
database storage
数据库存储的两大控制(空间和时间)
-
Spatial Control:【空间控制】
→ 怎么把页写道磁盘上
→ 把用户读的数据放在缓存池上,读的时候直接从缓存池中读
-
Temporal Control:【时间控制】
- 最大限度的减少用户在存取磁盘的等待(写的时候修改新数据不要马上写回硬盘,读的时候不要等待数据从磁盘中读出来,最佳的方式是用户读的时候你已经从硬盘中将数据读出来了,例如一些高频数据可以进行预读)
→ 什么时候将磁盘的内存页读到内存中去
→ 什么时候将脏数据(用户上传和修改的修数据)写回到磁盘中去
把数据从磁盘读到内存中去,才能交给后续的执行器等执行
buffer pool manager
缓存池的管理器
-
buffer pool organizations:【缓存池的策略】
内存上的一个帧和缓存上的一个页是对应的,怎么样索引内存池中的页?
- buffer pool meta-data【缓存池的元数据】
怎么知道缓存池上缓存了那些数据?,引入了相当于目录索引的机制,有一个page table对应缓存池加载的page、而且不单单是索引,可以给索引打上标记,告诉我这个数据以后还要用,不要给我清除掉、还可以使用锁将一个page table的一个位置锁住,用于以后加载特定数据存放
-
Locks VS. Latches
提到🔒锁,就辨析了一下Locks&Latches两种
- Locks:(一般指的是一个逻辑上的🔒,我想读这个表,别人都不能读)(抽象的)
- Latches:(具体的底层的🔒,我把和我操作相关的page加上锁,一般叫latches)(具体的)(一般有时候叫mutex)
-
page table&page directory(不做区分,意义不大)
-
allocation policies:【分配策略】内存(磁盘中缓存上来的页)中拿到这个页都放在哪里
-
global policies:【全局策略】
数据库会同时并发的跑很多事物,统一安排所有的事务所需文件页需要缓存到哪
-
local policies:【本地策略】
为每一个需要缓存的文件页有单独的策略
-
缓存池的策略:
-
Multiple Buffer Pools【多内存池】
→ 使用多个数据库缓冲实例
→ 打开数据库实例,一个实例可以有多个database,每一个database给一个缓存池
→ 很多数据库都是这样一个策略,索引专门一个内存池,存数据专门一个内存池,存元数据的有专门内存池,多种缓存数据类型的池子,降低锁的冲突
- Approach #1:Object Id:记住那条数据去了那个缓存池(按照具体数据分)
- Approach #2:Hashing:哈希将数据缓存出一个数,然后看具体去那个缓存池
这两个策略是降低冲突和锁竞争用的
-
Pre-Fetching【预读】
→ Sequential Scans【全表扫描】
要读整个表全遍历一遍,数据库就知道0-5(假定page0-5)全要,缓存池中使用完0,1page时可以通过将2、3page预读的方式来减少开销,然后读到2号页的时候就可以将4、5page预先的拉取到内存池中
→ Index Scans【读取索引上的一段】
树型索引B/B+Tree,加入找到100-250号之间,通过索引的树结构可以很好的去找到遍历的位置,然后将其的索引页加载到缓冲区中
-
Scan Sharing【共享扫描】
如果发现两个SQL在读同一个表,那么可以同步扫描(在扫描一个表的时候发现另一个查询也打算这么做,那么两个查询就可以合并为一个查询扫描)
- PG(postgres):两条查询Q1、Q2去扫描磁盘disk(page0~page5)buffer pool(3page_size),
Q1 SELECT SUM(val) FROM A; Q2 SELECT AVG(val) FROM A (LIMIT 100);
Q1先进入然后执行查询,buffer pool加入0、1、2page然后2page执行完成之后,page3替换掉page0,这时候Q2进入,因为Q2(最开始没有括号中的内容)Q2也要全表扫描,然后Q1&Q2因为都是全表扫描所以Q2&Q1一起执行,执行完了之后,Q2在回去执行前面的没有执行到的page,这就是共享扫描(两个query并发查询相同就合成一个扫描)
但是Scan Sharing会有问题,如果Q2加入limit100如果没有scan sharing那么Q2应该从page0开始扫描前一百个,但是有了scan sharingQ2可能就被Q1拐跑了,从Q1执行的位置开始扫描一百个,这种情况时可能出现不同的结果的。你不能控制数据库执行器是否做了这种优化不能控制是否从第一条开始,如果强制从某某条开始可能需要加限制条件比如说order by之类的
-
Buffer Pool Bypass【缓存池旁路】
有的数据我只用一次之后也不用了,你别给我进缓存池,扫描出来的数据放在一个没有池化的区域,扫描出来我就交给执行器,执行器执行完成我就将这部分内存释放,或者把内存给GC垃圾回收
很多数据库叫做“Light Scans”轻扫描(不进缓存池的扫描)
→ 如果出现算子需要从磁盘中读取一大片页的时候,很可能只使用一次,值是否就不让他进来
→ 或者sorting、joins的中间结果也是不进内存池的
-
OS oage cache【操作系统自己对于文件页的缓存】
操作系统对于文件也也有缓存的,两份缓存,这个时候需要绕过操作系统缓存,不需要操作系统缓存我们数据库的文件,操作系统不知道数据库的东西需要什么缓存策略什么时候淘汰,需要保留多久操作系统都不知道,OS就是一个简单的缓存和自己本地的缓存还是冲突冗余的,所以对于操作系统 的缓存一般需要Bypass掉 Most DBMSs use direct I/O (O_DIRECT) to bypassthe OS’s page cache.【大多数的DBMS直接使用O_DIRECT去管理页缓存】
→ 操作系统缓存一份,数据库再缓存一份它是多余的
→ 数据库知道这个页什么时候淘汰,操作系统是不知道的什么时候淘汰(OS淘汰策略可能是差的)
→ 数据库没法直接去优化磁盘的IO
-
buffer replacement policies
新的页过来之后的替换策略【替换策略】
目标:
- → 正确的
- → 准确的
- → 快速的
- → 元数据开销不能太大
🎈怎么将磁盘的数据从磁盘读取到内存
-
Least-Recently Used(LRU)
小林coding:最近最久未使用页面置换算法
-
Clock
小林coding:时钟页面置换算法
-
LRU&Clock Q
LRU和Clock的问题:LRU和Clock对于sequential flooding是非常差的(顺序的读取),机械的把最老的页扔了,完全不管这些页是不是有用
数据颠簸:没有注意到后续会继续使用。只是从目前看,确实很久没有用,但是后面马上就用到的数据,被换出了。一个数据刚被换入就换出,但是之后还要换入
所以有一些改进策略
- buffer policies:LRU-K:【LRU优化】以前LRU记上次的访问时间,LRU-K就是记它K次的访问时间,从K次的访问时间寻找一些规律,这个页是不是周期性的去读
- buffer policies:localization:【本地化策略】和本人相关的你可以去驱逐,和别人相关的不要轻易的驱逐
- buffer policies:priority hints:【优先级的提醒】执行器执行的时候给缓存池一些指示,例如使用策略或者机器学习算法分析出来,或者直接告诉缓存池后面我这个页还要用,你不要清理
🎈怎么将内存的数据写回到磁盘
-
dirty pages:脏数据(修改未保存的数据,在内存和磁盘中的样子是不一样的,对于数据库来说就是脏页)
如果缓存池的某个页面不脏,那么DBMS可以简单的删除这个页,如果某个页是脏的,那么DBMS必须将其写回磁盘,确保器更改可以得到保留(redo log页可以不写回去,但是日志要先写到磁盘中去)
-
background writing:每隔一段时间,把内存中的数据刷到磁盘中去,异步刷脏。一个脏页安全的写回磁盘了,那么脏页就可以直接驱逐(如果认为后面会使用也可以不驱逐)。需要小心的处理日志和脏页之间的关系
other memory pools
除了缓存数据的内存池子之外的池子,数据库不光需要缓存磁盘上的数据,还需要缓存其他的东西
- Sorting+Join Buffers:缓存住热点的数据
- Query Cache:执行结果的缓存
- Maintenance Buffers:维护缓存:查询缓存前提是数据没后发生变化
- Log Buffers:日志缓存,记录操作日志(读写磁盘代价很高,经常会记录一个中间状态)
- Dictionary Cache:字典缓存
conclusion
数据库自己处理缓存永远比操作系统处理更好,因为数据库是清楚之后需要使用那些页,而操作系统是不清楚的,所以数据库要自己处理这些缓存
我们可以做的优化:利用一些执行计划:驱逐、放置、预拉取的操作