《MySQL是怎么运行的》
都说这本书好,我倒要去看看
# 第0章 楔子
叫我逐章学习,不要跳着阅读
# 第1章 装作自己是个小白
介绍了一番MySQL客户端,服务器模式,客户端连接服务器方式。
服务器处理客户端请求,具体步骤和MySQL执行过程一样。MySQL执行流程
# 第2章 启动选项和系统变量
没看
# 第3章 字符集和比较规则
字符集
我们知道,计算机中实际存储的是二进制数据,那它是怎么存储字符串呢?当然是建立字符与二进制数据的映射关系了。
- 把哪些字符映射成二进制数据
- 字符映射到二进制数据叫编码,反之叫解码
比较规则
不区分大小写,全部转为大写或者小写
MySQL中的字符集
- utf8mb3: “阉割”过的utf8,使用1-3字节表示字符;
- utf8ub4: 正宗的utf8,使用1-4字节表示字符。
MySQL中的比较规则
- utf8_general_ci: ci(case insensitive)不区分大小写。
# 第4章 InnoDB记录存储结构
页是InnoDB中磁盘和内存交互的基本单位,也是InnoDB管理存储空间的基本单位,默认大小为16KB。也就是说,一次从磁盘中读取内容到内存中是16KB,从内容写到磁盘,也是16KB。
COMPACT行格式(额外信息,真实数据)
变长字段长度列表 | NULL值列表 | 记录头信息 | 列1的值 | 列2的值 | ... | 列n的值 | |
---|---|---|---|---|---|---|---|
记录1 | 01 03 04 | 00 | |||||
记录2 | 03 04 | 06 |
变长字段长度列表
列名 | 存储内容 | 内容长度(十六进制) |
---|---|---|
c1 | 'aaaa' | 0x04 |
c3 | 'bbb' | 0x03 |
c4 | 'd' | 0x01 |
那么就逆序存储变长字段的长度
NULL值列表
如果一个表中有9个值允许为NULL的列,则这个记录的NULL值列表部分需要2个字节表示,这个数据中的每一位代表真实数据是否为NULL。
如c3, c4位NULL,则值位0x110 也就是06。
# 第5章 InnoDB数据页结构
页是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。InnoDB为了不同的目的设计了多种不同类型的页,这里介绍存放记录的页,数据页(索引页)。
InnoDB数据页结构
名称 | 中文名 | 占用空间大小 | 简单描述 |
---|---|---|---|
File Header | 文件头部 | 38字节 | 页的一些通用信息 |
Page Header | 页面头部 | 56字节 | 数据页专有的一些信息 |
Infimum + Supremum | 页面中的最小记录和最大记录 | 26字节 | 两个虚拟的记录 |
User Records | 用户记录 | 不确定 | 用户存储的记录内容 |
Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory | 页目录 | 不确定 | 页中某些记录的相对位置 |
File Trailer | 文件尾部 | 8字节 | 校验页是否完整 |
用户记录
我们自己存储的数据一开始在Free Space部分,随后将这部分划分到User Records中,当Free Space全部用完时,代表这个页使用完了。如果有新的记录插入,就需要去申请新的页了。
上一章的行记录格式中的 记录头信息
记录头信息
名称 | 大小(比特〕 | 描述 |
---|---|---|
预留位 1 | 1 | 没有使用 |
预留位 2 | 1 | 没有使用 |
deleted_flag | 1 | 标记该记录是否被删除 |
min_rec_flag | 1 | B+ 树中每层非叶子节点中的 最小的目录项记录都会添加该标记 |
n_owned | 4 | 一个页面中的记录会被分成若干个组,每个组中有一个记录是"带头大哥其余的记录都是"小弟","带头大哥"记录的 n_owned值代表该组中所有的记录条数,"小弟"记录的 n_owned 值都为 0 |
heap_no | 13 | 表示当前记录在页面堆中的相对位置 |
record_type | 3 | 表示当前记录的类型 。0 表示普通记录,1 表示 B+ 树非叶节点的目录项记录 . 2 表示 Infimum 记录 ,3 表示 Supremum 记录 |
next record | 16 | 表示下一条记录的相对位置 |
页目录
要找主键值为x的记录,通过二分法确定该记录所在分组对应的槽,然后找到该槽所在分组中主键值最小的那条记录,然后通过记录的next_record属性遍历该槽所在的组中的各个记录。
页面的头部
存储数据页中的记录状态信息,比如数据页中已经存储了多少条记录、Free Space在页面中的地址偏移量、页目录中存储了多少个槽等。
文件头部
存放各种页的信息,比如这个页的编号是多少,上一页下一页是谁。
文件尾部
- 前4字节代表页的校验和,验证该页是否完整
- 后4字节代表页面被最后修改时对应的LSN的后4字节
总结
每个记录的头信息中都有一个 next record 属性 ,从而可以 使页 中的 所有记录串联成一个单向链表.
lnnoDB 会把页中的记录划分为若干个组,每个组的最后一个记录的地址偏移量作为一个槽,存放在 Page Directory 中一个槽占用 2 字节.在一个页中根据主键查找记录是非常快的,分为两步.
通过 二分法确定该记录所在分 组对应的槽,并找到该糟所在分组中主键值最小的那条记录。
通过记录的 next record 属性遍历该槽所在的组中 的各个记录.每个数据页的 File Header 部分 都有上 一个页和下一个页的编号,所以所有的数据页会组成一个双向链表。
在将页从内存刷新到磁盘时 , 为了保证页的完整性 ,页首 和 **页尾 **都会存储页中数据的校验和,以及页面最 后修改时对应 的 LSN 值 (页尾只会存 储 LSN 值的后 4 字节) . 如果页首和页尾的校验和以及 LSN 值校验不成功 , 就说明刷新期间出现了问题。
# 第6章 B+树索引
各个数据页可以组成一个双向链表,数据页中每条记录会按照主键值从小到大组成一个单向链表。每个数据页会生成一个页目录,通过页目录二分法找到对应的槽,再去顺序遍历槽对应的分组中的记录。
存放页目录的页,复用原来的数据页,所以记录中的记录头信息record_type,用来标记是用户记录还是目录项。存放页目录的页,中的记录只存放两个值,主键值与对应的页号。
聚簇索引
使用记录主键值的大小进行记录和页的排序
- 页内的节点按照主键值大小排成一个单向链表,页内的节点分成若干个组,每组中最大主键值当成一个槽;
- 存放页目录的页,根据主键值大小排成双向链表;
- 存放用户记录的页,根据主键值大小排成双向链表;
B+树的叶子节点存储的是完整的用户记录。
二级索引
存放页目录的页中的记录只存放多个值,主键值,索引值与对应的页号。叶子节点包含索引值与主键值,再生成一颗B+树,但叶子节点不包含完整记录,找到叶子节点对应的主键值,再到主键索引中查找完整记录,需要进行一次回表操作;
联合索引
存放页目录的页中的记录只存放多个值,主键值,n个索引值与对应的页号。
MyISAM中的索引,都是二级索引,其它类似。
# 第7章 B+树索引的应用
每个索引都对应一颗B+树。B+树分为好多层,最下边一层是叶子节点,其余是内节点。所有用户记录都存储在B+树的叶子结点,所有目录项记录都存储在内节点。
InnoDB存储引擎会自动为主键简历聚簇索引,聚簇索引的叶子节点包含完整的用户记录。但是建立的二级索引中的叶子节点只包含主键和索引列,若需要查找到非索引列,则需要拿到主键值,到聚簇索引中再查找一次,简称回表。
聚簇索引和二级索引示意图
扫描区间和边界条件分析
索引在空间和时间上都会“拖后腿”
- 空间上的代价
每建立一个索引,都要为它建立一颗B+树。每一颗B+树的每一个节点都是一个数据页(一个节点中包含多条记录,还包含一些其它信息)。
时间上的代价
因为索引列的值是有序的关系,如果新插入的数据,有可能会造成页面分裂、页面回收、还要维护索引列中的排序关系。
在执行查询语句前,首先要生成一个执行计划。MySQL会分析使用不同索引执行查询所需要的成本,索引越多分析的时间越长。
扫描区间和边界分析
去翻书吧。
更好的创建索引和使用索引
- 只为用于搜索、排序或分组的列创建索引
- 考虑索引列中不重复值的个数
- 索引列的类型尽量小
- 为列前缀建立索引,节省空间
- 索引覆盖,只查询索引列,避免回表
- 让索引列以列名的形式在搜索条件中单独出现
select * from single_table where key2 * 2 < 4; #不适用索引,全表扫描
select * from single_table where key2 < 4/2;
- 新插入记录时主键大小对效率的影响,忽大忽小的插入可能会造成页面分裂
# 第8章 数据的家
MySQL 服务器程序在启动时会到数据目录中加载数据,运行过程中产生的数据也会被存储到数据目录中系统变量也扭曲表明了数据目录的路径。
每个数据库都对应着数据目录下的一个子目录,该子目录中包含一个名为 db.opt 的文件这个文件包含了该数据库的一些属性,比如该数据库的字符集和比较规则等。
对于InnoDB 存储引擎来说 :
- 如果使用系统表空间存储表中的数据,那么只会在该表所在数据库对应的子目录下创建一个名为"表名 frm" 的文件,表中的数据会存储在系统表空间对应的文件中;
- 如果使用独立表空间存储表中的数据,那么会在该表所在数据库对应的子目录下创建一个名为"表名.frm"的文件和一个名为"表名.idb"的文件,表中的数据会存储这个"表名.idb"文件中。
# 第9章 InnoDB的表空间
# 第10章 单表访问方式
# 第11章 连接的原理
从本质上说,连接就是将多个表的数据查询出来依次匹配,组成一个结果集返回。如果没有过滤条件,产生的结果集那么就是笛卡尔积。
MySQL中分为内连接和外连接,内连接也就是取匹配中的交集,外连接就是,被驱动表中有没有数据匹配与驱动表匹配,都保留驱动表中的数据,内连接则不保存。
嵌套循环连接算法是值驱动表只访问一次,对于每一条驱动表中的数据,都会访问一次被驱动表。因此设计了Join Buffer(连接缓冲区)存放于内存,把驱动表中查询出来的数据放到缓冲区中,然后对于查询被驱动表中的每一条数据,都与缓冲区中的数据进行匹配。这样,就假如缓冲区足够大,那么只需访问一次被驱动表。
# 第12章 基于成本的优化
# 成本
- I/O成本:当查询表中的记录时,需要把数据或者索引加载到内存中,然后再进行操作。这个从磁盘到内存的加载过程损耗的时间成为I/O成本。
- CPU成本:读取记录以及检测记录是否满足对应的搜索条件、对结果集进行排序等这些操作损耗的时间称为CPU成本。
对于InnoDB存储引擎来说,读取一个页面花费的成本默认是1.0;读取以及检测一条记录是否符合搜索条件的成本默认是0.2。
# 基于成本的优化步骤
- 根据搜索条件,找出所有可能使用的索引
- 计算全表扫描的代价
- 计算使用不同索引执行查询的代价
- 对比各种执行方案的代价,找出成本最低的那个方案
# 基于索引统计数据的成本计算
每张表都有一个对应的统计表,来记录这张表的信息。比如有个索引统计数据,index statistics,show index from 表名
,统计索引列中的各种信息,有一个比较重要的信息,Cardinality
,表示该列中不重复值的数量。根据这个属性,可以计算出在某一列中一个值平均出现了多少次,大约重复次数等于函数初一Cardinality
值。
# 连接查询的成本
# 条件过滤
因为在连接查询中,驱动表会被访问一次,被驱动表可能会被访问多次。所以,对于两表连接查询来说,它的查询成本由两部分构成:
- 单词查询驱动表的成本
- 多次查询被驱动表的成本
驱动表的记录条数成为扇出(fanout),然后呢,就要计算扇出值。
对于那些条件中没有使用的索引列的数据,完全靠猜测来计算符合条件的记录条数,对于有索引列,那么通过Cardinality
来猜测条数。
# 两表连接的成本分析
连接查询的总成本=单词访问驱动表的成本 + 驱动表的扇出值*单词访问被驱动表的成本
所以也需要考虑最优的表连接顺序,优化的重点就是后面那个大头:
- 尽量减少驱动表的扇出值
- 访问被驱动表的成本要尽量底
# 多表连接的成本分析
n张表的连接方式就有n!种连接方式,通过一些启发式规则来分析多表连接的成本,比如
- 提前结束某种连接顺序的成本评估
- 防止无穷无尽地分析各种连接顺序的成本,只分析小于一定的连接表数量
# 调节成本常数
有两张表,engine_cost
,server_cost
在server层进行连接管理、查询缓存、语法解析、查询优化等操作;
在存储引擎层执行具体的数据存取操作。
# 第13章 InnoDB统计数据是如何收集的
# 第14章 基于规则的优化
# 一些简单的优化
- 移除不必要的括号
- 常量传递
a = 5 AND b > a
a = 5 AND b > 5
等值传递
移除没用的条件
表达式计算
HAVING字句和WHERE字句的合并,如果没用出现SUM、MAC等聚集函数以及GROUP BY字句,优化器就把HAVING字句和WHERE字句合并
# 常量表检测
这两种查询话费的时间特别少
- 查询的表中一条记录没有,或者只有一条记录(根据统计信息(但不准确))
- 使用主键等值匹配或者唯一二级索引列等值匹配
# 外连接消除
在被驱动表中没有找到符合条件的记录,就会加入一条NULL值的记录到结果集中。如果在where条件中拒绝空值,这时候的查询结果是等价于内连接的,那么就可以将外连接转换为内连接。
转为内连接的好处是,优化器可以评估不同连接顺序的成本,进而选出成本最低的那种连接顺序进行查询。
# 子查询MySQL执行过程
# 不相关标量子查询
SELECT * FROM s1
WHERE key1 = (SELECT common_field FROM s2 WHERE key3 = 'a' LIMIT 1);
- 先单独执行子查询
SELECT common_field FROM s2 WHERE key3 = 'a' LIMIT 1
- 将上一步子查询的结果作为外层循环的条件
# 相关标量子查询
SELECT * FROM s1 WHERE
key1 = (SELECT common_field FROM s2 WHERE s1.key3 = s2.key3 LIMIT 1);
- 从外循环取出一条记录
- 获取上一步的记录所需的列,然后执行子查询
- 如果符合条件,查出子查询的结果
- 再将子查询的结果拼接到外层查询的WHERE子句中
# IN子查询的优化
SELECT * FROM s1
WHERE key1 IN (SELECT common_field FROM s2 WHERE key3 = 'a');
将IN后面的子查询结果,写入到一个临时表中(物化表)materialized_table
那么就相当于有两张表,就有一下两种考虑
- 从原表
s1
的角度看待,先获取s1表中的一条记录,然后判断这条记录是否存在于物化表中。 - 从物化表看待,先获取物化表中的值,然后判断s1表中是否有这个值的记录。
也就是说,可以相当于是一个内连接,等价于
SELECT s1.* FROM s1 INNER JOIN materialized_table ON key1 = m_val;
是内连接,那么优化器就会评估它们的连接顺序。
# 将子查询转换为semi-join
直接将子查询转换为连接。
对于这样一个查询
SELECT * FROM s1
WHERE key1 IN (SELECT common_field FROM s2 WHERE key3 = 'a');
尝试将其转换为连接
SELECT s1.* FROM s1 INNER JOIN s2
ON s1.key1 = s2.common_field
WHERE s2.key3 = 'a';
这个查询的结果集是s1的记录,如果对于表s1的某条记录来说,s2表中有多条s1.key1=s2.common_field
,那么s1的这条记录会加入多次到结果集中,这一种情况与原来的查询等价,原来的查询只关心是否存在记录满足s1.key1=s2.common_field
二不关心具体有多少条记录与之匹配。
半连接
Table pullout(子查询中的表上拉)
当子查询的查询列表是只有主键或者唯一索引列时
SELECT * FROM s1 WHERE key2 IN (SELECT key2 FROM s2 WHERE key3 = 'a');
可以转换为
SELECT s1.* FROM s1 INNER JOIN s2 ON s1.key2 = s2.key2 WHERE s2.key3 = 'a';
因为唯一索引或主键不存在有多条记录与之匹配
DuplicateWeedout execution strategy (重复值消除)
SELECT * FROM s1 WHERE key1 IN (SELECT common_field FROM s2 WHERE key3 = 'a');
可以创建一个临时表
CREATE TABLE tmp ( id PRIMARY KEY );
这样,如果要将s1的某条记录加入到结果集中,那先将该条记录的id插入到临时表中,如果插入成功,那可以将该条记录加入到结果集。
半连接适用条件
SELECT ... FROM outer_tables
WHERE expr IN (SELECT ... FROM inner_tables ...) AND ...
如果IN
子查询不符合转换为semi-join
的条件,那么查询优化器再二选一
- 先将子查询物化之后再执行查询
- 执行
IN to EXISTS
转换
IN to EXISTS
转换
outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)
转换为
EXISTS (SELECT inner_expr FROM ... WHERE subquery_where AND outer_expr=inner_expr)
转换为EXISTS
的好处是,子查询可能可以用上索引
看到这里脑子嗡嗡的~
# 第15章 EXPLAIN详解
一条查询语句在经过MySQL的查询优化器后,会基于成本或规则进行优化,可以通EXPLAIN
来查看具体的执行计划。
# 执行输出中各列
mysql> EXPLAIN SELECT * FROM s1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
# table
无论查询语句中包含多少个表,最终都会对每个表进行单表查询
# id
一个大的查询可以拆分成多个SELECT
,每一个SELECT
对应一个id。
对于连接查询因为是同一个SELECT
子句,所以查询的两个表是同一个id值。
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | NULL |
| 1 | SIMPLE | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 9954 | 100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
2 rows in set, 1 warning (0.01 sec)
对于联合查询或者子查询,则会分配多个唯一id值。
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| 1 | PRIMARY | s1 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 9688 | 100.00 | Using where |
| 2 | SUBQUERY | s2 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 9954 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.02 sec)
# select_type
一个大的SELECT
可以拆分成多个小SELECT
,对每一个`SELECT分配角色。
- SIMPLE
不包含UNION
或者子查询的查询都算作SIMPLE
类型
mysql> EXPLAIN SELECT * FROM s1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
- PRIVATE
包含UNION
,或者子查询的查询,其中最左边的表就是PRIVATE
mysql> EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | NULL |
| 2 | UNION | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 9954 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)
- UNION
上边,除了最左边的s1,其它都是UNION
- UNION RESULT
上边,需要将两个查询的结果集合并,这里就是存放到一个临时表中,并去重。
- SUBQUERY
包含子查询的查询,不能转为对应的semi-join
,并且是不相关子查询
- DEPENDENT SUBQUERY
相关子查询
- DEPENDENT UNION
- DERIVED
派生表
mysql> EXPLAIN SELECT * FROM (SELECT key1, count(*) as c FROM s1 GROUP BY key1) AS derived_s1 where c > 1;
+----+-------------+------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 33.33 | Using where |
| 2 | DERIVED | s1 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 9688 | 100.00 | Using index |
+----+-------------+------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
- MATERIALIZED
物化表
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2);
+----+--------------+-------------+------------+--------+---------------+------------+---------+-------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+---------------+------------+---------+-------------------+------+----------+-------------+
| 1 | SIMPLE | s1 | NULL | ALL | idx_key1 | NULL | NULL | NULL | 9688 | 100.00 | Using where |
| 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_key> | <auto_key> | 303 | xiaohaizi.s1.key1 | 1 | 100.00 | NULL |
| 2 | MATERIALIZED | s2 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 9954 | 100.00 | Using index |
+----+--------------+-------------+------------+--------+---------------+------------+---------+-------------------+------+----------+-------------+
3 rows in set, 1 warning (0.01 sec)
# type
type | 类型 |
---|---|
system | 对于统计精确的引擎,表中仅含一条记录 |
const | 单表访问,主键或者唯一二级索引列与等值匹配 |
eq_ref | 连接查询,被驱动表通过主键或者唯一二级索引列与等值匹配 |
ref | 普通的二级索引列与等值匹配 |
fulltext | 全文索引 |
ref_or_null | 普通的二级索引列与等值匹配,包含NULL |
index_merge | 索引合并 |
unique_subquery | 连接查询,被驱动表使用eq_ref |
index_subquery | 连接查询,被驱动表使用ref |
range | 使用索引获取某些范围区间 |
index | 需要扫描索引上的所有数据 |
ALL | 全表扫描 |
# possible_keys和key
可能使用到的索引,和实际用到的索引
# key_len
# ref
使用索引列等值匹配去执行查询时,展示与什么进行等值匹配,与某个常数const,某一列,某个函数。
# rows
预计需要扫描的行数
# filtered
过滤条件后,还剩多少记录,1~100%。
未完待续。。。
# 第16章 optimizer trace的神奇功效
# 第17章 InnoDB的Buffer Pool
磁盘太慢,用内存作为缓存,不至于每次都访问磁盘。
Buffer Poll是向操作系统申请的连续内存,一个个的chunk组成,每个chunk由控制块
和缓存页
组成,控制块那肯定就是存放一些缓存页的信息,上一页,下一页,锁啥的。
那在内存中弄了一个这样的玩意,那肯定就有这些问题
咋知道缓存中是否加载了某一页,使用表空间号 + 页号
作为key
,缓存页作为value
,建立哈希表。
缓存中修改了页(脏页
),加入到flush链表,等待时机刷入磁盘。
Buffer Poll满了怎么办,采用LRU链表,LRU链表分为young区和old区,新加入的数据,放到LRU的old头,在间隔一定时间外访问,就会吧他送到young头。没有足够的空闲缓存页,首先淘汰old中的页。
少于1G缓存数据,不建议搞多个Buffer Poll。
# 第18章 事务
ACID,原子性,一致性,隔离性,持久性