科普文:软件架构数据库系列之【MySQL:InnoDB预读Ahead-read(线性预读lin ...

打印 上一主题 下一主题

主题 802|帖子 802|积分 2406

概叙

操作体系文件预读(Prefetching)

科普文:软件架构Linux系列之【Linux的文件预读readahead】-CSDN博客
前面文章我们从操作体系角度表明了文件预读readahead,指Linux体系内核将指定文件的某地区预读进页(OS page cache)缓存起来,便于接下来对该地区举行读取时,不会因缺页(page fault)而阻塞。因为从内存读取比从磁盘读取要快许多。预读可以有效的淘汰磁盘的寻道次数和应用程序的I/O等候时间,是改进磁盘读I/O性能的重要优化本领之一。
预读(Prefetching)是一种优化技术,用于提前加载将在未来需要的数据或文件。在Linux中,预读通常是通过文件的预读守卫历程(Prefetch Daemon)实现的,它可以在体系启动时主动运行,也可以手动启动。
Linux 体系中的预读机制目标:为了进步文件读取的性能,通过预先读取相邻的数据块到内存中以便后续的读操作能够更快地举行。预读通常是由文件体系主动举行的,但是用户可以通过一些工具或编程接口来影响预读的行为。这可以帮助进步后续对同一文件地区的实际读取操作性能。
留意:文件预读readahead这只是一个建议,内核可能会忽略这个请求,或者根据自己的预读策略来举行实际的预读。
预读(Prefetching)应用场景:


  • 数据库:数据库管理体系(如PostgreSQL, MySQL)经常使用预读来加速查询过程。
  • 高性能文件服务器:服务器应用程序预读大文件,以便客户端请求可以快速响应。
MySQL的MRR(Multi-Range Read Optimization)

MRR(Multi-Range Read Optimization)是一种预读机制。‌MRR通过改变数据检索的顺序,并利用操作体系缓存举行预读,从而显着淘汰I/O操作数目,进步查询速率‌。
科普文:软件架构数据库系列之【MySQL查询优化器中的优化策略optimizer_switch--MRR 优化器】-CSDN博客
https://dev.mysql.com/doc/refman/8.4/en/mrr-optimization.html
MRR的工作原理
MRR通过将磁盘随机读取转换为磁盘顺序读取,利用磁盘预读机制(Prefetching)来优化索引的查询性能。
具体来说,MRR会按照一定的顺序读取索引范围,而不是随机读取,如许可以淘汰磁盘I/O操作,进步查询服从‌。MRR 【Multi-Range Read】将ID或键值读到buffer排序,通过把「随机磁盘读」,转化为「顺序磁盘读」,淘汰磁盘IO,从而进步了索引查询的性能。
MRR的应用场景
MRR特别实用于包罗范围条件(如BETWEEN、<、>等)的查询,以及需要通过辅助索引访问表数据的场景。在这些情况下,MRR能够显着进步查询性能‌。
 ‌InnoDB的Buffer Pool具有以下四大特性‌:


  • Change Buffer‌:​ Change Buffer是InnoDB存储引擎中的一个特别数据布局,用于缓存对不在buffer pool中的二级索引页所做的修改。这些修改可能来自INSERT、UPDATE或DELETE操作(DML)。当这些页被其他读操作加载到buffer pool时,Change Buffer中的修改会被合并到这些页中,从而淘汰对磁盘的随机I/O操作‌。
  • Double Write‌:Double Write机制用于确保InnoDB在写操作时的数据完整性。在写入磁盘之前,数据起首被写入到两个独立的缓冲区中,然后再从这两个缓冲区写入到最终的磁盘位置。假如此中一个缓冲区写入失败,另一个缓冲区中的数据仍然可以保证数据的一致性,从而防止数据损坏‌。
  • Adaptive Hash Index‌:Adaptive Hash Index(AHI)是InnoDB主动为Buffer Pool中的热点页面构建的哈希索引。这可以加速等值查找操作,进步查询性能。AHI会根据访问模式动态调解,以反映热点页面的变革,从而进步查询服从‌。(MySQL 8.4版本开始,默认关闭自适应哈希索引(Adaptive Hash Index,AHI)特性。
  • Read Ahead‌:Read Ahead机制猜测未来的I/O请求,预先读取数据页到buffer pool中。如许可以淘汰实际的磁盘I/O操作,进步读取性能。Read Ahead通过猜测未来的访问模式,提前加载数据页,从而淘汰查询时的等候时间‌。
科普文:软件架构数据库系列之【MySQL 8.4 LTS版本20个 InnoDB体系变量默认值修改说明】_mysql 8.4对于8.0-CSDN博客
Innodb的Read Ahead预读机制

MySQL :: MySQL 8.4 Reference Manual :: 17.8.3.4 Configuring InnoDB Buffer Pool Prefetching (Read-Ahead)

从官方文档目次“Configuring InnoDB Buffer Pool Prefetching”可以看出Prefetching是用来设置Innodb缓冲池的(O(∩_∩)O)。

重要是:Innodb_buffer_pool_read_ahead,Innodb_buffer_pool_read_ahead_evicted, Innodb_buffer_pool_read_ahead_rnd这三个参数,用来优化innodb 磁盘io。
设计目标:
Read-Ahead用于异步预取buffer pool中的多个page的一个猜测行为。
InnoDB使用两种提前预读Read-Ahead算法来进步I/O性能。
Linear read-ahead:线性预读

选择是否预读下一个 Extent 的数据。有一个重要的参数 innodb_read_ahead_threshold,假如当前 Extent 中一连读取的数据页凌驾规定值,就会将下一个 Extent 的数据也读到缓冲池中。innodb_read_ahead_threshold 的范围是 0-64(因为一个 Extent 也就64页)。
 
Random read-ahead:随机预读(默认关闭)

用来设置是否将当前 Extent 的剩余页也预读到缓冲池中,由于这种预读性能不稳定,所以MySQL 5.5开始默认关闭。

评估预读算法的有效性


The  SHOW ENGINE INNODB STATUS command displays statistics to help you evaluate the effectiveness of the read-ahead algorithm. Statistics include counter information for the following global status variables:


  • Innodb_buffer_pool_read_ahead 通过预读读入buffer pool中数据page数
  • Innodb_buffer_pool_read_ahead_evicted 通过预读没有被访问就被驱逐的page
  • Innodb_buffer_pool_read_ahead_rnd 通过随机预读的次数
Innodb预读详解:Configuring InnoDB Buffer Pool Prefetching

预读从字面意义就能理解,就是通过一系列指标,判断先读取某些数据 加载到内存中,从而淘汰实时IO请求。
预读(Read-Ahead)是InnoDB预估实验当前的请求可能之后会读取某些数据页,就预先把它们加载到 Buffer Pool中。预读数据页访问机制、缓冲池刷新策略息息相干。

数据库发展到现在,IO交互的代价照旧最高的,特别是传统的机械硬盘下预读本领确实能提供性能。但前提是内存充裕,比如刚预读,数据还没有读取,内存容量不敷,就得立即从缓存中镌汰掉,那就即是做了无用的事情。
Innodb预读前提:InnoDB Buffer Pool足够大(不可描述,反正小了肯定没有结果;重点参考‌InnoDB Buffer Pool的命中率>99%)。
关于InnoDB Buffer Pool的优化,后面再单独说明。
  1. InnoDB Buffer Pool的命中率
  2. mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';
  3. +----------------------------------+--------+
  4. | Variable_name                    | Value  |
  5. +----------------------------------+--------+
  6. | Innodb_buffer_pool_read_requests | 153125 |
  7. +----------------------------------+--------+
  8. 1 row in set (0.03 sec)
  9. mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';
  10. +--------------------------+-------+
  11. | Variable_name            | Value |
  12. +--------------------------+-------+
  13. | Innodb_buffer_pool_reads | 916   |
  14. +--------------------------+-------+
  15. 1 row in set (0.01 sec)
  16. mysql> select (1- 916/153125)*100;
  17. +---------------------+
  18. | (1- 916/153125)*100 |
  19. +---------------------+
  20. |             99.4018 | -- InnoDB Buffer Pool的命中率 > 99%
  21. +---------------------+
  22. 1 row in set (0.01 sec)
  23. mysql>
  24. Innodb_buffer_pool_reads
  25. The number of logical reads that InnoDB could not satisfy from the buffer pool, and had to read directly from disk.
  26. InnoDB是MySQL数据库使用的一种存储引擎。它将数据存储在内存中的缓冲池中,称为InnoDB缓冲池。
  27. 当从数据库请求数据时,首先搜索InnoDB缓冲池。如果数据未在缓冲池中找到,则必须将其从磁盘读入缓冲池。这也就是官网上说的Cound not satisfy from the buffer pool,缓冲池中无法中找到满足条件的数据,而必须从磁盘中读入缓冲池,这个操作称为Innodb_buffer_pool_reads(InnoDB缓冲池读取)。
  28. Innodb_buffer_pool_reads读衡量了需要从磁盘读取到缓冲池的次数。
  29. 通常情况下,我们希望Innodb_buffer_pool_reads的值越小越好,因为它表示从磁盘中读取数据页到缓冲池中的次数越少,缓冲池的命中率越高,查询性能和系统响应时间也会更好。
  30. 如果Innodb_buffer_pool_reads的值较大,则可能表示缓冲池的大小不足或者热数据没有被缓存到缓冲池中,需要增加缓冲池的大小或者优化MySQL的查询语句,以提高缓存命中率和减少磁盘I/O的次数。
  31. Innodb_buffer_pool_read_requests
  32. The number of logical read requests.
  33. 当从数据库请求数据时,首先搜索Innodb缓冲池,如果数据已经在缓冲池中存在,则可以从磁盘读取,此操作称为Innodb_buffer_pool_read_requests   InnoDB(缓冲池读取请求)。
  34. InnoDB缓冲池读请求指标衡量了InnoDB能够直接从缓冲池中读取满足数据请求的次数,而无需从磁盘中读取数据。
  35. 与Innodb_buffer_pool_reads不同的是,Innodb_buffer_pool_reads衡量了需要从磁盘读取数据到缓冲池的次数,而Innodb_buffer_pool_read_requests衡量了数据已经在缓冲池中而无需从磁盘读取数据。
  36. Innodb_buffer_pool_reads涉及到 IOPS 资源的消耗,Innodb_buffer_pool_read_requests涉及到 CPU 资源的消耗。
复制代码

上图是InnoDB逻辑存储单元,重要分为:表空间、段、区和页(行)。
层级关系为:表空间tablespace -> 段segment -> 区extent(64个page,1M) -> 页page(默认16kb)。
innodb引擎对于数据页变动的操作是异步举行的,但对于读请求来说可以使read-time读之外,通过预读方式举行先加载策略。
MySQL 内部一般都会使用缓冲池,而假如多次语句操作的是相邻的记录,那么就会多次举行磁盘读取,导致速率降低,所以 MySQL 一般在读取数据时都是采用预读方式,读取指定命据周围的多条数据。
而在 InnoDB 引擎中的数据是以页为单元举行存储的,并且提出了“数据页”概念。

数据页的布局如上图,巨细默以为 16K,关于数据页这里就不过多阐述。对硬盘上的数据读取最小单元就是数据页。

 InnoDB 引擎在预读时, 有两种预读算法。线性预读和随机预读。

InnoDB在I/O的优化上有个比较重要的特性为预读,预读请求是一个i/o请求,它会异步地在缓冲池中预先回迁多个页面,预计很快就会需要这些页面,这些请求在一个范围内引入所有页面。
InnoDB以64个page为一个extent,那么InnoDB的预读是以page为单元照旧以extent?
InnoDB的预读机制是以page为单元举行的‌。InnoDB在内存与磁盘之间是以page页为单元举行数据交互的,因为使用page页可以淘汰磁盘I/O操作,进步内存利用率和性能‌。
数据库请求数据的时候,会将读请求交给文件体系,放入请求队列中;相干历程从请求队列中将读请求取出,根据需求到相干数据区(内存、磁盘)读取数据;取出的数据,放入响应队列中,最后数据库就会从响应队列中将数据取走,完成一次数据读操作过程。
接着历程继承处理请求队列,(假如数据库是全表扫描的话,数据读请求将会占满请求队列),判断后面几个数据读请求的数据是否相邻,再根据自身体系IO带宽处理量,举行预读,举行读请求的合并处理,一次性读取多块数据放入响应队列中,再被数据库取走。(如此,一次物理读操作,实现多页数据读取,rrqm>0(# iostat -x),假设是4个读请求合并,则rrqm参数表现的就是4)
InnoDB预读机制的工作原理

InnoDB使用两种预读算法:线性预读(Linear read-ahead)和随机预读(Random read-ahead)。


  • 线性预读(Linear read-ahead)‌:这种预读方式会猜测在查询过程中会使用到的数据页,并将下一个extent提前读取到buffer pool中。线性预读通过参数innodb_read_ahead_threshold控制,当顺序读取的页数达到或凌驾该参数值时,InnoDB会异步地将下一个extent读取到buffer pool中‌。
  • 随机预读(Random read-ahead)‌:这种预读方式通过buffer pool中已有的页来猜测哪些页可能很快会被访问,并在发现一连13个页时异步读取该区段剩余的页。随机预读通过参数innodb_random_read_ahead控制‌。
InnoDB预读设置参数



  • innodb_read_ahead_threshold‌:控制线性预读的触发条件,默认值为56,范围为0-64‌。
  • innodb_random_read_ahead‌:控制随机预读的开关,默认关闭‌。
通过这些设置参数,可以优化InnoDB的预读行为,从而进步数据库的性能。

1、线性预读(innodb_read_ahead_threshold)

假如一个extent中的被顺序读取的page凌驾或者即是   innodb_read_ahead_threshold参数变量时,Innodb将会异步的将下一个extent读取到buffer pool中,innodb_read_ahead_threshold可以设置为0-64的任何值(注:innodb中每个extent就只有64个page),默以为56。值越大,访问模式查抄就越严格。
例如,假如将值设置为48,则InnoDB只有在顺序访问当前extent中的48个pages时才触发线性预读请求,将下一个extent读到内存中。假如值为8,InnoDB触发异步预读,纵然程序段中只有8页被顺序访问。你可以在MySQL设置文件中设置此参数的值,或者使用SET GLOBAL需要该SUPER权限的下令动态更改该参数。
在没有该变量之前,当访问到extent的最后一个page的时候,Innodb会决定是否将下一个extent放入到buffer pool中。

2、随机预读(innodb_random_read_ahead)

 假如当同一个extent中一连的13个page在buffer pool中发现时,Innodb会将该extent中的剩余page读到buffer pool中。控制参数  innodb_random_read_ahead  默认没有开启。
随机预读方式则是表现当同一个extent中的一些page在buffer pool中发现时,Innodb会将该extent中的剩余page一并读到buffer pool中,由于随机预读方式给Innodb code带来了一些不必要的复杂性,同时在性能也存在不稳定性。
备注:MySQL5.5中已经将Random read-ahead:随机预读这种预读方式废弃。MySQL8.4也是默认关闭了Random read-ahead:随机预读

3、异步IO:AIO的影响

科普文:软件架构Linux系列之【五种IO模型小结】-CSDN博客
预读操作是通过aio来举行:
InnoDB使用Linux上的异步I/O子体系(native AIO)对数据文件页实验预读和写请求。该行为由innodb_use_native_aio(默认开启)设置选项控制。
对于AIO机制下, 磁盘I/O调度算法一般建议使用noop和deadline I/O调度器。
  1. mysql> show variables like '%aio%';
  2. +-----------------------+-------+
  3. | Variable_name         | Value |
  4. +-----------------------+-------+
  5. | innodb_use_native_aio | ON    |
  6. +-----------------------+-------+
复制代码
InnoDB在特性进步了大量I/o限制体系的可伸缩性,这些体系通常在show ENGINE INNODB STATUS\G输出中表现许多挂起的读/写。
  1. mysql> SHOW ENGINE INNODB STATUS\G
  2. --------
  3. FILE I/O
  4. --------
  5. Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
  6. ibuf aio reads:, log i/o's:, sync i/o's:
  7. Pending flushes (fsync) log: 0; buffer pool: 0
  8. 1501 OS file reads, 345 OS file writes, 46 OS fsyncs
  9. 0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
复制代码
aio 线程读取文件是受体系参数控制。特别是运行大量InnoDB I/O线程,比犹如一台服务器机器上运行多个如许的实例,可能会凌驾Linux体系的容量限制。
在这种情况下,可能会收到以下错误:
EAGAIN: The specified maxevents exceeds the user's limit of available events.
可以通过在/proc/sys/fs/aio-max-nr中写入更高的限制来解决这个错误。
  1. shell> cat /proc/sys/fs/aio-max-nr
  2. 65536
复制代码
假如操作体系中的异步I/O子体系出现问题导致InnoDB无法启动,或
InnoDB检测到tmpdir位置、tmpfs文件体系和Linux内核不支持tmpfs上的AIO等潜在问题,也可以在启动过程中主动禁用该选项innodb_use_native_aio=0启动服务。
4、InnoDB Buffer Pool缓冲池的LRU算法和LRU链表

InnoDB 的缓冲池数据的存储算法是改进版的 LRU 算法,以此来避免了传统 LRU 算法的两个问题,预读失效和缓冲池污染。
LRU 算法简单来说,假如用链表来实现,将最近命中(加载)的数据页移在头部,未使用的向后偏移,直至移除链表。如许的镌汰算法就叫做 LRU 算法。但是其会含有前面说得两个问题。
LRU算法有以下的尺度算法:


  • 1)3/8的list信息是作为old list,这些信息是被驱逐的对象。
  • 2)list的中点就是我们所谓的old list头部和new list尾部的毗连点,相称于一个边界。
  • 3)新数据的读入起首会插入到old list的头部。
  • 4)假如是old list的数据被访问到了,这个页信息就会变成new list,变成young page,就会将数据页信息移动到new sublist的头部。
  • 5)在数据库的buffer pool内里,不管是new sublist照旧old sublist的数据假如不会被访问到,最后都会被移动到list的尾部作为牺牲者。

