一、背景
MySQL 5.6、InnoDB
要执行的 SQL:
SELECT * FROM common.user WHERE id = 10;
复制
二、MySQL 如何存储数据
要想知道数据如何读取,首先应该知道数据是如何存储的。
根据 my.cnf 中配置,确定数据的存储位置:
datadir=/var/lib/mysql
复制
查看
ll /var/lib/mysql
复制
total 303628
drwxr-xr-x 14 mysql mysql 4096 Apr 9 18:35 .
drwxr-xr-x. 41 root root 4096 Mar 30 16:40 ..
drwx------ 2 mysql mysql 4096 Nov 29 2019 common
-rw-rw---- 1 mysql mysql 79691776 Apr 9 18:35 ibdata1
-rw-rw---- 1 mysql mysql 50331648 Apr 9 18:35 ib_logfile0
-rw-rw---- 1 mysql mysql 50331648 Apr 9 18:26 ib_logfile1
-rw-rw---- 1 mysql mysql 105743537 Jul 18 2019 master.000001
-rw-rw---- 1 mysql mysql 18950 Aug 2 2019 master.000002
-rw-rw---- 1 mysql mysql 32 Jul 18 2019 master.index
-rw-rw---- 1 mysql mysql 49 Apr 9 18:35 master.info
drwx------ 2 mysql mysql 4096 May 17 2019 mysql
-rw-rw---- 1 mysql mysql 3859 Aug 3 2019 mysqld-bin.000001
-rw-rw---- 1 mysql mysql 557 Aug 3 2019 mysqld-bin.000002
-rw-rw---- 1 mysql mysql 239 Aug 3 2019 mysqld-bin.000003
-rw-rw---- 1 mysql mysql 239 Aug 3 2019 mysqld-bin.000004
-rw-rw---- 1 mysql mysql 239 Aug 3 2019 mysqld-bin.000005
-rw-rw---- 1 mysql mysql 239 Aug 3 2019 mysqld-bin.000006
-rw-rw---- 1 mysql mysql 1511 Aug 3 2019 mysqld-bin.000007
-rw-rw---- 1 mysql mysql 239 Aug 3 2019 mysqld-bin.000008
-rw-rw---- 1 mysql mysql 11344 Aug 8 2019 mysqld-bin.000009
-rw-rw---- 1 mysql mysql 872 Aug 8 2019 mysqld-bin.000010
-rw-rw---- 1 mysql mysql 3142 Aug 8 2019 mysqld-bin.000011
-rw-rw---- 1 mysql mysql 1839890 Apr 9 18:26 mysqld-bin.000012
-rw-rw---- 1 mysql mysql 214 Apr 9 18:35 mysqld-bin.000013
-rw-rw---- 1 mysql mysql 260 Apr 9 18:26 mysqld-bin.index
-rw-rw---- 1 mysql mysql 205 Aug 3 2019 mysqld-relay-bin.000001
-rw-rw---- 1 mysql mysql 205 Aug 3 2019 mysqld-relay-bin.000002
-rw-rw---- 1 mysql mysql 205 Aug 3 2019 mysqld-relay-bin.000003
-rw-rw---- 1 mysql mysql 205 Aug 3 2019 mysqld-relay-bin.000004
-rw-rw---- 1 mysql mysql 205 Aug 3 2019 mysqld-relay-bin.000005
-rw-rw---- 1 mysql mysql 205 Aug 3 2019 mysqld-relay-bin.000006
-rw-rw---- 1 mysql mysql 205 Aug 3 2019 mysqld-relay-bin.000007
-rw-rw---- 1 mysql mysql 174 Aug 8 2019 mysqld-relay-bin.000008
-rw-rw---- 1 mysql mysql 143 Aug 8 2019 mysqld-relay-bin.000009
-rw-rw---- 1 mysql mysql 143 Aug 8 2019 mysqld-relay-bin.000010
-rw-rw---- 1 mysql mysql 143 Apr 9 18:26 mysqld-relay-bin.000011
-rw-rw---- 1 mysql mysql 143 Apr 9 18:35 mysqld-relay-bin.000012
-rw-rw---- 1 mysql mysql 312 Apr 9 18:26 mysqld-relay-bin.index
drwx------ 2 mysql mysql 4096 May 17 2019 performance_schema
-rw-rw---- 1 mysql mysql 39 Apr 9 18:35 relay-log.info
复制
主要文件说明:
ibdata1:undo log
ib_logfile*: redo log
mysqld-bin.*: bin log
mysqld-relay-bin.*: 主从复制中继日志
master.info: 主库信息文件
relay-log.info: relay 应用的信息文件
查看表空间信息
ll common/
复制
total 716
-rw-rw---- 1 mysql mysql 61 Nov 18 2019 db.opt
-rw-rw---- 1 mysql mysql 8969 Nov 29 2019 user_menu.frm
-rw-rw---- 1 mysql mysql 114688 Jan 3 18:44 user_menu.ibd
-rw-rw---- 1 mysql mysql 9098 Nov 20 2019 user.frm
-rw-rw---- 1 mysql mysql 589824 Jan 10 18:45 user.ibd
复制
*.frm: 表结构文件,不区分存储引擎
*.idb: 数据文件,InnoDB 特有
从 InnoDB 存储结构看,所有数据都被逻辑地存放在一个空间中,称之为表空间 (tablespace),如 common。
InnoDB 管理磁盘的最小单位是页(page),每个页默认大小时是 16KB,也是 InnoDB 中磁盘与内存交互的最小单位。页有很多种,比如数据页、索引页等。
注意:操作系统管理磁盘的最小单位是磁盘块,是操作系统读写磁盘最小单位,Linux 中页一般是 4KB。
三、如何执行一条 SQL
知道了数据是怎么存的,那么 SQL 是如何执行的呢?
先看一下 MySQL 整体架构图。
再看一下执行流程的简易图。
大致流程描述:
MySQL 客户端通过协议将 SQL 语句发送给 MySQL 服务器;
首先连接器会根据用户名密码做认证和鉴权;
然后查询缓存,服务器会先检查查询缓存中是否有执行过这条 SQL;
如果命中缓存,则将结果返回;
否则进入下一个环节(查询缓存默认不开启);
接下来解析器负责对 SQL 进行解析,涉及词法解析、语法分析、语义分析,最终会解析为抽象语法树;
紧接着是预处理器处理,预处理器会进一步去检查解析树是否合法,预处理之后会得到一个新的解析树;
执行前还有经过优化器处理,查询优化器的作用就是根据解析树生成不同的执行计划,然后选择一种最优的执行计划;
服务器根据查询优化器给出的执行计划,再调用存储引擎的 API 执行查询;
将结果返回给客户端,如果开启查询缓存,则会备份一份到查询缓存中。
四、索引介绍
InnoDB 存储引擎是怎么执行查询的呢?
InnoDB 存储引擎中的索引分为两大类,主键索引和辅助索引。主键索引的叶子节点存储数据行,辅助索引只会存储主键值。InnoDB 的索引是基于 B+ 树实现的。
根据 InnoDB 的要求,所有表都必须要有主键。那么问题来了,上文中的 id 字段是不是主键呢?
1. 如果 id 是主键,那么通过主键索引可以定位到当前行所在的页,但是查找的时候要经过多少索引页呢?每在一个索引页上检索一次意味着一次 IO 的开销,都需要从磁盘读取 16K 数据都内存。至于到底需要几次 IO 这个需要根据 id 的字段类型以及 user 表的数据量共同决定。当在索引页中定位到行 id,然后通过 id 找到行所在数据页,然后把整个数据页读到 buffer pool 中,然后在内存中过滤出 id=10 的数据行,最后把数据返回给 server 端;
2. 如果 id 不是主键索引,仅仅是辅助索引,那么会在辅助索引树上先定位出对应的主键,然后在通过主键索引查找数据;
3. 如果 id 没有任何索引。直接读取数据页头节点, 顺序扫描, 返回符合条件记录,直到最终节点结束。
五、扩展
关于 MySQL 读操作,MySQL 官网文档中给出了两种读:一致性读和锁定读。你知道这两者有什么区别吗?在锁定读的情况下,数据的读取又是什么样子呢?如果基于当前条件无法判断的话,需要添加什么条件才可以呢?欲知详情如何,且看下回分解。