除了常见的索引,事务管理,对于内存的淘汰机制我们在面试过程中也需要格外注意。

MySQL

对于MySQL,我们重点关注InnoDB的内存管理。我们也是期望数据库查询尽量从内存返回,比从磁盘返回数据要快得多。

在InnoDB中有Buffer Pool缓冲池的概念,其其主要作用是用来加速更新,以及加速查询。

InnoDB的Buffer Pool的大小依赖于参数innodb_buffer_pool_size,一般建议设置为物理内存的60%到80%。

内存管理算法:优化过后的LRU算法

为什么要进行优化?传统的LRU不行吗?:如果按照普通的LRU算法实现,某个查询需要淘汰大部分的内存页,使得Buffer Pool的内存命中速率急速下降,磁盘压力增加,SQL查询语句明显变慢。

改进后的LRU算法:

MySQL使用的优化过的LRU算法的具体数据结构实现是一个双向链表和一个哈希表。

双向链表中的每个节点代表一个缓存块,按照访问时间从新到旧排列,最新被访问的缓存块放在链表头部,最久未被访问的缓存块放在链表尾部。哈希表中的每个节点是一个指针,指向双向链表中的对应节点。

当一个缓存块被访问时,MySQL会先在哈希表中查找对应节点的指针,然后通过该指针在双向链表中找到对应节点,并将该节点移动到链表头部。

这样设计的原因是因为双向链表可以方便地维护缓存块的访问顺序,哈希表可以提高查找效率。同时,在链表头部的缓存块是最热门的,最有可能被再次访问,因此可以优先被保留在缓存中,从而提高缓存命中率。

细节之处在于,实际实现中,MySQL按照5:3的比例将链表划分为young区域和old区域,LRU_old标记指向old区域的第一个位置。

mysql 内存碎片 mysql内存淘汰机制_redis

具体的执行流程如下:

1. 如果访问的数据页在young区域,则将该数据页移动至链表头部。

2. 如果访问的数据页不在链表中,那么就会将链表尾部的数据页淘汰,然后将新的数据页插入到old区域开始的地方(LRU_old)

3. 处于old区域的数据页在被访问时:如果这个数据页在LRU链表中存在的时间超过了1s,就把它移动到链表头部;如果数据页存在时间少于1s,则保持位置不变,该时间由innodb_old_blocks_time参数控制,单位是ms。

这个数据页第一次被访问和最后一次被访问的时间间隔不会超过一秒,所以就会一直在old区域

在继续扫描后面的数据页,之前的这个数据页也不会被访问到,因此就会一直在old区域,也就很快就会被淘汰掉。

可以看到这个策略的最大收益,就是在扫描的过程中,虽然也用到了缓冲池,但是不会对young区域造成影响,也就保证了bufferpool响应业务的内存命中率。


Redis

对于非关系型数据库,我们最关心的还是Redis

我们在设置变量的时候肯定都用过expire这个参数,对于利用redis实现并发控制很有帮助。

对于任何redis对象,都会一个过期时间参数,如果不主动设置,那么将不会过期。

set key_melon "cantaloupe"
expire key_melon 450

 查看某个key的过期时间

ttl key_melon

 使key永久化,我们可以手动操作

ttl key_melon

expire的底层实现:

Redis底层源码存储expire使用的是一种称为渐进式过期的方法。

具体来说,Redis会为每个键设置一个过期时间(expire time),这个时间是一个UNIX时间戳(即秒数)。当键被设置了过期时间后,Redis会将这个键添加到一个专门的过期字典(expire dict)中,过期字典是一个哈希表,键是过期时间,值是一个链表,链表中存储了所有过期时间为该值的键。

Redis使用一个单独的线程(即过期键清理线程)来扫描过期字典,将过期的键从数据库中删除。当一个键被访问时,Redis会检查它是否过期,如果过期则将其删除。

渐进式过期是指Redis并不会立即删除所有过期的键,而是在过期键清理线程扫描过期字典时,逐步地删除一部分过期的键。这样可以避免在一瞬间处理大量过期键导致系统阻塞的情况。


半自动+主动结合的淘汰策略

其过期删除策略是【惰性删除+定期(主动)删除】结合使用。

惰性删除:不主动删除过期键,每次从数据库访问key时,都检测key是否过期,如果过期,则删除该key(指的其具体的对象)。

定期删除:每隔一段时间,随机从数据库中取出一定数量的key进行检查,并删除其中的过期key。

主动过期机制的相关源码:redis/expire.c at a92921da135e38eedd89138e15fe9fd1ffdd9b48 · redis/redis (github.com)

Redis提供了五种内存淘汰策略:

  1. noeviction:当内存不足时,Redis不会淘汰任何数据,所有写操作都会返回错误。
  2. allkeys-lru:Redis会优先淘汰最近最少使用的key,以腾出更多的空间。
  3. allkeys-random:Redis会随机淘汰一些key。
  4. volatile-lru:Redis会优先淘汰设置了过期时间的key中最近最少使用的key。
  5. volatile-random:Redis会随机淘汰一些设置了过期时间的key。

其中,noeviction策略是默认策略,其他四种策略可以通过配置文件或命令来设置。当Redis使用的内存超过maxmemory限制时,Redis会按照设置的策略来淘汰一些数据,以便为新的数据腾出空间。


参考资料:

MySQL 内存页淘汰策略 - 腾讯云开发者社区-腾讯云 (tencent.com)

mysql内存数据淘汰机制和大查询会不会把内存打爆? - 掘金 (juejin.cn)

Redis 常见面试题 | 小林coding (xiaolincoding.com)

How does redis expire keys? - Stack Overflow

How To Expire Keys in Redis | DigitalOcean