11.怎么给字符串字段加索引?

MySQL支持前缀索引,你可以定义字符串的一部分作为索引。不指定前缀长度,那么索引就会包含整个字符串。

mysql> alter table SUser add index index1(email);
或
mysql> alter table SUser add index index2(email(6));

部分字符串作为索引占用的空间更小,但是可能会增加额外的记录扫描次数,即区分度不够好,重复的
键值多,导致需要扫描更多的行数进行区分。

查看下4~7个字节的前缀索引

mysql> select
count(distinct left(email,4))as L4,
count(distinct left(email,5))as L5,
count(distinct left(email,6))as L6,
count(distinct left(email,7))as L7,
from SUser;

前缀索引对覆盖索引的影响

使用前缀索引就用不上覆盖索引对查询性能的优化。

前缀的区分度不够好如何解决

  • 使用倒序存储
  • 使用hash字段

它们的相同点是,都不支持范围查询

区别,主要体现在以下三个方面:

  1. 从占用的额外空间来看,倒序存储方式在主键索引上,不会消耗额外的存储空间,而hash字段方法需要增加一个字段。当然,倒序存储方式使用4个字节的前缀长度应该是不够的,如果再长一点,这个消耗跟额外这个hash字段也差不多抵消了。
  2. 在CPU消耗方面,倒序方式每次写和读的时候,都需要额外调用一次reverse函数,而hash字段的方式需要额外调用一次crc32()函数。如果只从这两个函数的计算复杂度来看的话,reverse函数额外消耗的CPU资源会更小些。
  3. 从查询效率上看,使用hash字段方式的查询性能相对更稳定一些。因为crc32算出来的值虽然有冲突的概率,但是概率非常小,可以认为每次查询的平均扫描行数接近1。而倒序存储方式毕竟还是用的前缀索引的方式,也就是说还是会增加扫描行数。

字符串字段创建索引的场景

  1. 直接创建完整索引,这样可能比较占用空间;
  2. 创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引;
  3. 倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题;
  4. 创建hash字段索引,查询性能稳定,有额外的存储和计算消耗,跟第三种方式一样,都不支持范围扫描。

12.为什么我的MySQL会“抖”一下?

你的SQL语句为什么变“慢”了

当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。

平时执行很快的更新操作,其实就是在写内存和日志,而MySQL偶尔“抖”一下的那个瞬间,可能就是在刷脏页(flush)。

什么场景下触发flush(刷脏页)?

  1. InnoDB的redo log写满了。这时候系统会停止所有更新操作,把checkpoint往前推进,redo log留出空间可以继续写。
  2. 系统内存不足。当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是“脏页”,就要先将脏页写到磁盘。
  3. 是MySQL认为系统“空闲”的时候,但是在“繁忙”的时候也要见缝插针地找时间,只要有机会就刷一点“脏页”。
  4. MySQL正常关闭的情况。这时候,MySQL会把内存的脏页都flush到磁盘上,这样下次MySQL启动的时候,就可以直接从磁盘上读数据,启动速度会很快。

InnoDB的策略是尽量使用内存,因此对于一个长时间运行的库来说,未被使用的页面很少。而当要读入的数据页没有在内存的时候,就必须到缓冲池中申请一个数据页。这时候只能把最久不使用的数据页从内存中淘汰掉:如果要淘汰的是一个干净页,就直接释放出来复用;但如果是脏页呢,就必须将脏页先刷到磁盘,变成干净页后才能复用。

InnoDB刷脏页的控制策略

innodb_io_capacity参数。它会告诉InnoDB你的磁盘能力。这个值我建议你设置成磁盘的IOPS。

fio -filename=$filename -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs

控制刷脏页的速度的因素

  • 脏页比例
  • redo log写盘速度。

InnoDB会根据这两个因素先单独算出两个数字。参数innodb_max_dirty_pages_pct是脏页比例上限,默认值是75%。InnoDB会根据当前的脏页比例(假设为M),算出一个范围在0到100之间的数字。

InnoDB每次写入的日志都有一个序号,当前写入的序号跟checkpoint对应的序号之间的差值,我们假设为N。InnoDB会根据这个N算出一个范围在0到100之间的数字,这个计算公式可以记为F2(N)。F2(N)算法比较复杂,你只要知道N越大,算出来的值越大就好了。然后,根据上述算得的F1(M)和F2(N)两个值,取其中较大的值记为R,之后引擎就可以按照innodb_io_capacity定义的能力乘以R%来控制刷脏页的速度。