Flush list

Flush 链表中的所有节点都是脏页(Dirty page),脏页就是这些数据页被修改过,但是还没来得及被刷新到磁盘上。假如频仍的将修改过的数据立即刷新到磁盘将会严肃影响性能,所以有了脏页的存在。那这些脏页要放到哪里呢?所以就多了Flush链表来管理这些脏页。Flush 链表上的数据都是需要被刷新到磁盘中,所以叫Flush 链表。
LRU list

LRU 链表是缓冲池最重要的链表,所有读取的数据页都会放到LRU链表上。缓冲池使用的LRU(least recently used,最近最少使用)算法是LRU算法的一种变体,当需要向缓冲池添加新数据页时,最近最少使用的数据页将被清除,新数据页将被添加到列表的中心。

这种中点插入策略将一个列表分为两个子列表:


  • 头部列表:存放最新的最近访问的数据页
  • 尾部列表:存放旧的最近访问的数据页


1、预读失效(预读数据被挤出buffer pool)

在磁盘上读取数据时,可能会因为操作不当导致多个用不到的数据页加载到缓冲池。
从而导致之前经常被使用的数据页缓存被无用的数据页挤到尾部,乃至被移出缓存,那么就会降低性能。
而 InnoDB 的解决方案是将缓冲池分为两部分,新生代和老年代,比例默以为5:3,分别存储常用的数据页以及不常用的数据页,新生代位于头部,新生代位于尾部,这两部分都有头部和尾部。
当从磁盘的数据页移入缓冲池中时,起首是放入老年代的头部,然后举行筛选,使用到的数据页会移入新生代的头部,未使用的数据页会随着时间流逝而渐渐移入老年代的尾部,直至镌汰。


