日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
如何創(chuàng)建高效的索引,你知道嗎?

本文介紹索引創(chuàng)建時(shí)需遵循一些準(zhǔn)則,以達(dá)到最佳的查詢性能和數(shù)據(jù)維護(hù)效率,這些準(zhǔn)則也是PawSQL索引推薦引擎的內(nèi)部工作邏輯。本文基于最常用的B+樹索引來舉例,其他索引的使用有一定的限制條件,具體請參考??《數(shù)據(jù)庫索引的類型》??。

成都創(chuàng)新互聯(lián)專注于洛寧網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供洛寧營銷型網(wǎng)站建設(shè),洛寧網(wǎng)站制作、洛寧網(wǎng)頁設(shè)計(jì)、洛寧網(wǎng)站官網(wǎng)定制、重慶小程序開發(fā)公司服務(wù),打造洛寧網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供洛寧網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。

準(zhǔn)則1:基于您的工作負(fù)載創(chuàng)建索引

創(chuàng)建高效的索引最重要的原則是,基于您的工作負(fù)載(workload)創(chuàng)建索引,而不是基于您的表結(jié)構(gòu)。針對(duì)數(shù)據(jù)庫執(zhí)行的所有 SQL 語句構(gòu)成了該數(shù)據(jù)庫的工作負(fù)載。索引的目的是為了提升數(shù)據(jù)庫中操作的效率,因此任何其他不以工作負(fù)載出發(fā)的索引創(chuàng)建方法都是錯(cuò)誤的。

在針對(duì)一個(gè)工作負(fù)載構(gòu)建一組索引時(shí),需要考慮工作負(fù)載的以下屬性:

  • SQL類型,在用戶執(zhí)行頻繁插入新數(shù)據(jù)和修改現(xiàn)有數(shù)據(jù)的OLTP場景,多個(gè)索引可能會(huì)對(duì)性能產(chǎn)生負(fù)面影響,并對(duì)系統(tǒng)資源造成壓力,建議創(chuàng)建最少數(shù)量的索引來滿足您的索引要求。而在以查詢?yōu)橹鞯腛LAP場景,您可以添加更多索引,每個(gè)索引具有多個(gè)鍵列,甚至可以添加函數(shù)索引和條件索引。
  • SQL頻率,為最頻繁使用的查詢創(chuàng)建索引,通過為這些查詢創(chuàng)建最好的索引,能夠最大限度的提升系統(tǒng)的整體性能。
  • SQL的重要性,查詢越重要,您可能越想通過創(chuàng)建索引來進(jìn)行性能優(yōu)化。
  • SQL的本身的結(jié)構(gòu),詳見下一章節(jié)。

準(zhǔn)則2:基于單個(gè)SQL的結(jié)構(gòu)創(chuàng)建索引

索引的作用如下:

  • 快速定位數(shù)據(jù)
  • 避免排序
  • 避免回表
  • 實(shí)現(xiàn)行級(jí)鎖(MySQL,另文討論)
  • 實(shí)現(xiàn)唯一性約束

本章節(jié)我們將通過分析SQL的結(jié)構(gòu)來實(shí)現(xiàn)以上的功能,創(chuàng)建高效的索引。

為快速定位創(chuàng)建索引

索引可以通過匹配查詢的條件快速的定位數(shù)據(jù),查詢的條件可能在WHERE子句、HAVING子句、ON子句中。索引和條件的匹配原則遵循最左前綴匹配原則.

最左前綴原則

最左前綴原則指的是,如果查詢的時(shí)候等值的查詢條件精確匹配索引的左邊連續(xù)一列或幾列,則此列就可以被用到,同時(shí)遇到范圍查詢(>、<、between、like)就會(huì)停止匹配,包括范圍條件。

對(duì)于聯(lián)合索引lineitem(l_shipdate,l_quantity),下面的SQL中前兩個(gè)符合最左前綴原則,可以使用該索引。最后一個(gè)不滿足最左前綴原則,無法使用該索引。

select * from lineitem where l_shipdate = date '2021-12-01' and l_quantity = 100; -- 可以使用索引
select * from lineitem where l_shipdate = date '2021-12-01'; -- 可以使用索引
select * from lineitem where l_quantity = 100; -- 不滿足最左前綴原則,無法使用該索引

這三個(gè)SQL對(duì)于的執(zhí)行計(jì)劃如下:

-> Index lookup on lineitem using lidx (L_QUANTITY=100.00, L_SHIPDATE=DATE'2021-12-01')  (cost=0.35 rows=1)
-> Index lookup on lineitem using lidx ( L_SHIPDATE=DATE'2021-12-01')  (cost=0.35 rows=1)
-> Filter: (lineitem.L_QUANTITY = 100.00)  (cost=15208.05 rows=49486)
-> Table scan on lineitem (cost=15208.05 rows=148473)

