MySQL深入研究--學習總結(4)
阿新 • • 發佈:2021-03-09
# 前言
接上文,繼續學習後續章節。細心的同學已經發現,我整理的並不一定是作者講的內容,更多是結合自己的理解,加以闡述,所以建議結合原文一起理解。
# 第13章《為什麼表資料刪除一般,表文件大小不變?》
我們在知道MySQL表的儲存,在8.0之前的版本,表結構相關資料存在.frm檔案中,表資料存在.ibd檔案中。可以通過innoDB_file_per_table控制,OFF表示表資料頁放在系統共享表空間,這時候刪除表資料,檔案大小也不會變。
但即使,表資料單獨存在.ibd檔案中,當我們通過delete刪除時,發現檔案大小也不會變。
這是因為表資料在B+結構中,當我們刪除一條資料時,並不會真把這個資料給徹底刪除,只是在這個位置上做個刪除的標記。被標記刪除的位置,可以被複用,等待下次有資料插入時,可以儲存在該位置,但實際檔案大小不會表的。不僅刪除,插入也是如此,當插入時,出現也分裂,就可能出現空洞。也就是說一個表經過大量的增刪改,就會出現大量的空洞。所以我們會發現但我們刪除表資料,檔案大小也不會表。
那麼如何解決這個問題,把表壓縮下呢?
可以通過:重建表
這裡,你可以使用alter table A engine=innoDB命令來重建表。
在5.5版本版本之前,這個命令做了如下幾個操作:
1、新建一個與表A一樣結構的表B。
2、將表A的資料按ID自增的順序寫入表B。
但是在這個過程中,增刪改是會丟失的。
所以在5.6版本之後,引入了Online DDL。
1、新增一個臨時檔案,掃描A主鍵的所有資料頁。
2、根據資料頁中表A的記錄生成B+樹,儲存到臨時檔案。
3、在生成過程中,將對A的所有更新操作,記錄在row log 檔案中。
4、臨時檔案生成完畢後,將row log中的資料,維護到臨時檔案中,然後用臨時檔案替代表A的資料檔案。
# 第14章 《count(*)特別慢,怎麼辦?》
當一張資料量很大的表,比如一張1千萬資料量的表,做count(*)時,很慢這是什麼原因呢?
首先我們要從儲存引擎區分,在MyISAM中,count(*)的結果記錄在檔案中,所以會直接返回。
而innoDB中,count(*)時,需要一行一行掃描統計行數。所以當資料量很大時,就會變的很慢。
那為什麼innoDB不跟MyISAM一樣呢,這是因為MyISAM是不支援事務的。而在innoDB中,同一時刻,由於可重複讀的隔離級別特性,不同事務做統計查詢,查詢的結果可能是不一樣的。
所以不能直接用一個統計給所有事務通用。
那業務上我們的確需要統計全表的數量,怎麼辦?
1、把count(*)的資料儲存在Redis中,每當插入刪除時,更新快取,但這不適用於帶where條件的查詢。
2、把count(*)的數值存在mysql表中。
優缺點:
如果把統計資料放在Redis,在單機器下,宕機就沒法用了,雖然可以通過叢集來達到高可用。當資料庫的操作與Redis的操作,存在資料一致性的問題。比如既要返回count資料,又要返回最新的100條資料,就可能出現最新的資料不一定在count統計中,或者統計了最新的資料,但並不在表中,想一想這是為什麼?
所以建議,如果對count的實時性不是特別高的時候,可以使用該方案。
正因為有Redis和MySQL沒法同時支援分散式事務,如果把count資料存在MySQL表中,通過事務就可以解決這樣的問題,但顯然兩次表操作也是缺點所在。
**count(\*) 、count(1) 、count(id)、 count(欄位)不同方式有什麼區別?**
count(*)和count(1)都是隻統計行數,不返回資料,效能最優,MySQL官方也說明了兩者本質沒有區別。
count(id)掃描所有id,再統計行數,多了id欄位的返回,索引效能比count(*)和count(1)略差.
count(欄位)掃描該欄位並返回,如果該欄位允許為null.就得統計不為null的資料,所以比count(id)效能差。
# 第16章《order by是如何工作的?》
背景條件:
有這樣一張表
```
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`city` varchar(16) NOT NULL,
`name` varchar(16) NOT NULL,
`age` int(11) NOT NULL,
`addr` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `city` (`city`)
) ENGINE=InnoDB;
```
```
--執行這樣的一個查詢語句,MySQL是怎麼工作的呢?
select city,name,age from t where city='杭州' order by name limit 1000;
```
1、首先根據輔助索引查詢出city="杭州"的資料。
2、發現第一個city=“杭州”資料時,得到對應的id,再根據id去主鍵索引中查出該行的完整資料。
3、查詢得到完整資料後,取city,name,age三個欄位,放入sort buffer中。
4、重複上述動作,查詢出索引city=“杭州”的資料。
5、將sort buffer中的資料,按name排序,取出前1000條並返回。查詢結果。
這個稱為**全欄位排序**。
幾個概念:
sort buffer:MySQL為了排序的高效,為每個執行緒會在記憶體中開闢一塊記憶體,專門用於排序。
sort buffer size:決定了排序緩衝區的大小,超過排序緩衝區的資料,便只能使用額外的檔案用於排序。
max_length_for_sort_data:限定用於排序時,單行資料的大小,超過這個大小的行,就會使用另外一個排序方法,如下。
幾個疑問:
如果單行的資料很大,sort buffer中可存放的資料就少了,那麼就要分成很多臨時檔案(歸併排序),排序的效能就會很低,怎麼辦?
MySQL發現當行資料的大小超過設定的max_length_for_sort_data時,就會採用另外一種演算法,整個排序過程如下:
1、首先根據輔助索引查詢出city="杭州"的資料。
2、發現第一個city=“杭州”資料時,得到對應的id,再根據id去主鍵索引中查出該行的完整資料。
3、查詢得到完整資料後,**取id,name兩個欄位,放入sort buffer中。**
4、重複上述動作,查詢出索引city=“杭州”的資料。
5、將sort buffer中的資料,按name排序,取出前1000條。
6、再根據sort buffer中name與id的對應關係,**根據id再回表遍歷查詢出整行資料,取name,city,age三個欄位**返回給客戶端。
我們會發現比第一種方法,多了一次回表查詢。這個成為**rowid排序**。
# 第18章《為什麼這些SQL語句邏輯相同,效能卻差異巨大?》
主要講了三個場景,其實SQL優化的核心就是,能不能用到索引,能不能減少回表查詢,能不能使用到覆蓋索引,本質就是以空間換時間。
場景一:條件欄位做函式操作
當一個查詢語句的where條件欄位做了函式操作,是無法走索引的,比如where id +1 =2;
本質上就是因為MySQL無法判斷該欄位函式操作後不再有序了,只能全索引掃描。
場景二:隱式型別轉換
就是型別轉換,比如原本欄位是varchar,缺沒有帶“”查詢,就會針對這個欄位做型別轉換函式,MySQL發現字元型別和數值型別比較時,會把字串轉換成數值。
顯然當一個查詢語句的where條件欄位做了函式操作,是無法走索引的。
場景三:隱式字元編碼轉換
當兩個表關聯查詢,關聯條件的兩個欄位,字符集編碼不一致時,也需要進行函式轉換,同樣的也不會走