2、缓冲池污染(预读数据被挤出buffer pool)

在处理数据页时,假如需要对大量数据页举行筛选(但是没有用到),那么照旧会使大量的热点数据页被挤出。
如 select * from student where name like '张%';name字段包罗索引,那么在实验时虽然会先加载到老年代的头部,但是因为每条数据都需要筛选,所以都会移入新生代头部,导致新生代热点数据页被挤到老年代乃至移除。
InnoDB 为相识决这个问题,使用了 "老年代停留时间窗口" 机制,这个机制是设置一个时间,假如在老年代的数据页被调用后还需要去查抄它在老年代的停留时间是否达到了这个规定时间,达到了才能移入新生代头部,否则只会移到老年代头部。
  1. MySQL InnoDB数据参数设置:
  2. 参数:innodb_buffer_pool_size
  3. 介绍:配置缓冲池的大小,在内存允许的情况下,DBA往往会建议调大这个参数,越多数据和索引放到内存里,数据库的性能会越好。
  4. 参数:innodb_old_blocks_pct
  5. 介绍:老生代占整个LRU链长度的比例,默认是37,即整个LRU中新生代与老生代长度比例是63:37。
  6. 画外音:如果把这个参数设为100,就退化为普通LRU了。
  7. 参数:innodb_old_blocks_time
  8. 介绍:老生代停留时间窗口,单位是毫秒,默认是1000,即同时满足“被访问”与“在老生代停留时间超过1秒”两个条件,才会被插入到新生代头部。
  9. 总结:
  10. 缓冲池(buffer pool)是一种常见的降低磁盘访问的机制;
  11. 缓冲池通常以页(page)为单位缓存数据;
  12. 缓冲池的常见管理算法是LRU,memcache,OS,InnoDB都使用了这种算法;
  13. InnoDB对普通LRU进行了优化:
  14. 将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,页被访问,才进入新生代,以解决预读失效的问题
  15. 页被访问,且在老生代停留时间超过配置阈值的,才进入新生代,以解决批量数据访问,大量热数据淘汰的问题