由于最左前綴原則,在創(chuàng)建聯(lián)合索引時(shí),索引字段的順序需要考慮字段值去重之后的個(gè)數(shù)(Cardinality),Cardinality較大的放前面。

等值條件(Index lookup)

  • 單表等值條件
  • COL = 'A'
  • COL IN ('A')
  • 關(guān)聯(lián)等值條件, 在一個(gè)表作為被驅(qū)動(dòng)表時(shí)的等值關(guān)聯(lián)條件也可以被認(rèn)為時(shí)等值條件被索引匹配使用。
  • T1.COL = T2.COL
  • select * from orders, lineitem where o_orderkey = l_orderkey;
    -> Nested loop inner join  (cost=484815.77 rows=1326500)
    -> Table scan on orders (cost=20540.71 rows=200128)
    -> Index lookup on lineitem using lineitem_idx(L_ORDERKEY=orders.O_ORDERKEY) (cost=1.66 rows=7)

范圍條件(Index range scan)

  • 范圍操作符(>,>=,<,<=,BETWEEN)
  • IN ('A','B')
  • IS NOT NULL
  • IS NULL
  • LIKE 'ABC%'
  • COL = 'A' OR COL = 'B'

范圍條件也可以用來快速定位數(shù)據(jù)。

create index lshipdate_idx on lineitem(l_shipdate);
explain format = tree select * from lineitem where l_shipdate >= date '2021-12-01';
-> Index range scan on lineitem using lshipdate_idx over ('2021-12-01' <= L_SHIPDATE), with index condition: (lineitem.L_SHIPDATE >= DATE'2021-12-01')  (cost=11855.06 rows=26344)

由于最左匹配原則,位于范圍條件后面的索引列無法利用該索引。

為避免排序創(chuàng)建索引

對(duì)于B+樹索引,由于其是按照索引鍵排序的,因此可以通過索引來避免在SQL執(zhí)行中進(jìn)行排序。涉及的SQL結(jié)構(gòu)主要包括:

  • GROUP BY
  • ORDER BY
  • DISTINCT
  • PARTITION BY... ORDER BY...
create index lshipdate_idx on lineitem(l_shipdate);

可以看到下面的SQL的執(zhí)行計(jì)劃通過訪問lshipdate_idx索引避免了排序。

  • SQL1 (ORDER BY)
select * from lineitem order by l_shipdate limit 10;
  • SQL1執(zhí)行計(jì)劃
-> Limit: 10 row(s)  (cost=0.02 rows=10)
-> Index scan on lineitem using lshipdate_idx (cost=0.02 rows=10)
  • SQL2(GROUP BY)
select l_shipdate, sum(l_quantity) as sum_qty from lineitem group by l_shipdate;
  • SQL2執(zhí)行計(jì)劃
-> Group aggregate: sum(lineitem.L_QUANTITY)  (cost=30055.35 rows=148473)
-> Index scan on lineitem using lshipdate_idx (cost=15208.05 rows=148473)
  • SQL3(DISTINCT)
select DISTINCT l_shipdate from lineitem;
  • SQL3執(zhí)行計(jì)劃
-> Covering index skip scan for deduplication on lineitem using lshipdate_idx  (cost=4954.90 rows=15973)
  • SQL4(PARTITION BY... ORDER BY...)
select rank() over (partition by L_SHIPDATE order by L_ORDERKEY)  from lineitem;
  • SQL4執(zhí)行計(jì)劃
WindowAgg  (cost=0.29..545.28 rows=10000 width=28)
-> Index Only Scan using lshipdate_idx on lineitem (cost=0.29..370.28 rows=10000 width=20)

需要注意

  1. 對(duì)于分組和去重,順序不一致是沒有關(guān)系的。
  2. 對(duì)于排序,排序字段的順序需要和索引字段的順序一致,否則沒有辦法利用索引來避免排序。
  3. 對(duì)于同時(shí)有分組和排序的,需要把排序的索引列放在前面。

譬如對(duì)于下面的SQL。

select l_shipdate, l_orderkey,  sum(l_quantity) as sum_qty from lineitem group by l_shipdate,l_orderkey order by l_orderkey;
  • 情形1,建索引(l_shipdate, l_orderkey),索引訪問,需排序,代價(jià)為486.526。