脏页比例是通过Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total得到

MySQL中的一个机制,可能让你的查询会更慢:在准备刷一个脏页的时候,如果这个数据页旁边的数据页刚好是脏页,就会把这个“邻居”也带着一起刷掉;而且这个把“邻居”拖下水的逻辑还可以继续蔓延,也就是对于每个邻居数据页,如果跟它相邻的数据页也还是脏页的话,也会被放到一起刷。

在InnoDB中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为1的时候会有上述的“连坐”机制,值为0时表示不找邻居,自己刷自己的。在MySQL 8.0中,innodb_flush_neighbors参数的默认值已经是0了。

13.为什么表数据删掉一半,表文件大小不变?

一个InnoDB表包含两部分,即:表结构定义和数据。在MySQL 8.0版本以前,表结构是存在以.frm为后缀的文件里。而MySQL 8.0版本,则已经允许把表结构定义放在系统数据表中了。

参数innodb_file_per_table

表数据既可以存在共享表空间里,也可以是单独的文件。这个行为是由参数innodb_file_per_table控制

  • 这个参数设置为OFF表示的是,表的数据放在系统共享表空间,也就是跟数据字典放在一起;
  • 这个参数设置为ON表示的是,每个InnoDB表数据存储在一个以 .ibd为后缀的文件中。

从MySQL 5.6.6版本开始,它的默认值就是ON了。

如果是放在共享表空间中,即使表删掉了,空间也是不会回收的。

删掉一个数据页里的某个记录,再往这中间插入一个记录可能复用这个位置,磁盘文件的大小并不会缩小。比如在300 500 600这样的数据里,删除500,插入400可能会复用位置,但是插入800不会复用。

删掉了一个数据页上的所有记录,则整个数据页就可以被复用了且可以复用到任何位置。

如果相邻的两个数据页利用率都很小,系统就会把这两个页上的数据合到其中一个页上,另外一个数据页就被标记为可复用。

delete命令只是把所有的数据页被标记为可复用。但是磁盘上,文件不会变小。

不止是删除数据会造成空洞,插入数据也会。经过大量增删改的表,都是可能是存在空洞的。所以,如果能够把这些空洞去掉,就能达到收缩表空间的目的。而重建表,就可以达到这样的目的。

重建表

  1. 建立一个临时文件,扫描表A主键的所有数据页;
  2. 用数据页中表A的记录生成B+树,存储到临时文件中;
  3. 生成临时文件的过程中,将所有对A的操作记录在一个日志文件(row log)中,对应的是图中state2的状态;
  4. 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表A相同的数据文件,对应的就是图中state3的状态;
  5. 用临时文件替换表A的数据文件。

Online 和 inplace

重建出来的数据是放在“tmp_file”里的,这个临时文件是InnoDB在内部创建出来的。整个DDL过程都在InnoDB内部完成。对于server层来说,没有把数据挪动到临时表,是一个“原地”操作,这就是“inplace”名称的来源。

  1. DDL过程如果是Online的,就一定是inplace的;
  2. 反过来未必,也就是说inplace的DDL,有可能不是Online的。截止到MySQL 8.0,添加全文索引(FULLTEXT index)和空间索引(SPATIAL index)就属于这种情况。

14.count(×)这么慢,我该怎么办?

没有过滤条件的count(*):

MyISAM引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数。

InnoDB引擎执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。

MySQL优化器会找到最小的那棵树来遍历。在保证逻辑正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一。

通过show table status 命令得到的行数也是不准确的,通过采样来估算的误差可能达到40%到50%。

不同的count用法

分析性能差别的时候,你可以记住这么几个原则:

  1. server层要什么就给什么;
  2. InnoDB只给必要的值;
  3. 现在的优化器只优化了count(*)的语义为“取行数”,其他“显而易见”的优化并没有做。

count(*)、count(主键id)和count(1) 都表示返回满足条件的结果集的总行数;而count(字段),则表示返回满足条件的数据行里面,参数“字段”不为NULL的总个数。

对于count(主键id)来说,InnoDB引擎会遍历整张表,把每一行的id值都取出来,返回给server层。server层拿到id后,判断是不可能为空的,就按行累加。

对于count(1)来说,InnoDB引擎遍历整张表,但不取值。server层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。

单看这两个用法的差别的话,你能对比出来,count(1)执行得要比count(主键id)快。因为从引擎返回id会涉及到解析数据行,以及拷贝字段值的操作