复制代码


5、监控Innodb的预读

1、可以通过show engine innodb status表现统计信息
  1. mysql> show engine innodb status\G
  2. ----------------------
  3. BUFFER POOL AND MEMORY
  4. ----------------------
  5. ……
  6. Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
  7. ……
  8.   1、Pages read ahead:表示每秒读入的pages;
  9.   2、evicted without access:表示每秒读出的pages;
  10.   3、Random read ahead  一般随机预读都是关闭的,也就是0。
复制代码
2、通过两个状态值,评估预读算法的有效性
  1. mysql> show global status like '%read_ahead%';
  2. +---------------------------------------+-------+
  3. | Variable_name                         | Value |
  4. +---------------------------------------+-------+
  5. | Innodb_buffer_pool_read_ahead_rnd     | 0     |
  6. | Innodb_buffer_pool_read_ahead         | 2303  |
  7. | Innodb_buffer_pool_read_ahead_evicted | 0     |
  8. +---------------------------------------+-------+
  9. 3 rows in set (0.01 sec)
  10.   1、Innodb_buffer_pool_read_ahead:通过预读(后台线程)读入innodb buffer pool中数据页数
  11.   2、Innodb_buffer_pool_read_ahead_evicted:通过预读来的数据页没有被查询访问就被清理的pages,无效预读页数
  12.    
  13.     3、Innodb_buffer_pool_read_ahead_rnd 随机预读取的页数:默认关闭随机预读,该值是0
