Mysql GTID

从5.6 mysql引入了gtid的概念,但是到8的版本,默认gtid是关闭,如果要打开gtid需要做如下操作:

GTID_MODE = ON

同时 设置:ENFORCE_GTID_CONSISTENCY = ON,

否则会报错:2016-11-29T07:39:35.265167Z 0 [ERROR] GTID_MODE = ON requires ENFORCE_GTID_CONSISTENCY = ON. 从而导致数据库无法启动;在官方文档当中gtid的组成结构为:

GTID = source_id:transaction_id

其中 source_id 是mysql server id,实质它的值和 server_uuid 是一样的;做个简单的测试:

[HuSi@HuoSi]mysql8 $ bin/mysql -uroot -proot -e "show variables like '%uuid%'"
mysql: [Warning] Using a password on the command line interface can be insecure.
+---------------+--------------------------------------+
| Variable_name | Value                                |
+---------------+--------------------------------------+
| server_uuid   | 300e39aa-7e2c-11e6-a991-22aaeca26514 |
+---------------+--------------------------------------+

在来看看binlog 中记录的值:

#161129 15:52:48 server id 8  end_log_pos 219 CRC32 0x186fb5bf 	GTID	last_committed=0	sequence_number=1
SET @@SESSION.GTID_NEXT= '300e39aa-7e2c-11e6-a991-22aaeca26514:1'/*!*/;

可以看到 source_id 和 uuid 是相同的,需要注意uuid 本身的值是可以更改的;

这里有这样一个场景假如:1、此时uuid为 a,2、修改为b,3、再次修改为a,会响应数据库吗?

证券交易所行情数据架构的简单实践

公司引进了上海证券交易所和深圳证券交易所行情数据,行情数据分为两个解绑level-1,和level-2 ,我们用的是level-1,在level-1中 上交所数据的更新频率是 5s(据说要升级到3s),深交所的更新频率是 3s;截止到2016 最新的上市公司情况为:

2016年10月18日中国股市上市公司数量、总市值
股市总况 深圳股市 上海股市 沪深合计
上市公司(家) 1,824 1,140 2,964
上市证券(只) 4,225 8,664 12,889
总 市 值(亿元) 227,321.16 275,085.14 502,406
流通市值(亿元) 155,204.35 233,509.40 388,714

那么一天的交易时间按照 4小时计算,单是股票行情逐笔数据的数据量大概为:2964*(60/3)*4*60 条,同时需要汇总生成 1分钟,5分钟,15分钟,1小时等行情数据,每个交易日大概为1600w左右,如果再加上其它指数等数据数据量还会增加不少,而这些数据要全部存储下来,行情的历史数据还是比较值钱的;如果把连续几年的逐笔数据存储下来然后进行相关的研究对金融方面的理解还是比较有益的;整体来说这种数据每天的量不是很大,但是一般这种数据都是供行情应用使用的,所以对稳定性要求高一些;下面简单介绍下,笔者使用架构,很普通,够用就好;

level1

一般接收证交所的行情数据都是使用专线的,这个没有什么特别的,一般的IDC都提供专线接入;证交所的数据一般需要落地到一台windows上,因为其提供的基本就是这种win版的数据同步客户端,这里称之为sessinfo node;我们使用的Mysql作为短期存储,开始使用的基本架构是:

34a1587b-b8e3-411e-9539-a2082e11597f

在sess-info-net node上面同时运行了自行开发的转码工具,将证交所发送过来的数据快照,转码为数据库数据,数据库使用传统的M-S架构,这种架构存在的缺点是master 会成为单点,Replication 功能可能会停止,这都会造成前端数据的不正常,而影响整体的功能;那么我们发展的一个方向是改造成,多主结构,即数据会同时写入多个节点,但是多写的数据一致性是需要保证的,完全自己单独开发一个中间件似乎也不太现实,这里我们选取了cobar–简单、轻量、易用,还有源码;

level2

需要注意的是 cobar如何完成多多个表的同时写,注意这里不是分表;经过一段的测试并没有网上所说的有关cobar的那些问题;那么此时的问题是cobar 成为单点,对cobar做一个高可用,似乎不妥,因为cobar节点的切换也是需要时间的,那么怎么办呢?于是又做了些改进:

