>데이터 베이스 >MySQL 튜토리얼 >MySQL의 쿼리 최적화 프로그램에 대한 심층 분석(작동 원리에 대한 자세한 설명)

MySQL의 쿼리 최적화 프로그램에 대한 심층 분석(작동 원리에 대한 자세한 설명)

青灯夜游
青灯夜游앞으로
2022-07-04 20:35:073547검색

이 기사는 MySQL의 쿼리 최적화 프로그램에 대한 심층적인 분석을 제공하고 mysql 쿼리 최적화 프로그램의 작동 원리를 이해하는 데 도움이 되기를 바랍니다.

MySQL의 쿼리 최적화 프로그램에 대한 심층 분석(작동 원리에 대한 자세한 설명)

SQL 문의 경우 쿼리 최적화 프로그램은 먼저 JOIN으로 변환할 수 있는지 확인한 후 JOIN을 최적화합니다.

최적화는 1. 조건부 최적화, 2. 전체 테이블 스캔 비용 계산으로 나뉩니다. , 3. 사용할 수 있는 모든 인덱스 알아보기, 4. 인덱스별로 서로 다른 액세스 방법의 비용 계산, 5. 비용이 가장 작은 인덱스 및 액세스 방법 선택

1. 쿼리 최적화 로그 켜기

-- 开启
set optimizer_trace="enabled=on"; 
-- 执行sql
-- 查看日志信息
select * from information_schema.OPTIMIZER_TRACE;
-- 关闭
set optimizer_trace="enabled=off";

2. 최적화 변환기 원리

  • 1. 상수 전파(constant_propagation)
    a = 1 AND b > a
    위의 SQL은 다음과 같이 변환될 수 있습니다.
    a = 1 AND b >

    2. 등가 전송( Equality_propagation)
  • a = b 및 b = c 및 c = 5
  • 위의 SQL은 다음과 같이 변환될 수 있습니다.

    a = 5 및 b = 5 및 c = 5


    3. (trivial_condition_removal)
  • a = 1 및 1 = 1
  • 위의 SQL은 다음과 같이 변환될 수 있습니다.

    a = 1


    4. 비용에 따라
  • 쿼리는 다양한 실행 계획을 가질 수 있으며, 쿼리에 대한 인덱스를 선택할 수 있습니다. 또는 전체 테이블 Scan을 선택할 수 있으며, 쿼리 최적화 프로그램은 쿼리를 실행하기 위해 가장 낮은 비용의 솔루션을 선택합니다.
  • 1) I/O 비용

    InnoDB 스토리지 엔진은 데이터와 인덱스를 모두 디스크에 저장합니다. 테이블의 레코드를 쿼리하려면 먼저 데이터나 인덱스를 메모리에 로드한 후 작동해야 합니다. 디스크에서 메모리까지 로딩하는 과정에서 손실된 시간을 I/O 비용이라고 합니다
    2) CPU 비용
    레코드를 읽어 해당 검색 조건에 맞는지 확인하고 결과 집합을 정렬하는 등의 과정에서 손실된 시간을 CPU라고 합니다. 비용.
    InnoDB 스토리지 엔진에서는 페이지를 읽는 비용이 기본적으로 1.0이고, 레코드를 읽고 검색 조건을 충족하는지 확인하는 비용이 기본적으로 0.2로 규정되어 있습니다.


    3. 비용 기반 최적화 단계
단일 테이블 쿼리 문이 실제로 실행되기 전에 MySQL의 쿼리 최적화 프로그램은 문 실행에 가능한 모든 솔루션을 찾아 이를 비교하여 가장 저렴한 솔루션을 찾습니다. 가장 저렴한 솔루션은 소위 실행 계획이며, 스토리지 엔진에서 제공하는 인터페이스를 호출하여 실제로 쿼리를 실행하는 것입니다.

이러한 단계를 예를 들어 분석해 보겠습니다.

select * from employees.titles where emp_no > &#39;10101&#39; and emp_no < &#39;20000&#39; and to_date = &#39;1991-10-10&#39;;


1. 검색 조건에 따라 가능한 모든 인덱스를 찾습니다.

• emp_no > 기본 키 인덱스 PRIMARY를 사용할 수 있습니다. • to_date = '1991-10-10', 이 검색 조건은 보조 인덱스 idx_titles_to_date를 사용할 수 있습니다.