复制代码
innodb预读和LRU缓冲队列

Mysql的预读 mysql预读机制_mob6454cc7aec82的技术博客_51CTO博客

实战一:innodb线性预读对磁盘io性能的提拔

MySQL:线性预读浅析 - 简书
线程预读重要是在举行物理page读取的时候,假如满足一定规则,则接纳预读。线性预读触发条件:buf_read_ahead_linear函数,逻辑比较简单,大概有如下一些原则:


  • 必须设置了innodb_read_ahead_threshold参数
  • 当访问到某个extent的边界值比如有如下4个extent
  1. ext1   ext2         ext3      ext4
  2. 0-63  64-127 128-191 192-255
复制代码
假如访问到128这个page或者191这个page,则举行ext3所有page的查抄,查抄方式为访问ext3中64个page的最后访问时间,分2种方式


  • 假如访问是128这个 low limit,则举行降序查抄也就是需要满足如下
  1. page 128的访问时间 > page 129的访问时间 > page 130的访问时间...
复制代码
这可能是一种反向的全索引扫描。比如DESC 反向索引全扫描


  • 假如访问是191这个 high limit,则举行升序查抄也就是需要满足如下
  1. page 128的访问时间 < page 129的访问时间 < page 130的访问时间...
复制代码
正向全表扫描或者索引扫描使用这种查抄,因为page在叶子节点有序。假如查抄出来不符合访问顺序的page大于了 64 - innodb_read_ahead_threshold(默认56) = 8 个那么,则不举行线性预读。
这里包罗一个关键问题就是一个extent的block是否能够按照索引(或者主键)的顺序举行排列,假如不能满足这个要求那么这个判定算法就不能建立,因为假如extent的block是乱序的则访问时间不能按顺序排列。
从索引的分裂来看,假如一个extent已经满了,往中心插入数据肯定会分配到新的extent,那么这种乱序插入数据,在全表扫描的时候也不一定会完全用到线性预读,因为默认的情况下要扫描56个page满足规则才可以。
线性预读方式分2种


  • 假如是反向扫描,则直接访问前一个extent
  • 假如是顺序扫描,则直接访问后一个extent