level3

这里自行开发了 快照传输程序,即将行情快照,同时向一个节点将快照发送出来,然后在reciver node上进行转码然后存储到一个备份mysql当中,如果cobar有问题,或者几个主同时宕机了也不会丢数据;对外提供数据时,同时走代理将负载分发到不同的节点上,这里的代理的使用可以参考:confd+haproxy+etcd;

上面就是整个架构的简单描述,细节上需要注意的是自己开发的快照转码产生的数据量,以及快照传输问题,还有就是代理的设置问题,比如代理节点的修改,无缝切换(haproxy 无缝切换)等问题;

Innodb 文件结构分析 (一) –系统隐藏表

这里是Innodb 文件结构分析的预备知识,主要介绍数据字典表;在mysql 8中.frm 的数据字典文件已经消失,所有的数据文件都已经存储Innodb 的数据表中;在 information_schema 中存在数个表面看是数据字典表(实际是视图),

Mysql-HuoSi->show tables;
+---------------------------------------+
| Tables_in_information_schema          |
+---------------------------------------+
| CHARACTER_SETS                        |
| COLLATION_CHARACTER_SET_APPLICABILITY |
| COLLATIONS                            |
| COLUMN_PRIVILEGES                     |
| COLUMNS                               |
| ENGINES                               |
| EVENTS                                |
| FILES                                 |
| GLOBAL_STATUS                         |
| GLOBAL_VARIABLES                      |
| INNODB_BUFFER_PAGE                    |
| INNODB_BUFFER_PAGE_LRU                |
| INNODB_BUFFER_POOL_STATS              |
| INNODB_CACHED_INDEXES                 |
| INNODB_CMP                            |
| INNODB_CMP_PER_INDEX                  |
| INNODB_CMP_PER_INDEX_RESET            |
| INNODB_CMP_RESET                      |
| INNODB_CMPMEM                         |
| INNODB_CMPMEM_RESET                   |
| INNODB_FT_BEING_DELETED               |
| INNODB_FT_CONFIG                      |
| INNODB_FT_DEFAULT_STOPWORD            |
| INNODB_FT_DELETED                     |
| INNODB_FT_INDEX_CACHE                 |
| INNODB_FT_INDEX_TABLE                 |
| INNODB_LOCK_WAITS                     |
| INNODB_LOCKS                          |
| INNODB_METRICS                        |
| INNODB_SYS_COLUMNS                    |
| INNODB_SYS_DATAFILES                  |
| INNODB_SYS_FIELDS                     |
| INNODB_SYS_FOREIGN                    |
| INNODB_SYS_FOREIGN_COLS               |
| INNODB_SYS_INDEXES                    |
| INNODB_SYS_TABLES                     |
| INNODB_SYS_TABLESPACES                |
| INNODB_SYS_TABLESTATS                 |
| INNODB_SYS_VIRTUAL                    |
| INNODB_TEMP_TABLE_INFO                |
| INNODB_TRX                            |
| KEY_COLUMN_USAGE                      |
| OPTIMIZER_TRACE                       |
| PARAMETERS                            |
| PARTITIONS                            |
| PLUGINS                               |
| PROCESSLIST                           |
| PROFILING                             |
| REFERENTIAL_CONSTRAINTS               |
| ROUTINES                              |
| SCHEMA_PRIVILEGES                     |
| SCHEMATA                              |
| SESSION_STATUS                        |
| SESSION_VARIABLES                     |
| SHOW_STATISTICS                       |
| SHOW_STATISTICS_DYNAMIC               |
| STATISTICS                            |
| STATISTICS_BASE                       |
| STATISTICS_DYNAMIC                    |
| TABLE_CONSTRAINTS                     |
| TABLE_PRIVILEGES                      |
| TABLES                                |
| TABLES_DYNAMIC                        |
| TABLESPACES                           |
| TRIGGERS                              |
| USER_PRIVILEGES                       |
| VIEWS                                 |
+---------------------------------------+
67 rows in set (0.00 sec)

