【原谅我标题党了】
当然不可能有哪一个SQL语句会这么出名,以至于大家叫它“老鼠屎”;但是有一些SQL语句确实主是做着这样的事;由于程序的
局部性原理,数据库会把常用的数据缓存到内存中,对于这种场景通常是使用LRU算法。
【原生的LRU算法有空子】
这里不是说LRU算法有BUG、只是说它在数据库这个场景下是有“问题”的,LRU算法本来是为了尽可能的把“热”数据保存在内存里。
数据表结构如果设计的不好的话就可能使得一些语句要走全表扫描,我们假设在“全表扫描”执行之前LRU中的数据是“热”的,由于
“全表扫描”会把大量的数据载入到内存,载入多少新的数据就会“淘汰”同等数量的“热”数据。真正的问题就在于因“全表扫描”而载入
到内存的数据可能再也不用了,也就是说因“全表扫描”而载入的数据大多数情况下是冷的;那么“全表扫描”语句就铸成了这样一个客户
的事实 ---- 全表扫描淘汰了大量的热数据,换来的只是载入了同等数量的冷数据。
【MySQL对LRU算法的改进】
MySQL对原生的LRU算法进行了改进、它把原生的LRU列表在逻辑上分成了两段,前面的5/8页面用来保存热数据,后面的3/8用来保存
“温”数据。 为什么说这3/8是“温”数据呢? 当有新的数据(页面)要载入内存时,这些数据会先被插入到“温”列表的顶部,如果这个时候
“温”列表的已经满了那么“温”列表底部数据(页面)就会被淘汰以容纳新的数据(页面)。也就是说“温”列表是一个正常的LRU列表,那数据
什么时候会被调入到“热”列表呢?而是从问题入手,因“全表扫描”而载入内存的语句通常只会被访问一次,而其它SQL载入的页面可能
要被多次的访问。解决方案就有了如果一个页面被调入“温”列表之后再也没有被访问那么它就不会被调入“热”列表,如果一个目前在“温”
列表中的时候又被访问了一次那么这个页面就会被调入“热”列表。DBA也可以手工设定“温”列表占整个列表的比较innodb_old_blocks_pct
参数就是做这个事的,默认值是37是一个非常直接3/8*100 (0.375*100)的值
就算MySQL对LRU算法做了这个优化,不过还是有一个小问题;比如说我现在的主机是512G的数据库400G给了buffer pool 一个400G
的buffer pool “温”列表也就有150G的大小;150G大小的列表如果一个页面从进入“温”列表顶部到它慢慢的被移到底部这个可能半个小时
过去了,如果这个时候它刚好被访问了一次,这下就不得了了,平地一声雷它要上天啦,它会被直接调到“热”列表的顶部!这个明显不是
DBA想要看到的结果。 MySQL针对这个也做了优化innodb_old_blocks_time这个参数用来控制时间维度,如果页面的第二次访问的时间
超过了innodb_old_blocks_time设定的值那么页面不会被调入“热”列表。
----