溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

MySQL索引優(yōu)化案例分析

發(fā)布時間:2022-08-26 14:22:30 來源:億速云 閱讀:96 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“MySQL索引優(yōu)化案例分析”,在日常操作中,相信很多人在MySQL索引優(yōu)化案例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”MySQL索引優(yōu)化案例分析”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

    數(shù)據(jù)準(zhǔn)備

    CREATE TABLE `employees` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
     `age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡',
     `position` varchar(20) NOT NULL DEFAULT '' COMMENT '職位',
     `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入職時間',
     PRIMARY KEY (`id`),
     KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
     ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='員工記錄表';
    
     INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
     INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
     INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());
    
     ‐‐ 插入一些示例數(shù)據(jù)
     drop procedure if exists insert_emp;
     delimiter ;;
     create procedure insert_emp()
     begin
     declare i int;
     set i=1;
     while(i<=100000)do
     insert into employees(name,age,position) values(CONCAT('zhuge',i),i,'dev');
     set i=i+1;
     end while;
     end;;
     delimiter ;
     call insert_emp();

    如上有 employees 表,有主鍵索引和 (name, age, position ) 聯(lián)合索引, 看下面的查詢示例:

    聯(lián)合索引的首字段用范圍查詢

    EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';

    MySQL索引優(yōu)化案例分析

    結(jié)論:聯(lián)合索引第一個字段就用范圍查找不會走索引,mysql內(nèi)部可能覺得第一個字段就用范圍,結(jié)果集應(yīng)該很大,回表效率不高,還不如就全表掃描

    強制走索引

    EXPLAIN SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei' AND age = 22 AND position ='manager';

    MySQL索引優(yōu)化案例分析

    結(jié)論:雖然使用了強制走索引讓聯(lián)合索引第一個字段范圍查找也走索引,掃描的行rows看上去也少了點,但是最終查找效率不一定比全表掃描高,因為回表效率不高, 一般不會使用這個手段,除非有證據(jù)能證明強制走索引后效率大幅度提高

    覆蓋索引優(yōu)化

    EXPLAIN SELECT name,age,position FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';

    MySQL索引優(yōu)化案例分析

     將 select * 修改為 select name, age, posiion , 只選擇索引中已經(jīng)存在的列,可以不用回表,所以會利用索引

    in和or什么時候會走索引

    在表數(shù)據(jù)量比較大的情況會走索引,數(shù)據(jù)量不多的情況下會選擇全表掃描,示例如下:

    in 查詢

    EXPLAIN SELECT * FROM employees WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position ='manager';

    MySQL索引優(yōu)化案例分析

    用到全部索引 or 查詢

    EXPLAIN SELECT * FROM employees WHERE (name = 'LiLei' or name = 'HanMeimei') AND age = 22 AND position ='manager';

    MySQL索引優(yōu)化案例分析

    用到全部索引

    下面新建一張 employees_copy 表,結(jié)構(gòu)和 employee 一樣,但數(shù)據(jù)只有三條, 再執(zhí)行上面兩個查詢

    in 查詢

    EXPLAIN SELECT * FROM employees_copy WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position ='manager';

    MySQL索引優(yōu)化案例分析

    全表掃描

    or查詢

    EXPLAIN SELECT * FROM employees_copy WHERE (name = 'LiLei' or name = 'HanMeimei') AND age = 22 AND position ='manager';

    MySQL索引優(yōu)化案例分析

    全表掃描

    like xx% 一般都會走索引,和數(shù)據(jù)量無關(guān)

    大表

    EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';

    MySQL索引優(yōu)化案例分析

    小表

    EXPLAIN SELECT * FROM employees_copy WHERE name like 'LiLei%' AND age = 22 AND position ='manager';

    MySQL索引優(yōu)化案例分析

    可以看到,無論表的數(shù)據(jù)量大小,都會利用索引,為什么呢?

    其實 like 用到了索引下推的優(yōu)化

    索引下推

    對于輔助聯(lián)合索引,正常情況下按照最左前綴原則, SELECT * from employees where name like 'LiLei%' and age = 22 and position = 'dev' 這種情況下只會走name字段的索引,因為根據(jù)name字段過濾完,得到的索引行里的age和position是無序的,無法很好的利用索引。

    MySQL5.6之前的版本,這個查詢只能在聯(lián)合索引里匹配到名字是 'LiLei' 開頭的索引,然后拿這些索引對應(yīng)的主鍵逐個回表,到主鍵索引上找出相應(yīng)的記錄,再比對age和position這兩個字段的值是否符合。

    MySQL 5.6引入了索引下推優(yōu)化,可以在索引遍歷過程中,對索引中包含的所有字段先做判斷,過濾掉不符合條件的記錄之后再回表,可以有效的減少回表次數(shù)。使用了索引下推優(yōu)化后,上面那個查詢在聯(lián)合索引里匹配到名字是 'LiLei' 開頭的索引之后,同時還會在索引里過濾age和position這兩個字段,拿著過濾完剩下的索引對應(yīng)的主鍵id再回表查整行數(shù)據(jù)。

    索引下推會減少回表次數(shù),對于innodb引擎的表索引下推只能用于二級索引,innodb的主鍵索引(聚簇索引)樹葉子節(jié)點上保存的是全行數(shù)據(jù),所以這個時候索引下推并不會起到減少查詢?nèi)袛?shù)據(jù)的效果。

    為什么范圍查找沒有用索引下推優(yōu)化?

    估計應(yīng)該是Mysql認為范圍查找過濾的結(jié)果集過大,like KK% 在絕大多數(shù)情況來看,過濾后的結(jié)果集比較小,所以這里Mysql選擇給 like KK% 用了索引下推優(yōu)化,當(dāng)然這也不是絕對的,有時like KK% 也不一定就會走索引下推。

    如何選擇索引

    先看下面的兩個查詢:

    MySQL索引優(yōu)化案例分析

    MySQL索引優(yōu)化案例分析

    同樣的表,同樣的字段,因為條件的不同,選擇的索引也不同,MySQL 是如何選擇的呢?

    Trace 工具

    MySQl 提供了一個工具,可以看到選擇索引的計算過程, 用法如下:

    mysql> set session optimizer_trace="enabled=on",end_markers_in_json=on;  --開啟trace
    
    mysql> select * from employees where name > 'a' order by position;
    
    mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;

    下面是對 trace 字段的解析

    {
      "steps": [
        {
          "join_preparation": {    //第一階段:SQL準(zhǔn)備階段,格式化sql
            "select#": 1,
            "steps": [
              {
                "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`employees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from `employees` where (`employees`.`name` > 'a') order by `employees`.`position`"
              }
            ] /* steps */
          } /* join_preparation */
        },
        {
          "join_optimization": {    //第二階段:SQL優(yōu)化階段
            "select#": 1,
            "steps": [
              {
                "condition_processing": {    //條件處理
                  "condition": "WHERE",
                  "original_condition": "(`employees`.`name` > 'a')",
                  "steps": [
                    {
                      "transformation": "equality_propagation",
                      "resulting_condition": "(`employees`.`name` > 'a')"
                    },
                    {
                      "transformation": "constant_propagation",
                      "resulting_condition": "(`employees`.`name` > 'a')"
                    },
                    {
                      "transformation": "trivial_condition_removal",
                      "resulting_condition": "(`employees`.`name` > 'a')"
                    }
                  ] /* steps */
                } /* condition_processing */
              },
              {
                "substitute_generated_columns": {
                } /* substitute_generated_columns */
              },
              {
                "table_dependencies": [    //表依賴詳情
                  {
                    "table": "`employees`",
                    "row_may_be_null": false,
                    "map_bit": 0,
                    "depends_on_map_bits": [
                    ] /* depends_on_map_bits */
                  }
                ] /* table_dependencies */
              },
              {
                "ref_optimizer_key_uses": [
                ] /* ref_optimizer_key_uses */
              },
              {
                "rows_estimation": [    //預(yù)估表的訪問成本
                  {
                    "table": "`employees`",
                    "range_analysis": {
                      "table_scan": {     //全表掃描情況
                        "rows": 10123,    //掃描行數(shù)
                        "cost": 2054.7    //查詢成本
                      } /* table_scan */,
                      "potential_range_indexes": [    //查詢可能使用的索引
                        {
                          "index": "PRIMARY",    //主鍵索引
                          "usable": false,
                          "cause": "not_applicable"
                        },
                        {
                          "index": "idx_name_age_position",    //輔助索引
                          "usable": true,
                          "key_parts": [
                            "name",
                            "age",
                            "position",
                            "id"
                          ] /* key_parts */
                        }
                      ] /* potential_range_indexes */,
                      "setup_range_conditions": [
                      ] /* setup_range_conditions */,
                      "group_index_range": {
                        "chosen": false,
                        "cause": "not_group_by_or_distinct"
                      } /* group_index_range */,
                      "analyzing_range_alternatives": {    //分析各個索引使用成本
                        "range_scan_alternatives": [
                          {
                            "index": "idx_name_age_position",
                            "ranges": [
                              "a < name"      //索引使用范圍
                            ] /* ranges */,
                            "index_dives_for_eq_ranges": true,
                            "rowid_ordered": false,    //使用該索引獲取的記錄是否按照主鍵排序
                            "using_mrr": false,
                            "index_only": false,       //是否使用覆蓋索引
                            "rows": 5061,              //索引掃描行數(shù)
                            "cost": 6074.2,            //索引使用成本
                            "chosen": false,           //是否選擇該索引
                            "cause": "cost"
                          }
                        ] /* range_scan_alternatives */,
                        "analyzing_roworder_intersect": {
                          "usable": false,
                          "cause": "too_few_roworder_scans"
                        } /* analyzing_roworder_intersect */
                      } /* analyzing_range_alternatives */
                    } /* range_analysis */
                  }
                ] /* rows_estimation */
              },
              {
                "considered_execution_plans": [
                  {
                    "plan_prefix": [
                    ] /* plan_prefix */,
                    "table": "`employees`",
                    "best_access_path": {    //最優(yōu)訪問路徑
                      "considered_access_paths": [   //最終選擇的訪問路徑
                        {
                          "rows_to_scan": 10123,
                          "access_type": "scan",     //訪問類型:為scan,全表掃描
                          "resulting_rows": 10123,
                          "cost": 2052.6,
                          "chosen": true,            //確定選擇
                          "use_tmp_table": true
                        }
                      ] /* considered_access_paths */
                    } /* best_access_path */,
                    "condition_filtering_pct": 100,
                    "rows_for_plan": 10123,
                    "cost_for_plan": 2052.6,
                    "sort_cost": 10123,
                    "new_cost_for_plan": 12176,
                    "chosen": true
                  }
                ] /* considered_execution_plans */
              },
              {
                "attaching_conditions_to_tables": {
                  "original_condition": "(`employees`.`name` > 'a')",
                  "attached_conditions_computation": [
                  ] /* attached_conditions_computation */,
                  "attached_conditions_summary": [
                    {
                      "table": "`employees`",
                      "attached": "(`employees`.`name` > 'a')"
                    }
                  ] /* attached_conditions_summary */
                } /* attaching_conditions_to_tables */
              },
              {
                "clause_processing": {
                  "clause": "ORDER BY",
                  "original_clause": "`employees`.`position`",
                  "items": [
                    {
                      "item": "`employees`.`position`"
                    }
                  ] /* items */,
                  "resulting_clause_is_simple": true,
                  "resulting_clause": "`employees`.`position`"
                } /* clause_processing */
              },
              {
                "reconsidering_access_paths_for_index_ordering": {
                  "clause": "ORDER BY",
                  "steps": [
                  ] /* steps */,
                  "index_order_summary": {
                    "table": "`employees`",
                    "index_provides_order": false,
                    "order_direction": "undefined",
                    "index": "unknown",
                    "plan_changed": false
                  } /* index_order_summary */
                } /* reconsidering_access_paths_for_index_ordering */
              },
              {
                "refine_plan": [
                  {
                    "table": "`employees`"
                  }
                ] /* refine_plan */
              }
            ] /* steps */
          } /* join_optimization */
        },
        {
          "join_execution": {    //第三階段:SQL執(zhí)行階段
            "select#": 1,
            "steps": [
            ] /* steps */
          } /* join_execution */
        }
      ] /* steps */
    }
    // 結(jié)論:全表掃描的成本低于索引掃描,所以mysql最終選擇全表掃描
    mysql> select * from employees where name > 'zzz' order by position;
    mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;
    
    # 查看trace字段可知索引掃描的成本低于全表掃描,所以mysql最終選擇索引掃描
    
    mysql> set session optimizer_trace="enabled=off";    //關(guān)閉trace

    深入優(yōu)化

    order by 和 group by

    order by 和 group by 也會遵循左前綴法則, 如下例子 :

    MySQL索引優(yōu)化案例分析

    根據(jù)左前綴法則,用到了 name 字段的索引,同時使用 age 字段用來排序, 因為 extra 種沒有 filesort

    order by 或者 group by 用到的索引不會參與到 key_len 的計算,索引 key_len 仍然只是 74, 即 name字段的長度

    再看下面一個例子:

    MySQL索引優(yōu)化案例分析

    where 條件是name 排序字段是 position 跳過了age字段,所以只能用 name 索引,無法利用 position 索引進行索引排序,用到是文件排序

    再看第三個例子:

    MySQL索引優(yōu)化案例分析

    使用name條件查詢, 同時使用 age position 雙字段排序,沒有跳過聯(lián)合索引的字段. 所以可以用索引排序

    然后顛倒一下排序順序,先position 再 age: 

    MySQL索引優(yōu)化案例分析

    發(fā)現(xiàn)此時只能文件排序了

    再看下面的例子

    MySQL索引優(yōu)化案例分析

    雖然排序字段與索引字段不一樣,但仍然是索引排序, 因為查詢條件中 用到是 (name, age)索引,排序中用到是 position 索引,并沒有顛倒順序。所以還是索引排序

    如果一個正序一個倒序呢? 

    MySQL索引優(yōu)化案例分析

    雖然排序字段與索引字段順序相同, 但是 age 是正序, position 是倒敘,導(dǎo)致與索引的排序方式不同,無法利用索引。從而發(fā)生了文件排序, Mysql8以上版本有降序索引可以支持該種查詢方式。

    先 in 查詢: 

    MySQL索引優(yōu)化案例分析

     對于排序來說,多個相等條件也是范圍查詢, 無法利用索引排序

    先范圍查詢: 

    MySQL索引優(yōu)化案例分析

    這里發(fā)生了全表掃描,沒有任何索引,排序自然也無法利用索引了,可以使用覆蓋索引優(yōu)化: 

    MySQL索引優(yōu)化案例分析

    優(yōu)化總結(jié)

    • MySQL支持兩種方式的排序filesort和index,Using index是指MySQL掃描索引本身完成排序。index效率高,filesort效率低。

    2、order by滿足兩種情況會使用Using index。

    • order by語句使用索引最左前列。

    • 使用where子句與order by子句條件列組合滿足索引最左前列。

    • 盡量在索引列上完成排序,遵循索引建立(索引創(chuàng)建的順序)時的最左前綴法則。

    • 如果order by的條件不在索引列上,就會產(chǎn)生Using filesort。

    • 能用覆蓋索引盡量用覆蓋索引

    • group by與order by很類似,其實質(zhì)是先排序后分組,遵照索引創(chuàng)建順序的最左前綴法則。對于group by的優(yōu)化如果不需要排序的可以加上order by null禁止排序。注意,where高于having,能寫在where中的限定條件就不要去having限定了。

    Using filesort文件排序原理詳解

    單路排序模式;

    是一次性取出滿足條件行的所有字段,然后在sort buffer中進行排序;用trace工具可以看到sort_mode信息里顯示< sort_key, additional_fields >或者< sort_key, packed_additional_fields >

    雙路排序(又叫回表排序模式)

    是首先根據(jù)相應(yīng)的條件取出相應(yīng)的排序字段和可以直接定位行數(shù)據(jù)的行 ID,然后在 sort buffer 中進行排序,排序完后需要再次取回其它需要的字段;用trace工具可以看到sort_mode信息里顯示< sort_key, rowid >

    MySQL 通過比較系統(tǒng)變量 max_length_for_sort_data(默認1024字節(jié)) 的大小和需要查詢的字段總大小來判斷使用哪種排序模式。

    • 如果 字段的總長度小于max_length_for_sort_data ,那么使用 單路排序模式;

    • 如果 字段的總長度大于max_length_for_sort_data ,那么使用 雙路排序模&middot;式。

    分頁優(yōu)化

    常規(guī)的limit分頁

    有如下查詢語句

    select * from employees limit 10000,10;

    該sql并不是只查詢了10條,而是查找了10010條,然后把前10000條結(jié)果給舍棄掉, 因此要查詢一個大表靠后的內(nèi)容,執(zhí)行效率是非常低的

    優(yōu)化

    根據(jù)主鍵排序

    上面的下面的sql語句沒有指定排序方式,默認使用ID排序。當(dāng)使用ID排序時,我們可以使用下面的優(yōu)化。

    select * from employees where id > 90000 limit 5;

    如果id是連續(xù)自增的,和limit 90000,5 結(jié)果沒有差別,是 90001 ~ 90005 的數(shù)據(jù)。

    但是如果在90000之前刪除了一條數(shù)據(jù),結(jié)果就不一樣了,id > 90000 limit 5 的結(jié)果是 90001 ~ 90005, 但是limit 90000, 5 的結(jié)果是 90002 ~ 90006, 很明顯 90002 ~ 90006 才是符合我們直覺的。所以這個優(yōu)化只能限制與排序條件是連續(xù)的。如果id不是自增的呢?會出現(xiàn)什么情況,假如 90000 這條數(shù)據(jù)有兩個,limit 90000, 5 的結(jié)果是 90000 ~ 90004,而 id > 90000 limit 5 的結(jié)果仍是 90001 ~ 90005, 會把 id= 90000 的數(shù)據(jù)漏掉一條。

    所以這個優(yōu)化只能用于排序的字段是連續(xù)自增的,并且不能重復(fù)

    非主鍵排序的優(yōu)化

    有如下查詢語句

    EXPLAIN select * from employees ORDER BY name limit 90000,5;

    MySQL索引優(yōu)化案例分析

    發(fā)現(xiàn)并沒有用上name的索引,因為 select * ,掃描聯(lián)合索引時,無法的到全部數(shù)據(jù),需要回表,成本比全表掃描更高,所以優(yōu)化器放棄使用索引。

    可以使用索引覆蓋的方法,使用分頁查詢僅僅找到少量的主鍵,然后在使用主鍵查找整行數(shù)據(jù), 如下:

    select * from employees e inner join (select id from employees order by name limit 90000,5) ed on e.id = ed.id;

    看下執(zhí)行計劃:

    MySQL索引優(yōu)化案例分析

    原 SQL 使用文件排序,優(yōu)化后的使用索引排序

    表關(guān)聯(lián)優(yōu)化

    先造一些數(shù)據(jù):

    CREATE TABLE `t1` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `a` int(11) DEFAULT NULL,
      `b` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `idx_a` (`a`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    create table t2 like t1;
    
    -- 插入一些示例數(shù)據(jù)
    -- 往t1表插入1萬行記錄
    drop procedure if exists insert_t1; 
    delimiter ;;
    create procedure insert_t1()
    begin
      declare i int;
      set i=1;
      while(i<=10000)do
        insert into t1(a,b) values(i,i);  
        set i=i+1;
      end while;
    end;;
    delimiter ;
    call insert_t1();
    
    -- 往t2表插入100行記錄
    drop procedure if exists insert_t2; 
    delimiter ;;
    create procedure insert_t2()
    begin
      declare i int;
      set i=1;
      while(i<=100)do
        insert into t2(a,b) values(i,i);  
        set i=i+1;
      end while;
    end;;
    delimiter ;
    call insert_t2();

    新建 t1 t2 表,結(jié)構(gòu)一樣, 都在a字段上有索引,b字段沒有索引,t1表有 10000 行記錄,t2表只有100條記錄。

    常見的表關(guān)聯(lián)算法

    • 內(nèi)嵌循環(huán)連接算法 Nested-Loop Join

    • 基于塊的嵌套循環(huán)連接算法 Block Nested-Loop Join

    內(nèi)嵌循環(huán)連接算法

    一次一行循環(huán)地從第一張表(稱為驅(qū)動表)中讀取行,在這行數(shù)據(jù)中取到關(guān)聯(lián)字段,根據(jù)關(guān)聯(lián)字段在另一張表(被驅(qū)動表)里取出滿足條件的行,然后取出兩張表的結(jié)果合集。

    一般關(guān)聯(lián)字段有索引的時候使用這種算法, 示例:

    EXPLAIN select * from t1 inner join t2 on t1.a= t2.a;

    MySQL索引優(yōu)化案例分析

    從執(zhí)行計劃中可以看到這些信息:

    • 驅(qū)動表是 t2,被驅(qū)動表是 t1。先執(zhí)行的就是驅(qū)動表;優(yōu)化器一般會優(yōu)先選擇小表做驅(qū)動表,用where條件過濾完驅(qū)動表,然后再跟被驅(qū)動表做關(guān)聯(lián)查詢。所以使用 inner join 時,排在前面的表并不一定就是驅(qū)動表

    • 當(dāng)使用left join時,左表是驅(qū)動表,右表是被驅(qū)動表,當(dāng)使用right join時,右表時驅(qū)動表,左表是被驅(qū)動表

    • 使用了 NLJ算法。一般 join 語句中,如果執(zhí)行計劃 Extra 中未出現(xiàn) Using join buffer 則表示使用的 join 算法是 NLJ。

    上面sql的大致流程如下:

    • 從表 t2 中讀取一行數(shù)據(jù)(如果t2表有查詢過濾條件的,用先用條件過濾完,再從過濾結(jié)果里取出一行數(shù)據(jù));

    • 從第 1 步的數(shù)據(jù)中,取出關(guān)聯(lián)字段 a,到表 t1 中查找;

    • 取出表 t1 中滿足條件的行,跟 t2 中獲取到的結(jié)果合并,作為結(jié)果返回給客戶端;

    • 重復(fù)上面 3 步。

    整個過程會讀取 t2 表的所有數(shù)據(jù)(掃描100行),然后遍歷這每行數(shù)據(jù)中字段 a 的值,根據(jù) t2 表中 a 的值索引掃描 t1 表中的對應(yīng)行(掃描100次 t1 表的索引,1次掃描可以認為最終只掃描 t1 表一行完整數(shù)據(jù),也就是總共 t1 表也掃描了100行)。因此整個過程掃描了 200 行

    基于塊的嵌套循環(huán)算法

    當(dāng)關(guān)聯(lián)字段沒有沒有索引的時候會使用這種算法

    把驅(qū)動表的數(shù)據(jù)讀入到 join_buffer 中,然后掃描被驅(qū)動表,把被驅(qū)動表每一行取出來跟 join_buffer 中的數(shù)據(jù)做對比。

    如下:

    EXPLAIN select * from t1 inner join t2 on t1.b= t2.b;

    MySQL索引優(yōu)化案例分析

    Extra 中 的Using join buffer (Block Nested Loop)說明該關(guān)聯(lián)查詢使用的是 BNL 算法。

    上面sql的大致流程如下:

    • 把 t2 的所有數(shù)據(jù)放入到 join_buffer 中

    • 把表 t1 中每一行取出來,跟 join_buffer 中的數(shù)據(jù)做對比

    • 返回滿足 join 條件的數(shù)據(jù)

    整個過程對表 t1 和 t2 都做了一次全表掃描,因此掃描的總行數(shù)為10000(表 t1 的數(shù)據(jù)總量) + 100(表 t2 的數(shù)據(jù)總量) = 10100。并且 join_buffer 里的數(shù)據(jù)是無序的,因此對表 t1 中的每一行,都要做 100 次判斷,所以內(nèi)存中的判斷次數(shù)是 100 * 10000= 100 萬次。

    這個例子里表 t2 才 100 行,要是表 t2 是一個大表,join_buffer 放不下怎么辦呢?&middot;

    join_buffer 的大小是由參數(shù) join_buffer_size 設(shè)定的,默認值是 256k。如果放不下表 t2 的所有數(shù)據(jù)話,策略很簡單,就是分段放。

    比如 t2 表有1000行記錄, join_buffer 一次只能放800行數(shù)據(jù),那么執(zhí)行過程就是先往 join_buffer 里放800行記錄,然后從 t1 表里取數(shù)據(jù)跟 join_buffer 中數(shù)據(jù)對比得到部分結(jié)果,然后清空 join_buffer ,再放入 t2 表剩余200行記錄,再次從 t1 表里取數(shù)據(jù)跟 join_buffer 中數(shù)據(jù)對比。所以就多掃了一次 t1 表。

    為什么要使用 BNLJ 算法呢?

    如果上面第二條sql使用 Nested-Loop Join,那么掃描行數(shù)為 100 * 10000 = 100萬次,這個是磁盤掃描。

    很顯然,用BNL磁盤掃描次數(shù)少很多,相比于磁盤掃描,BNL的內(nèi)存計算會快得多。

    因此MySQL對于被驅(qū)動表的關(guān)聯(lián)字段沒索引的關(guān)聯(lián)查詢,一般都會使用 BNL 算法。如果有索引一般選擇 NLJ 算法,有索引的情況下 NLJ 算法比 BNL算法性能更高

    對于關(guān)聯(lián)sql的優(yōu)化

    • 關(guān)聯(lián)字段加索引,讓mysql做join操作時盡量選擇NLJ算法,驅(qū)動表因為需要全部查詢出來,所以過濾的條件也盡量要走索引,避免全表掃描,總之,能走索引的過濾條件盡量都走索引

    • 小表驅(qū)動大表,寫多表連接sql時如果明確知道哪張表是小表可以用straight_join寫法固定連接驅(qū)動方式,省去mysql優(yōu)化器自己判斷的時間

    straight_join解釋:straight_join功能同join類似,但能讓左邊的表來驅(qū)動右邊的表,能改表優(yōu)化器對于聯(lián)表查詢的執(zhí)行順序。

    比如:select * from t2 straight_join t1 on t2.a = t1.a; 代表指定mysql選著 t2 表作為驅(qū)動表。

    • straight_join只適用于inner join,并不適用于left join,right join。(因為left join,right join已經(jīng)代表指定了表的執(zhí)行順序)

    • 盡可能讓優(yōu)化器去判斷,因為大部分情況下mysql優(yōu)化器是比人要聰明的。使用straight_join一定要慎重,因為部分情況下人為指定的執(zhí)行順序并不一定會比優(yōu)化引擎要靠譜。

    小表的定義

    在決定哪個表做驅(qū)動表的時候,應(yīng)該是兩個表按照各自的條件過濾,過濾完成之后,計算參與 join 的各個字段的總數(shù)據(jù)量,數(shù)據(jù)量小的那個表,就是“小表”,應(yīng)該作為驅(qū)動表。不單單是表的總數(shù)據(jù)量

    in和exsits優(yōu)化

    原則:小表驅(qū)動大表,即小的數(shù)據(jù)集驅(qū)動大的數(shù)據(jù)集

    • in:當(dāng)B表的數(shù)據(jù)集小于A表的數(shù)據(jù)集時,in優(yōu)于exists

    select * from A where id in (select id from B)
    #等價于:   
    for(select id from B){
        select * from A where A.id = B.id
    }
    • exists:當(dāng)A表的數(shù)據(jù)集小于B表的數(shù)據(jù)集時,exists優(yōu)于in

    將主查詢A的數(shù)據(jù),放到子查詢B中做條件驗證,根據(jù)驗證結(jié)果(true或false)來決定主查詢的數(shù)據(jù)是否保留

    select * from A where exists (select 1 from B where B.id = A.id)
    #等價于: 
    for(select * from A){
      select * from B where B.id = A.id 
    } 
    #A表與B表的ID字段應(yīng)建立索引

    關(guān)于Exists

    • EXISTS (subquery)只返回TRUE或FALSE,因此子查詢中的SELECT * 也可以用SELECT 1替換,官方說法是實際執(zhí)行時會忽略SELECT清單,因此沒有區(qū)別

    • EXISTS子查詢的實際執(zhí)行過程可能經(jīng)過了優(yōu)化而不是我們理解上的逐條對比

    • EXISTS子查詢往往也可以用JOIN來代替,何種最優(yōu)需要具體問題具體分析

    count 查詢優(yōu)化

    有下面四條查詢語句:

    EXPLAIN select count(1) from employees;
    EXPLAIN select count(id) from employees;
    EXPLAIN select count(name) from employees;
    EXPLAIN select count(*) from employees;

    只有 count(字段名) 不會把該字段為null 計入總數(shù)

    其實上面四條的查詢計劃都一樣,效率上沒有太大的差別

    MySQL索引優(yōu)化案例分析

    當(dāng)字段有索引

    count(*)&asymp;count(1)>count(字段)>count(主鍵 id)

    字段有索引,count(字段)統(tǒng)計走二級索引,二級索引存儲數(shù)據(jù)比主鍵索引少,所以count(字段)>count(主鍵 id)

    當(dāng)字段沒有索引

    count(*)&asymp;count(1)>count(主鍵 id)>count(字段)

    字段沒有索引count(字段)統(tǒng)計走不了索引,count(主鍵 id)還可以走主鍵索引,所以count(主鍵 id)>count(字段)

    count(1)

    count(1)跟count(字段)執(zhí)行過程類似,不過count(1)不需要取出字段統(tǒng)計,就用常量1做統(tǒng)計,count(字段)還需要取出字段,所以理論上count(1)比count(字段)會快一點。

    count(*)

    count(*) 是例外,mysql并不會把全部字段取出來,而是專門做了優(yōu)化,不取值,按行累加,效率很高,所以不需要用count(列名)或count(常量)來替代 count(*)

    為什么對于count(id),mysql最終選擇輔助索引而不是主鍵聚集索引?因為二級索引相對主鍵索引存儲數(shù)據(jù)更少,檢索性能應(yīng)該更高,mysql內(nèi)部做了點優(yōu)化(應(yīng)該是在5.7版本才優(yōu)化)。

    常見優(yōu)化方法

    自己維護的總行數(shù)

    show table status 如果只需要知道表總行數(shù)的估計值可以用如下sql查詢,性能很高 show table status like 'employee' 

    MySQL索引優(yōu)化案例分析

    將總數(shù)維護到Redis里 插入或刪除表數(shù)據(jù)行的時候同時維護redis里的表總行數(shù)key的計數(shù)值(用incr或decr命令),但是這種方式可能不準(zhǔn),很難保證表操作和redis操作的事務(wù)一致性

    索引設(shè)計原則

    索引設(shè)計原則:

    1、代碼先行,索引后上

    等到主體業(yè)務(wù)功能開發(fā)完畢,把涉及到該表相關(guān)sql都要拿出來分析之后再建立索引。

    2、聯(lián)合索引盡量覆蓋條件

    比如可以設(shè)計一個或者兩三個聯(lián)合索引(盡量少建單值索引),讓每一個聯(lián)合索引都盡量去包含sql語句里的where、order by、group by的字段,還要確保這些聯(lián)合索引的字段順序盡量滿足sql查詢的最左前綴原則。

    3、不要在小基數(shù)字段上建立索引

    索引基數(shù)是指這個字段在表里總共有多少個不同的值,比如一張表總共100萬行記錄,其中有個性別字段,其值不是男就是女,那么該字段的基數(shù)就是2。

    如果對這種小基數(shù)字段建立索引的話,還不如全表掃描了,因為你的索引樹里就包含男和女兩種值,根本沒法進行快速的二分查找,那用索引就沒有太大的意義了。

    一般建立索引,盡量使用那些基數(shù)比較大的字段,就是值比較多的字段,那么才能發(fā)揮出B+樹快速二分查找的優(yōu)勢來。

    4、長字符串我們可以采用前綴索引

    盡量對字段類型較小的列設(shè)計索引,比如說什么tinyint之類的,因為字段類型較小的話,占用磁盤空間也會比較小,此時你在搜索的時候性能也會比較好一點。

    當(dāng)然,這個所謂的字段類型小一點的列,也不是絕對的,很多時候你就是要針對varchar(255)這種字段建立索引,哪怕多占用一些磁盤空間也是有必要的。

    對于這種varchar(255)的大字段可能會比較占用磁盤空間,可以稍微優(yōu)化下,比如針對這個字段的前20個字符建立索引,就是說,對這個字段里的每個值的前20個字符放在索引樹里,類似于 KEY index(name(20),age,position)

    此時你在where條件里搜索的時候,如果是根據(jù)name字段來搜索,那么此時就會先到索引樹里根據(jù)name字段的前20個字符去搜索,定位到之后前20個字符的前綴匹配的部分?jǐn)?shù)據(jù)之后,再回到聚簇索引提取出來完整的name字段值進行比對。

    但是假如你要是order by name,那么此時你的name因為在索引樹里僅僅包含了前20個字符,所以這個排序是沒法用上索引的, group by也是同理

    5、where與order by沖突時優(yōu)先where

    在where和order by出現(xiàn)索引設(shè)計沖突時,到底是針對where去設(shè)計索引,還是針對order by設(shè)計索引?到底是讓where去用上索引,還是讓order by用上索引?

    一般這種時候往往都是讓where條件去使用索引來快速篩選出來一部分指定的數(shù)據(jù),接著再進行排序。

    因為大多數(shù)情況基于索引進行where篩選往往可以最快速度篩選出你要的少部分?jǐn)?shù)據(jù),然后做排序的成本可能會小很多。

    舉個例子

    有 employees表,name, age, sex, position 列, 有聯(lián)合索引 (name, age, sex, position),

    sex : 性別,取值0 或1

    有如下查詢: select id from employees where name = 'zhangsan' and age = 18 and position = 'dev' 因為跳過了 sex 字段,position 無法利用索引

    因為 sex 只有兩個取值,我們在查詢語句上把 sex 的值全部枚舉出來, 如下:

    select id from employees where name = 'zhangsan' and age = 18 and sex in (0, 1) and position = 'dev'

    這樣一來就可以利用全部索引了。

    再舉個例子

    加入我們要查詢最近一周登錄的用戶,首先想到的是 last_login_time > {一周之前的時間}

    這是一個范圍查詢,在后面的所有字段便無法利用索引了,我們可以再設(shè)計一個字段,recent_login_flag(tinyint) 標(biāo)識是否最近登錄過。用定時任務(wù)定期更新該字段的值。這樣就由范圍查詢變成了等值查詢,數(shù)據(jù)可能不是太及時變化,就看業(yè)務(wù)是否允許了。

    總之就是想辦法最大限度的利用索引。

    到此,關(guān)于“MySQL索引優(yōu)化案例分析”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

    向AI問一下細節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI