MySQL優化

MySQL優化步驟

首先學會如何定位到SQL語句mysql


1.1查看SQL語句的執行次數

在MySQL中能夠經過命令查看服務器該表狀態信息sql

show status like 'Com_______';

image-20201207215917015

若是想查看整個數據庫信息數據庫

show global status like 'Com_______';

下面這些對於全部存儲引擎的表操做都會進行累計json

  • Com_select:執行 select 操做的次數,一次查詢只累加 1。
  • Com_insert:執行 INSERT 操做的次數,對於批量插入的 INSERT 操做,只累加一次。
  • Com_update:執行 UPDATE 操做的次數。
  • Com_delete:執行 DELETE 操做的次數。

有專門針對Innodb統計的,其中 rows_read表明的是讀取的行數。服務器

show status like 'Innodb_rows_%';

image-20201207221031143

對於事務型的應用,經過 Com_commit 和 Com_rollback 能夠了解事務提交和回滾的狀況, 對於回滾操做很是頻繁的數據庫,可能意味着應用編寫存在問題。session


1.2 定位執行效率較低的SQL語句

  • 經過慢查詢日誌定位那些執行效率較低的 SQL 語句,用--log-slow-queries[=file_name]選 項啓動時,mysqld 寫一個包含全部執行時間超過 long_query_time 秒的 SQL 語句的日誌 文件。具體能夠查看本書第 26 章中日誌管理的相關部分。
  • 慢查詢日誌在查詢結束之後才紀錄,因此在應用反映執行效率出現問題的時候查詢慢查 詢日誌並不能定位問題,可使用show processlist命令查看當前MySQL在進行的線程, 包括線程的狀態、是否鎖表等,能夠實時地查看 SQL 的執行狀況,同時對一些鎖表操 做進行優化。

經過下面命令能夠查看MySQL進程mysql優化

image-20201207222623991

  • Id:數據庫鏈接id
  • User:顯示當前用戶
  • Host:從哪一個ip的哪一個端口上發的
  • db:數據庫
  • Command:鏈接的狀態,休眠(sleep),查詢(query),鏈接(connect)
  • Time:秒
  • State:SQL語句執行狀態,可能須要通過copying to tmp table、sorting result、sending data等狀態才能夠完成
  • Info:SQL語句

1.3 經過 EXPLAIN 分析低效SQL的執行計劃

找到相應的SQL語句以後,能夠EXPLALIN獲取MySQL的執行信息。app

image-20201209212623162

其中每一個列的解釋:ide

id:id相同表示加載表的執行順序從上到下,id越大加載的優先級越高性能

select_type:表示 SELECT 的類型,常見的取值有

  • SIMPLE(簡單表,即不使用錶鏈接 或者子查詢)
  • PRIMARY(主查詢,即外層的查詢)
  • UNION(UNION 中的第二個或 者後面的查詢語句)
  • SUBQUERY(子查詢中的第一個 SELECT)

table:輸出結果集的表

type:表示表的鏈接類型,性能好到壞的結果

  • system(表中僅有一行,即常量表)
  • const(單表中最多有一個匹配行,只能查詢出來一條)
  • eq_ref(對於前面的每一行,在此表中只有一條查詢數據,相似於主鍵和惟一索引)
  • ref(與eq_ref類式,區別是不使用主鍵和惟一索引)
  • ref_ir_null(與ref相似,區別在於對NULL的查詢)
  • index_merge(索引合併優化)
  • unique_subquery(in 的後面是一個查詢主鍵字段的子查詢)
  • index_subquery(與 unique_subquery 相似, 區別在於 in 的後面是查詢非惟一索引字段的子查詢)
  • range(單表中的範圍查詢)、
  • index(對於前面的每一行,都經過查詢索引來獲得數據)
  • all(對於前面的每一行, 207 都經過全表掃描來獲得數據)

possible_keys:表示查詢時,可能使用的索引。

key:表示實際使用的索引

key_len:索引字段的長度

rows:掃描行的數量

Extra:執行狀況的說明和描述

根據以上內容建立 TeacherStudent表,經過ClassID關聯

create table Teacher
(
   teacherId int not NULL AUTO_INCREMENT,
	 teacherName VARCHAR(50),
	 ClassID int,
	 primary key (teacherId)
) ENGINE =innodb DEFAULT charset=utf8;

create table Student
(
   StudentID int not NULL AUTO_INCREMENT,
	 ClassId int,
	 StudentName varchar(50),
	 primary key (StudentID)
) ENGINE = INNODB DEFAULT charset=utf8;

INSERT into Teacher(teacherName,ClassID) values("小李",204),("小劉",205),("小楊",206);

INSERT into Student(ClassId,StudentName) VALUES(204,"張三"),(205,"李四"),(206,"王五");

explain-id

(1)、Id相同表示執行順序從上到下