读取方式为循环这64个page,大概如下:
  1. for (i = low; i < high; i++) {buf_read_page_low(i)}
复制代码
这里的low和high分别对应预读extent的开始和结束page no,也就是举行分别读取每个page,但是使用的方式是异步AIO,最终每次调用io_submit将IO需求发送给操作体系。而异步IO线程则通过调用:
  1. io_handler_thread
  2. ->fil_aio_wait
  3.    ->os_aio_handler
  4.       ->os_aio_linux_handler
  5.          ->LinuxAIOHandler::poll
  6.              ->LinuxAIOHandler::collect
  7.                   ->io_getevents
复制代码
举行io完成情况的网络,然后举行IO完成后调用buf_page_io_complete完成IO操作,比如解除IO fixed状态,
  1. buf_page_set_io_fix(bpage, BUF_IO_NONE);
复制代码
预读使用异步IO也是可以理解的,当我们再次访问page查看是否在innodb buffer中的时候会发现page已经存在于buffer中也就不需要再次举行物理IO了,而将读取IO的任务交给异步IO来完成,这实际上做到了前台session和背景IO线程同步并发举行操作的原则,进步的性能。
当我们将异步IO线程参加等候sleep后,发现实际上前台session在查询page的时候会发现这个page还没有完成buffer的读取,也就是发现这个page已经被fixed住了,则举行等候。并且show engine出现大量的pending read。
从一连数据的线性预读来看,结果照旧比较显着,一连数据只的是顺序插入的表且主键是自增,表巨细1G左右下图:


  1. #0  0x00007ffff7bcdf90 in pread64 () from /lib64/libpthread.so.0
  2. #1  0x00000000050a066b in SyncFileIO::execute (this=0x7fffc8561c10, request=...) at /newdata/mysql-8.0.23/storage/innobase/os/os0file.cc:2083
  3. #2  0x00000000050a47bc in os_file_io (in_type=..., file=39, buf=0x7fffd5478000, n=16384, offset=162430976, err=0x7fffc856209c, e_block=0x0)
  4.     at /newdata/mysql-8.0.23/storage/innobase/os/os0file.cc:5067
  5. #3  0x00000000050a50c5 in os_file_pread (type=..., file=39, buf=0x7fffd5478000, n=16384, offset=162430976, err=0x7fffc856209c) at /newdata/mysql-8.0.23/storage/innobase/os/os0file.cc:5246
  6. #4  0x00000000050a51fa in os_file_read_page (type=..., file_name=0x7fffe26c1db0 "./t10/testbig.ibd", file=39, buf=0x7fffd5478000, offset=162430976, n=16384, o=0x0, exit_on_err=true)
  7.     at /newdata/mysql-8.0.23/storage/innobase/os/os0file.cc:5287
  8. #5  0x00000000050a6357 in os_file_read_func (type=..., file_name=0x7fffe26c1db0 "./t10/testbig.ibd", file=39, buf=0x7fffd5478000, offset=162430976, n=16384)
  9.     at /newdata/mysql-8.0.23/storage/innobase/os/os0file.cc:5723
  10. #6  0x00000000050a9477 in os_aio_func (type=..., aio_mode=SYNC, name=0x7fffe26c1db0 "./t10/testbig.ibd", file=..., buf=0x7fffd5478000, offset=162430976, n=16384, read_only=false,
  11.     m1=0x7fffe26c1e90, m2=0x7fffd1a2ffb0) at /newdata/mysql-8.0.23/storage/innobase/os/os0file.cc:7263
  12. #7  0x0000000005432194 in pfs_os_aio_func (type=..., aio_mode=SYNC, name=0x7fffe26c1db0 "./t10/testbig.ibd", file=..., buf=0x7fffd5478000, offset=162430976, n=16384, read_only=false,
  13.     m1=0x7fffe26c1e90, m2=0x7fffd1a2ffb0, src_file=0x706fb90 "/newdata/mysql-8.0.23/storage/innobase/fil/fil0fil.cc", src_line=8116)
  14.     at /newdata/mysql-8.0.23/storage/innobase/include/os0file.ic:226
  15. #8  0x00000000054443e7 in Fil_shard::do_io (this=0x7fffe02e2840, type=..., sync=true, page_id=..., page_size=..., byte_offset=0, len=16384, buf=0x7fffd5478000, message=0x7fffd1a2ffb0)
  16.     at /newdata/mysql-8.0.23/storage/innobase/fil/fil0fil.cc:8116
  17. #9  0x0000000005444909 in fil_io (type=..., sync=true, page_id=..., page_size=..., byte_offset=0, len=16384, buf=0x7fffd5478000, message=0x7fffd1a2ffb0)
  18.     at /newdata/mysql-8.0.23/storage/innobase/fil/fil0fil.cc:8264
  19. #10 0x0000000005367758 in buf_read_page_low (err=0x7fffc856301c, sync=true, type=0, mode=132, page_id=..., page_size=..., unzip=false)
  20.     at /newdata/mysql-8.0.23/storage/innobase/buf/buf0rea.cc:123
  21. #11 0x0000000005367de7 in buf_read_page (page_id=..., page_size=...) at /newdata/mysql-8.0.23/storage/innobase/buf/buf0rea.cc:287
  22. #12 0x000000000532ab34 in Buf_fetch<Buf_fetch_normal>::read_page (this=0x7fffc8563470) at /newdata/mysql-8.0.23/storage/innobase/buf/buf0buf.cc:3895
  23. #13 0x000000000531b669 in Buf_fetch_normal::get (this=0x7fffc8563470, block=@0x7fffc8563418: 0x0) at /newdata/mysql-8.0.23/storage/innobase/buf/buf0buf.cc:3522
  24. #14 0x000000000532b761 in Buf_fetch<Buf_fetch_normal>::single_page (this=0x7fffc8563470) at /newdata/mysql-8.0.23/storage/innobase/buf/buf0buf.cc:4089
  25. #15 0x000000000531bc9a in buf_page_get_gen (page_id=..., page_size=..., rw_latch=1, guess=0x0, mode=NORMAL, file=0x701bb88 "/newdata/mysql-8.0.23/storage/innobase/btr/btr0pcur.cc",
  26.     line=312, mtr=0x7fffc8563ee0, dirty_with_no_latch=false) at /newdata/mysql-8.0.23/storage/innobase/buf/buf0buf.cc:4283
  27. #16 0x00000000052ef88e in btr_block_get_func (page_id=..., page_size=..., mode=1, file=0x701bb88 "/newdata/mysql-8.0.23/storage/innobase/btr/btr0pcur.cc", line=312, index=0xb6086d0,
  28.     mtr=0x7fffc8563ee0) at /newdata/mysql-8.0.23/storage/innobase/include/btr0btr.ic:76
  29. #17 0x00000000052f0c1d in btr_pcur_t::move_to_next_page (this=0xb659c10, mtr=0x7fffc8563ee0) at /newdata/mysql-8.0.23/storage/innobase/btr/btr0pcur.cc:311
  30. #18 0x0000000004fea918 in btr_pcur_t::move_to_next (this=0xb659c10, mtr=0x7fffc8563ee0) at /newdata/mysql-8.0.23/storage/innobase/include/btr0pcur.h:973
  31. #19 0x00000000051a3a3c in row_search_mvcc (buf=0xb5c9468 "", mode=PAGE_CUR_G, prebuilt=0xb6599a0, match_mode=0, direction=1) at /newdata/mysql-8.0.23/storage/innobase/row/row0sel.cc:5912
  32. #20 0x0000000004f26b95 in ha_innobase::general_fetch (this=0xb61b798, buf=0xb5c9468 "", direction=1, match_mode=0) at /newdata/mysql-8.0.23/storage/innobase/handler/ha_innodb.cc:10052
  33. #21 0x0000000004f27661 in ha_innobase::rnd_next (this=0xb61b798, buf=0xb5c9468 "") at /newdata/mysql-8.0.23/storage/innobase/handler/ha_innodb.cc:10329
  34. #22 0x0000000003b717dc in handler::ha_rnd_next (this=0xb61b798, buf=0xb5c9468 "") at /newdata/mysql-8.0.23/sql/handler.cc:2980
  35. #23 0x00000000036eba58 in TableScanIterator::Read (this=0xb66f140) at /newdata/mysql-8.0.23/sql/records.cc:361
  36. #24 0x0000000003ed6add in AggregateIterator::Read (this=0xb66f170) at /newdata/mysql-8.0.23/sql/composite_iterators.cc:295
  37. #25 0x0000000003919704 in SELECT_LEX_UNIT::ExecuteIteratorQuery (this=0xb66be88, thd=0xaabeb10) at /newdata/mysql-8.0.23/sql/sql_union.cc:1228
  38. #26 0x0000000003919a2e in SELECT_LEX_UNIT::execute (this=0xb66be88, thd=0xaabeb10) at /newdata/mysql-8.0.23/sql/sql_union.cc:1281
  39. #27 0x0000000003871ef4 in Sql_cmd_dml::execute_inner (this=0xb66e288, thd=0xaabeb10) at /newdata/mysql-8.0.23/sql/sql_select.cc:827
  40. #28 0x000000000387145a in Sql_cmd_dml::execute (this=0xb66e288, thd=0xaabeb10) at /newdata/mysql-8.0.23/sql/sql_select.cc:612
  41. #29 0x00000000037fa060 in mysql_execute_command (thd=0xaabeb10, first_level=true) at /newdata/mysql-8.0.23/sql/sql_parse.cc:4407
  42. #30 0x00000000037fbf41 in dispatch_sql_command (thd=0xaabeb10, parser_state=0x7fffc8566a50) at /newdata/mysql-8.0.23/sql/sql_parse.cc:4988
  43. #31 0x00000000037f2543 in dispatch_command (thd=0xaabeb10, com_data=0x7fffc8567b00, command=COM_QUERY) at /newdata/mysql-8.0.23/sql/sql_parse.cc:1836
  44. #32 0x00000000037f095e in do_command (thd=0xaabeb10) at /newdata/mysql-8.0.23/sql/sql_parse.cc:1320
  45. #33 0x00000000039c5c91 in handle_connection (arg=0x9bd6950) at /newdata/mysql-8.0.23/sql/conn_handler/connection_handler_per_thread.cc:301
  46. #34 0x000000000562cd84 in pfs_spawn_thread (arg=0xad3b3a0) at /newdata/mysql-8.0.23/storage/perfschema/pfs.cc:2900
  47. #35 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
  48. #36 0x00007ffff5e388dd in clone () from /lib64/libc.so.6
