SQL語句是如何執行的?
連接器:第一步,先連接到這個數據庫上,這時候接待你的就是連接器。連接器負責跟客戶端建立連接、獲取權限、維持和管理連接。連接命令一般是這么寫的: mysql -h$ip -P$port -u$user -p 連接建立完成后,你就可以執行 select 語句了。
查詢緩存:第二步執行邏輯,MySQL 拿到一個查詢請求后,會先到查詢緩存看看,之前是不是執行過這條語句。之前執行過的語句及其結果可能會以 key-value 對的形式,被直接緩存在內存中。key 是查詢的語句,value 是查詢的結果。如果你的查詢能夠直接在這個緩存中找到 key,那么這個 value 就會被直接返回給客戶端。但是大多數情況下我會建議你不要使用查詢緩存,為什么呢?因為查詢緩存往往弊大於利。
分析器:先會做“詞法分析”。你輸入的是由多個字符串和空格組成的一條 SQL 語句,MySQL 需要識別出里面的字符串分別是什么,代表什么。MySQL 從你輸入的"select"這個關鍵字識別出來,這是一個查詢語句。它也要把字符串“T”識別成“表名 T”,把字符串“ID”識別成“列 ID”。
優化器:是在表里面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯(join)的時候,決定各個表的連接順序。
執行器:MySQL 通過分析器知道了你要做什么,通過優化器知道了該怎么做,於是就進入了執行器階段,開始執行語句。開始執行的時候,要先判斷一下你對這個表 T 有沒有執行查詢的權限,如果沒有,就會返回沒有權限的錯誤,如下所示 (在工程實現上,如果命中查詢緩存,會在查詢緩存返回結果的時候,做權限驗證。查詢也會在優化器之前調用 precheck 驗證權限)。
索引分類?
單列索引,聚合索引,全文索引
單列索引又分:主鍵索引,唯一索引,普通索引
主鍵索引 :又稱聚簇索引,主鍵索引的葉子節點存的是整行數據,根據主鍵查詢可以直接查詢出記錄,沒有回表的操作。
普通索引: 又稱二級索引,非主鍵索引的葉子節點內容是主鍵的值,基於非主鍵索引的查詢需要多掃描一棵索引樹(回表)。
覆蓋索引:查詢的結果直接在索引樹上 (select 字段在索引樹上),不需要回表。覆蓋索引是指,索引上的信息足夠滿足查詢請求,不需要再回到主鍵索引上去取數據。由於覆蓋索引可以減少樹的搜索次數,顯著提升查詢性能,所以使用覆蓋索引是一個常用的性能優化手段,explain查詢計划優化章節,即explain的輸出結果Extra字段為Using index時,能夠觸發索引覆蓋。
count()最優?
首先count(*)、count(主鍵 id) 和 count(1) 都表示返回滿足條件的結果集的總行數;
至於分析性能差別的時候,你可以記住這么幾個原則:
1)server 層要什么就給什么;
2)InnoDB 只給必要的值;
3)現在的優化器只優化了count(*)的語義為取行數
count(主鍵 id) :InnoDB 引擎會遍歷整張表,把每一行的 id 值都取出來,返回給 server 層。server 層拿到 id 后,判斷是不可能為空的,就按行累加。
count(1) :InnoDB 引擎遍歷整張表,但不取值。server 層對於返回的每一行,放一個數字“1”進去,判斷是不可能為空的,按行累加。
count(*): 是例外,並不會把全部字段取出來,而是專門做了優化,不取值。count(*) 肯定不是 null,按行累加
count(字段):則表示返回滿足條件的數據行里面,參數“字段”不為 NULL 的總個數
count性能比較,count(字段)<count(主鍵)<count(1)基本等於count(*),但是MySQL目前只對count(*)進行了優化,因此建議使用count(*)
排序原理?
MySQL會為每個線程分配一個sort_buffer,當查詢出符合條件的記錄后,會將記錄存放到sort_buffer。 當sort_buffer_size夠用的時候,就直接在內存中排序,如果當sort_buffer_size不夠用的時候,就會借助磁盤進行排序。 一般MySQL會優先選擇全字段排序,只有當記錄數量比較大時,才使用_rowid排序,使用Rowid排序會有回表的過程,因此性能也會有所降低。
回表查詢?
一次查詢索引樹就能到得結果的,不會回表查詢,需要多次查詢索引樹,才能得到需要的結果稱為回表查詢。
如粉紅色路徑,需要掃碼兩遍索引樹:
(1)先通過普通索引定位到主鍵值id=5;
(2)在通過聚集索引定位到行記錄;
這就是所謂的回表查詢,先定位主鍵值,再定位行記錄,它的性能較掃一遍索引樹更低。
例如:
CREATE TABLE t ( id INT PRIMARY KEY, age INT NOT NULL DEFAULT 0, addr VARCHAR(64), INDEX age(age) ) ENGINE = INNODB; SELECT * FROM t WHERE id=1;
使用主鍵索引 SELECT * FROM t WHERE age BETWEEN 3 AND 5;使用普通索引age,並且會有回表操作,select * 查詢的字段不全在索引樹上,需要通過主鍵再次去主鍵索引樹上查找,所以有回表操作
SELECT id FROM t WHERE age BETWEEN 3 AND 5;使用覆蓋索引,沒有回表操作
覆蓋索引?
白話就講解就是 在非聚集索引上面是否能一次性獲取select的字段,能獲取就為覆蓋索引,否則就不是
create table user ( id int primary key, name varchar(20), sex varchar(5), key(name) )engine=innodb;
能夠命中name索引,索引葉子節點存儲了主鍵id,通過name的索引樹即可獲取id和name,無需回表,符合索引覆蓋,效率較高
能夠命中name索引,索引葉子節點存儲了主鍵id,但sex字段必須回表查詢才能獲取到,不符合索引覆蓋,需要再次通過id值掃碼聚集索引獲取sex字段,效率會降低。
普通索引VS唯一索引?
查詢
唯一索引:掃描索引樹,如果找到,則停止掃描。
普通索引:掃描索引樹,如果找到,則繼續掃描,直到找不到。
更新
唯一索引:用不到change buffer
普通索引:可能會用到change buffer,提高性能
1.新增:唯一索引和普通索引的查詢性能差距微乎其微
2.更新: 為了說明普通索引和唯一索引對更新語句性能的影響這個問題,先了解一下 change buffer。當需要更新一個數據頁時,如果數據頁在內存中就直接更新,而如果這個數據頁還沒有在內存中的話,在不影響數據一致性的前提下,InooDB 會將這些更新操作緩存在 change buffer 中,這樣就不需要從磁盤中讀入這個數據頁了。在下次查詢需要訪問這個數據頁的時候,將數據頁讀入內存,然后執行 change buffer 中與這個頁有關的操作。通過這種方式就能保證這個數據邏輯的正確性。需要說明的是,雖然名字叫作 change buffer,實際上它是可以持久化的數據。也就是說,change buffer 在內存中有拷貝,也會被寫入到磁盤上。將 change buffer 中的操作應用到原數據頁,得到最新結果的過程稱為 merge。除了訪問這個數據頁會觸發 merge 外,系統有后台線程會定期 merge。在數據庫正常關閉(shutdown)的過程中,也會執行 merge 操作。
組合索引?
在表中的多個字段組合上創建的索引,只有在查詢條件中使用了這些字段的左邊字段時,索引才會被使用,使用組合索引時遵循最左前綴原則。
索引下推?
MySQL 5.6 引入的索引下推優化, 可以在索引遍歷過程中,對索引中包含的字段先做判斷,直接過濾掉不滿足條件的記錄,優化減少回表次數。
CREATE TABLE `tuser` ( `id` int(11) NOT NULL, `name` varchar(32) DEFAULT NULL, `age` int(11) DEFAULT NULL, `ismale` tinyint(1) DEFAULT NULL, PRIMARY KEY (`id`), KEY `name_age` (`name`,`age`) ) ENGINE=InnoDB;
SELECT * from tuser where name like '陳%' and age=20
Mysql5.6及之前版本
會忽略age這個字段,直接通過name進行查詢,在(name,age)這課樹上查找到了兩個結果,id分別為2,1,然后拿着取到的id值一次次的回表查詢,因此這個過程需要回表兩次。
Mysql5.6及之后版本
InnoDB並沒有忽略age這個字段,而是在索引內部就判斷了age是否等於20,對於不等於20的記錄直接跳過,因此在(name,age)這棵索引樹中只匹配到了一個記錄,此時拿着這個id去主鍵索引樹中回表查詢全部數據,這個過程只需要回表一次。
根據explain解析結果可以看出Extra的值為Using index condition,表示已經使用了索引下推。