EXPLAIN select * from Teacher t,Student s where t.ClassID=s.ClassID;

image-20201209223229190

(2)、Id不一樣表示,Id越大越先執行

explain  select *from Teacher where ClassId =( select ClassId from Student where StudentName='張三');

image-20201209223750003

(3)、Id有相同的也有不一樣的,先執行Id大的,再從上到下執行。


explain select_type

(1)、SIMLPLE簡單的select查詢,不包含子查詢或者UNION

explain select * from Teacher;

image-20201210220352988

(2)、PRIMARY查詢當中包含了子查詢,最外層就是改查詢的標記

(3)、SUBQUERY在select或者Where中包含了子查詢

explain select *from Teacher where ClassId=(select ClassId from Student where StudentId=1);

image-20201210220847393

(4)、DERIVED在form列表包含子查詢

explain  select * from (select * from Student where Student.StudentID>2  )   a where a.ClassID=204;

image-20201215214322476

若是查詢顯示都是SIMLPLE是由於mysql5.7對 derived_merge 參數默認設置爲on,也就是開啓狀態,咱們在mysql5.7中把它關閉 shut downn 使用以下命令就能夠了

set session optimizer_switch=`derived_merge=off`;
set global optimizer_switch=`derived_merge=off`;

(5)、UNION 、UNION RESULT

explain select * from Student where StudentID=1  union select * from Student where StudentID=2;

image-20201215223848880

UNION指的是後面那個Select,UNION RESULT 將前面的select語句和後面的select聯合起來。


explain-type

(1)、NULL直接返回結果,不訪問任何表索引

select NOW();

image-20201215224329518

(2)、system查詢結果只有一條的數據,const類型的特例

explain select * from (select * from Student where StudentID=1) a;

image-20201215224802706

(3)、const根據主鍵或者惟一索引進行查詢,表示一次就找到了

EXPLAIN select * from Student where StudentID=1;

image-20201215225620366

(4)、eq_ref 索引是主鍵或者惟一索引,使用多表關聯查詢查詢出來的數據只有一條

explain select * from Student s,Teacher t where  s.StudentID=t.teacherId

image-20201216222926210

(5)、ref 根據非惟一性的索引查詢,返回的記錄有多條,好比給某個字段添加索引

explain select * from Student s WHERE  StudentName='張三1';

image-20201216224858127

(6)、range 範圍查詢 between <> in等操做,前提是用索引,要本身設定索引字段;

explain select * from Student where StudentID in (2,3);

image-20201216225335048

(7)、index 遍歷整個索引樹,至關於查詢了整張表的索引

explain select  StudentID from Student;

image-20201216225755186

(8)、ALL 遍歷全部數據文件

explain select  * from Student;

image-20201216225633881

經過這個Type就能夠判斷當前查詢返回了多少行,有沒有走索引仍是走全表掃描

結果從最好到最壞

NULL > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL


system > const > eq_ref > ref > range > index > ALL

explain-key

image-20201217221447301

(1)possible_keys:可能用到的索引

(2)key:實際用到的索引

(3)key_len:key的長度,越短越好

explain-rows

sql語句執行掃描的行數

explain-extra

(1)using filesort :會對進行文件排序即內容,而不是按索引排序,效率慢

EXPLAIN select *from Student order by StudentName;

image-20201217222406780

若是要優化的話能夠對該字段建索引

(2)using index 根據根據索引直接查,避免訪問表的數據行

explain select StudentID from Student order by StudentID ;

image-20201217223530114

(3)using temporary 使用臨時表保存結果,在沒有索引的狀況下,須要進行優化

EXPLAIN select * from Teacher t GROUP BY teacherName;

image-20201217230812749

報錯:Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'demo_01.Teacher.teacherName' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

解決辦法:
一、找到mysql的配置文件 my.ini (通常在mysql根目錄)

二、在my.cn中將如下內容添加到 [mysqld]下

個人是:etc/my.cnf

sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES

1.4 show profile分析SQL

show profile能夠分析sql運行的時間,經過 have_profiling能夠查看MySQL是否支持profile

image-20201218203651567

默認profiling是關閉的,能夠經過語句打開

set profiling=1;//打開

image-20201218203922282

執行SQL語句以後樂意經過show profiles指令,來查看語句的耗時

show profiles;

image-20201218204516161

能夠經過Show profile for query Query_id查看每一個階段的耗時

Show  profile for query 2;

image-20201218205555339

其中Sending data表示來講訪問數據庫並把結果返回給數據庫的過程,MySQL須要作大量的磁盤讀取操做,所以是最耗時的。

在知道最消耗時間的狀態後,能夠選擇all、cpu、block to、context switch、page fault等明細查看在什麼資源上浪費了時間

show profile cpu for query 2;

image-20201218210149487

1.5 trace分析優化器執行計劃

