SQLServerのIndex整理

## 目的
韓国から[SQL Serverチュニングガイド](https://product.kyobobook.co.kr/detail/S000001914354)と言う本を購入して勉強していますが、SQLServerのIndexについて分かりやすかったので日本語で整理して見ました。
韓国語で書いている本なのでいい内容は日本語で整理するので初心者に参考になればと思います。

## 実行環境
Docker（mcr.microsoft.com/mssql/server:2017-latest）からSQL Server環境を作ってAzure Data Studioで実行しました。

使ったテーブル
```:sql
CREATE TABLE [members](
	[id] [int] IDENTITY(1,1) NOT NULL,
	[name] [nvarchar](4000) NOT NULL,
	[age] [int] NOT NULL,
	[created_at] [datetime2](6) NOT NULL,
	[updated_at] [datetime2](6) NOT NULL
) 

```

## Indexについて
B-Tree構造に保存されます。
以下のような構造になってデータはページ単位で保存されます。

![b_tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/471f3f33-bdbd-1563-3d52-a3e7570b5be1.png)

LeafページにはIndexキーを基準に整列されたデータが保存されます。
Intermediate/Rootページは子ページの最初Indexキーと一緒に該当するページの位置を指すポイントの役割をしています。


## Indexの種類
IndexはClustered IndexとNon-Clustered Indexが存在します。


### Clustered Index
1\.テーブル自体をIndexにする。
2\.キーカラムを基準にして整列された状態を維持する。
3\.テーブルに1個だけ生成可能。

```
CREATE CLUSTERED INDEX [インデックス名]
	ON [テーブル名] ([列名1],[列名2])
GO
```
![スクリーンショット 2022-12-24 8.16.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/0937c2e4-73ca-535d-b7ab-2145e532896c.png)



例）age列をキーにした場合、
ageが3のデータを探す場合、最初Page1のageで3のageがPage2の最初に存在するのがわかるのでLeafのPage2に移動、最初の行から全てのデータ取得可能。



### Non-Clustered Index
1\.Non-Clustered Indexはのテーブルと別に生成される。
2\.キーと一緒にRID（テーブルの行位置アドレス）を保存する。
3\.キーカラムを基準にして整列、最大999個作れる。
```
CREATE NONCLUSTERED INDEX [インデックス名]
	ON [テーブル名] ([列名])
GO
```
![スクリーンショット 2022-12-24 8.17.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/5de4eb2b-8b8d-f2c5-7240-c952d072025e.png)

例）age列をキーにした場合、
ageが3のデータを探す場合、最初Page1のageで3のageがPage2の最初に存在するのがわかるのでLeafのPage2に移動、最初の行からRIDを取得してRIDからHeap Tableを参照して全てのデータ取得。
Non-Clustered Indexがあるテーブル検索の時に実行計画にRID Lookupが表示されるのはHeap Table（整列されてない元テーブル）を参照する意味になります。

## Index Scan方式
データを照会するための使用される方法としてScanとSeekがあります。
Scanはテーブル全体もしくはIndex全体を読む方式、SeekはIndexを通じて条件に該当する特定範囲だけを読む方式です。

### Table Scan
テーブル全体を最初から読みながらスキャンする方式
全てのデータを読んで最終結果を作る。
![スクリーンショット 2022-12-24 8.37.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/60e7caf6-7ec8-427d-a243-d4ed94f91b8d.png)

```:sql
select * from members where age = 3

```
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/b28f685b-0327-dde6-a117-438f53b5ce03.png' width=400/>



### Clustered Index Scan
検索条件にIndexキーを指定してない場合、全ての行を読みながらスキャンする方式

![スクリーンショット 2022-12-24 8.47.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/aec99281-5ff3-0868-0a54-4266160c6bfb.png)

例）ageカラムのIndexが存在する時にnameカラムで検索
Indexが存在しないので全ての行を読む
```:sql
CREATE CLUSTERED INDEX IX_members_age  
    ON [master].[dbo].[members] (age);   
GO  

select * from members where name = '田中'
```
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/bcd4505c-6c5f-e1f6-97b2-1a57ac1477ea.png' width=400/>


### Clustered Index Seek
Clustered Indexキーカラムを検索条件に入れて検索する場合、B-Treeを利用して早く該当するデータを見つけられる。
![スクリーンショット 2022-12-24 9.02.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/edc308f1-5ab8-1cd7-9147-33b5144a9a91.png)

例）ageカラムのIndexが存在する時にageカラムを検索条件に入れて検索

```:sql
CREATE CLUSTERED INDEX IX_members_age  
    ON [members] (age);   
GO  

select * from members where age = 2
```

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/9972151e-da03-791e-c8a0-b774c741734c.png' width=400/>




### Non-Clustered Index Scan
条件のカラムがNon-Clustered Indexに含まれているが、検索条件として使用できない場合発生

![スクリーンショット 2022-12-24 9.10.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/d2f76eb0-a5a7-19c1-024b-bef1573e1ecf.png)


例）ageカラムのIndexが存在する時にageカラムを検索条件に入れて検索

```:sql
CREATE NONCLUSTERED INDEX IX_members_age  
    ON [members] (age);   
GO 

select count(*) from members where age + 1 > 10
```
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/b7f80b97-0211-0126-4030-88ab6f109c89.png' width=600/>

### Non-Clustered Index Seek
要求されたカラムが Non-Clustered Indexに全て含まれた場合発生
![スクリーンショット 2022-12-24 11.13.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/18317116-132a-d1d6-9505-8de9c8370921.png)

```:sql
CREATE NONCLUSTERED INDEX IX_members_age  
    ON [master].[dbo].[members] (age);   
GO  

select name from members where age = 3
```
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/16931014-108c-02e2-5558-3afdfc3e33a8.png'width=500/>

### RID Lookup
Non-Clustered Index検索後参照したいカラムのデータがない場合、Heepテーブルをjoinする必要がある
Non-Clustered Indexがageカラムをキーにした場合、nameデータはIndexに存在しないため、RIDとHeap Tableをjoinしてnameを取得する過程
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/be9cbf68-c65a-61f8-0c68-67904ed96fd2.png' width=600/>

```:sql
CREATE NONCLUSTERED INDEX IX_members_age  
    ON [master].[dbo].[members] (age);   
GO  

select name from members where age = 3
```
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/35352e62-a4f1-501e-c9c5-89241089da12.png'width=300/>


### Key Lookup
Non-ClusteredIndexとClusteredIndexが両方存在するときにNon-ClusteredIndexに必要なカラムが存在しな場合、Clusered Indexから足りないデータを取得する過程
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/e23eec6a-2a3c-1e3e-2bbe-068b04ca09a5.png' width=600/>


```:sql
CREATE CLUSTERED INDEX IX_members_age  
    ON [master].[dbo].[members] (age);   
GO  

CREATE NONCLUSTERED INDEX IX_members_id  
    ON [master].[dbo].[members] (id);   
GO  

select name from members where id = 3

```

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/82d21da6-9265-4884-9f85-44e53f49ea30.png' width=400/>


## まとめ
SQLServerがIndexを使ってどのようにデータを検索してデータを取得するのか理解できたし実行計画で表示されるRID Lookup,Key Lookupについて理解できました！
この記事を読んだ人に少しは役に立つと幸いです。引き続き完読目指します！