mysql系列 sql實現原理

2022-07-03 20:54:08 字數 3867 閱讀 1289

myisam 引擎把⼀個表的總⾏數存在了磁碟上,因此執⾏ count(*) 的時候會直接返回這個數,效率很⾼;但是加了條件則不能快速返回

⽽ innodb 引擎就麻煩了,它執⾏ count(*) 的時候,需要把資料⼀⾏⼀⾏地從引擎⾥⾯讀出來,然後累積計數。

innodb選擇一行行計算是因為不同事物中讀物到的數量不同,單行讀取能保證事物資料的正確性。

針對count(*)mysql做了優化,普通索引比主鍵索引資料少,count(*)對於每個索引計算出的值都是相同的,mysql優化器會選擇最小的索引樹遍歷

myisam 表雖然 count(*) 很快,但是不⽀持事務;

show table status 命令雖然返回很快,但是不準確;

innodb 表直接 count(*) 會遍歷全表,雖然結果準確,但會導致效能問題。

對於頻繁查詢總數的場景,可自己記錄表總數

快取計數:啟動redis查詢總數後續自己維護數量增減,但是增減redis數量和資料庫插入資料之間不是原子操作會導致實際數量和資料有出入,同時不支援分布式事物。

資料庫計數:資料庫中儲存每個表的總數,利用事物達到數量和實際資料的一致性

⾸先要弄清楚 count() 的語義。count() 是⼀個聚合函式,對於返回的結果集,⼀⾏⾏地判斷,如果 count 函式的引數不是 null,累計值就加 1,否則不加。最後返回累計值。

所以,count(*)、count(主鍵 id) 和 count(1) 都表示返回滿⾜條件的結果集的總⾏數;⽽count(字段),則表示返回滿⾜條件的資料⾏⾥⾯,引數「字段」不為 null 的總個數。

⾄於分析效能差別的時候,你可以記住這麼⼏個原則:

1. server 層要什麼就給什麼;

2. innodb 只給必要的值;

3. 現在的優化器只優化了 count(*) 的語義為「取⾏數」,其他「顯⽽易⻅」的優化並沒有做。

count(主鍵 id),innodb 引擎會遍歷整張表,把每⼀⾏的 id 值都取出來,返回給server 層。server 層拿到 id 後,判斷是不可能為空的,就按⾏累加。

count(1),innodb 引擎遍歷整張表,但不取值。server 層對於返回的每⼀⾏,放⼀個數字「1」進去,判斷是不可能為空的,按⾏累加。

單看這兩個⽤法的差別的話,你能對⽐出來,count(1) 執⾏得要⽐ count(主鍵 id) 快。因為從引擎返回 id 會涉及到解析資料⾏,以及拷⻉字段值的操作。

count(字段)

1. 如果這個「字段」是定義為 not null 的話,⼀⾏⾏地從記錄⾥⾯讀出這個字段,判斷不能為null,按⾏累加;

2. 如果這個「字段」定義允許為 null,那麼執⾏的時候,判斷到有可能是 null,還要把值取出來再判斷⼀下,不是 null 才累加。

也就是前⾯的第⼀條原則,server 層要什麼字段,innodb 就返回什麼字段。

但是 count(*) 是例外,並不會把全部字段取出來,⽽是專⻔做了優化,不取值。count(*) 肯定不是 null,按⾏累加。

主鍵 id 肯定⾮空啊,為什麼不能按照count(*) 來處理優化下,因為類似優化過多,count(*)已優化,其他暫不優化

結論是:按照效率排序的話,count(字段)select(*)>select(自增主鍵)>select(業務數字主鍵)>select(業務字元型主鍵)>select(普通唯一索引)

全欄位排序

mysql 會給每個執行緒分配⼀塊記憶體⽤於排序,稱為 sort_buffer。

select city,name,age from t where city='杭州' order by name limit 1000;

1. 初始化 sort_buffer,確定放⼊ name、city、age 這三個字段;

2. 從索引 city 找到第⼀個滿⾜ city='杭州』條件的主鍵 id,也就是圖中的 id_x;