Mysql有一個優化器按照規則對SQL進行優化處理,trace就是用來分析優化器的執行計劃

首先開啓trace開關,而後設置trace文件佔用的內存空間

set optimizer_trace="enabled=on",end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;

執行SQL語句以後檢查系統表就能夠知道如何執行的SQL

select * from information_schema.optimizer_trace\G;
*************************** 1. row ***************************
                            QUERY: select * from Student where StudentId<1
                            TRACE: {
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `Student`.`StudentID` AS `StudentID`,`Student`.`ClassId` AS `ClassId`,`Student`.`StudentName` AS `StudentName` from `Student` where (`Student`.`StudentID` < 1)" //把*查詢的都解析出來了
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "(`Student`.`StudentID` < 1)",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(`Student`.`StudentID` < 1)"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(`Student`.`StudentID` < 1)"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(`Student`.`StudentID` < 1)"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            } /* substitute_generated_columns */
          },
          {
            "table_dependencies": [
              {
                "table": "`Student`",
                "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": [
              {
                "table": "`Student`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 4,
                    "cost": 3.9
                  } /* table_scan */,
                  "potential_range_indexes": [
                    {
                      "index": "PRIMARY",
                      "usable": true,
                      "key_parts": [
                        "StudentID"
                      ] /* key_parts */
                    },
                    {
                      "index": "index_id_Student",
                      "usable": true,
                      "key_parts": [
                        "StudentID"
                      ] /* key_parts */
                    },
                    {
                      "index": "index_Name_Student",
                      "usable": false,
                      "cause": "not_applicable"
                    }
                  ] /* 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": "PRIMARY",
                        "ranges": [
                          "StudentID < 1"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": true,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 1,
                        "cost": 1.21,
                        "chosen": true
                      },
                      {
                        "index": "index_id_Student",
                        "ranges": [
                          "StudentID < 1"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 1,
                        "cost": 2.21,
                        "chosen": false,
                        "cause": "cost"
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */,
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "range_scan",
                      "index": "PRIMARY",
                      "rows": 1,
                      "ranges": [
                        "StudentID < 1"
                      ] /* ranges */
                    } /* range_access_plan */,
                    "rows_for_plan": 1,
                    "cost_for_plan": 1.21,
                    "chosen": true
                  } /* chosen_range_access_summary */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`Student`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 1,
                      "access_type": "range",
                      "range_details": {
                        "used_index": "PRIMARY"
                      } /* range_details */,
                      "resulting_rows": 1,
                      "cost": 1.41,
                      "chosen": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 1,
                "cost_for_plan": 1.41,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "(`Student`.`StudentID` < 1)",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`Student`",
                  "attached": "(`Student`.`StudentID` < 1)"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "refine_plan": [
              {
                "table": "`Student`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

1.6 索引使用

(1)索引對查詢效率的提高

根據有索引的ID和名字查詢結果,數據量不是很大隻有兩萬可能不是很明顯,有索引的快一些

image-20201218221940368

若是查詢的條件值沒有索引,能夠經過建立索引來達到快速查詢的目的

(2)全值匹配,先建立聯合索引,全部列都指定具體值

create index idx_Stuname_id on Student(ClassId,StudentName);
explain select * from Student where StudentName='貨物9000號' and ClassId=9000;

image-20201218224023944

(3)最左前綴法則,從最左邊一個 索引開始匹配,順序位置不受where影響,法則是查詢的結果包含索引的最左列,且後面沒有跳過其餘列。

explain select * from Student where StudentName='貨物9000號' and ClassId=9000;

image-20201219144252170

若是將where後面最左列匹配的索引ClassId增長一個其餘字段就沒法用到idx_Stuname_id索引

explain select * from Student where   ClassId=9000  and  StudentID=20771   AND StudentName=20771;

image-20201219145054957

走索引就至關於爬樓梯,從一層一層開始爬,一層爬完爬二層,不能直接從二層開始爬,也不能爬了二層開始爬第三層

(3)在範圍查詢的字段後面索引失效

explain select *from Student where 索引1= and 字段>2 and 索引2=

所以索引2將會失效,用不到該索引

(4)若是對某一個列進行了計算操做,索引失效

explain select * from Student where ClassId  BETWEEN 20771 and 20111

image-20201219151532920

(5)、若是字符串不加單引號,索引會失效。

(6)、使用覆蓋索引(只訪問索引的查詢),避免使用select *

在查詢的時候將*號改爲須要查詢的字段或者索引,減小沒必要要的開銷,使用索引查詢,using index condition 會將須要的字段查詢出來

using index :使用覆蓋索引的時候就會出現
using where:在查找使用索引的狀況下,須要回表去查詢所需的數據
using index condition:查找使用了索引,可是須要回表查詢數據
using index ; using where:查找使用了索引,可是須要的數據都在索引列中能找到,因此不須要回表查詢數據

(7)、若是有 or後面的字段沒有索引,則整個索引失效

explain select * from Teacher where  teacherId=2;

本來主鍵索引

image-20201219153246707

加上or以後,索引失效

explain select * from Teacher where   ClassId=204  or teacherId=2;

image-20201219153357659

(8)、以like '%XX'開頭不走索引

正常走索引

explain select * from Student where StudentName LIKE '貨物9000號%';

image-20201219154341290

在like前加上%號

explain select * from Student where StudentName LIKE '%貨物9000號%' ;

image-20201219154554779

不走索引解決辦法:使用覆蓋索引,將*號改爲有索引的列,再經過索引查詢

explain select StudentID from Student where StudentName LIKE '%貨物9000號%'

image-20201219154757088

(8)若是再一張表中,一個字段數據基本全是1,只有爲2。這時候給該字段創建索引,查詢1的時候mysql認爲走全錶速度更快就不會走索引,若是查詢2就會走索引。

(9)IS NUL、IS NOT NULL有時走索引

若是一個字段中全部數據都不爲空,那麼查詢該字段時會走索引,是少許的就會走索引,大多數不會走索引。

EXPLAIN  select * from Student  where StudentName is NULL;
EXPLAIN  select * from Student  where StudentName is NOT NULL;

image-20201219160409169

(10)in走索引、not in 不走索引,但也不是絕對的,按照第八條

(11)單列索引和複合索引

create index idx_Stuname_id on Student(ClassId,StudentName);
就至關於建立了三個索引 : 
	ClassId
	StudentName
	ClassId + StudentName

若是建立單個索引,數據庫不會所有使用,而是選擇一個最優的。通常選擇辨識度最高的。

(12)查看全部使用狀況

show status like 'Handler_read%';	
show global status like 'Handler_read%';//全局

image-20201219162100069

Handler_read_first:索引中第一條被讀的次數。若是較高,表示服務器正執行大量全索引掃描(這個值越低越好)。

Handler_read_key:若是索引正在工做,這個值表明一個行被索引值讀的次數,若是值越低,表示索引獲得的性能改善不高,由於索引不常用(這個值越高越好)。

Handler_read_next :按照鍵順序讀下一行的請求數。若是你用範圍約束或若是執行索引掃描來查詢索引列,該值增長。

Handler_read_prev:按照鍵順序讀前一行的請求數。該讀方法主要用於優化ORDER BY ... DESC。

Handler_read_rnd :根據固定位置讀一行的請求數。若是你正執行大量查詢並須要對結果進行排序該值較高。你可能使用了大量須要MySQL掃描整個表的查詢或你的鏈接沒有正確使用鍵。這個值較高,意味着運行效率低,應該創建索引來補救。

Handler_read_rnd_next:在數據文件中讀下一行的請求數。若是你正進行大量的表掃描,該值較高。一般說明你的表索引不正確或寫入的查詢沒有利用索引。

1.7 SQL優化

優化批量插入

(1)大批量插入數據時,須要將主鍵按順序插入會快不少

(2)若是插入過程當中有惟一索引,能夠先關閉索引檢查,防止每插入一條時對索引進行篩查

set unique_checks=1;//1爲打開 0爲關閉

(3)手動提交事務,關閉自動提交事務

set autocommit=1;//1爲打開 0爲關閉

優化insert語句

(1)將多條insert語句改成一條

(2)手動開啓事務,所有插入以後,再提交

(3)儘可能按主鍵順序插入

優化Order by語句

(1)若是按照多字段排序,要麼統一升序要麼統一降序

(2)order 不用後面的字段須要和索引的順序保持一致

(3)若是Extra列還出現Using filesort,表示進行了額外的一次排序,考慮使用聯合索引

優化Group by語句

(1)使用Group by若是Extra列出現Using filesort,表示Group by語句默認進行了排序,可使用Order by null取消排序

(2)使用Group by若是Extra列出現Using Temporary,能夠給字段創建索引提升效率

優化嵌套查詢

(1)把多表鏈接查詢替換爲子查詢

優化OR查詢

(1)若是須要用到索引,則每一個列須要單首創建索引,不能用複合索引

(2)使用Union替換Or

優化分頁查詢

(1)根據主鍵進行排序分頁操做,獲得主鍵再回原表進行查詢

(2)主鍵自增時,能夠直接根據ID查詢,數據沒刪除的狀況下

SQL提示

(1)USE index,在有多個索引的狀況下,但願Mysql使用該索引,但不是必定會用。

explain select * from sales2 use index (ind_sales2_id) where id = 3

(2)ignore index能夠忽略使用該索引,使用其餘索引

(3)在數據量不少的狀況下,查詢數據佔很大比重,即便使用了索引,數據庫也不會用,這時候使用force index強制指定索引。

相關文章
相關標籤/搜索