목차
MySQL Internals-Index Merge优化
Louis Hust
0  前言
1  Index Merge理论基础
2  初始化数据
3  执行计划
4  代码分析
References
데이터 베이스 MySQL 튜토리얼 MySQL Internals-Index Merge优化_MySQL

MySQL Internals-Index Merge优化_MySQL

Jun 01, 2016 pm 01:36 PM
역사

bitsCN.com

 

MySQL Internals-Index Merge优化

Louis Hust

 

0  前言

之前搞错了,以为Index Merge是MySQL5.6的新特性,原来不是,发现5.5也有,看了下manual,发现5.0的manual就已经存在了, 可以说是一个历史悠久的优化手段了,好吧,不管怎么样,今天就拨开其神秘的面纱,看看其内部到底如何生成这种Index Merge的计划的。 这里只详细介绍Intersect操作,对于Union和Sort-Union的具体代码,还没开始研究。

 

1  Index Merge理论基础

Index Merge——索引归并,即针对一张表,同时使用多个索引进行查询,然后将各个索引查出来的结果进行进一步的操作,可以是求交 ——Intersect,也可以是求和——Union,针对union还有一种补充算法——Sort-Union,很奇怪为什么没有Sort-Intersect,按道理也是可以做的。

 

什么情况下,同时使用多个索引会有利呢?比如说WHERE条件是C1=10 AND C2 =100,但是只有分别针对C1和C2的索引,而没有(C1,C2)这种索引, 两个索引同时使用才有意义,通过两个索引都可以快速定位到一批数据,然后对这一批数据进行进一步的求交或求和操作即可,这样的效率可能比 全表扫描或者只使用其中一个索引进行扫描然后再去主索引查询要快。

 

Intersect和Union都需要使用的索引是ROR的,也就时ROWID ORDERED,即针对不同的索引扫描出来的数据必须是同时按照ROWID排序的,这里的 ROWID其实也就是InnoDB的主键(如果不定义主键,InnoDB会隐式添加ROWID列作为主键)。只有每个索引是ROR的,才能进行归并排序,你懂的。 当然你可能会有疑惑,查不记录后内部进行一次sort不一样么,何必必须要ROR呢,不错,所以有了SORT-UNION。SORT-UNION就是每个非ROR的索引 排序后再进行Merge。至于为什么没有SORT-INTERSECT,我也很是迷茫。

 

2  初始化数据

mysql> show create table im/G*************************** 1. row ***************************       Table: imCreate Table: CREATE TABLE `im` (  `c1` int(11) DEFAULT NULL,  `c2` int(11) DEFAULT NULL,  `c3` int(11) DEFAULT NULL,  KEY `c1` (`c1`,`c3`),  KEY `c2` (`c2`,`c1`)) ENGINE=InnoDB DEFAULT CHARSET=latin11 row in set (0.00 sec)mysql> show create procedure fill_im1/G*************************** 1. row ***************************           Procedure: fill_im1            sql_mode: NO_ENGINE_SUBSTITUTION    Create Procedure: CREATE DEFINER=`root`@`127.0.0.1` PROCEDURE `fill_im1`(cnt int)begin declare i int default 0; repeat insert into im values(100, 50, 100); set i=i+1; until i > cnt end repeat; endcharacter_set_client: utf8collation_connection: utf8_general_ci  Database Collation: latin1_swedish_ci1 row in set (0.07 sec)mysql> show create procedure fill_im2/G*************************** 1. row ***************************           Procedure: fill_im2            sql_mode: NO_ENGINE_SUBSTITUTION    Create Procedure: CREATE DEFINER=`root`@`127.0.0.1` PROCEDURE `fill_im2`(cnt int)begin declare i int default 0; repeat insert into im values(100, 100, 50); set i=i+1; until i > cnt end repeat; endcharacter_set_client: utf8collation_connection: utf8_general_ci  Database Collation: latin1_swedish_ci1 row in set (0.00 sec)mysql> call fill_im1(2000)mysql> call fill_im2(2000)mysql> insert into im values(100,50,50);Query OK, 1 row affected (0.00 sec)mysql> insert into im values(100,50,50);Query OK, 1 row affected (0.00 sec)mysql> commit;Query OK, 0 rows affected (0.05 sec)mysql> select * from im where c1=100 and c2 = 50 and c3 = 50/G*************************** 1. row ***************************c1: 100c2: 50c3: 50*************************** 2. row ***************************c1: 100c2: 50c3: 502 rows in set (0.13 sec)
로그인 후 복사
 

3  执行计划

mysql> explain select * from im where c1=100 and c2 = 50 and c3 = 50/G*************************** 1. row ***************************           id: 1  select_type: SIMPLE        table: im         type: index_mergepossible_keys: c1,c2          key: c1,c2      key_len: 10,10          ref: NULL         rows: 1001        Extra: Using intersect(c1,c2); Using where; Using index1 row in set (0.00 sec)
로그인 후 복사
 

4  代码分析

从生成数据的方法可以看出来,是专门针对查询的语句进行构造的。无论是根据(c1,c3)的索引查询还是根据(c2,c1)的索引查询, 都会查出一般的数据,即效率接近于全表扫描的一半。但是如果利用两个索引同时进行过滤,那么过滤出来的数据就很少了,也就是 结果中的两条。

 

也就是说如果单独查询各个索引,过滤效果不明显,但是如果联合两个索引进行MERGE过滤,那么效果可能很明显,这里所说的过滤,用更 专业的词来说是选择因子——selectivity。而计划的选择时代价的计算,便是计算这个选择因子。如果综合多个索引,导致选择因子很小,从而 达到索引merge出来的结果集很小的话,那么计划就更倾向于Index Merge,反之则不然。

 

下面是选择子计算的代码:

static double ror_scan_selectivity(const ROR_INTERSECT_INFO *info,                                    const ROR_SCAN_INFO *scan){  double selectivity_mult= 1.0;  const TABLE * const table= info->param->table;  const KEY_PART_INFO * const key_part= table->key_info[scan->keynr].key_part;  /**    key values tuple, used to store both min_range.key and    max_range.key. This function is only called for equality ranges;    open ranges (e.g. "min_value covered_fields,                                        key_part->fieldnr-1));  key_range min_range;  key_range max_range;  min_range.key= key_val;  min_range.flag= HA_READ_KEY_EXACT;  max_range.key= key_val;  max_range.flag= HA_READ_AFTER_KEY;  ha_rows prev_records= table->file->stats.records;  DBUG_ENTER("ror_scan_selectivity");  for (sel_arg= scan->sel_arg; sel_arg;       sel_arg= sel_arg->next_key_part)  {    DBUG_PRINT("info",("sel_arg step"));    cur_covered= test(bitmap_is_set(&info->covered_fields,                                    key_part[sel_arg->part].fieldnr-1));    if (cur_covered != prev_covered)    {      /* create (part1val, ..., part{n-1}val) tuple. */      bool is_null_range= false;      ha_rows records;      if (!tuple_arg)      {        tuple_arg= scan->sel_arg;        /* Here we use the length of the first key part */        tuple_arg->store_min(key_part[0].store_length, &key_ptr, 0);        is_null_range|= tuple_arg->is_null_interval();        keypart_map= 1;      }      while (tuple_arg->next_key_part != sel_arg)      {        tuple_arg= tuple_arg->next_key_part;        tuple_arg->store_min(key_part[tuple_arg->part].store_length,                             &key_ptr, 0);        is_null_range|= tuple_arg->is_null_interval();        keypart_map= (keypart_map param->use_index_statistics ||        // (1)          is_null_range ||                             // (2)          !(records= table->key_info[scan->keynr].                     rec_per_key[tuple_arg->part]))    // (3)      {        DBUG_EXECUTE_IF("crash_records_in_range", DBUG_SUICIDE(););        DBUG_ASSERT(min_range.length > 0);        records= (table->file->                  records_in_range(scan->keynr, &min_range, &max_range));      }      if (cur_covered)      {        /* uncovered -> covered */        double tmp= rows2double(records)/rows2double(prev_records);        DBUG_PRINT("info", ("Selectivity multiplier: %g", tmp));        selectivity_mult *= tmp;        prev_records= HA_POS_ERROR;      }      else      {        /* covered -> uncovered */        prev_records= records;      }    }    prev_covered= cur_covered;  }  if (!prev_covered)  {    double tmp= rows2double(table->quick_rows[scan->keynr]) /                rows2double(prev_records);    DBUG_PRINT("info", ("Selectivity multiplier: %g", tmp));    selectivity_mult *= tmp;  }  // Todo: This assert fires in PB sysqa RQG tests.  // DBUG_ASSERT(selectivity_mult  <p>刚看到这段代码时,确实有点犯懵,代码的注释给了很大的帮助:</p><pre class="brush:php;toolbar:false">/*  Get selectivity of adding a ROR scan to the ROR-intersection.  SYNOPSIS    ror_scan_selectivity()      info  ROR-interection, an intersection of ROR index scans       scan  ROR scan that may or may not improve the selectivity            of 'info'        NOTES    Suppose we have conditions on several keys    cond=k_11=c_11 AND k_12=c_12 AND ...  // key_parts of first key in 'info'         k_21=c_21 AND k_22=c_22 AND ...  // key_parts of second key in 'info'          ...         k_n1=c_n1 AND k_n3=c_n3 AND ...  (1) //key_parts of 'scan'    where k_ij may be the same as any k_pq (i.e. keys may have common parts).    Note that for ROR retrieval, only equality conditions are usable so there    are no open ranges (e.g., k_ij > c_ij) in 'scan' or 'info'    A full row is retrieved if entire condition holds.    The recursive procedure for finding P(cond) is as follows:    First step:    Pick 1st part of 1st key and break conjunction (1) into two parts:      cond= (k_11=c_11 AND R)    Here R may still contain condition(s) equivalent to k_11=c_11.    Nevertheless, the following holds:      P(k_11=c_11 AND R) = P(k_11=c_11) * P(R | k_11=c_11).    Mark k_11 as fixed field (and satisfied condition) F, save P(F),    save R to be cond and proceed to recursion step.    Recursion step:    We have a set of fixed fields/satisfied conditions) F, probability P(F),    and remaining conjunction R    Pick next key part on current key and its condition "k_ij=c_ij".    We will add "k_ij=c_ij" into F and update P(F).    Lets denote k_ij as t,  R = t AND R1, where R1 may still contain t. Then     P((t AND R1)|F) = P(t|F) * P(R1|t|F) = P(t|F) * P(R1|(t AND F)) (2)    (where '|' mean conditional probability, not "or")    Consider the first multiplier in (2). One of the following holds:    a) F contains condition on field used in t (i.e. t AND F = F).      Then P(t|F) = 1    b) F doesn't contain condition on field used in t. Then F and t are     considered independent.     P(t|F) = P(t|(fields_before_t_in_key AND other_fields)) =          = P(t|fields_before_t_in_key).     P(t|fields_before_t_in_key) = #records(fields_before_t_in_key) /                                   #records(fields_before_t_in_key, t)    The second multiplier is calculated by applying this step recursively.  IMPLEMENTATION    This function calculates the result of application of the "recursion step"    described above for all fixed key members of a single key, accumulating set    of covered fields, selectivity, etc.    The calculation is conducted as follows:    Lets denote #records(keypart1, ... keypartK) as n_k. We need to calculate     n_{k1}      n_{k2}    --------- * ---------  * .... (3)     n_{k1-1}    n_{k2-1}    where k1,k2,... are key parts which fields were not yet marked as fixed    ( this is result of application of option b) of the recursion step for      parts of a single key).    Since it is reasonable to expect that most of the fields are not marked    as fixed, we calculate (3) as                                  n_{i1}      n_{i2}    (3) = n_{max_key_part}  / (   --------- * ---------  * ....  )                                  n_{i1-1}    n_{i2-1}    where i1,i2, .. are key parts that were already marked as fixed.    In order to minimize number of expensive records_in_range calls we    group and reduce adjacent fractions. Note that on the optimizer's    request, index statistics may be used instead of records_in_range    @see RANGE_OPT_PARAM::use_index_statistics.  RETURN    Selectivity of given ROR scan, a number between 0 and 1. 1 means that    adding 'scan' to the intersection does not improve the selectivity.*/
로그인 후 복사
 

注释想说明的就是选择因子的概率如何进行计算,其实就是不同INDEX之间差异性的索引列会引起选择因子不断变小,即 Index之间差异性越大,过滤的记录就越多,选择出来的数据集就会越少。INDEX的差异性就是INdex之间索引列列是否重复出现在 不同索引之间,两个INDEX约相似,那么MERGE的结果集越大。具体的实现大家自己看看吧,明白了原理,实现都是浮云了。

 

BTW, 5.6的Optimizer trace十分好用,对于想要跟踪Optimizer内部的同学来说,可以先把详细的计划生成流程通过Optimizer trace 打印出来,对照优化流程,就能更好的定位到代码。

 

References

[1]
index-merge-optimization




File translated fromTEXby TTH,version 4.03.
On 28 Jan 2013, 22:35.

bitsCN.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Mingtide 침몰 역사 속의 5개 등대의 위치 소개 Mingtide 침몰 역사 속의 5개 등대의 위치 소개 Mar 07, 2024 pm 03:58 PM

가라앉는 조수의 역사 퀘스트에서 등대 5개를 찾고 계시나요? 이 가이드는 이러한 등대가 어디에서 발견되기를 기다리고 있는지에 대한 자세한 설명을 제공합니다. 이것이 필요한 등대를 빠르게 찾고 임무를 성공적으로 완료하는 데 도움이 되기를 바랍니다! Mingtide Five Lighthouse의 침몰 역사를 소개하고 구체적인 위치를 나열합니다. 1. 첫 번째 등대 : Beiluoye 바로 위에 위치한 불모의 돌고원으로 가십시오. 2. 두 번째 등대: 다음으로 북동쪽 순간이동 지점 주변에 있는 Zhongqu 플랫폼으로 이동하세요. 3. 세 번째 등대: 후커우산 남동쪽으로 이동하여 우밍만(Wuming Bay)을 따라 찾으세요. 4. 네번째 등대 : 앵그리버드 지 남동쪽 끝 절벽 근처 순간이동 지점으로 가주세요. 5. 다섯 번째 등대: 빛 없는 숲의 첫 번째 침묵 구역으로 이동하면 절벽 가장자리에 있습니다.

Linux 명령 기록을 보고 관리하는 방법 Linux 명령 기록을 보고 관리하는 방법 Aug 01, 2023 pm 09:17 PM

Linux에서 명령 기록을 보는 방법 Linux에서는 이전에 실행된 모든 명령 목록을 보려면 History 명령을 사용합니다. 이것은 매우 간단한 구문을 가지고 있습니다:historyhistory 명령과 쌍을 이루는 일부 옵션은 다음과 같습니다. 옵션 설명 -c는 현재 세션에 대한 명령 기록을 지웁니다. -w는 명령 기록을 파일에 기록합니다. -r은 기록 파일에서 명령 기록을 다시 로드합니다. n 최근 명령의 출력 수 제한 Linux 터미널에서 이전에 실행된 모든 명령 목록을 보려면 간단히 History 명령을 실행하십시오. 명령 기록을 보는 것 외에도 명령 기록을 관리하고 이전에 실행한 명령에 대한 수정을 수행할 수도 있습니다. 명령 기록을 검색하거나 기록을 완전히 삭제할 수도 있습니다.

Go 언어의 역사적 발전과 중요한 이정표 Go 언어의 역사적 발전과 중요한 이정표 Apr 04, 2024 am 08:12 AM

Go 언어는 원래 2007년에 개발되었으며 2012년에 버전 1.0이 출시되었습니다. 주요 이정표는 다음과 같습니다. 2012: Go 1.0이 출시되어 동시성, 메모리 안전 및 가비지 수집이 도입되었습니다. 2020: Go2가 출시되어 모듈화, 코루틴 개선, 제네릭 및 오류 처리 지원이 도입되었습니다. 2022: Go 1.19가 출시되어 일반 유형 및 기능에 대한 성능 최적화 및 지원을 제공합니다.

Kuaishou의 역사적 친밀성을 읽는 방법 Kuaishou의 역사적 친밀성을 읽는 방법 Apr 01, 2024 pm 04:51 PM

Kuaishou 역사적 친밀도를 통해 사용자는 사용자 프로필 페이지에 자신과 특정 친구 사이의 친밀도를 표시하여 서로 간의 친밀도를 보여줄 수 있습니다. 그러나 역사적 관계는 어떻습니까? 알고 싶다면 오늘 편집자가 공유한 튜토리얼을 공부해 보세요. Kuaishou의 역사적 친밀한 관계를 보려면 첫 번째 단계는 Kuaishou의 개인 홈페이지를 열고 위의 [친밀함] 아이콘을 클릭하는 것입니다. 2단계: 내 친한 친구 인터페이스로 들어가서 친한 친구 오른쪽에 있는 [친한 친구] 아이콘을 클릭하세요. 세 번째 단계는 친밀도 값 상세 페이지에 진입한 후, [친밀도 표시] 카드를 찾아 친밀도 관계가 성립된 시간을 확인하는 것입니다.

Phoenix News에서 기록을 보는 방법 기록을 보는 방법 Phoenix News에서 기록을 보는 방법 기록을 보는 방법 Mar 12, 2024 pm 07:16 PM

이 Phoenix News APP에서는 우리 모두가 다양한 뉴스 정보를 배울 수 있는 기회를 갖게 됩니다. 여기에는 많은 정보 리소스가 있으며 모든 종류의 핫 이벤트를 마스터할 수 있으므로 모든 사람이 크고 작은 것을 알 수 있습니다. 대부분의 경우 여기에는 많은 뉴스 섹션이 있고 누구나 자유롭게 볼 수 있는 다양한 정보 섹션이 있습니다. 물론 누구나 볼 수 있습니다. 관심을 갖고 구독할 수 있는 일부 관련 정보는 귀하의 요구에 매우 부합합니다. 이는 귀하가 일반적으로 보는 내용에 매우 만족합니다. 저장되어 누구나 언제든지 자신의 내용을 확인할 수 있습니다.

C 언어의 기원과 발전 역사 C 언어의 기원과 발전 역사 Mar 18, 2024 pm 06:48 PM

제목: C 언어의 기원과 개발 역사 C 언어는 시스템 소프트웨어 및 응용 소프트웨어 개발에 널리 사용되는 고급 프로그래밍 언어입니다. 구조, 모듈성, 이식성의 특징을 가지고 있으며 컴퓨터 분야에서 가장 중요하고 대중적인 프로그래밍 언어 중 하나입니다. 이 기사에서는 C 언어의 기원과 개발 역사를 소개하고 구체적인 코드 예제를 통해 설명합니다. 1. C 언어의 기원 C 언어의 역사는 Bell Labs의 Dennis Ritchie와 Ken Thompson이 개발한 1969년으로 거슬러 올라갑니다.

iPhone에서 기록을 지우는 방법 iPhone에서 기록을 지우는 방법 Jun 29, 2023 pm 01:13 PM

Safari에서 iPhone 기록을 지우는 방법은 무엇입니까? Apple Safari에서 탐색 및 검색 기록을 삭제하려면 기기에서 설정 앱을 열어야 합니다. 설정을 선택한 후 아래로 스크롤하여 Safari를 선택해야 합니다. 그러면 다른 메뉴가 나타나고 기록 및 웹 사이트 데이터 지우기를 선택해야 합니다. 이제 메뉴에서 기록 및 데이터 지우기를 선택해야 합니다. 그러면 Apple의 Safari 브라우저에서 모든 검색 기록, 인터넷 사용 기록, 쿠키 및 데이터가 삭제됩니다. 이제 모든 이전 검색 기록과 검색 기록이 Safari에서 삭제됩니다. Safari에서 모든 검색 기록을 삭제하고 싶지 않은 경우

Linux에서 과거 로그인 사용자 기록을 보는 방법은 무엇입니까? Linux에서 과거 로그인 사용자 기록을 보는 방법은 무엇입니까? Feb 21, 2024 pm 05:09 PM

오픈 소스 운영 체제인 Linux 시스템에서는 사용자가 몇 가지 간단한 명령을 통해 시스템의 로그인 사용자 기록을 볼 수 있습니다. 이는 시스템 관리자에게 매우 중요한 유지 관리 작업 중 하나입니다. 다음은 Linux 시스템에서 기록된 로그인 사용자 기록을 보는 방법에 대한 몇 가지 방법을 소개합니다. 1. last 명령을 사용하면 최근 로그인한 사용자의 기록을 표시할 수 있습니다. 터미널에 다음 명령을 입력하십시오. last 시스템은 사용자 이름, 로그인을 포함하여 가장 최근에 로그인한 사용자의 기록을 표시합니다.

See all articles