결론적으로 위 쿼리문에서 사용할 수 있는 인덱스, 즉 가능한 키는 PRIMARY와 idx_titles_to_date뿐입니다.

2 전체 테이블 스캔 비용 계산

InnoDB 스토리지 엔진에서 전체 테이블 스캔은 클러스터형 인덱스의 레코드를 주어진 검색 조건과 순차적으로 비교하여 해당 레코드를 일치시키는 것을 의미합니다. 검색 조건이 결과 세트에 추가되므로 클러스터형 인덱스에 해당하는 페이지를 메모리에 로드한 후 레코드가 검색 조건을 충족하는지 여부를 감지합니다. 쿼리 비용 = I/O 비용 + CPU 비용이므로 전체 테이블 스캔 비용을 계산하려면 두 가지 정보가 필요합니다.

1) 클러스터형 인덱스가 차지하는 페이지 수

2) 클러스터형 인덱스가 차지하는 레코드 수 테이블

MySQL 각각의 테이블은 일련의 통계 정보를 유지 관리합니다. SHOW TABLE STATUS 문을 사용하면 테이블의 통계 정보를 볼 수 있습니다.

SHOW TABLE STATUS LIKE 'titles';

Rows

는 테이블의 레코드 수를 나타냅니다. 이 값은 MyISAM 스토리지 엔진을 사용하는 테이블에 대해 정확하며 InnoDB 스토리지 엔진을 사용하는 테이블에 대한 추정치입니다.
Data_length

는 테이블이 차지하는 저장 공간의 바이트 수를 나타냅니다. MyISAM 스토리지 엔진을 사용하는 테이블의 경우 이 값은 데이터 파일의 크기입니다. InnoDB 스토리지 엔진을 사용하는 테이블의 경우 이 값은 클러스터형 인덱스가 차지하는 저장 공간과 동일하므로 다음과 같이 값을 계산할 수 있습니다. 크기:

Data_length = 聚簇索引的页面数量 x 每个页面的大小
제목은 기본 페이지 크기인 16KB를 사용하고 위 쿼리 결과에서는 Data_length의 값이 20512768이므로 클러스터형 인덱스의 페이지 수를 역으로 추론할 수 있습니다.

聚簇索引的页面数量 = Data_length ÷ 16 ÷ 1024 = 20512768 ÷ 16 ÷ 1024 = 1252
이제 클러스터링이 있습니다. 인덱스가 차지하는 페이지 수와 테이블의 레코드 수 추정으로 전체 테이블 스캔 비용을 계산할 수 있습니다. 그러나 MySQL은 실제로 비용을 계산할 때 약간의 세부 조정을 수행합니다.

I/O 비용: 1252

1 = 1252. 1252는 클러스터형 인덱스가 차지하는 페이지 수를 나타내고, 1.0은 페이지를 로드하는 비용 상수를 나타냅니다.

CPU 비용: 442070

0.2=88414. 442070은 통계에서 테이블에 있는 레코드 수를 의미하며 이는 InnoDB 스토리지 엔진에 대한 추정치이며 0.2는 레코드에 액세스하는 데 필요한 비용 상수를 의미합니다

총 비용: 1252+88414 = 89666.

결론적으로 전체 제목 표를 스캔하는 데 드는 총 비용은 89666입니다.

MySQL의 쿼리 최적화 프로그램에 대한 심층 분석(작동 원리에 대한 자세한 설명)
我们前边说过表中的记录其实都存储在聚簇索引对应B+树的叶子节点中,所以只要我们通过根节点获得了最左边的叶子节点,就可以沿着叶子节点组成的双向链表把所有记录都查看一遍。也就是说全表扫描这个过程其实有的B+树内节点是不需要访问的,但是MySQL在计算全表扫描成本时直接使用聚簇索引占用的页面数作为计算I/O成本的依据,是不区分内节点和叶子节点的。

3、计算PRIMARY需要成本

计算PRIMARY需要多少成本的关键问题是:需要预估出根据对应的where条件在主键索引B+树中存在多少条符合条件的记录。