对于count(字段)来说:

  1. 如果这个“字段”是定义为not null的话,一行行地从记录里面读出这个字段,判断不能为null,按行累加;
  2. 如果这个“字段”定义允许为null,那么执行的时候,判断到有可能是null,还要把值取出来再判断一下,不是null才累加。

count(*)是例外,并不会把全部字段取出来,而是专门做了优化,不取值。count(*)肯定不是null,按行累加。

按照效率排序的话,count(字段)<count(主键id)<count(1)≈count(*),所以我建议你,尽量使用count(*)。

15.答疑文章(一):日志和索引相关问题

崩溃恢复时的判断规则。

  1. 如果redo log里面的事务是完整的,也就是已经有了commit标识,则直接提交;
  2. 如果redo log里面的事务只有完整的prepare,则判断对应的事务binlog是否存在并完整:
  3. 如果是,则提交事务;
  4. 否则,回滚事务。

MySQL怎么知道binlog是完整的?

一个事务的binlog是有完整格式的:

  • statement格式的binlog,最后会有COMMIT;
  • row格式的binlog,最后会有一个XID event。

另外,在MySQL 5.6.2版本以后,还引入了binlog-checksum参数,用来验证binlog内容的正确性。对于binlog日志由于磁盘原因,可能会在日志中间出错的情况,MySQL可以通过校验checksum的结果来发现。所以,MySQL还是有办法验证事务binlog的完整性的。

redo log 和 binlog是怎么关联起来的?

它们有一个共同的数据字段,叫XID。崩溃恢复的时候,会按顺序扫描redo log:

  • 如果碰到既有prepare、又有commit的redo log,就直接提交;
  • 如果碰到只有parepare、而没有commit的redo log,就拿着XID去binlog找对应的事务。

正常运行中的实例,数据写入后的最终落盘,是从redo log更新过来的 还是从buffer pool更新过来的呢?

这里涉及到了,“redo log里面到底是什么”的问题。 实际上,redo log并没有记录数据页的完整数据,所以它并没有能力自己去更新磁盘数据页,也 就不存在“数据最终落盘,是由redo log更新过去”的情况。

  1. 如果是正常运行的实例的话,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘。这个过程,甚至与redo log毫无关系。
  2. 在崩溃恢复场景中,InnoDB如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让redo log更新内存内容。更新完成后,内存页变成脏页,就回到了 第一种情况的状态

redo log buffer是什么?是先修改内存,还是先写redo log文件?

在一个事务的更新过程中,日志是要写多次的。比如下面这个事务:

begin;
insert into t1 ...
insert into t2 ...
commit;

这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先保存起来,但又不能在还没commit的时候就直接写到redo log文件里。

所以,redo log buffer就是一块内存,用来先存redo日志的。也就是说,在执行第一个insert的时 候,数据的内存被修改了,redo log buffer也写入了日志。

但是,真正把日志写到redo log文件(文件名是 ib_logfile+数字),是在执行commit语句的时候做的。

单独执行一个更新语句的时候,InnoDB会自己启动一个事务,在语句执行完成的时候提交。过 程跟上面是一样的,只不过是“压缩”到了一个语句里面完成。

16.“order by”是怎么工作的?

假设你要查询城市是“杭州”的所有人名字,并且按照姓名排序返回前1000个人的姓名、年龄。

CREATE TABLE `t` (
`id` int(11) NOT NULL,
`city` varchar(16) NOT NULL,
`name` varchar(16) NOT NULL,
`age` int(11) NOT NULL,
`addr` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `city` (`city`)
) ENGINE=InnoDB;

查询语句如下:

select city,name,age from t where city='杭州' order by name limit 1000 ;

用explain命令可以看到Extra这个字段中的“Using filesort”表示的就是需要排序,MySQL会给每个线程分配一块内存用于排序,称为sort_buffer。

图中“按name排序”这个动作,可能在内存中完成,也可能需要使用外部排序,这取决于排序所需的内存和参数sort_buffer_size。

sort_buffer_size,就是MySQL为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。

全字段排序 VS rowid排序

如果MySQL实在是担心排序内存太小,会影响排序效率,才会采用rowid排序算法(可以设置max_length_for_sort_data),这样排序过程中一次可以排序更多行,但是需要再回到原表去取数据。

如果MySQL认为内存足够大,会优先选择全字段排序,把需要的字段都放到sort_buffer中,这样排序后就会直接从内存里面返回查询结果了,不用再回到原表去取数据。

这也就体现了MySQL的一个设计思想:如果内存够,就要多利用内存,尽量减少磁盘访问。

