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條件欄位做了函式操作,是無法走索引的。

場景三:隱式字元編碼轉換

當兩個表關聯查詢,關聯條件的兩個欄位,字符集編碼不一致時,也需要進行函式轉換,同樣的也不會走索引。