MySQL基础 - 基于成本的优化

什么是成本

I/O成本
我们的表经常使用的MyISAM、InnoDB存储引擎都是将数据和索引都存储到磁盘上的,当我们想查询表中的记录时,需要先把数据或者索引加载到内存中然后再操作。这个从磁盘到内存这个加载的过程损耗的时间称之为I/O成本。
CPU成本
读取以及检测记录是否满足对应的搜索条件、对结果集进行排序等这些操作损耗的时间称之为CPU成本。
对于InnoDB存储引擎来说,页是磁盘和内存之间交互的基本单位,设计MySQL的大叔规定读取一个页面花费的成本默认是1.0,读取以及检测一条记录是否符合搜索条件的成本默认是0.2。1.0、0.2这些数字称之为成本常数

单表查询的成本

CREATE TABLE single_table (
    id INT NOT NULL AUTO_INCREMENT,
    key1 VARCHAR(100),
    key2 INT,
    key3 VARCHAR(100),
    key_part1 VARCHAR(100),
    key_part2 VARCHAR(100),
    key_part3 VARCHAR(100),
    common_field VARCHAR(100),
    PRIMARY KEY (id),
    KEY idx_key1 (key1),
    UNIQUE KEY idx_key2 (key2),
    KEY idx_key3 (key3),
    KEY idx_key_part(key_part1, key_part2, key_part3)
) Engine=InnoDB CHARSET=utf8;

基于成本的优化步骤

SELECT * FROM single_table WHERE 
    key1 IN (a, b, c) AND 
    key2 > 10 AND key2 < 1000 AND 
    key3 > key2 AND 
    key_part1 LIKE %hello% AND
    common_field = 123;

1. 根据搜索条件,找出所有可能使用的索引

上边的查询语句可能用到的索引,也就是possible keys只有idx_key1和idx_key2。

2. 计算全表扫描的代价

由于查询成本=I/O成本+CPU成本,所以计算全表扫描的代价需要两个信息:

聚簇索引占用的页面数 和 该表中的记录数
mysql> USE xiaohaizi;
Database changed

mysql> SHOW TABLE STATUS LIKE single_table\G
*************************** 1. row ***************************
           Name: single_table
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 9693
 Avg_row_length: 163
    Data_length: 1589248
Max_data_length: 0
   Index_length: 2752512
      Data_free: 4194304
 Auto_increment: 10001
    Create_time: 2018-12-10 13:37:23
    Update_time: 2018-12-10 13:38:03
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options:
        Comment:

Rows
对于使用MyISAM存储引擎的表来说,该值是准确的,对于使用InnoDB存储引擎的表来说,该值是一个估计值。

Data_length
Data_length = 聚簇索引的页面数量 x 每个页面的大小(16*1024)

聚簇索引的页面数量 = 1589248 ÷ 16 ÷ 1024 = 97

I/O成本
97 x 1.0 + 1.1 = 98.1
//97指的是聚簇索引占用的页面数,1.0指的是加载一个页面的成本常数,后边的1.1是一个微调值,我们不用在意。

CPU成本:
9693 x 0.2 + 1.0 = 1939.6
//9693指的是统计数据中表的记录数,对于InnoDB存储引擎来说是一个估计值,0.2指的是访问一条记录所需的成本常数,后边的1.0是一个微调值,我们不用在意。

总成本:
98.1 + 1939.6 = 2037.7
综上所述,对于single_table的全表扫描所需的总成本就是2037.7。

3. 计算使用不同索引执行查询的代价

使用idx_key2执行查询的成本分析

 

 MySQL基础 - 基于成本的优化

 

 

范围区间数量

不论某个范围区间的二级索引到底占用了多少页面,查询优化器粗暴的认为读取索引的一个范围区间的I/O成本和读取一个页面是相同的。本例中使用idx_key2的范围区间只有一个:(10, 1000),所以相当于访问这个范围区间的二级索引付出的I/O成本就是:
1 x 1.0 = 1.0

需要回表的记录数

步骤1:找到满足key2 > 10这个条件的第一条记录,我们把这条记录称之为区间最左记录

步骤2:找到满足key2 < 1000这个条件的最后一条记录,我们把这条记录称之为区间最右记录
步骤3:找到满足key2 > 10这个条件的第一条记录,我们把这条记录称之为区间最左记录

 

MySQL基础 - 基于成本的优化

 

 

 

idx_key2在区间(10, 1000)之间大约有95条记录。

访问这个范围区间的二级索引付出的I/O成本就是: 1 x 1.0 = 1.0

回表操作带来的I/O成本就是:95 x 1.0 = 95.0

读取这95条二级索引记录需要付出的CPU成本就是:95 x 0.2 + 0.01 = 19.01

回表操作带来的CPU成本就是: 95 x 0.2 = 19.0

 

综上所述,使用idx_key2执行查询的总成本就是:

96.0 + 38.01 = 134.01

 

使用idx_key1执行查询的成本分析

 

I/O成本: 3.0 + 118 x 1.0 = 121.0 (范围区间的数量 + 预估的二级索引记录条数)
CPU成本:: 118 x 0.2 + 0.01 + 118 x 0.2 = 47.21 (读取二级索引记录的成本 + 读取并检测回表后聚簇索引记录的成本)
综上所述,使用idx_key1执行查询的总成本就是:
121.0 + 47.21 = 168.21
 

4.对比各种执行方案的代价,找出成本最低的那一个

全表扫描的成本:2037.7
使用idx_key2的成本:134.01
使用idx_key1的成本:168.21
所以当然选择idx_key2来执行查询喽。

 

基于索引统计数据的成本计算

SELECT * FROM single_table WHERE key1 IN (‘aa1‘, ‘aa2‘, ‘aa3‘, ... , ‘zzz‘);

获取索引对应的B+树的区间最左记录和区间最右记录,然后再计算这两条记录之间有多少记录。通过直接访问索引对应的B+树来计算某个范围区间对应的索引记录条数的方式称之为index dive。
对于InnoDB存储引擎来说,使用SHOW INDEX语句展示出来的某个索引列的Cardinality属性是一个估计值,并不是精确的。

当IN语句中的参数个数大于或等于系统变量eq_range_index_dive_limit的值的话,就不会使用index dive的方式计算各个单点区间对应的索引记录条数,而是使用索引统计数据
一个值的重复次数 ≈ Rows ÷ Cardinality

成本=一个值的重复次数*(IN语句中数量)


 

MySQL基础 - 基于成本的优化

上一篇:开发中遇到的数据库死锁引发的技术总结


下一篇:Mybatis如何调用oracle存储过程?入参为日期类型