-> Sort: lineitem.L_ORDERKEY  (actual time=479.465..486.526 rows=149413 loops=1)
-> Stream results (cost=30055.35 rows=148473) (actual time=0.175..423.447 rows=149413 loops=1)
-> Group aggregate: sum(lineitem.L_QUANTITY) (cost=30055.35 rows=148473) (actual time=0.170..394.978 rows=149413 loops=1)
-> Index scan on lineitem using lshipdate_idx2 (cost=15208.05 rows=148473) (actual time=0.145..359.567 rows=149814 loops=1)
  • 情形2,建索引(l_orderkey,l_shipdate),索引訪問,避免排序,代價(jià)228.401,性能提升120%。
-> Group aggregate: sum(lineitem.L_QUANTITY)  (cost=30055.35 rows=148473) (actual time=0.067..228.401 rows=149413 loops=1)
-> Index scan on lineitem using lshipdate_idx3 (cost=15208.05 rows=148473) (actual time=0.052..194.479 rows=149814 loops=1)

為避免回表創(chuàng)建索引(Covering index scan)

當(dāng)查詢中的列都在索引列中時(shí),數(shù)據(jù)庫只需要訪問索引即可獲取所需的數(shù)據(jù),避免了回表操作。在某些場景下,可以大幅的提升查詢效率。

對(duì)于如下的SQL語句。

select l_shipdate, l_orderkey,  sum(l_quantity) as sum_qty from lineitem group by l_orderkey,l_shipdate;
  • 索引(l_orderkey,l_shipdate)中沒有包含??l_quantity??,需要回表,執(zhí)行計(jì)劃如下,代價(jià)194.875。
-> Group aggregate: sum(lineitem.L_QUANTITY)  (cost=30055.35 rows=148473) (actual time=0.044..194.875 rows=149413 loops=1)
-> Index scan on lineitem using lshipdate_idx3 (cost=15208.05 rows=148473) (actual time=0.034..159.863 rows=149814 loops=1)
  • 索引(l_orderkey,l_shipdate,l_quantity )中包含l_quantity,不需要回表,執(zhí)行計(jì)劃如下,代價(jià)113.433,性能提升約71.8%。
-> Group aggregate: sum(lineitem.L_QUANTITY)  (cost=30055.35 rows=148473) (actual time=0.035..113.433 rows=149413 loops=1)
-> Covering index scan on lineitem using lshipdate_idx4 (cost=15208.05 rows=148473) (actual time=0.026..82.266 rows=149814 loops=1)

其他相關(guān)主題

分區(qū)表的索引

對(duì)于分區(qū)表,不同的數(shù)據(jù)庫對(duì)分區(qū)表索引的支持不一樣,總體來說,分區(qū)表可以建立以下三種類型的索引。

  • 本地分區(qū)索引(PostgreSQL/MySQL/Oracle/Opengauss)。
  • 全局分區(qū)索引(Oracle)。
  • 全局非分區(qū)索引(Oracle/Opengauss)。
本地分區(qū)索引

在索引維護(hù)性方面,本地索引比全局索引容易管理,當(dāng)你在進(jìn)行添加、刪除、truncate表分區(qū)時(shí),本地索引會(huì)自動(dòng)維護(hù)其索引分區(qū)。MySQL和PostgreSQL只支持本地分區(qū)索引; Oracle和Opengauss創(chuàng)建本地分區(qū)索引時(shí)需指定關(guān)鍵字local。

create index lshipdate_idx on lineitem(l_shipdate) local;
全局分區(qū)索引

和表分區(qū)類似,索引的分區(qū)鍵和表的分區(qū)鍵沒有必然的關(guān)系,甚至非分區(qū)表也可以建立全局分區(qū)索引。Oracle支持全局分區(qū)索引。

全局非分區(qū)索引

對(duì)于全局非分區(qū)索引,當(dāng)你對(duì)表分區(qū)進(jìn)行操作時(shí),索引可能變得不可用,需顯式的更新或重建索引。在索引效率方面,全局索引在不包含分區(qū)字段的查詢中,效率比本地分區(qū)索引更高效。Oracle和Opengauss為分區(qū)表默認(rèn)創(chuàng)建的是全局非分區(qū)索引。

create index lshipdate_idx on lineitem(l_shipdate) global;
create index lshipdate_idx on lineitem(l_shipdate);

在進(jìn)行分區(qū)操作時(shí),需要增加update global index關(guān)鍵字重建索引,否則索引不可用。

alter table t DROP PARTITION partition_name update global index;

函數(shù)索引

函數(shù)索引(或表達(dá)式索引)即基于函數(shù)或表達(dá)式的索引,它使用函數(shù)或是表達(dá)式提供計(jì)算好的值作為索引列構(gòu)建索引,可以在不修改應(yīng)用程序的情況下提高查詢性能。

