1 文檔說明
本文檔為個人學習SQL索引相關,同步形成學習文檔,主要是將重點放在聚集索引與非聚集索引方面,文檔中包含測試索引帶來的查詢速度提升測試,由於服務器性能問題,僅供參考。
文檔內容寫的比較亂,主要是自己隨便寫的,其中難免有不規范甚至吐槽,且個人水平原因,難免有錯漏。后續可能會對文檔進行不定期補充。
該文檔暫未完成,有部分內容缺失,等待后續補充。
2 索引介紹
索引,使用索引可快速訪問數據庫表中的特定信息。索引是對數據庫表中一列或多列的值進行排序的一種結構。
在關系數據庫中,索引是一種與表有關的數據庫結構,它可以使對應於表的SQL語句執行得更快。索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。當表中有大量記錄時,若要對表進行查詢,第一種搜索信息方式是全表搜索,是將所有記錄一一取出,和查詢條件進行一一對比,然后返回滿足條件的記錄,這樣做會消耗大量數據庫系統時間,並造成大量磁盤I/O操作;第二種就是在表中建立索引,然后在索引中找到符合查詢條件的索引值,最后通過保存在索引中的ROWID(相當於頁碼)快速找到表中對應的記錄。
索引是一個單獨的、物理的數據庫結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。
索引提供指向存儲在表的指定的列中的數據值的指針,然后根據您指定的排序順序對這些指針排序。數據庫使用索引的方式與您使用書籍中的索引的方式很相似:它搜索索引以找到特定值,然后順指針找到包含該值的行。
在數據庫關系圖中,可以在選定表的“索引/鍵”屬性頁中創建、編輯或刪除每個索引類型。當保存索引所附加到的表,或保存該表所在的關系圖時,索引將保存在數據庫中。
個人理解:索引就是目錄,就像倉庫庫存表,就好比字典,使用索引可以快速的查詢表中信息,因為沒有索引直接查詢相當於根據查詢條件,將表中全部數據一一取出與條件比對,直到遍歷全表后結束並返回結果,相當於表里面數據全翻一遍;而使用索引則不同,通過建立索引,查詢先在索引中找符合條件的索引值,然后根據索引值快速找到表中的記錄。舉例,查字典,如果沒有目錄(索引),查“張”字,需要從第一頁開始,逐條找,從“啊”一直找到“張”,而且還要一直找到字典最后一頁,才算完成;使用目錄(索引),先找到“張”所在頁數(索引值),然后根據頁數(索引值),快速翻到對應頁,找到對應記錄,這樣對比,查詢速度提升簡直了。
3 索引的利弊
最簡單來講,使用索引可以顯著提升查詢速度,但是使用索引會:1、增加數據庫的存儲空間,2、在插入和修改數據時要花費較多的時間,因為索引也要隨之變動(為何不是增刪改?等待使用18750000 兩表進行測試)
優點:
1、大大加快數據的檢索速度;(已經測試,通過18750000 兩表進行相同條件查詢,使用耗時大大縮短,某查詢語句耗時僅為不用索引耗時的0.35%)
2、通過創建唯一性索引,保證數據庫表中每一行數據的唯一性(等待測試,准備創建測試表,創建unique索引,嘗試插入相同數據);
3、加速表和表之間的連接(個人理解:select c1,c2,c3 from t1,t2,t3 where c1=c2 and c1=c3 假設t1 t2 t3表各1萬行,則無索引直接查詢可能的組合有為1萬*1萬*1萬,即從t1表的第一個值開始逐條取出t2表的值進行1萬行遍歷,然后再從t1表的第一個值開始逐條取出t3表的值進行1萬行遍歷,以上遍歷完成后,從t1表取第二個值再進行一次上述操作,直至t1表全部數據取完,檢索數據條數很多,影響速度。使用索引,直接依索引值快速查找,加快速度);
4、在使用分組和排序子句進行數據檢索時,可以顯著減少查詢中分組和排序的時間(同上,待測試;已經測試order by,速度同樣有明顯提升);
4 適用范圍
什么時候該使用索引呢?
索引很好用,但是往往很好用的東西都不能隨便濫用,索引也有其適用場合
什么時候該用聚集索引,什么時候該用非聚集索引?
忘記從哪里看到的,聚集索引不應該浪費在自增長id列上,比如一張表,有id、dealtype、dealdetail、dealtime等列,考慮到應用程序打開后首先默認根據dealtime列讀取最近3個月記錄,若表中數據量少則影響不嚴重,但若表中含有大量數據,則如果id列設置了聚集索引,每次打開應用程序不使用索引仍要花費較長時間,若將聚集索引設置在datetime時間列上,應用程序直接利用索引,快速顯示程序界面,帶給用戶良好的操作體驗!要不然用戶打開應用程序就得等半天空白。
5 索引分類
5.1 普通索引
這是最基本的索引類型,而且它沒有唯一性之類的限制。普通索引可以通過以下幾種方式創建:
創建索引,例如 CREATE INDEX <索引的名字> ON table_name (列的列表)
create index index_id on table_test (id)
修改表,例如ALTER TABLE table_name ADD INDEX [索引的名字] (列的列表)
alter table table_test add index index_id (id)
創建表的時候指定索引,例如CREATE TABLE table_name ([…] ,INDEX [索引的名字] (列的列表))
create table table_test(id int,name char(10),address char(10) ,index index_id)
5.2 唯一索引
唯一索引是不允許其中任何兩行具有相同索引值的索引。
當現有數據中存在重復的鍵值時,大多數數據庫不允許將新創建的而唯一索引與表一起保存。數據庫還可能防止添加將在表中創建重復鍵值的新數據。例如,如果在employee表中志願的姓(ename)上創建了唯一索引,則任何兩個員工都不能同姓。
創建唯一索引的幾種方式:
創建索引,例如CREATE UNIQUE INDEX <索引的名字> ON table_name(列的列表)
create unique index index_id on table_test(id)
修改表,例如ALTER TABLE table_name ADD UNIQUE INDEX[索引的名字] (列的列表)
alter table table_test add unique index index_id (id)
創建表的時候指定索引,例如CREATE TABLE table_name([…],UNIQUE [索引的名字] (列的列表))
5.3 主鍵索引
主鍵索引要求主鍵中的每一個值是唯一的(創建主鍵自動創建主鍵索引)
可以設置創建主鍵時設置為非聚集索引
create table table_1(
id int,
name varchar(20),
address varchar(20),
gender varchar(10)
constraint pk_id primary key nonclustered(id)
)
也在創建表后手動刪除原有主鍵,然后手動創建非聚集索引
5.4 聚集索引
聚集索引(CLUSTERED):表中各行的物理順序與鍵值的邏輯(索引)順序相同,表中只能包含一個聚集索引,主鍵列默認為聚集索引
5.5 非聚集索引
非聚集索引(NONCLUSTERED):表中各行的物理順序與鍵值的邏輯(索引)順序不匹配,表中可以有249個非聚集索引
6 創建(刪除)索引
現有表table_test
create table table_test(
id int,
name char(10),
address char(20)
)
普通索引
create index index_name on table_name(id)
主鍵索引
主鍵索引在創建表設置主鍵時自動設置
create table table_name(
id int primary key,
name varchar(20)
)
或者
create table table_11(
id int,
name varchar(10)
constraint pk_id primary key(id)
)
alter table table_name add constraint pk_name primary key (column_name)
聚集索引
create clustered index clustered_index_id on table_test(id)
非聚集索引
create nonclustered index noclustered_index_id on table_test(id)
刪除索引
drop index index_name on table_name
alter table table_name drop index index_name
alter table table_name drop constraint pk_id
這條語句僅限於當索引是由於創建表時由於設置主鍵而默認生成的主鍵索引
7 關於聚集索引與非聚集索引
7.1 聚集索引
聚集索引包含稠密索引與稀疏索引
其中對比如圖(網絡摘抄)
聚集索引指的是,一個文件中可以有多個索引,分別基於不同的搜索碼。(搜索碼:用在文件中查找記錄的屬性或屬性集)如果包含記錄的文件按照某個特定的順序排序,那么該搜索碼對應的索引就是聚集索引。
聚集索引確定表中數據的物理順序。由於聚集索引規定數據在表中的物理存儲順序,而一個表只能有一種物理順序,所以一張表只能包含一個聚集索引。
聚集索引的缺點是對表進行修改速度較慢,這是為了保持表中的記錄的物理順序與索引的順序一致,而把記錄插入到數據頁的相應位置,必須在數據頁中進行數據重排,降低了執行速度。
個人理解:
聚集索引就是倉庫里物品明細貨架單,何貨物放在何貨架的何位置都有一一對應且唯一的順序,比如安全帽一定是放在A貨架的**位置,而鑽頭一定是放在Z貨架的**位置,且貨架A一定是在倉庫的靠門區域,,而貨架Z一定是在倉庫的靠里位置(舉例)
聚集索引示意圖(網絡摘抄)
聚集索引直接指向數據位置
7.2 非聚集索引
該索引中索引的邏輯順序與磁盤上行的物理存儲順序不同。
示意圖
索引是通過二叉樹的數據結構來描述的,我們可以這么理解聚簇索引:索引的葉節點就是數據節點。而非聚簇索引的葉節點仍然是索引節點,只不過有一個指針指向對應的數據塊。
個人理解:
非聚集索引首先指向了另一個索引,這個索引指向指針,而該指針存放無邏輯順序無物理排序(相對聚集索引而言),通過該指針再去指向實際數據,也就是說,當表中數據發生變化時,非聚集索引只需要更新指針內容即可,不需要涉及到物理邏輯,也就是說,操作數據時不需要考慮數據在數據頁的響應位置,不需要進行數據重排,數據增刪改速度會增加(相對聚集索引)?
8 索引帶來的查詢速度測試對比
使用兩張表,分別批量插入18750000行數據。
兩張表均無任何索引時,使用相同的查詢條件進行查詢耗時基本接近
select * from table_test where name=’Jarry’
select * from table_test where name=’Jarry’
查詢耗時(ms) |
第一次 |
第二次 |
第三次 |
第四次 |
第五次 |
平均 |
table_test |
9153 |
9166 |
9150 |
9046 |
9090 |
9121 |
table_test1 |
9206 |
9140 |
9046 |
9193 |
9163 |
9149.6 |
1、對比聚集索引與無索引
table_test1表的name列創建聚集索引,test表不創建任何索引,執行相同的查詢語句,對比查詢耗時
select * from table_test where name=’Jarry’
select * from table_test where name=’Jarry’
查詢耗時:
查詢耗時(ms) |
第一次 |
第二次 |
第三次 |
第四次 |
第五次 |
平均 |
耗時對比 |
table_test |
9153 |
9146 |
9106 |
9203 |
9160 |
9153.6 |
0.352% |
table_test1 |
96 |
16 |
6 |
23 |
20 |
32.2 |
由此可以看出通過在name列設置索引,查詢速度有了從紙飛機到F35的提升,可以想到,若是用戶實際使用,等待9秒和等待0.096秒是截然不同的,一個是卡死了,一個是Duang~的一下立馬出現,嗯這個可以有。
2、對比非聚集索引與無索引
在table_test1表 address列創建非聚集索引,test表同樣不創建任何索引,使用address查詢條件進行查詢,比較耗時
select * from table_test where address='Jarry18750000的家'
select * from table_test1 where address='Jarry18750000的家'
比較耗時:
查詢耗時(ms) |
第一次 |
第二次 |
第三次 |
第四次 |
第五次 |
平均 |
效率對比 |
table_test |
16026 |
9013 |
9246 |
9400 |
9246 |
10586.2 |
0.463% |
table_test1 |
30 |
26 |
26 |
33 |
130 |
49 |
可以看出非聚集索引同樣帶來巨大的速度提升。奇怪的是第一次查詢時,test表耗時較長,可能與服務器后台其他運算影響性能有關。
3、對比聚集索引與非聚集索引
在test表name列創建非聚集索引,而test1表name列已經創建聚集索引。
select * from table_test where name=’Jarry’
select * from table_test where name=’Jarry’
比較耗時:
查詢耗時(ms) |
第一次 |
第二次 |
第三次 |
第四次 |
第五次 |
平均 |
效率對比 |
table_test |
46 |
3 |
0 |
0 |
0 |
0 |
0 |
table_test1 |
63 |
23 |
0 |
0 |
0 |
0 |
奇怪的是只有第一次查詢的時候有較明顯的耗時,第二次及以后查詢耗時均為0,原因暫未知(后來把精度調整為納秒,瘋了,仍是0)。
4、對比索引在order by上耗時
相同的查詢語句,只是order by 排序依據不同,對比耗時
使用test1表,聚集索引name列與無索引id列
declare @timee datetime
set @timee=GETDATE()
select top(2000) * from table_test1 order by name
select [Time for select (ms)] =DATEDIFF(ms,@timee,GETDATE())
go
declare @timee1 datetime
set @timee1=GETDATE()
select top(2000) * from table_test1 order by id
select [Time for select (ms)]=DATEDIFF(ms,@timee1,getdate())
比較耗時,可以看到速度提升80倍
查詢耗時(ms) order by |
第一次 |
第二次 |
第三次 |
第四次 |
第五次 |
平均 |
對比 |
name |
183 |
216 |
196 |
190 |
193 |
195.6 |
80 |
id |
15523 |
15950 |
15676 |
15586 |
15520 |
15651 |
使用test表,非聚集索引name與無索引id對比
declare @timee datetime
set @timee=GETDATE()
select top(2000) * from table_test order by name
select [Time for select (ms)] =DATEDIFF(ms,@timee,GETDATE())
go
declare @timee1 datetime
set @timee1=GETDATE()
select top(2000) * from table_test order by id
select [Time for select (ms)]=DATEDIFF(ms,@timee1,getdate())
比較耗時,可以看到速度提升76.5倍
查詢耗時(ms) order by |
第一次 |
第二次 |
第三次 |
第四次 |
第五次 |
平均 |
對比 |
name |
260 |
183 |
173 |
213 |
186 |
203 |
76.52 |
id |
15916 |
15396 |
15620 |
15483 |
15256 |
15534.2 |
附:
1、顯示查詢耗時語句
declare @timee datetime
set @timee=getdate()
select * from table_test where name=’Jarry’
select[Time for select(ms)]=DATEDIFF(MS,@timee,GETDATE())
2、批量向表中插入大量數據語句(奇偶不同)
if exists(select 1 from sysobjects where xtype='u' and name='table_test' )
drop table table_test
create table table_test(
id int,
name char(50),
address char(50),
gender char(10)
)
declare @n int
declare @name varchar(50)--使用變量
declare @name1 varchar(50)--設置奇數用
declare @name2 varchar(50)--設置偶數用
declare @addre varchar(50)--使用變量
declare @sql varchar(200)
set @n=0
set @addre='的家'
set @name=' '
set @name1='Tom'
set @name2='Jarry'
while @n<20
begin
set @n=@n+1
if @n%2=1
begin
set @name=@name1+convert(varchar,@n)
set @addre=@name+@addre
set @sql='insert into table_test (id,name,address,gender) values('+convert(int,@n)+','+@name+','+@addre+',''male'')'
set @addre='的家'
print(@sql)
exec(@sql)
end
else
begin
set @name=@name2+convert(varchar,@n)
set @addre=@name+@addre
set @sql='insert into table_test (id,name,address,gender) values('+convert(int,@n)+','+@name+','+@addre+',''female'')'
set @addre='的家'
print(@sql)
exec(@sql)
end
end