复制代码
实战二:innodb随机预读对磁盘io性能的提拔

MySQL Read-Ahead预处理 - 墨天轮
变量和指标:对于预读参数有以下2个指标:
  1. mysql> show variables like '%read_ahead%';
  2. +-----------------------------+-------+
  3. | Variable_name               | Value |
  4. +-----------------------------+-------+
  5. | innodb_random_read_ahead    | OFF   |
  6. | innodb_read_ahead_threshold | 56    |
  7. +-----------------------------+-------+
复制代码


  • innodb_random_read_ahead:开启随机预读技术,优化InnoDB I/O。默认关闭
  • innodb_read_ahead_threshold:控制InnoDB用于将页面预取到缓冲池中的线性预读的灵敏度
验证过程:
  1. CREATE TABLE `sbtest1` (
  2.   `id` int NOT NULL AUTO_INCREMENT,
  3.   `k` int NOT NULL DEFAULT '0',
  4.   `c` char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
  5.   `pad` char(60) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
  6.   PRIMARY KEY (`id`)
  7. ) ENGINE=InnoDB
复制代码
通过sysbench创建一个 1个page 基本包罗16kb*1024=16384字节,
INT 4字节 ,char按照长度
Id(4字节)+k(4字节)+c(120字节)+ pad(60字节)=188字节
16384/188=88, 一个页平均下来大抵88行的数据。
Extent=64个页 * 88 =5632行的数据。
模式数据:
  1. shell> ./sysbench  ./lua/oltp_common.lua --mysql-user=root --mysql-password=123456  --mysql-socket=/opt/data8.0/mysql/mysql.sock    --mysql-db=sbtest --tables=1 --table-size=6000 --db-driver=mysql --report-interval=10 --threads=10 --time=120 prapare
  2. Initializing worker threads...
  3. Creating table 'sbtest1'...
  4. Inserting 6000 records into 'sbtest1'
  5. Creating a secondary index on 'sbtest1'...