在这里可以看起定义:

Mysql-HuoSi->show create table COLUMNS \G
*************************** 1. row ***************************
                View: COLUMNS
         Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `COLUMNS` AS select (`cat`.`name` collate utf8_tolower_ci) AS `TABLE_CATALOG`,(`sch`.`name` collate utf8_tolower_ci) AS `TABLE_SCHEMA`,(`tbl`.`name` collate utf8_tolower_ci) AS `TABLE_NAME`,(`col`.`name` collate utf8_tolower_ci) AS `COLUMN_NAME`,`col`.`ordinal_position` AS `ORDINAL_POSITION`,`col`.`default_value_utf8` AS `COLUMN_DEFAULT`,if((`col`.`is_nullable` = 1),'YES','NO') AS `IS_NULLABLE`,substring_index(substring_index(`col`.`column_type_utf8`,'(',1),' ',1) AS `DATA_TYPE`,internal_dd_char_length(`col`.`type`,`col`.`char_length`,`coll`.`name`,0) AS `CHARACTER_MAXIMUM_LENGTH`,internal_dd_char_length(`col`.`type`,`col`.`char_length`,`coll`.`name`,1) AS `CHARACTER_OCTET_LENGTH`,if((`col`.`numeric_precision` = 0),NULL,`col`.`numeric_precision`) AS `NUMERIC_PRECISION`,if(((`col`.`numeric_scale` = 0) and (`col`.`numeric_precision` = 0)),NULL,`col`.`numeric_scale`) AS `NUMERIC_SCALE`,`col`.`datetime_precision` AS `DATETIME_PRECISION`,(case `col`.`type` when 'MYSQL_TYPE_STRING' then if((`cs`.`name` = 'binary'),NULL,`cs`.`name`) when 'MYSQL_TYPE_VAR_STRING' then if((`cs`.`name` = 'binary'),NULL,`cs`.`name`) when 'MYSQL_TYPE_VARCHAR' then if((`cs`.`name` = 'binary'),NULL,`cs`.`name`) when 'MYSQL_TYPE_TINY_BLOB' then if((`cs`.`name` = 'binary'),NULL,`cs`.`name`) when 'MYSQL_TYPE_MEDIUM_BLOB' then if((`cs`.`name` = 'binary'),NULL,`cs`.`name`) when 'MYSQL_TYPE_BLOB' then if((`cs`.`name` = 'binary'),NULL,`cs`.`name`) when 'MYSQL_TYPE_LONG_BLOB' then if((`cs`.`name` = 'binary'),NULL,`cs`.`name`) when 'MYSQL_TYPE_ENUM' then if((`cs`.`name` = 'binary'),NULL,`cs`.`name`) when 'MYSQL_TYPE_SET' then if((`cs`.`name` = 'binary'),NULL,`cs`.`name`) else NULL end) AS `CHARACTER_SET_NAME`,(case `col`.`type` when 'MYSQL_TYPE_STRING' then if((`cs`.`name` = 'binary'),NULL,`coll`.`name`) when 'MYSQL_TYPE_VAR_STRING' then if((`cs`.`name` = 'binary'),NULL,`coll`.`name`) when 'MYSQL_TYPE_VARCHAR' then if((`cs`.`name` = 'binary'),NULL,`coll`.`name`) when 'MYSQL_TYPE_TINY_BLOB' then if((`cs`.`name` = 'binary'),NULL,`coll`.`name`) when 'MYSQL_TYPE_MEDIUM_BLOB' then if((`cs`.`name` = 'binary'),NULL,`coll`.`name`) when 'MYSQL_TYPE_BLOB' then if((`cs`.`name` = 'binary'),NULL,`coll`.`name`) when 'MYSQL_TYPE_LONG_BLOB' then if((`cs`.`name` = 'binary'),NULL,`coll`.`name`) when 'MYSQL_TYPE_ENUM' then if((`cs`.`name` = 'binary'),NULL,`coll`.`name`) when 'MYSQL_TYPE_SET' then if((`cs`.`name` = 'binary'),NULL,`coll`.`name`) else NULL end) AS `COLLATION_NAME`,`col`.`column_type_utf8` AS `COLUMN_TYPE`,`col`.`column_key` AS `COLUMN_KEY`,if((ifnull(`col`.`generation_expression_utf8`,'IS_NOT_GC') = 'IS_NOT_GC'),if((`col`.`is_auto_increment` = TRUE),concat(ifnull(concat('on update ',`col`.`update_option`,' '),''),'auto_increment'),ifnull(concat('on update ',`col`.`update_option`),'')),convert(if(`col`.`is_virtual`,'VIRTUAL GENERATED','STORED GENERATED') using utf8)) AS `EXTRA`,get_dd_column_privileges(`sch`.`name`,`tbl`.`name`,`col`.`name`) AS `PRIVILEGES`,ifnull(`col`.`comment`,'') AS `COLUMN_COMMENT`,ifnull(`col`.`generation_expression_utf8`,'') AS `GENERATION_EXPRESSION` from (((((`mysql`.`columns` `col` join `mysql`.`tables` `tbl` on((`col`.`table_id` = `tbl`.`id`))) join `mysql`.`schemata` `sch` on((`tbl`.`schema_id` = `sch`.`id`))) join `mysql`.`catalogs` `cat` on((`cat`.`id` = `sch`.`catalog_id`))) join `mysql`.`collations` `coll` on((`col`.`collation_id` = `coll`.`id`))) join `mysql`.`character_sets` `cs` on((`coll`.`character_set_id` = `cs`.`id`))) where (internal_get_view_warning_or_error(`sch`.`name`,`tbl`.`name`,`tbl`.`type`,`tbl`.`options`) and can_access_column(`sch`.`name`,`tbl`.`name`,`col`.`name`) and (not(`tbl`.`hidden`)))