范围区间数
当我们从索引中查询记录时,不管是=、in、>、 本例中使用PRIMARY的范围区间只有一个:(10101, 20000),所以相当于访问这个范围区间的索引付出的I/O成本就是:1 x 1.0 = 1.0

预估范围内的记录数
优化器需要计算索引的某个范围区间到底包含多少条记录,对于本例来说就是要计算PRIMARY在(10101, 20000)这个范围区间中包含多少条数据记录,计算过程是这样的:

步骤1:先根据emp_no > 10101这个条件访问一下PRIMARY对应的B+树索引,找到满足emp_no > 10101这个条件的第一条记录,我们把这条记录称之为区间最左记录。

步骤2:然后再根据emp_no

步骤3:如果区间最左记录和区间最右记录相隔不太远(只要相隔不大于10个页面即可),那就可以精确统计出满足emp_no > '10101' and emp_no

根据上面的步骤可以算出来PRIMARY索引的记录条数,所以读取记录的CPU成本为:26808*0.2=5361.6,其中26808是预估的需要读取的数据记录条数,0.2是读取一条记录成本常数。

PRIMARY的总成本
确定访问的IO成本+过滤数据的CPU成本=1+5361.6=5362.6

MySQL의 쿼리 최적화 프로그램에 대한 심층 분석(작동 원리에 대한 자세한 설명)

4、计算idx_titles_to_date需要成本

MySQL의 쿼리 최적화 프로그램에 대한 심층 분석(작동 원리에 대한 자세한 설명)
因为通过二级索引查询需要回表,所以在计算二级索引需要成本时还要加上回表的成本,而回表的成本就相当于下面这个SQL执行:

select * from employees.titles where 主键字段 in (主键值1,主键值2,。。。,主键值3);

所以idx_titles_to_date的成本 = 辅助索引的查询成本 + 回表查询的成本

5、比较各成本选出最优者

选择成本最小的索引

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

有时候使用索引执行查询时会有许多单点区间,比如使用IN语句就很容易产生非常多的单点区间,比如下边这个查询:

select * from employees.titles where to_date in ('a','b','c','d', ..., 'e');

很显然,这个查询可能使用到的索引就是idx_titles_to_date,由于这个索引并不是唯一二级索引,所以并不能确定一个单点区间对应的二级索引记录的条数有多少,需要我们去计算。计算方式我们上边已经介绍过了,就是先获取索引对应的B+树的区间最左记录和区间最右记录,然后再计算这两条记录之间有多少记录(记录条数少的时候可以做到精确计算,多的时候只能估算)。这种通过直接访问索引对应的B+树来计算某个范围区间对应的索引记录条数的方式称之为index pe

如果只有几个单点区间的话,使用index pe的方式去计算这些单点区间对应的记录数也不是什么问题,可是如果很多呢,比如有20000次,MySQL的查询优化器为了计算这些单点区间对应的索引记录条数,要进行20000次index pe操作,那么这种情况下是很耗性能的,所以MySQL提供了一个系统变量eq_range_index_pe_limit,我们看一下这个系统变量的默认值:SHOW VARIABLES LIKE ‘%pe%’;为200

也就是说如果我们的IN语句中的参数个数小于200个的话,将使用index pe的方式计算各个单点区间对应的记录条数,如果大于或等于200个的话,可就不能使用index pe了,要使用所谓的索引统计数据来进行估算。像会为每个表维护一份统计数据一样,MySQL也会为表中的每一个索引维护一份统计数据,查看某个表中索引的统计数据可以使用SHOW INDEX FROM 表名的语法。

Cardinality属性表示索引列中不重复值的个数。比如对于一个一万行记录的表来说,某个索引列的Cardinality属性是10000,那意味着该列中没有重复的值,如果Cardinality属性是1的话,就意味着该列的值全部是重复的。不过需要注意的是,对于InnoDB存储引擎来说,使用SHOW INDEX语句展示出来的某个索引列的Cardinality属性是一个估计值,并不是精确的。可以根据这个属性来估算IN语句中的参数所对应的记录数:

1)使用SHOW TABLE STATUS展示出的Rows值,也就是一个表中有多少条记录。

2)使用SHOW INDEX语句展示出的Cardinality属性。

3)根据上面两个值可以算出idx_key1索引对于的key1列平均单个值的重复次数:Rows/Cardinality