3. 到主鍵 id 索引取出整⾏,取 name、city、age 三個欄位的值,存⼊ sort_buffer 中;

4. 從索引 city 取下⼀個記錄的主鍵 id;

5. 重複步驟 3、4 直到 city 的值不滿⾜查詢條件為⽌,對應的主鍵 id 也就是圖中的 id_y;

6. 對 sort_buffer 中的資料按照字段 name 做快速排序;

7. 按照排序結果取前 1000 ⾏返回給客戶端。

「按 name 排序」這個動作,可能在記憶體中完成,也可能需要使⽤外部排序,這取決於排序所需的記憶體和引數 sort_buffer_size。

sort_buffer_size,就是 mysql 為排序開闢的記憶體(sort_buffer)的⼤⼩。如果要排序的資料量⼩於 sort_buffer_size,排序就在記憶體中完成。但如果排序資料量太⼤,記憶體放不下,則不得不利⽤磁碟臨時⽂件輔助排序。

rowid 排序

在上⾯這個演算法過程⾥⾯,只對原表的資料讀了⼀遍,剩下的操作都是在 sort_buffer 和臨時⽂件中執⾏的,問題在於欄位多時記憶體只能存放少量資料,需要分成許多臨時檔案,效率低

set max_length_for_sort_data = 16;

max_length_for_sort_data,是 mysql 中專⻔控制⽤於排序的⾏資料的⻓度的⼀個引數。意思是,如果單⾏的⻓度超過這個值,mysql 就認為單⾏太⼤,要換⼀個演算法

city、name、age 這三個欄位的定義總⻓度是 36,我把 max_length_for_sort_data 設定為16,換新演算法:

新的演算法放⼊ sort_buffer 的字段,只有要排序的列(即 name 字段)和主鍵 id。

但這時,排序的結果就因為少了 city 和 age 欄位的值,不能直接返回了,整個執⾏流程就變成如下所示的樣⼦:

1. 初始化 sort_buffer,確定放⼊兩個字段,即 name 和 id;

2. 從索引 city 找到第⼀個滿⾜ city='杭州』條件的主鍵 id,也就是圖中的 id_x;

3. 到主鍵 id 索引取出整⾏,取 name、id 這兩個字段,存⼊ sort_buffer 中;

4. 從索引 city 取下⼀個記錄的主鍵 id;

5. 重複步驟 3、4 直到不滿⾜ city='杭州』條件為⽌,也就是圖中的 id_y;

6. 對 sort_buffer 中的資料按照字段 name 進⾏排序;

7. 遍歷排序結果,取前 1000 ⾏,並按照 id 的值回到原表中取出 city、name 和 age 三個字段返回給客戶端。

即:通過where條件找到排序欄位和id放到sort_buffer中,排序後通過id到原表中取出結果返回。

排序是個耗效能的操作,可通過將排序欄位和篩選字段做成聯合索引,天然有序,或者使用覆蓋索引

為表誠意奉獻部分資料:

MYSQL索引原理,優化SQL

索引型別 b tree mongodb b bree mysql hash mysql引擎 innodb myisame memory b tree 多路平衡搜尋數,degree代表最多分的叉。為了減少樹的高度,從而減少 io次數。一頁 預設 16 kb 的話 每一頁 包含資料 b tree 公升級...

mysql索引實現原理

1.myisam引擎 非聚集索引 innodb引擎 聚集索引 索引是一種高效獲取資料的儲存結構,例 hash 二叉 紅黑。mysql為什麼不用上面三種資料結構而採用b tree 若僅僅是 select from user where id 100 上面三種演算法可以輕易實現,但若是select fr...

MySQL索引實現原理

1.2019年阿里資料庫索引面試題,100分鐘講透mysql索引底層原理!索引是資料庫提公升資料檢索的一種有序的資料結構 如果沒有索引,那麼dbms檢索資料時會全表檢索,效率低下 索引是一種有序的資料結構,可以是hashtable 雜湊表 binary search tree 二叉查詢樹 red b...