对于InnoDB表来说,rowid排序会要求回表多造成磁盘读,因此不会被优先选择。

17.如何正确地显示随机消息?

内存临时表

mysql> select word from words order by rand() limit 3;

Extra字段显示Using temporary,表示的是需要使用临时表;Using filesort,表示的是需要执行排序操作。
因此这个Extra的意思就是,需要临时表,并且需要在临时表上排序。

对于InnoDB表来说,执行全字段排序会减少磁盘访问,因此会被优先选择。

强调了“InnoDB表”,对于内存表,回表过程只是简单地根据数据行的位置,直接访问内存得到数据,根本不会导致多访问磁盘。优化器没有了这一层顾虑,那么它会优先考虑的,就是用于排序的行越少越好了,所以,MySQL这时就会选择rowid排序。

MySQL的表是用什么方法来定位“一行数据”的

rowid表示的是:每个引擎用来唯一标识数据行的信息。

对于有主键的InnoDB表来说,这个rowid就是主键ID;

对于没有主键的InnoDB表来说,这个rowid就是由系统生成的;

MEMORY引擎不是索引组织表。在这个例子里面,你可以认为它就是一个数组。因此,这个rowid其实就是数组的下标。

小结一下:order by rand()使用了内存临时表,内存临时表排序的时候使用了rowid排序方法。

tmp_table_size这个配置限制了内存临时表的大小,默认值是16M。如果临时表大小超过了tmp_table_size,那么内存临时表就会转成磁盘临时表。

磁盘临时表使用的引擎默认是InnoDB,是由参数internal_tmp_disk_storage_engine控制的。当使用磁盘临时表的时候,对应的就是一个没有显式索引的InnoDB表的排序过程。

18.为什么这些SQL语句逻辑相同,性能却差异巨大?

  1. SQL语句有函数
  2. 隐式类型转换

共同点:对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。

19.为什么我只查一行的语句也执行这么慢?

查询长时间不返回

  1. 等MDL锁。show processlist命令查看Waiting for table metadata lock
  2. 等flush。有一个flush tables命令被别的语句堵住,导致无法执行flush tables。
  3. 等行锁。有一个请求占有写锁,还未提交,导致查询被堵住。

查询慢

  1. 没有索引
  2. 在查询期间其他事务存在大量更新,导致一直读取undo log做回滚。

20.幻读是什么幻读有什么问题?

幻读:侧重【行数量】发生了变化,定义:当某个事务在读取某个范围的记录的时候,另外一个事务又在该范围插入了新的记录,当前事务再次读取这个范围的记录,会产生幻行(Phantom Data)。

可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。

如何解决幻读?

行锁+间隙锁(Gap Lock)

产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB只好引入新的锁,也就是间隙锁(Gap Lock)。

间隙锁之间都不存在冲突关系。跟行锁有冲突关系的是“另外一个行锁”。

间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间。也就是说,我们的表t初始化以后,如果用select * from t for update要把整个表所有记录锁起来,就形成了7个next-keylock,分别是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。

21.为什么我只改一行的语句锁这么多?

加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”。

  1. 原则1:加锁的基本单位是next-key lock。next-key lock是前开后闭区间。
  2. 原则2:查找过程中访问到的对象才会加锁。
  3. 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁。
  4. 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。
  5. 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

锁作用在覆盖索引上,主键不加锁。

next-key lock实际上是由间隙锁加行锁实现的,间隙锁之间不冲突,比如A上了next-key lock,B请求修改,这时候会有间隙锁,但是没有行锁,A想改这时候会导致死锁,因为要等B的间隙锁释放。

22.MySQL有哪些“饮鸩止渴”提高性能的方法?

慢查询性能问题

在MySQL中,会引发性能问题的慢查询,大体有以下三种可能:

  1. 索引没有设计好;
  2. SQL语句没写好;
  3. MySQL选错了索引。

23.MySQL是怎么保证数据不丢的?

binlog的写入机制

事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。

一个事务的binlog是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。这就涉及到了binlog cache的保存问题。

系统给binlog cache分配了一片内存,每个线程一个,参数 binlog_cache_size用于控制单个线程内binlog cache所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。

事务提交的时候,执行器把binlog cache里的完整事务写入到binlog中,并清空binlog cache。

binLog写入流程
binLog写入流程

每个线程有自己binlog cache,但是共用同一份binlog文件。图中的write,指的就是指把日志写入到文件系统的page cache,并没有把数据持久化到磁盘,所以速度比较快。

