乙個例子與InnoDB索引的幾個概念

2021-09-23 22:48:10 字數 2986 閱讀 1273

1、乙個簡單的sql語句問題

假設當前我們有乙個表記錄使用者資訊,結構如下:

a)      表結構

create table `u` (

`id` int(11) not null default 『0′,

`regdate` int(1) unsigned,

…..primary key (`id`),

key `regdate` (`regdate`)

) engine=innodb default charset=gbk

說明:1) 由於需要按照註冊時間單獨查詢,建了乙個regdate的索引

2) 其他資訊未列出, 一行長度100位元組左右,錶行數百萬級。 

b)      需求:需要乙個語句查出表中id為10000整數倍的記錄總數。

2、常規答案

乙個正常想到的語句是 select sum(id % 10000 = 0) from u; —— (sql1)

我們來看這個語句的執行流程:

a)      遍歷所有資料,取出id欄位

b)      計算id%10000=0的值並通過sum累計。

在構造的環境中這個語句的執行時間為2.6s.

3、查的多,查得快

假設我們同時要查出註冊時間在2023年之前的使用者總數,我們自然得到這個語句

select sum(id % 10000 = 0), sum(regdate<1167667200) from sbtest;—-(sql2)

執行結果發現這個語句執行時間約0.5s。 這個語句查的資料結果比sql1多,但執行時間卻降為1/5.

4、分析

可以直接從執行期間的磁碟引數,或者在os/os0file.c中將程式讀取的資料量輸出結果檢視,直觀結果是sql1讀取了更多的磁碟資料。

問題1:在sql1執行過程中,遍歷所有資料,innodb只從磁碟讀取了id這個字段,還是全部讀入?

實際上由於id是聚簇索引,並沒有乙個單獨的索引樹存id,因此在磁碟上,id索引樹的葉節點上就是資料。 innodb以page為單位讀取,在取id的過程中,必須將所有的資料讀入。

於是我們發現,在sql1中,我們只需要id欄位,而每行額外讀入了幾百位元組的資料。

問題2:sql2避免了讀全資料?

確實如此。

我們對比兩個語句的explain結果, 發現僅有的不同是選用的key結果不同。

sql1

sql2

key: primary

key: regdate

由於regdate是非聚簇(secondary index)索引,單獨存於另一棵樹。 我們知道使用非聚簇索引時,需要讀行資料的時候,需要再到聚簇索引中取得。顯然sql2不會再讀一遍全資料(否則效能必然低於sql1)。

而其原因是覆蓋索引(covering index)。 非聚簇索引的葉節點上是聚簇索引的字段值,需要取資料時,根據這個值再去聚簇索引上取。而這時innodb變「聰明」了, 需要取的值只是id,而id作為聚簇索引的key資訊,已經得到,不需要再到聚簇索引中讀取資料。

由於regdate索引樹上只有regdate和主鍵(id)的資訊,因此資料量遠小於全表資料,因此sql2的讀盤量小於sql1,執行速度快。

5、其他

這個例子涉及到幾個概念, 聚簇索引(cluster index)、非聚簇索引(secondary index), 覆蓋索引(covering index),還有磁碟的資料存放。都算是一些基本的內容,卻是平時見到的一些優化的理論基礎。舉幾個例子如下:

1)      我們經常被告誡select之後只填最必須的字段

其中的乙個原因是減少網路傳輸。但不一定能夠提公升伺服器執行效能。比如例子中的表,select  * from u where id = n; 與select user_name from u where id =n一樣。

當然有些時候效果會很理想,比如 select id from u where regdate=*** 就比select * from u where regdate=***快很多,原因已說明。

2)      查詢符合條件的第10w個記錄開始的10個記錄。

這個例子在其他博文上被多次提及,

select * from t order by a limit 100000, 10; 可以改進為

select * from t where a>=(select a from t order by a limit 100000,1) limit 10;

在筆者環境中效能提公升約1000倍。

原因即在於, 改進語句中,子查詢中的排序只在非聚餐索引a上執行,由於覆蓋索引,排序過程不需要訪問聚簇索引。實際讀讀取全資料的只有10條記錄,而原語句則需要讀所有記錄的全資料。

當然執行排序的過程消耗是一樣的。 

6、結束

回到開頭,如果只需要查id滿足特定條件的記錄總數,可以使用select sum(id % 10000 = 0) from u force index (`regdate`);  

把sum(id %10000=0)換成其他操作對執行效率均沒有影響。 

但若查詢內容**現除id和regdate外的其他字段,則force index優化無效,可自行分析。

LineDDA的乙個例子

unit unit1 inte ce uses windows,messages,sysutils,variants,classes,graphics,controls,forms,dialogs,extctrls,stdctrls,buttons type tfmmain class tform ...

SQL GROUP CONCAT的乙個例子

我有乙個這樣的資料庫 user info 現在有乙個需求是把這樣 9 條記錄按照 username 來 group 成3條記錄 目標 shu female 201 lee male 202 yuki female 181 如果用select from user info group by usern...

explode的乙個例子

select level as level,explode split 1,2,3 as value 可以生成結果 level value level 1 level 2 level 3 lateral view 1.lateral view 用於和udtf函式 explode,split 結合來使...