1. MySQL 體系結構
如下圖:
Mysql是由SQL接口,解析器,優化器,緩存,存儲引擎組成的(SQL Interface、 Parser、 Optimizer、Caches&Buffers、Pluggable Storage Engines)
-
Connectors指的是不同語言中與SQL的交互
-
Management Serveices & Utilities: 系統管理和控制工具,例如備份恢復、Mysql復制、集群等
-
Connection Pool: 連接池:管理緩沖用戶連接、用戶名、密碼、權限校驗、線程處理等需要
-
SQL Interface: SQL接口:接受用戶的SQL命令,並且返回用戶需要查詢的結果。比如select from就是調用SQL Interface
-
Parser: 解析器, SQL命令傳遞到解析器的時候會被解析器驗證和解析。主要功能:
a . 將SQL語句分解成數據結構,並將這個結構傳遞到后續步驟,以后SQL語句的傳遞和處理就是基於這個結構的
b. 如果在分解構成中遇到錯誤,那么就說明這個sql語句是不合理的
-
Optimizer: 查詢優化器, SQL語句在查詢之前會使用查詢優化器對查詢進行優化
-
Cache和Buffer(高速緩存區): 查詢緩存,如果查詢緩存有命中的查詢結果,查詢語句就可以直接去查詢緩存中取數據。
通過LRU算法將數據的冷端溢出,未來得及時刷新到磁盤的數據頁,叫臟頁。
這個緩存機制是由一系列小緩存組成的。比如表緩存,記錄緩存, key緩存,權限緩存等 -
Engine :存儲引擎。存儲引擎是MySql中具體的與文件打交道的子系統。也是Mysql最具有特色的一個地方。
-
MySQL相關底層文件
2. MySQL 文件
構成MySQL整個數據庫的是所有的相關文件,這些文件有:
參數文件my.cnf:告訴MySQL實例在啟動的時候去哪里找數據庫文件,並指定初始化文件參數,包括定義內存緩沖池大小等等
日志文件:用來記錄MySQL實例對某些條件作出響應時寫入的文件,包括錯誤日志文件,二進制日志文件,慢查詢日志文件,查詢日志文件等
Socket文件:當用Unix套接字方式連接時使用的文件
Pid文件:MySQL實例的進程ID文件
MySQL表結構文件:用來存放表結構定義的文件
存儲引擎相關文件:每個存儲引擎都有自己相關的文件來保存各種數據,包括表數據和索引數據等等
參數文件:當MySQL實例啟動時,數據庫會先去讀一個配置參數文件,用來尋找數據庫的各種文件所在位置以及指定的初始化參數
2.1 MySQL 參數文件
數據庫參數其實是一個鍵值對(key/value),比如innodb_buffer_pool_size=1G。
可以通過show variables
命令來查看所有的參數,也可以通過like關鍵詞來過濾特定的
參數,還可以通過performance_schema.global_variables
視圖(5.7.6版本以后)來查看參數
show variables like '%innodb_buffer_pool%' select * from performance_schema.global_variables where variable_name='innodb_buffer_pool_size';
MySQL數據庫中的參數可以分為動態參數和靜態參數兩種
https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html #官方文檔, 對一些參數的詳細解釋
2.1.1 動態參數
參數的詳細解釋 動態參數是指在數據庫運行的過程中可以動態修改的參數,可以通過set命令對動態參數進行修改
用法:
mysql> help set Name: 'SET' Description: Syntax: SET variable_assignment [, variable_assignment] ... variable_assignment: user_var_name = expr | param_name = expr | local_var_name = expr | [GLOBAL | SESSION] system_var_name = expr | [@@global. | @@session. | @@] system_var_name = expr
Global和session是指該參數的修改是基於當前會話還是基於整個實例的生命周期
設置為global參數修改,並不影響my.cnf中的變化,當數據庫下次重啟依然是參數文件中的配置
注: 如果不重啟mysql 的情況下動態修改參數,先看看該參數是不是動態參數, 如果是動態參數 則可以用set global 方式修改 , 修改完動態參數文文件,再加入到my.cnf
例如autocommit 是dynamic 動態參數
2.1.2 靜態參數
靜態參數是指在數據庫運行過程中不能修改的參數,必須在配置文件my.cnf中修改並且數據庫重啟后才能生效
比如datadir參數,如果使用動態參數修改方式,則會報錯:
mysql> set global datadir='/usr/local/mysql/data2'; ERROR 1238 (HY000): Variable 'datadir' is a read only variable
2.2 MySQL 日志文件
MySQL日志文件包含以下幾種: 錯誤日志(error log)
二進制日志(binlog)
慢查詢日志(slow log)
查詢日志(general_log)
2.2.1 錯誤日志
是對MySQL的啟動,運行和關閉過程進行了記錄。遇到問題時首先應該查詢此文件以便定位問題。
可以通過show variables like ‘log_error’ 命令來定位文件位置
默認情況下錯誤日志的文件名是該服務器的主機名
2.2.2慢查詢日志
可以定位可能存在性能問題的SQL語句,從而進行SQL語句層面的優化。 通過設置long_query_time參數來設置一個閾值,將運行時間超過該值的所有SQL語句都記錄到慢查詢日志文件中。
show variables like '%long_query%'; show variables like '%slow_query%';
另一個和慢查詢日志相關的參數是log_queries_not_using_indexes
參數,如果運行的SQL語句沒有使用索引,則會把這條SQL語句記錄到慢查詢日志中
例如
#一個會話窗口,實時查看日志 tail -f /usr/local/mysql/data/master01-slow.log #另一個窗口 執行一個做慢查詢模擬
慢查詢日志中不光會記錄select語句,對數據庫修改語句如果符合條件也會記錄
執行sql語句的時間 比 long_query_time 大都會被記錄
隨着MySQL數據庫服務器運行時間的增加,會有越來越多的SQL語句記錄到慢查詢日志中,此時分析該文件顯得不那么簡單和直觀,這個時候可以使用
mysqldumpslow命令
來協助分析慢查詢日志 也可以通過pt 工具來分析,推薦用pt的工具。
例如提取執行時間最長的3條SQL 語句
mysqldumpslow -s t -n 3 -a master01-slow.log
如果慢查詢的日志文件查詢看着不舒服 想通過sql 方式的來看
哪可以通過動態修改log_output參數將慢查詢輸出到mysql庫下的表中
默認是以文件的輸出的方式
show variables like 'log_output'; 修改為 set global log_output='table'; 查看一下表結構 desc mysql.slow_log;
模擬慢查詢 就會輸出到mysql.slow_log表中
作用 : 分析常用sql ,看懂sql的具體作用,是否可以優化
2.2.3 查詢日志
查詢日志記錄了所有對MySQL數據庫請求的信息。
通過兩個參數來啟動:
general_log=on general_log_file=/usr/local/mysql/data/general_log
開啟了這個文件 會記錄 mysql 會話請求連接中 會實時記錄所操作 的 ddl ,dml 語句 ,會導致這個文件變得,不一會就會變得很大, 影響存儲。 開啟這個查詢日志一般都是用於排除一些異常才會開啟。
2.2.4 二進制日志文件
二進制日志binary log記錄了對MySQL數據庫執行更改的所有操作
,但不包括select和show這類操作。其主要作用為: 恢復:例如在一個數據庫全備文件恢復后,用戶可以通過二進制日志進行增量恢復
復制:通過執行二進制日志使遠程的一台MySQL數據庫與本數據庫進行數據同步
審計:用戶可以通過二進制日志中的信息來進行審計,判斷是否有對數據庫進行注入攻擊
通過配置參數log-bin[=name]可以啟動二進制日志,如果不指定name,則默認二進制日志文件名為主機名,后綴名為二進制日志的序列號
比如在服務器上的mysql-bin.000015為一個二進制日志文件,mysql-bin.index文件為二進制的索引文件,用來存儲過往產生的二進制日志序號
影響二進制日志文件的其他參數:
Max_binlog_size:指定了單個二進制日志文件的最大值
,如果超過該值,則產生新的二進制日志文件,后綴名+1,並記錄到.index文件中,默認是1G
binlog_cache_size:對InnoDB來說,所有未提交的事務的二進制日志都會先寫入到緩存中,只有當事務提交時將緩存中的二進制日志寫入到日志文件中。而緩存的大小由binlog_cache_size決定,默認是32K。當一個線程開啟一個事務時,會自動分配32K的大小的binlog緩存空間,當事務的記錄大於32K大小的時候,則會把緩存中的日志寫入到臨時文件中,可以通過查詢binlog_cache_disk_use參數查看寫入到臨時文件的次數
在默認情況下由於緩存的存在,所以每個事務並不是在發起的時候就寫入到二進制日志中,所以當數據庫在事務執行過程中宕機,則會有部分二進制日志未寫入到文件的情況,參數sync_binlog=[N]用來控制此行為。 N參數表示每寫多少次緩存就同步數據到磁盤,如果設置為1,則表示將緩存的內容同步寫入到磁盤中
sync_binlog默認取值為1 在5.7.x
5.6 版本好像是0
binlog_do_db和binlog_ignore_db表示需要寫入和忽略哪些庫的二進制日志的寫入,默認是空,表示所有數據庫的二進制日志都要寫入
Log_slave_update參數用來將從master上取得並執行的二進制日志寫入到自己的二進制日志文件中去,通常在需要搭建master=>slave=>slave (一主多從,多主多從)架構的復制時,需要設置該參數
Binlog_format參數決定了二進制日志文件的內容格式,其取值可以是statement,row或者是mixed
2.3 MySQL 套接字文件
套接字文件:在unix系統下本地連接MySQL可以采用unix域套接字方式,這種方式需要一個套接字(socket)文件,其位置和名稱由參數socket控制,一般在/tmp目錄下,名為mysql.sock
show variables like 'socket';
對mysql.sock來說,其作用是程序與mysqlserver處於同一台機器, 發起本地連接時可用。
例如你無須定義連接host的具體IP地址,只要為空或localhost就可以。 在此種情況下,即使你改變mysql的外部port也是一樣可能正常連接
2.4 MySQL進程文件
Pid文件:當MySQL實例啟動時,會將自己的進程ID寫入到一個文件中,該文件由參數pid_file控制,默認是在數據庫目錄下,文件名為主機名.pid
show variables like 'pid_file';
2.5 MySQL 表結構文件
表結構定義文件:MySQL無論表采用哪種存儲引擎,都會產生一個以frm為后綴名的文件,這個文件記錄了該表的表結構定義
frm還用來存放視圖定義,該文件是文本文件,可以直接使用cat命令來查看視圖定義
只有視圖的的frm 可以直接查看該結構的文件
2.6 MySQL存儲引擎文件
InnoDB存儲引擎文件包括以下幾種:
表空間文件:
分為共享表空間文件(ibdata1)和獨立表空間
由innodb_data_file_path參數控制,所有基於InnoDB存儲引擎的表的數據都會記錄到該共享表空間中
而如果設置了innodb_file_per_table參數,則每個innodb表都會產生一個獨立的表空間,獨立表空間的命令規則為表名.ibd,通過這種方式,用戶不用將所有數據都存放在默認表空間中。
需要說明的是獨立表空間文件僅存儲該表的數據、索引等信息,其余信息還是存放在共享表空間中
,
例如undo_log ,buffer,Innodb表的元數據都放在ibdata1 里面 等等
InnoDB存儲引擎文件包括以下幾種:
重做日志文件:默認情況下,在InnoDB存儲引擎的數據目錄下會有兩個名ib_logfile0和ib_logfile1的文件,叫重做日志文件,記錄列對於InnoDB存儲引擎的事務日志,當數據庫實例重啟時,InnoDB存儲引擎會使用重做日志恢復到重啟前的時刻,以此來保證數據的完整性
重做日志和二進制日志的區別在於:
二進制日志會記錄所有MySQL數據庫有關的日志記錄,而重做日志僅記錄有關InnoDB存儲引擎本身的事務日志
二進制日志的內容是每個事務的具體操作內容,而重做日志文件記錄的是關於每個數據頁的更改情況
3 InnoDB 體系結構
4 Mysql 后台線程
mysql> use performance_schema mysql> select name,count(*) from threads group by name; +----------------------------------------+----------+ | name | count(*) | +----------------------------------------+----------+ | thread/innodb/buf_dump_thread | 1 | | thread/innodb/dict_stats_thread | 1 | | thread/innodb/io_ibuf_thread | 1 | | thread/innodb/io_log_thread | 1 | | thread/innodb/io_read_thread | 4 | | thread/innodb/io_write_thread | 4 | | thread/innodb/page_cleaner_thread | 1 | | thread/innodb/srv_error_monitor_thread | 1 | | thread/innodb/srv_lock_timeout_thread | 1 | | thread/innodb/srv_master_thread | 1 | | thread/innodb/srv_monitor_thread | 1 | | thread/innodb/srv_purge_thread | 1 | | thread/innodb/srv_worker_thread | 3 | | thread/sql/compress_gtid_table | 1 | | thread/sql/main | 1 | | thread/sql/one_connection | 1 | | thread/sql/signal_handler | 1 | | thread/sql/thread_timer_notifier | 1 | +----------------------------------------+----------+
Master主線程
1、 Master thread線程的優先級最高,內部主要是4個循環loop組成:主循環、后台循環、刷新循環、暫停循環。
2、在master thread線程里,每1秒或每10秒會觸發1oop(循環體)工作,loop為主循環,大多數情況下都運行在這個循環體。 loop通過sleep()來實現定時的操作,所以操作時間不精准。負載高的情況下可能會有延遲;
3、dirty page:當事務(Transaction)需要修改某條記錄(row)時,InnoDB需要將該數據所在的page從disk讀到buffer pool中,事務提交后,InnoDB修改page中的記錄(row)。這時buffer pool中的page就已經和disk中的不一樣了,我們稱buffer pool中的被修改過的page為dirty page。 Dirty page等待flush到disk上。
4、insert buffer merge:
innodb使用insert buffer” 欺騙”數據庫:對於為非唯一索引,輔助索引的修改操作並非實時更新索引的葉子頁,而是把若干對同一頁面的更新緩存起來做合並(merge)為一次性更新操作
,轉化隨機IO為順序IO,這樣可以避免隨機IO帶來性能損耗,提高數據庫的寫性能。
(1)Insert Buffer是Innodb處理非唯一索引更新操作時的一個優化。最早的Insert Buffer,僅僅實現Insert操作的Buffer,這也是Insert Buffer名稱的由來。在后續版本中,Innodb多次對Insert Buffer進行增強,到Innodb 5.5版本,Insert Buffer除了支持Insert,還新增了包括Update/Delete/Purge等操作的buffer功能,Insert Buffer也隨之更名為Change Buffer。
(2)insert buffer merge分為主動給merge和被動merge。
(2.1)master thread線程里的insert buffer merge是主動merge,原理是:
a、若過去1秒內發生的IO小於系統IO能力的5%,則主動進行一次insert buffer merge(merge的頁面數為系統IO能力的5%且讀取page采用async io模式)。
b、每10秒,必須觸發一次insert buffer merge(merge的頁面數仍舊為系統IO能力的5%)
(2.2)被動Merge,則主要是指在用戶線程執行的過程中,由於種種原因,需要將insert buffer的修改merge到page之中。被動Merge由用戶線程完成,因此用戶能夠感知到merge操作帶來的性能影響。
例如:
a、 Insert操作,導致頁面空間不足,需要分裂。由於insert buffer只能針對單頁面,不能buffer page split,因此引起頁面的被動Merge;
b、 insert操作,由於其他各種原因,insert buffer優化返回失敗,需要真正讀取page時,也需要進行被動Merge;
c、在進行insert buffer操作時,發現insert buffer已經太大,需要壓縮insert buffer。
5、 check point:
(1)checkpoint干的事情:將緩沖池中的臟頁刷新到磁盤
(2)checkpoint解決的問題:
a、縮短數據庫的恢復時間(數據庫宕機時,不需要重做所有的日志,因checkpoint之前的頁都已經刷新回磁盤啦)
b、緩沖池不夠用時,將臟頁刷新到磁盤(緩沖池不夠用時,根
據LRU算法算出最近最少使用的頁,若此頁為臟頁,需要強制執行checkpoint將臟也刷回磁盤)
c、重做日志不可用時,刷新臟頁(采用循環使用的,並不是無限增大。當重用時,此時的重做日志還需要使用,就必須強制執行checkpoint將臟頁刷回磁盤)
IO thread
在innodb存儲引擎中大量使用AIO來處理IO請求,這樣可以極大提高數據庫的性能,而IO thread的工作就是負責這些IO請求的回調處理(call back);
lock monitor thread
error monitor thread
purge thread
1、 事務被提交后,其所使用的undo log可能將不再需要,因此需要purge thread來回收已經使用並分配的undo頁;
2、從mysql5.5開始,purge操作不再做主線程的一部分,而作為獨立線程。
3、開啟這個功能:innodb_purge_threads=1。調整innodb_purge_batch_size來優化purge操作,batch size指一次處理多少undo log pages, 調大這個參數可以加塊undo log清理(類似oracle的undo_retention)。
從mysql5.6開始,innodb_purge_threads調整范圍從0–1到0–32,支持多線程purge,innodb-purgebatch-size會被多線程purge共享
page cleaner thread
page cleaner thread是在innodb1.2.x中引用的,作用是將之前版本中臟頁的刷新操作都放入到單獨的線程中來完成,其目的是為了減輕master thread的工作及對於用戶查詢線程的阻塞,進一步提高innodb存儲引擎的性能。
5 MySQL語句執行過程
mysql執行一個查詢的過程,執行的步驟包括:
-
客戶端發送一條查詢給服務器;
-
服務器先檢查查詢緩存,如果命中了緩存,則立刻返回存儲在緩存中的結果。否則進入下一階段。
-
服務器段進行SQL解析、預處理,在優化器生成對應的執行計划;
-
mysql根據優化器生成的執行計划,調用存儲引擎的API來執行查詢。
-
將結果返回給客戶端。
簡單的來說:
SQL權限的檢查 –>SQL語法語意分析 –> 查詢緩存 –> 服務器SQL解析 –> 執行
5.1 查詢狀態
對於mysql連接,任何時刻都有一個狀態,該狀態表示了mysql當前正在做什么。
使用show full processlist
命令查看當前狀態。在一個查詢生命周期中,狀態會變化很多次,下面是這些狀態的解釋:
sleep:線程正在等待客戶端發送新的請求;
query:線程正在執行查詢或者正在將結果發送給客戶端;
locked:在mysql服務器層,該線程正在等待表鎖。
analyzing and statistics:線程正在收集存儲引擎的統計信息,並生成查詢的執行計划;
copying to tmp table:線程在執行查詢,並且將其結果集復制到一個臨時表中,這種狀態一般要么是做group by操作,要么是文件排序操作,或者union操作。如果這個狀態后面還有on disk標記,那表示mysql正在將一個內存臨時表放到磁盤上。
sorting Result:線程正在對結果集進行排序。
sending data:線程可能在多個狀態間傳送數據,或者在生成結果集,或者在想客戶端返回數據。
5.2 查詢緩存
在解析一個查詢語句之前,如果查詢緩存是打開的,那么mysql會優先檢查這個查詢是否命中查詢緩存中的數據
。這個檢查是通過一個對大小寫敏感的哈希查找實現的。這個檢查是通過一個對大小寫敏感的哈希查找實現的。
如果當前的查詢恰好命中了查詢緩存,那么在返回查詢結果之前mysql會檢查一次用戶權限。這仍然是無須解析查詢SQL語句的,因為在查詢緩存中已經存放了當前查詢需要訪問的表信息。如果權限沒有問題,mysql會跳過所有其他階段,直接從緩存中拿到結果並返回給客戶端
。沒權限這種情況下,查詢不會被解析,不用生成執行計划,不會被執行。
5.3 查詢優化處理
查詢的生命周期的下一步是將一個SQL轉換成一個執行計划,
mysql在依照這個執行計划和存儲引擎進行交互。這包含多個子階段:解析SQL、預處理、優化SQL執行計划
。這個過程中任何錯誤都可能終止查詢
語法解析器和預處理:首先mysql通過關鍵字將SQL語句進行解析,並生成一顆對應的“解析樹”。 mysql
解析器將使用mysql語法規則驗證和解析查詢;預處理器則根據一些mysql規則進一步檢查解析樹是否合法。
查詢優化器:當語法樹被認為是合法的了,並且由優化器將其轉化成執行計划。 一條查詢SQL語句可以有很多種執行方式,最后都返回相同的結果。優化器的作用就是找到這其中最好的執行計划。
執行計划:mysql不會生成查詢字節碼來執行查詢,mysql生成查詢的一棵指令樹,然后通過存儲引擎執行完成這棵指令樹並返回結果。最終的執行計划包含了重構查詢的全部信息。
5.4 查詢執行引擎
在解析和優化階段,mysql將生成查詢對應的執行計划,mysql的查詢執行引擎則根據這個執行計划來完成整個查詢
。這里執行計划是一個數據結構,而不是和很多其他的關系型數據庫那樣對應的字節碼 mysql簡單的根據執行計划給出的指令逐步執行
。在根據執行計划逐步執行的過程中,有大量的操作需要通過調用存儲引擎實現的接口來完成。為了執行查詢,mysql只需要重復執行計划中的各個操作,直到完成所有的數據查詢。
5.5 返回結果給客戶端
查詢執行的最后一個階段是將結果返回給客戶端。即使查詢不需要返回結果給客戶端,mysql仍然會返回這個查詢的一些信息,如該查詢影響到的行數。如果查詢可以被緩存,那么mysql在這個階段也會將結果放到查詢緩存中。
mysql將結果集返回客戶端是一個增量、逐步返回的過程。這樣有兩個好處:
-
服務器端無須存儲太多的結果,也就不會因為返回太多結果而消耗太多的內存;
-
這樣處理也讓mysql客戶端第一時間獲得返回的結果。
結果集中的每一行都會以一個滿足mysql客戶端/服務器通信協議的包發送,再通過tcp協議進行傳輸,在tcp傳輸的過程中,可能對mysql的封包進行緩存然后批量傳輸。
6 MySQL查詢優化器
MySQL采用了基於開銷的優化器
,以確定處理查詢的最解方式,也就是說執行查詢之前,都會先選擇一條自以為最優的方案,然后執行這個方案來獲取結果。 在很多情況下, MySQL能夠計算最佳的可能查詢計划,但在某些情況下, MySQL沒有關於數據的足夠信息,或者是提供太多的相關數據信息,估測就不那么友好了
MySQL優化器中,一個主要的目標是只要可能就是用索引,而且使用條件最嚴格的索引來盡可能多、盡可能快地排除那些不符合索引條件的數據行
,說白了就是選擇怎樣使用索引,當然優化器還受其他的影響。
6.1 影響優化器的使用有哪些
-
強制索引
通過FORCE INDEX(索引1[,索引2])或者使用USE INDEX(索引1[,索引2]),來指定使用哪個索引,也可以指定多個索引,讓
優化器從中挑選。 -
忽略索引
可以使用IGNORE INDEX(索引1[,索引2])來忽略一些索引,這樣優化器,就不會考慮使用這些所有,減少優化器優化時間。 -
STRAGHT_JOIN
這個會優化器使用數據表的順序
一般情況下,MySQL優化器會自行決定按照哪種順序掃描數據表才能最快地檢索出數據,但是我們可以通過STRAGHT_JOIN強制優化器按特定的順序使用數據表,畢竟優化器做的判斷不一定都是最優的。
使用原則是:
讓限制最強的選取操作最先執行。 STRAIGHT_JOIN可以放在SELECT后面,也可以放在FROM子句中。
注:STRAIGHT_JOIN只適用於inner join,並不使用與left join,right join。
6.2 查詢優化器所做的事情
-
常量轉化
它能夠對sql語句中的常量進行轉化,比如下面的表達式: WHERE col1 = col2 AND col2 = ‘x’; 依據傳遞性:如果A=B and B=C,那么就能得出A=C。所以上面的表達式mysql查詢優化器能進行如下的優化:WHERE col1 = ‘x’ AND col2 = ‘x’ -
無效代碼的排除
查詢優化器會對一些無用的條件進行過濾,比如說 WHERE 0=0 AND column1=’y’ 因為第一個條件是始終為true的,所以可以移除該條件,變為:WHERE column1=’y’再見如下表達式:WHERE (0=1 AND s1=5) OR s1=7因為前一個括號內的表達式始終為false,因此可以移除該表達式,變為:WHERE s1=7
一些情況下甚至可 以將整個WHERE子句去掉,見下面的表達式:WHERE (0=1 AND s1=5)我們可以看到, WHERE子句始終為FALASE,那么WHERE條件是不可能發生的。當然我們也可以講,WHERE條件被優化掉了 -
常量計算
如下表達式:WHERE col1 = 1 + 2轉化為:WHERE col1 = 3 Mysql會對常量表達進行計算,然后將結果生成條件 -
存取類型
當我們評估一個條件表達式,MySQL判斷該表達式的存取類型。下面是一些存取類型,按照從最優到最差的順序進行排列:
system系統表,並且是常量表
const 常量表
eq_ref unique/primary索引,並且使用的是’=’進行存取
ref 索引使用’=’進行存取
ref_or_null 索引使用’=’進行存取,並且有可能為NULL
range 索引使用BETWEEN、 IN、 >=、 LIKE等進行存取
ALL 表全掃描
優化器根據存取類型選擇合適的驅動表達式。考慮如下的查詢語句:以下是引用片段:
SELECT * FROM Table1 WHERE indexed_column=5 AND unindexed_column=6
因為indexed_column擁有更好的存取類型,所以更有可能使用該表達式做為驅動表達式。考慮到這個查詢語句有兩種可能的執行方法:
1) 不好的執行路徑:讀取表的每一行(稱為“全表掃描” ),對於讀取到的每一行,檢查相應的值是否滿足indexed_column以及 unindexed_column對應的條件。
2) 好的執行路徑:通過鍵值indexed_column=5查找B樹,對於符合該條件的每一行,判斷是否滿足unindexed_column對應的條件。
一般情況下,索引查找比全表掃描需要更少的存取路徑,尤其當表數據量很大,並且索引的類型是UNIQUE的時候。因此稱它為好的執行路徑,使用 indexed_column列作為驅動表達式。
-
范圍存取類型
一些表達式可以使用索引,但是屬於索引的范圍查找。這些表達式通常對應的操作符是:>、 >=、 <、 <=、IN、 LIKE、 BETWEEN。
對優化器而言,如下表達式:
column1 IN (1,2,3)
該表達式與下面的表達式是等價的:
column1 = 1 OR column1 = 2 OR column1 = 3
並且 MySQL也是認為它們是等價的,所以沒必要手動將IN改成OR,或者把OR改成IN。
優化器將會對下面的表達式使用索引范圍查找:column1 LIKE ‘x%’,但對下面的表達式就不會使用到索引了:column1 LIKE ‘%x’,這是因為當首字符是通配符的時候, 沒辦法使用到索引進行范圍查找。
對優化器而言,如下表達式:
column1 BETWEEN 5 AND 7
該表達式與下面的表達式是等價的:
column1 >= 5 AND column1 <= 7同樣,
MySQL也認為它們是等價的。 -
索引存取類型
考慮如下的查詢語句:SELECT column1 FROM Table1;如果column1是索引列, 優化器更有可能選擇索引全掃描,而不是采用表全掃描。
這是因為該索引覆蓋了我們所需要查詢的列。
再考慮如下的查詢語句:
SELECT column1,column2 FROM Table1; 如果索引的定義如下,那么就可以使用索引全掃描:
CREATE INDEX … ON Table1(column1,column2);
也就是說,所有需要查詢的列必須在索引中出現。但是如下的查詢就只能走全表掃描了: select col3 from Table1;由於col3沒有建立索引所以只能走全表掃描。 -
轉換
MySQL對簡單的表達式支持轉換。比如下面的語法:
WHERE -5 = column1轉換為: WHERE column1 = -5 盡管如此,對於有數學運算存在的情況不會進行轉換。
比如下面的語法:
WHERE 5 = -column1不會轉換為:WHERE column1 = -5,所以盡量減少列上的運算,而將運算放到常量上 -
AND
帶AND的查詢的格式為: AND ,考慮如下的查詢語句:
WHERE column1=’x’ AND column2=’y’
優化的步驟:
1) 如果兩個列都沒有索引,那么使用全表掃描。
2) 否則,如果其中一個列擁有更好的存取類型(比如,一個具有索引,另外一個沒有索引;再或者,一個是唯一索引,另外一個是非唯一索引),那么使用該列作為驅動表達式
-
OR
帶OR的查詢格式為: OR ,考慮如下的查詢語句:WHERE column1=’x’ OR column2=’y’
優化器做出的選擇是采用全表掃描。當然,在一些特定的情況,可以使用索引合並,這里不做闡述。如果兩個條件里面設計的列是同一列,那么又是另外一種情況,考慮如下的查詢語句:WHERE column1=’x’ OR column1=’y’在這種情況下,該查詢語句采用索引范圍查找 -
UNION
所有帶UNION的查詢語句都是單獨優化的,考慮如下的查詢語句:以下是引用片段:
SELECT * FROM Table1 WHERE column1='x' UNION ALL SELECT * FROM Table1 WHER column2='y'
-
order by
一般而言,ORDER BY的作用是使結果集按照一定的順序排序,如果可以不經過此操作就能產生順序的結果,可以跳過ORDER BY操作。考慮如下的查詢 語句:
SELECT column1 FROM Table1 ORDER BY ‘x’;優化器將去除該ORDER BY子句,因為此處的ORDER BY 子句沒有意義。再考慮另外的一個查詢語句:SELECT column1 FROM Table1 ORDER BY column1;在這種情況下,如果column1類上存在索引,優化器將使用該索引進行全掃描,這樣產生的結果集是有序的,從而不需要進行ORDER BY操作。
再考慮另外的一個查詢語句:SELECT column1 FROM Table1 ORDER BY column1+1; 假設column1上存在索引,我 們也許會覺得優化器會對column1索引進行全掃描,並且不進行ORDER BY操作。實際上,情況並不是這樣,優化器是使用column1列上的索引進行全掃表,僅僅是因為索引全掃描的效率高於表全掃描。對於索引全掃描的結果集 仍然進行ORDER BY排序操作
-
GROUP BY
這里列出對GROUP BY子句以及相關集函數進行優化的方法:
1) 如果存在索引,GROUP BY將使用索引。
2) 如果沒有索引,優化器將需要進行排序,一般情況下會使用HASH表的方法
3) 如果情況類似於“GROUP BY x ORDER BY x”,優化器將會發現ORDER BY子句是沒有必要的,因為GROUP BY產生的結果集是按照x進行排序的
4) 盡量將HAVING子句中的條件提升中WHERE子句中。
5) 對於MyISAM表,“SELECT COUNT(*) FROM Table1;”
直接返回結果,而不需要進行表全掃描。但是對於InnoDB表,則不適合該規則。補充一點,如果column1的定義是NOT NULL的,那么語句“SELECT COUNT(column1) FROM Table1;” 等價於“SELECT COUNT(*) FROM Table1;” 。
6) 考慮MAX()以及MIN()的優化情況。考慮下面的查詢語句:以下是引用片段:
SELECT MAX(column1) FROM Table1 WHERE column1<’a’; 如果column1列上存在索引,優化器使用’a’進行索引定位,然后返回前一條記錄。
7) 考慮如下的查詢語句:
SELECT DISTINCT column1 FROM Table1;在特定的情況下,語句可以轉化為:
SELECT column1 FROM Table1 GROUP BY column1;轉換的前提條件是:column1上存 在索引, FROM上只有一個單表,沒有WHERE條件並且沒有LIMIT條件