目錄
問題說明
解決方案 - 場景A
解決方案 - 場景B
解決方案 - 場景C
深入理解索引 - 索引的邏輯結構
深入理解索引 - 索引的三個特點
深入理解索引 - 索引的注意事項
問題說明:
系統上線後,用戶數據不斷增長,從最初的幾十萬記錄發展至上千萬、甚至上億條記錄。運維人員反饋原有的存儲過程運行效率愈發低下。review時發現以下可改進之處:
場景A:數據表的索引與查詢語句子條件(where)篩選字段無關,未合理創建索引;
場景B:業務邏輯分析不足,產生非必要的表關聯,50%的語句可改寫爲單表查詢語句;
場景C:數據表本身已有相關索引,但在查詢時未正確使用索引;
各場景解決方案: 由於不方便用項目代碼舉例,以下sql涉及的字段名、數據表名均用別名替換。
場景A:.必要表連接 + 無索引,可調整爲 必要表連接 + 索引檢索
表b:file_name,file_month,file_type(索引I:file_month)
表d: fee,file_name,file_month(無索引)
SELECT fee
FROM d
WHERE file_name IN (SELECT file_name FROM b WHERE file_month=202002 and file_type= 'XXX');
【執行計劃 - 必要表連接 + 無索引】
【優化分析】由於業務層面上,B與D表的file_month不等價,需保留表連接。執行計劃中的索引掃描“INDEX(FAST FULL SCAN)”用到了b表的索引I(file_month),可在 表d 的file_name列新建索引提升查詢性能。
【新建索引】CREATE INDEX d_file_name_idx ON d (file_name) ;
【執行計劃 - 必要表連接 + 索引檢索】
【成本優化】:269416 -- 1087
場景B:非必要表連接 + 無索引 ,可調整爲 無表連接 + 索引檢索:
表c:file_name, file_month
表d:fee,file_name,file_month(數據量 3018488)
SELECT fee
FROM d
WHERE file_name IN (SELECT file_name FROM c WHERE file_month=202002);
【執行計劃 - 非必要表連接 + 無索引 】:
【優化分析】d表的file_month與c表的file_month在業務層面是等價的,因此,c、d表無需關聯,並在file_month列建索引:
【無表連接】
SELECT fee
FROM d
WHERE file_month= 202002;
【執行計劃 - 無表連接 + 無索引】
【新建索引】 CREATE INDEX d_file_month_idx ON d (file_month) ;
【執行計劃 - 無表連接 + 索引檢索】
【成本優化】268758 - 267987 - 5
場景C:索引列嵌套函數,未合理使用索引
表d:amount, file_day(索引)
SELECT dis_info_fee
FROM d
WHERE ROUND(file_day/100)=202002;
【執行計劃 - 索引列嵌套函數】
【優化分析】表d的索引列file_day也在使用中嵌套了函數,數據查詢時無法使用索引,爲避免在索引列使用函數,需以業務邏輯爲基準改寫查詢方式。業務需求爲查詢當月的數據,故修改爲:
SELECT dis_info_fee
FROM d
WHERE file_day >= 202002*100+1 AND file_day < (202002+1)*100
【執行計劃 - 索引列無附加函數】
【成本優化】268170 - 14930
深入理解索引:
1、索引的邏輯結構:
索引的邏輯結構分爲三部分:Root(根塊)、Branch(莖塊)、Leaf(葉子塊)。
Leaf(葉子塊)存儲【 key column value(索引列具體值)+ 數據塊所在rowid】,即索引塊,當一個Leaf(葉子塊)存滿後,則開闢新空間,存儲到新的Leaf(葉子快),這時出現了兩個Leaf(葉子塊)的管理者 ---- Branch(莖塊)。
Branch(莖塊)存儲指向Leaf(葉子塊)的指針,當一個Branch(莖塊)存滿後,則開闢新空間,存儲到新的Branch(莖塊),這時出現了兩個Branch(莖塊)的管理者 ---- Root(根塊);
... ... ... ...
以此類推,索引的邏輯結構可一直往上堆疊,呈現“金字塔”結構。
由於索引塊只包含個別字段的信息,而數據塊包含數據表所有字段的信息,使用索引列查詢可提高查詢效率。
2、索引的三個特點:
a.索引高度較低:
b.索引存儲列值 + ROWID值,且 索引列取值可爲空
c.索引本身有序
以下示例仍以表d爲例, 數據量1.3億。表d:fee,cdr_seq(索引列)
2.1 特點a應用 :索引列 = 某一具體值
SELECT *
FROM d
WHERE cdr_seq = 'XXXXXXX';
【執行計劃】
按“金字塔”結構存儲索引,五百萬記錄,索引高度可能爲 兩 層;
500G數據包含幾百億條記錄,索引高度僅爲 六 層;
通過索引檢索一條數據只需完成(高度 + 1)次I/O操作。
需注意:查詢字段包括除索引列的字段時,會產生回表(TABLE ACCESS (BY INDEX ROWID)),增加 I/O開銷。
2.2 特點b應用 --- 統計函數 COUNT( ) / SUM( ) / AVG( )
SELECT COUNT(*)
FROM d --- 1.3億,58.84s
【執行計劃】
SELECT COUNT(*)
FROM d
WHERE cdr_seq IS NOT NULL --- 1.3億,22.84s
【執行計劃】
【成本優化】1086226 - 166539
本示例說明索引列取值可爲空,若指定索引字段取值非空,COUNT(*) / SUM(*) / AVG(*)函數可以通過索引字段統計信息,提高統計效率。
2.3 特點c應用 --- ORDER BY / DISTINCT
SELECT cdr_seq
FROM d
WHERE cdr_seq >= 1230201 ORDER BY cdr_seq
【執行計劃 - 無索引】
【執行計劃 - 索引檢索】
本示例說明新建索引後,因索引的有序性,可消除ORDER BY語句產生的排序(SORT (ORDER BY))
2.4 特點b/c綜合應用 --- 統計函數 MAX( ) / MIN( )
SELECT MAX(cdr_seq)
FROM d;
【執行計劃】
本示例並未限定索引列非空,在執行計劃中仍然用到索引掃描,再次說明索引列可以取值爲空。另一方面,索引本身是有序的,在讀取最大值時,只需讀取最右邊葉子塊的最後一條記錄,即可獲得最大值。
綜上,總結四種索引掃描類型:
掃描類型 | 說明 | ||
---|---|---|---|
1 | INDEX RANGE SCAN |
局部掃描; 返回記錄越少,效率越少; |
索引列 = 具體取值 |
2 | INDEX FAST FULL SCAN |
全局掃描,一次讀取多個索引塊;本身無序,可能需要額外排序; |
COUNT(),SUM(),AVG() |
3 | INDEX FAST SCAN |
全局掃描,一次讀取一個索引塊;本身有序,可消除排序; |
ORDER BY,DISTINCT |
4 | INDEX FULL SCAN(MIN/MAX) |
局部掃描,效率最高 |
MAX()只需讀取最右邊的葉子快;MN()只需讀取最左邊的葉子快; |
3、使用索引的注意事項:
a. 分區局部索引:若篩選條件只有索引字段,用不到分區字段,性能將會下降;
b. 可通過組合索引避免回表(TABLE ACCESS by INDEX ROWID)造成的性能下降;
c. 聚合因子越低 (表和索引兩者的排列順序相似度越高),排序計算量越低;
d. 分析執行計劃時應關注成本(COST),必要時可用空間(BYTES)消耗換取成本(COST)優化;
e. 不可在索引列嵌套函數;
f. 根據業務場景精簡表結構字段;