复制代码
通过status指标:
  1. #初次记录ahead指标
  2. mysql> show global status  like '%ahead%';
  3. +---------------------------------------+-------+
  4. | Variable_name                         | Value |
  5. +---------------------------------------+-------+
  6. | Innodb_buffer_pool_read_ahead_rnd     | 387   |
  7. | Innodb_buffer_pool_read_ahead         | 0     |
  8. | Innodb_buffer_pool_read_ahead_evicted | 0     |
  9. +---------------------------------------+-------+
  10. #不输出数据
  11. mysql> pager cat > /dev/null
  12. mysql>select * from sbtest1 where id <7000;
  13. mysql> nopager
  14. #记录结果
  15. mysql> show status  like '%ahead%';
  16. +---------------------------------------+-------+
  17. | Variable_name                         | Value |
  18. +---------------------------------------+-------+
  19. | Innodb_buffer_pool_read_ahead_rnd     | 487   |
  20. | Innodb_buffer_pool_read_ahead         | 0     |
  21. | Innodb_buffer_pool_read_ahead_evicted | 0     |
  22. +---------------------------------------+-------+
复制代码
Random read-ahead下通过上面6000行的数据大抵需要100页的读取。
Linear read-ahead下指标没有变革,统计信息村落问题。


  • Innodb_buffer_pool_read_ahead
    由预读背景线程读入InnoDB缓冲池的页数。
  • Innodb_buffer_pool_read_ahead_rnd:
    由InnoDB发起的“随机”预读次数。当查询以随机顺序扫描表的大部分时,就会发生这种情况。
  • Innodb_buffer_pool_read_ahead_evicted
    由预读背景线程读入InnoDB缓冲池的页面数,这些页面随后在没有被查询访问的情况下被逐出。


SHOW ENGINE INNODB STATUS还表现了预读页面的读取速率,以及这些页面在不被访问的情况下被驱逐的速率。每秒平均数据是基于上次调用SHOW ENGINE INNODB STATUS以来网络的统计数据,表现在SHOW ENGINE INNODB STATUS输出的BUFFER POOL and MEMORY部分。
  1. BUFFER POOL AND MEMORY
  2. ----------------------
  3. Total large memory allocated 137039872
  4. Dictionary memory allocated 375221
  5. Buffer pool size   8192
  6. Free buffers       6850
  7. Database pages     1338
  8. Old database pages 513
  9. 。。。
  10. Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
  11. LRU len: 1338, unzip_LRU len: 0
  12. I/O sum[0]:cur[0], unzip sum[0]:cur[0]
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万有斥力

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表