用 LSN 画个 PAGE MAP


  • GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。
  • GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。
  • 作者:花家舍


想法从何而来

获得雨果奖的科幻小说《三体》中出现了一个流行词汇:降维打击。更高维度文明对较低维度文明的打击不费吹灰之力。这里的“维度”一词,提醒了我看待事物时更换一个维度,也许会有更好的理解。在研究 MySQL 数据库的数据文件时,把数据页平铺,是不是可以有不同的发现。这里的降维,就是把维度放到数据页的维度,而不是内存或者程序角度。数据页平铺,肯定不是把页内所有内容平铺,可以选择一些内容着重分析,例如:LSN 。

我之前抱着学习目的解析过数据文件。学以致用,解析完了自然要用起来才有意义。现在把解析出来的 LSN 小小的利用一下,看看又有什么收获。

刚才说到“平铺”,怎么平铺,这里也是受别人启发,线条在纸上可以画出地图,点的矩阵铺在屏幕上,可以显示出各种图像。那么数据页中的 LSN 平铺,可以展示什么内容?

LSN

MySQL 是先写日志后写数据文件机制,数据落盘是异步的,也就是 WAL 机制。日志也就是 redo log 的落盘速度决定了事务的提交速度。较小的日志,越能快速的持久化。但是 redo log 在前面飞奔,数据的落盘可能会落的很远,那么大量内存中的数据页会是脏页状态,一旦实例崩溃,把 redo log 与数据页恢复到一致的状态就会非常耗时。所以 MySQL 还有 checkpoint 机制来推进数据的落盘,保证数据文件与 redo log 的“距离”不至于太远,虽然可能追不上,保持一种若即若离的感觉。不需要太近,那样会导致IO问题,也不至于太远,那样又会导致恢复时间变长。协调数据文件、redo log 和检查点之间的关系的,就是 LSN 。InnoDB 引擎通过 LSN(Log Sequence Number)来标记版本,redo  log 中始终是标记为最新的版本,数据文件中的会落后于 redo log,ChenckPoint 又会在推进内存脏页落盘时,在数据文件的数据页头部来标记 LSN。这样,我们就能通过查看数据页中的 LSN 来判断哪些数据页被频繁更新,而哪些数据页很少被用户想起,以至于很少更新了。虽然它很可能会比当前实例 redo log 的 LSN 稍稍落后,但足以提供给我们一些重要的信息:哪些数据被频繁的使用。

LSN在数据页中的什么位置呢?看看文档就了然了。https://dev.mysql.com/doc/internals/en/innodb-fil-header.html 数据文件则是由一个个数据页排排坐组成的,数据页的大小一般也会默认为16K。在每个数据页头部都会有固定的 38 字节大小 header。存储了 8 部分内容,LSN 就是第五个,它的长度固定为 8 个字节,下表也许胜过文字描述。

The Fil Header has 8 parts, as follows (38 byte):

Name Size Remarks
FIL_PAGE_SPACE 4 页的校验和(checkSum)值
FIL_PAGE_OFFSET 4 页号
FIL_PAGE_PREV 4 上一个页的页号
FIL_PAGE_NEXT 4 下一个页的页号
FIL_PAGE_LSN 8 页面被最后修改时对应的日志序列位置
FIL_PAGE_TYPE 2 该页的类型
FIL_PAGE_FILE_FLUSH_LSN 8 仅在系统表空间的页中定义,代表文件至少被刷到了对应的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4 当前页所属的表空间,其占用4个字节

LSN在文件中的位置:

如何实现画图

既然在数据页的header里存放了LSN,那么分析所有数据页的 LSN,再通过某种形式展示出来,就可以分析哪些是“热”数据,哪些是相对“冷”的数据了。必然要讲“相对”,一部分数据比另一部分数据被经常访问,它们的“热”就是相对那部分较少被访问的数据而言。这就是通过比较它们 LSN 来实现的。

怎么展示这种冷热区别呢?就是简单借用颜色上冷暖色调来区分,蓝色是经典的冷色调,由浅蓝到深蓝的变化过程,让它来表示数据的“冷”。黄色、红色都是火焰的颜色,虽然也有蓝色火苗,这里就不要较真了,就用黄色到红色的渐变来表示数据的变“热”。

我只是固定使用了11中颜色,如果增加更多颜色来体现渐变,那么也能更清晰的体现数据页的变化情况。这本质上与增加显示屏幕像素点数量,图像更清晰是相同的道理。这里多少也能感觉出,11中颜色的渐变过程,代表了数据文件中 LSN 会被从最小值到最大值之间分成 11 个范围,每一个数据页都会根据它的 LSN 值大小落在某个区间,进而被贴上一个颜色标签。

展示数据页用一个简单的小方框来显示,方框大小只影响一个屏幕能显示多少个多少个数据页而已,大的数据文件可能到 GB 甚至 TB级别。以 1GB 数据文件为例,它就有 (1024 1024 1024)/ 16384 = 65536 个数据页,那么小方框太大了,想象一下六万多数据页那该需要滚动多少次屏幕才能显示,所以方框力求更小。这里示例用的 10px 大小。视力良好的同学,更小的方框也未尝不可。下面就是大概的样子。

代码解析 LSN 的逻辑则相对简单,单线程打开数据文件,以16384为单位解析数据页 File Header 部分的 FIL_PAGE_LSN 值,也就是每个数据页的第 16 到 24 字节的值就可以了。再将这些 LSN 值中的最大值于最小值相减,就得到整个数据文件中 LSN 的范围。把这个范围十一等分,LSN 落在哪个等分的范围之内,就用范围所属的颜色标记这个数据页,也就是表示数据页的小方框会被涂上相应的颜色

最后,这些小方框用最简单的方式排列展示就可以了,这里使用的是 HTML + CSS + JS 来实现。而解析数据文件的逻辑使用 Golang 实现,逻辑并不复杂,代码可以参考文末的链接。

执行程序后分析,会在叫做 html 的目录下生成一个 pagemap.html 的文件,在浏览器中打开就可以了。

go build pageMap.go pageMap --ibd-file-path=sbtest1.ibd