character_set_client: latin1
collation_connection: latin1_swedish_ci
1 row in set (0.00 sec)

从这里可以看到这个视图的源表为 mysql.xxxx,那么我们直接去访问下,相关的表看看:

Mysql-HuoSi->use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
Mysql-HuoSi->show tables;
+---------------------------+
| Tables_in_mysql           |
+---------------------------+
| column_stats              |
| columns_priv              |
| component                 |
| db                        |
| default_roles             |
| engine_cost               |
| func                      |
| general_log               |
| gtid_executed             |
| help_category             |
| help_keyword              |
| help_relation             |
| help_topic                |
| innodb_index_stats        |
| innodb_table_stats        |
| plugin                    |
| procs_priv                |
| proxies_priv              |
| role_edges                |
| server_cost               |
| servers                   |
| slave_master_info         |
| slave_relay_log_info      |
| slave_worker_info         |
| slow_log                  |
| tables_priv               |
| time_zone                 |
| time_zone_leap_second     |
| time_zone_name            |
| time_zone_transition      |
| time_zone_transition_type |
| user                      |
+---------------------------+
32 rows in set (0.00 sec)

这里可以看到,并没有想要看的表,如果直接查询呢:

Mysql-HuoSi->select * from mysql.columns;
ERROR 3554 (HY000): Access to system table 'mysql.columns' is rejected.

这里会报错,那么这些表到底存不存在呢,答案是肯定的:

[HuSi@HuoSi]mysql $ pwd
/Volumes/ssd/hadoop/soft/mysql-8/mysql8/data/mysql
[HuSi@HuoSi]mysql $ ls
catalogs.ibd				help_relation.ibd			slow_log.CSM
character_sets.ibd			help_topic.ibd				slow_log.CSV
collations.ibd				index_column_usage.ibd			slow_log_103.SDI
column_stats.ibd			index_partitions.ibd			st_spatial_reference_systems.ibd
column_type_elements.ibd		index_stats.ibd				table_partition_values.ibd
columns.ibd				indexes.ibd				table_partitions.ibd
columns_priv.ibd			innodb_index_stats.ibd			table_stats.ibd
component.ibd				innodb_table_stats.ibd			tables.ibd
db.ibd					parameter_type_elements.ibd		tables_priv.ibd
default_roles.ibd			parameters.ibd				tablespace_files.ibd
engine_cost.ibd				plugin.ibd				tablespaces.ibd
events.ibd				procs_priv.ibd				time_zone.ibd
foreign_key_column_usage.ibd		proxies_priv.ibd			time_zone_leap_second.ibd
foreign_keys.ibd			role_edges.ibd				time_zone_name.ibd
func.ibd				routines.ibd				time_zone_transition.ibd
general_log.CSM				schemata.ibd				time_zone_transition_type.ibd
general_log.CSV				server_cost.ibd				triggers.ibd
general_log_102.SDI			servers.ibd				user.ibd
gtid_executed.ibd			slave_master_info.ibd			version.ibd
help_category.ibd			slave_relay_log_info.ibd		view_routine_usage.ibd
help_keyword.ibd			slave_worker_info.ibd			view_table_usage.ibd
[HuSi@HuoSi]mysql $ 

这里可以看到对应的 数据文件是存在的,那么之所以隐藏数据字典表的目的应该是防止直接对这些表的修改,从而导致的数据库错误;那么这些表可以查看吗,答案是可以,但是要将启动方式改为debug的方式,下面来看一一看:

[HuSi@HuoSi]mysql8 $ bin/mysqld-debug --defaults-file=./my.cnf &
[1] 4270

然后登陆数据库:

[HuSi@HuoSi]mysql8 $ bin/mysql -uroot -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 8.0.0-dmr-debug-log MySQL Community Server - Debug (GPL)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SET SESSION debug='+d,skip_dd_table_access_check';
Query OK, 0 rows affected (0.00 sec)

mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show create table columns;
+---------+----------------------------------------------------------------------------------------------------------+
| Table   | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+---------+----------------------------------------------------------------------------------------------------------+
| columns | CREATE TABLE `columns` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `table_id` bigint(20) unsigned NOT NULL,
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_tolower_ci NOT NULL,
  `ordinal_position` int(10) unsigned NOT NULL,
  `type` enum('MYSQL_TYPE_DECIMAL','MYSQL_TYPE_TINY','MYSQL_TYPE_SHORT','MYSQL_TYPE_LONG','MYSQL_TYPE_FLOAT','MYSQL_TYPE_DOUBLE','MYSQL_TYPE_NULL','MYSQL_TYPE_TIMESTAMP','MYSQL_TYPE_LONGLONG','MYSQL_TYPE_INT24','MYSQL_TYPE_DATE','MYSQL_TYPE_TIME','MYSQL_TYPE_DATETIME','MYSQL_TYPE_YEAR','MYSQL_TYPE_NEWDATE','MYSQL_TYPE_VARCHAR','MYSQL_TYPE_BIT','MYSQL_TYPE_TIMESTAMP2','MYSQL_TYPE_DATETIME2','MYSQL_TYPE_TIME2','MYSQL_TYPE_NEWDECIMAL','MYSQL_TYPE_ENUM','MYSQL_TYPE_SET','MYSQL_TYPE_TINY_BLOB','MYSQL_TYPE_MEDIUM_BLOB','MYSQL_TYPE_LONG_BLOB','MYSQL_TYPE_BLOB','MYSQL_TYPE_VAR_STRING','MYSQL_TYPE_STRING','MYSQL_TYPE_GEOMETRY','MYSQL_TYPE_JSON') COLLATE utf8_bin NOT NULL,
  `is_nullable` tinyint(1) NOT NULL,
  `is_zerofill` tinyint(1) DEFAULT NULL,
  `is_unsigned` tinyint(1) DEFAULT NULL,
  `char_length` int(10) unsigned DEFAULT NULL,
  `numeric_precision` int(10) unsigned DEFAULT NULL,
  `numeric_scale` int(10) unsigned DEFAULT NULL,
  `datetime_precision` int(10) unsigned DEFAULT NULL,
  `collation_id` bigint(20) unsigned DEFAULT NULL,
  `has_no_default` tinyint(1) DEFAULT NULL,
  `default_value` blob,
  `default_value_utf8` text COLLATE utf8_bin,
  `default_option` blob,
  `update_option` varchar(32) COLLATE utf8_bin DEFAULT NULL,
  `is_auto_increment` tinyint(1) DEFAULT NULL,
  `is_virtual` tinyint(1) DEFAULT NULL,
  `generation_expression` longblob,
  `generation_expression_utf8` longtext COLLATE utf8_bin,
  `comment` varchar(2048) COLLATE utf8_bin NOT NULL,
  `hidden` tinyint(1) NOT NULL,
  `options` mediumtext COLLATE utf8_bin,
  `se_private_data` mediumtext COLLATE utf8_bin,
  `column_key` enum('','PRI','UNI','MUL') COLLATE utf8_bin NOT NULL,
  `column_type_utf8` mediumtext COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `table_id` (`table_id`,`name`),
  UNIQUE KEY `table_id_2` (`table_id`,`ordinal_position`),
  KEY `collation_id` (`collation_id`),
  CONSTRAINT `columns_ibfk_1` FOREIGN KEY (`table_id`) REFERENCES `tables` (`id`),
  CONSTRAINT `columns_ibfk_2` FOREIGN KEY (`collation_id`) REFERENCES `collations` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3464 DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC |
+---------+-----------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


这里就可以看到具体的表了;那么在解析数据字典表的定义对解析 innodb的数据文件有什么帮助呢?这里做下说明,假如没有数据字典的定义,当拿到一个表的数据文件时,其具体的record 记录的存储格式为: variable length–null Column—extr byte—rowid–transaction id–roll back segment pointer—fields ;如果所有的列都是定长且不为空,那么fields 这里可能会造成无法判断其具体的长度,从而无法解析具体的数据;所以知道数据字典表的具体定义后可以,可以将对应表的定义找到,从而方便解析数据;数据库隐藏的表共有:

catalogs
character_sets
collations
columns
foreign_key_column_usage
foreign_keys
index_column_usage
indexes
index_stats
schemata
tables
table_stats

其实当一个表发生损坏时,一般不影响查看表的定义,这里可以作为一种预备知识,当数据库损坏比较严重时,以备不时之需,实际用处很少,只是一个兴趣方面的研究罢了;

mysql read ahead

read ahead的功能出现于mysql 5.0 的版本,此前只有 sequence read ahead和 random read ahead两种,其目的是为了提高相应场景的读性能,其功能是由单独的后台线程完成的;在5.6 的版本当中又引进了新的方式:logical read ahead;需要注意一下:在mysql 5.5 的官方手册中,预读的两种方式是 linear read ahead 和 random read ahead,而在5.7中是 sequence read ahead 和 random read ahead,但是根据定义 linear 和 sequence应该是相同的,这里我们统称为liear read ahead;

  • linear read ahead

linearra

此种预读是指在读取数据时,如果会顺序的对一个extent的page进行读取时,其读取的page数量超过或者等于参数()值时,后台线程会将与此extent相邻的下一个extent 全部异步读取到bp中;当用户线程试图读取bp中的一个page时,此时就会触发此种预读,mysql会首先判断此page是否是此extent的边界,如果是会接着统计其所在的区有多少个page位于bp以及对它们的读取方式,如果位于bp的page 数量超过参数()值,此时就会执行异步预读;

  • random read ahead

randra

此种方式的预读是指在读取bp中的每个page时,都会观察其所在extent 有多少位于bp当中,并且如果当前extent中的位于bp的page 当中有超过一定数量的page 全部被访问,此时就会对当前extent 其余的page 进行预读;也就说随机预读针对的是当前的extent;

  • logical read ahead

logicalra

当表的碎片化比较严重时,即使是执行全表扫描,速度也会下降,此时如果执行 linear 预读 ,其效果也会打折,因为读取整个extent未必能提升读性能,因为extent中可能都是无效的page,此时逻辑预读就可以发挥作用了;逻辑预读的工作方式是:

1、读取 主键的INODE page

2、然后收集 叶子的page number

3、按照page number 的排序读取page,按照主键的排序读取行数据

这样可以尽可能多的保障每个读取的page 都是有效的数据页