函數(shù)索引的使用需要函數(shù)或是表達(dá)式和SQL查詢中的表達(dá)式嚴(yán)格匹配,所以它使用的條件較為嚴(yán)格,適合針對(duì)重要查詢或是頻次較高的查詢重點(diǎn)優(yōu)化。

select * from lineitem where EXTRACT(DAY from l_shipdate) = 1;
  • 建在l_shipdate在執(zhí)行計(jì)劃中沒有被使用。
Seq Scan on lineitem  (cost=0.00..1870.24 rows=238 width=158) (actual time=0.502..10.655 rows=1616 loops=1)
Filter: (EXTRACT(day FROM l_commitdate) = '1'::numeric)
Rows Removed by Filter: 46000
Planning Time: 0.107 ms
Execution Time: 10.709 ms
  • 通過創(chuàng)建函數(shù)索引,執(zhí)行計(jì)劃中的代價(jià)降低為原來的1/10。
create index idx on lineitem(EXTRACT(DAY from l_shipdate));
Bitmap Heap Scan on lineitem  (cost=6.13..593.60 rows=238 width=158) (actual time=0.216..0.981 rows=1620 loops=1)
Recheck Cond: (EXTRACT(day FROM l_shipdate) = '1'::numeric)
Heap Blocks: exact=889
-> Bitmap Index Scan on idx (cost=0.00..6.08 rows=238 width=0) (actual time=0.149..0.149 rows=1620 loops=1)
Index Cond: (EXTRACT(day FROM l_shipdate) = '1'::numeric)
Planning Time: 0.102 ms
Execution Time: 1.075 ms

條件索引

條件索引又叫部分索引(Partial index),它是建立在一個(gè)表的子集上的索引,而該子集是由一個(gè)條件表達(dá)式定義的,該索引只包含表中那些滿足這個(gè)條件表達(dá)式的行。

條件索引被使用的條件比較嚴(yán)格,只有在數(shù)據(jù)庫能夠識(shí)別出該查詢的WHERE條件在邏輯上涵蓋了該索引的條件表達(dá)式定義時(shí),這個(gè)部分索引才能被用于該查詢。

以下的條件索引為例,其索引的條件表達(dá)式為l_shipdate > '2022-01-01'。

create index l_partkey_idx on lineitem(l_partkey) where l_shipdate > '2022-01-01';

由于下面的查詢語句的條件l_shipdate = date '2021-12-01'沒有落到此索引條件表達(dá)式的范圍內(nèi),該索引將不會(huì)被用到,所以執(zhí)行計(jì)劃采用的是全表掃描。

select l_partkey , count(1) from lineitem where l_shipdate = date '2021-12-01' and l_partkey < 100 group by l_partkey ;
GroupAggregate (cost=1870.25..1870.27 rows=1 width=12)
Group Key: l_partkey
-> Sort (cost=1870.25..1870.26 rows=1 width=4)
Sort Key: l_partkey
-> Seq Scan on lineitem (cost=0.00..1870.24 rows=1 width=4)
Filter: ((l_partkey < 100) AND (l_shipdate = '2021-12-01'::date))

而下面的查詢語句的條件l_shipdate = date '2022-12-01'在條件表達(dá)式的范圍內(nèi),數(shù)據(jù)庫優(yōu)化器將會(huì)采用此索引,可以看到性能有大幅提升。

select l_partkey , count(1) from lineitem where l_shipdate = date '2022-12-01' and l_partkey < 100 group by l_partkey ;
GroupAggregate  (cost=402.37..402.39 rows=1 width=12)
Group Key: l_partkey
-> Sort (cost=402.37..402.38 rows=1 width=4)
Sort Key: l_partkey
-> Index Scan using lorderkey_idx on lineitem (cost=0.28..402.36 rows=1 width=4)
Filter: ((l_partkey < 100) AND (l_shipdate = '2022-12-01'::date))

特別提示:MySQL目前還不支持條件索引,而PostgreSQL、Opengauss、Oracle都支持。

索引融合

索引融合(Index Merge)是使用多個(gè)索引來完成一次單表數(shù)據(jù)訪問的優(yōu)化技術(shù)。當(dāng)查詢中涉及一個(gè)表的多個(gè)條件時(shí),如果這些條件分別有合適的索引,索引融合可以在回表之前將多個(gè)索引的結(jié)果合并,以提高查詢性能。

在lineitem表上有在l_shipdate以及l(fā)_partkey的單列索引,對(duì)于以下的SQL。

select * from lineitem where l_shipdate = date '2010-12-01' or l_partkey=100;