4)所以总共需要回表的记录数就是:IN语句中的参数个数*Rows/Cardinality。

NULL值处理
上面知道在统计列不重复值的时候,会影响到查询优化器。
对于NULL,有三种理解方式:

  • NULL值代表一个未确定的值,每一个NULL值都是独一无二的,在统计列不重复值的时候应该都当作独立的。

  • NULL值在业务上就是代表没有,所有的NULL值代表的意义是一样的,所以所有的NULL值都一样,在统计列不重复值的时候应该只算一个。

  • NULL完全没有意义,在统计列不重复值的时候应该忽略NULL。
    innodb提供了一个系统变量:

show global variables like '%innodb_stats_method%';

这个变量有三个值:

  • nulls_equal:认为所有NULL值都是相等的。这个值也是innodb_stats_method的默认值。如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别多,所以倾向于不使用索引进行访问。

  • nulls_unequal:认为所有NULL值都是不相等的。如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别少,所以倾向于使用索引进行访问。

  • nulls_ignored:直接把NULL值忽略掉。

最好不在索引列中存放NULL值才是正解

五、统计数据

InnoDB提供了两种存储统计数据的方式:

• 统计数据存储在磁盘上。

• 统计数据存储在内存中,当服务器关闭时这些这些统计数据就都被清除掉了。

MySQL给我们提供了系统变量innodb_stats_persistent来控制到底采用哪种方式去存储统计数据。在MySQL 5.6.6之前,innodb_stats_persistent的值默认是OFF,也就是说InnoDB的统计数据默认是存储到内存的,之后的版本中innodb_stats_persistent的值默认是ON,也就是统计数据默认被存储到磁盘中。

不过InnoDB默认是以表为单位来收集和存储统计数据的,也就是说我们可以把某些表的统计数据(以及该表的索引统计数据)存储在磁盘上,把另一些表的统计数据存储在内存中。我们可以在创建和修改表的时候通过指定STATS_PERSISTENT属性来指明该表的统计数据存储方式。

  • 1、基于磁盘的永久性统计数据
    当我们选择把某个表以及该表索引的统计数据存放到磁盘上时,实际上是把这些统计数据存储到了两个表里:
    • innodb_table_stats存储了关于表的统计数据,每一条记录对应着一个表的统计数据
    • innodb_index_stats存储了关于索引的统计数据,每一条记录对应着一个索引的一个统计项的统计数据
  • 2、定期更新统计数据
    • 系统变量innodb_stats_auto_recalc决定着服务器是否自动重新计算统计数据,它的默认值是ON,也就是该功能默认是开启的。每个表都维护了一个变量,该变量记录着对该表进行增删改的记录条数,如果发生变动的记录数量超过了表大小的10%,并且自动重新计算统计数据的功能是打开的,那么服务器会重新进行一次统计数据的计算,并且更新innodb_table_stats和innodb_index_stats表。不过自动重新计算统计数据的过程是异步发生的,也就是即使表中变动的记录数超过了10%,自动重新计算统计数据也不会立即发生,可能会延迟几秒才会进行计算。
    •如果innodb_stats_auto_recalc系统变量的值为OFF的话,我们也可以手动调用ANALYZE TABLE语句来重新计算统计数据。ANALYZE TABLE single_table;
  • 3、控制执行计划
    Index Hints
    •USE INDEX:限制索引的使用范围,在数据表里建立了很多索引,当MySQL对索引进行选择时,这些索引都在考虑的范围内。但有时我们希望MySQL只考虑几个索引,而不是全部的索引,这就需要用到USE INDEX对查询语句进行设置。
    •IGNORE INDEX :限制不使用索引的范围
    •FORCE INDEX:我们希望MySQL必须要使用某一个索引(由于 MySQL在查询时只能使用一个索引,因此只能强迫MySQL使用一个索引)。这就需要使用FORCE INDEX来完成这个功能。
    基本语法格式:
SELECT * FROM table1 USE|IGNORE|FORCE INDEX (col1_index,col2_index) WHERE col1=1 AND col2=2 AND col3=3

【相关推荐:mysql视频教程

위 내용은 MySQL의 쿼리 최적화 프로그램에 대한 심층 분석(작동 원리에 대한 자세한 설명)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제