图中的fsync,才是将数据持久化到磁盘的操作。一般情况下,我们认为fsync才占磁盘的IOPS。

write 和fsync的时机,是由参数sync_binlog控制的:

  1. sync_binlog=0的时候,表示每次提交事务都只write,不fsync;
  2. sync_binlog=1的时候,表示每次提交事务都会执行fsync;
  3. sync_binlog=N(N>1)的时候,表示每次提交事务都write,但累积N个事务后才fsync。

因此,在出现IO瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成0,比较常见的是将其设置为100~1000中的某个数值。

将sync_binlog设置为N,对应的风险是:如果主机发生异常重启,会丢失最近N个事务的binlog日志。

redo log的写入机制

事务在执行过程中,生成的redo log是要先写到redo log buffer的。

redolog写入流程
redolog写入流程

这三种状态分别是:

  1. 存在redo log buffer中,物理上是在MySQL进程内存中,就是图中的红色部分;
  2. 写到磁盘(write),但是没有持久化(fsync),物理上是在文件系统的page cache里面,也就是图中的黄色部分;
  3. 持久化到磁盘,对应的是hard disk,也就是图中的绿色部分。日志写到redo log buffer是很快的,wirte到page cache也差不多,但是持久化到磁盘的速度就慢多了。

为了控制redo log的写入策略,InnoDB提供了innodb_flush_log_at_trx_commit参数,它有三种
可能取值:

  1. 设置为0的时候,表示每次事务提交时都只是把redo log留在redo log buffer中;
  2. 设置为1的时候,表示每次事务提交时都将redo log直接持久化到磁盘;
  3. 设置为2的时候,表示每次事务提交时都只是把redo log写到page cache。

InnoDB有一个后台线程,每隔1秒,就会把redo log buffer中的日志,调用write写到文件系统的page cache,然后调用fsync持久化到磁盘。

注意,事务执行中间过程的redo log也是直接写在redo log buffer中的,这些redo log也会被后台线程一起持久化到磁盘。也就是说,一个没有提交的事务的redo log,也是可能已经持久化到磁盘的。

实际上,除了后台线程每秒一次的轮询操作外,还有两种场景会让一个没有提交的事务的redo log写入到磁盘中。

  1. 一种是,redo log buffer占用的空间即将达到 innodb_log_buffer_size一半的时候,后台线程会主动写盘。注意,由于这个事务并没有提交,所以这个写盘动作只是write,而没有调用fsync,也就是只留在了文件系统的page cache。
  2. 另一种是,并行的事务提交的时候,顺带将这个事务的redo log buffer持久化到磁盘。假设一个事务A执行到一半,已经写了一些redo log到buffer中,这时候有另外一个线程的事务B提交,如果innodb_flush_log_at_trx_commit设置的是1,那么按照这个参数的逻辑,事务B要把redo log buffer里的日志全部持久化到磁盘。这时候,就会带上事务A在redolog buffer里的日志一起持久化到磁盘。

这里需要说明的是,我们介绍两阶段提交的时候说过,时序上redo log先prepare, 再写binlog,最后再把redo log commit。

如果把innodb_flush_log_at_trx_commit设置成1,那么redo log在prepare阶段就要持久化一次,因为有一个崩溃恢复逻辑是要依赖于prepare 的redo log,再加上binlog来恢复的。

每秒一次后台轮询刷盘,再加上崩溃恢复这个逻辑,InnoDB就认为redo log在commit的时候就不需要fsync了,只会write到文件系统的page cache中就够了。

通常我们说MySQL的“双1”配置,指的就是sync_binlog和innodb_flush_log_at_trx_commit都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是redo log(prepare 阶段),一次是binlog。

如果你的MySQL现在出现了性能瓶颈,而且瓶颈在IO上,可以通过哪些方法来提升性能呢?

  1. 设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count参数,减少binlog的写盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但没有丢失数据的风险。
  2. 将sync_binlog 设置为大于1的值(比较常见是100~1000)。这样做的风险是,主机掉电时会丢binlog日志。
  3. 将innodb_flush_log_at_trx_commit设置为2。这样做的风险是,主机掉电的时候会丢数据。

问题

当设置sync_binlog=0时,每次commit都只时write到page cache,并不会fsync。但是做实验时binlog文件中还是会有记录,这是什么原因呢?是不是后台线程每秒一次的轮询也会将binlog cache持久化到磁盘?还是有其他的参数控制呢?

回答:你看到的“binlog的记录”,也是从page cache读的哦。