PostgreSQL的執(zhí)行計(jì)劃。

Bitmap Heap Scan on lineitem  (cost=9.05..202.96 rows=59 width=158)
Recheck Cond: ((l_shipdate = '2010-12-01'::date) OR (l_partkey = 100))
-> BitmapOr (cost=9.05..9.05 rows=59 width=0)
-> Bitmap Index Scan on l_shipdate_idx (cost=0.00..4.70 rows=54 width=0)
Index Cond: (l_shipdate = '2010-12-01'::date)
-> Bitmap Index Scan on l_partkey_idx (cost=0.00..4.33 rows=5 width=0)
Index Cond: (l_partkey = 100)

MySQL的執(zhí)行計(jì)劃。

-> Filter: ((lineitem.L_SHIPDATE = DATE'2010-12-01') or (lineitem.L_PARTKEY = 100))  (cost=12.53 rows=21)
-> Deduplicate rows sorted by row ID (cost=12.53 rows=21)
-> Index range scan on lineitem using l_shipdate_idx over (L_SHIPDATE = '2010-12-01') (cost=1.11 rows=1)
-> Index range scan on lineitem using l_partkey_idx over (L_PARTKEY = 100) (cost=3.03 rows=20)

可以看出,MySQL及PostgreSQL都支持索引融合優(yōu)化優(yōu)化,索引融合的算法另文討論。

外鍵索引

應(yīng)該在外鍵上鍵上創(chuàng)建索引,這個(gè)原則似乎和第一個(gè)原則(基于您的工作負(fù)載創(chuàng)建索引)相矛盾,然而事實(shí)上卻是一致的,因?yàn)樵谡鎸?shí)應(yīng)用中,表之間的關(guān)聯(lián)絕大多數(shù)都是基于主外鍵來進(jìn)行的。通過在外鍵上建立索引,可以提升表關(guān)聯(lián)的效率,特別是在支持索引融合的數(shù)據(jù)庫上。

在MySQL中,如果某個(gè)字段被定義為外鍵,默認(rèn)會(huì)有對(duì)應(yīng)的索引建立在上面;而在PostgreSQL系列的數(shù)據(jù)庫中,將某些字段設(shè)置外鍵并不會(huì)自動(dòng)在這些字段上建上索引。

準(zhǔn)則3:創(chuàng)建索引時(shí)的約束條件

創(chuàng)建索引時(shí),雖然它們可以提高讀取性能,但是索引也不是免費(fèi)的午餐,創(chuàng)建索引也具有一定的代價(jià),譬如索引會(huì)對(duì)寫入性能產(chǎn)生負(fù)面影響,因?yàn)閷?duì)于數(shù)據(jù)庫管理器寫入表的每一行,它還必須更新任何受影響的索引?;诖?,我們通常會(huì)限制每個(gè)表創(chuàng)建的最大索引數(shù)量。而且索引還會(huì)占用一定的磁盤空間,在磁盤空間比較緊張的系統(tǒng)上,索引的大小和數(shù)量也需要進(jìn)行控制。這部分內(nèi)容我們稱之為約束條件。我們的目標(biāo)就是在給定的約束條件下,創(chuàng)建合適的索引,以最大限度的提升系統(tǒng)的整體性能。

  • 單表索引數(shù)目
  • 索引字段數(shù)目
  • 索引磁盤空間

針對(duì)以上的約束條件,我們通常通過以下的方法創(chuàng)建和維護(hù)索引:

  • 索引列取舍:通過對(duì)列的單值選擇率的評(píng)估,在過濾效果最好的列上建立索引; 通過對(duì)工作負(fù)載的分析,避免在頻繁更新的列上建立索引。
  • 索引取舍:通過對(duì)工作負(fù)載的分析,在最重要的SQL或是使用頻率最高的查詢上提供索引。
  • 索引合并:索引滿足組最左前綴匹配原則,所以可以通過設(shè)計(jì)索引列的排列順序,達(dá)到一個(gè)索引加速多個(gè)SQL的查詢。
  • 索引刪除:通過命令或工具定期采集索引的使用情況,將不再使用的索引進(jìn)行刪除。

總結(jié)

本周來講,索引創(chuàng)建的過程可以抽象化為基于以上的約束條件,定義索引的收益,使用啟發(fā)式算法,計(jì)算在滿足特定約束條件下,整個(gè)工作負(fù)載收益最大的索引集合,這也是PawSQL索引推薦引擎的內(nèi)部邏輯。


網(wǎng)站欄目:如何創(chuàng)建高效的索引,你知道嗎?
網(wǎng)站路徑:http://www.dlmjj.cn/article/dphhcch.html