大幅提升MySQL中InnoDB的全表扫描速度的方法 |
本文标签:MySQL,InnoDB 在 InnoDB中更加快速的全表扫描 在 Facebook我们用 mysqldump 来备份数据库. 正如你所知MySql提供两种备份方式,提供了物理备份和逻辑备份的命令和工具. 相对物理备份,逻辑备份有一定的优势,例如:
逻辑备份的主要缺点是数据库的完全备份和完全还原比物理的备份恢复慢得多 。 缓慢的完全逻辑备份往往会导致问题.如果数据库中存在很多大小支离破碎的表,它可能需要很长的时间 。在 脸书,我们面临 mysqldump 的性能问题,导致我们不能在合理的时间内对一些(基于HDD和Flashcache的)服务器完成完整逻辑备份 。我们知道 InnoDB做全表扫描并不高效,因为 InnoDB 实际上并没有顺序读取,在大多情况下是在随机读取 。这是一个已知多年的老问题了 。我们的数据库存储容量一直在增长,缓慢的全表扫描问题给我们造成了严重的影响,因此,我们决定加强 InnoDB 做顺序读取的速度 。最后我们的数据库攻坚工程师团队在InnoDB 中实现了"Logical Readahead"功能 。应用"Logical readahead",在通常生产工作负载下,我们全表扫描速比之从前度提高 9 ' 10 倍 。在超负荷生产中,全表扫描速度达到 15 ' 20 倍的速度甚至更快 。 全表扫描在大的、碎片化数据表上的问题 复制代码 代码如下: dd if=/data/mysql/dbname/large_table.ibd of=/dev/null bs=16k iflag=direct 你可能会发现即使在商业HDD服务器上,你可以达到高于比100 MB/s 乘以"驱动器数目"的速度 。超过1GB/s并不少见 。 不幸的是,在许多情况下主要关键页表存在碎片 。例如,如果您需要管理 user_id 和 object_id 映射,主键将会是(user_id,object_id) 。插入排序与 user_id并不一致,那么新插入/更新往往导致页拆分 。新的拆分页将被分配在远离当前页的位置 。这意味着页面将会碎片化 。 如果主键页是碎片化的,全表扫描将会变得极其缓慢 。图1阐释了这个问题 。在InnoDB读取叶子页#3之后,它需要读取页#5230,在那之后还要读页#4 。页#5230位置离页#3和页#4很远,所以磁盘读操作顺序开始变得几乎是随机的,而不是连续的 。大家都知道HDD上的随机读要比连续读慢得多 。一个有效的改进随机读性能的办法是使用SSD 。不过SSD每个GB的价钱要比HDD昂贵的多,所以使用SSD通常是不可能的 。 图 1.全表扫描实际没有连续读 线性预读取真的有意义吗? mysql> SELECT buf_warmup ("db1", "large_table"); /* loading into buf pool */ mysql> SELECT * FROM large_application_table; /* in-memory select */ buf_warmup() 是一个用户自定义函数,用来读取数据库“db1"的表”large_table"的整个ibd文件 。该函数需要花费时间将ibd文件从硬盘读取,但因为是顺序读取的,所以比随机读取要快的多 。在我的测试当中,比普通的线性预读取快差不多5倍左右 。 这证明ibd文件的顺序读取能够有效的改善吞吐率,但也存在一些缺点:
这看起来是一个足够好的解决方案,但我们的数据库设计团队想出了一个更好的解决方法叫做“逻辑预读取”(Logical Read Ahead),所以我们并不选择UDF的方法 。 逻辑预读取
整个流程如图2所示: Fig 2: Logical Read Ahead
为了使LRA工作,我们需要增加两个session变量 。一个是"innodb_lra_size",用来控制预读取叶子页面(page)大小 。另外一个是"innodb_lra_sleep",用来控制每一次预读取之间休眠多长时间 。我们用512MB~4096MB的大小以及50毫秒的休眠时间来进行测试,到目前为止我们还没有遇到任何严重问题(例如崩溃/阻塞/不一致等) 。这些session变量仅在需要进行全表的时候进行设置 。在我们的应用中,mysqldump以及其他一些辅助脚本启用了逻辑预读取 。 一次提交多个async I/O请求 我们注意到,另外一个导致性能问题的原因是InnoDB 每次i/o仅读取一个页面,即使开启了预读取技术 。每次仅读取16KB对于顺序读取来说实在是太小了,效率相比大的读取单元要低很多 。 在版本5.6中,InnoDB默认使用Linux本地I/O 。如果一次提交多个连续的16KB读请求,Linux在内部会将这些请求合并,读操作能够更有效的执行 。不幸的是,InnoDB一次只会提交一个页面的i/o请求 。我提交了一个bug report#68659.正如bug report中所写,在一个当代的HDD RAID 1+0环境中,如果我一次性提交64个连续的页面读取请求,我可以获得超过1000MB/s的硬盘读取速度;如果每次只提交一个页面读取请求,我们仅可以获得160MB/s的硬盘读取速度 。 为了使LRA在我们的应用环境中更好的工作,我们修正了这个问题 。在我们的MySQl中,InnoDB在调用io_submit()之前会提交多个页面i/o请求 。 基准测试 1. 纯HDD环境全表扫描 (基础的基准测试, 没有其他的工作负载) 2. Online schema change under heavy workload * dump time only, not counting data loading time
对于全表扫描来说InnoDB的工作效率不高,所以我们对它做了一定的修改 。我在两方面进行了改进,一是实现了逻辑预读取;一是实现了一次提交多个async read i/o请求 。对于我们生产环境中的数据库表来说,我们获得了8-18倍的性能提高,这对于减少备份时间、模式修改时间等来说是非常有用的 。我希望这些特性能够在InnoDB中获得Oracle官方支持,至少是主要的MySQL分支 。 |