Indkesy

Indeksy

Cezary Walenciuk

Indeksy

@walenciukC

Speaker

Agenda

Tabelki do dema
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Indeksy podstawy

Typy indeksów

Clustered

Clustered

Clustered

Clustered

blog
Copyright © Cezary Walenciuk

                    

                        SELECT 
                             [Id]
                            ,[Class]
                            ,[PowerLevel]
                            ,[NickName]
                            ,[Adress]
                            ,[CustomCodeAbilityId]
                        FROM [HeroDataBase].[dbo].[SuperHeroes]
                    
                

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk

                    

                        SELECT [Id]
                            ,[Class]
                            ,[PowerLevel]
                            ,[NickName]
                            ,[Adress]
                            ,[CustomCodeAbilityId]
                        FROM [HeroDataBase].[dbo].[SuperHeroes_NoIndex]
                        ORDER BY ID DESC
                    
                

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Najpierw jednak coś o przechowywowaniu danych

SQL Server przetrzymuje dane 8kb (8192b) kawałkach, które nazywamy "page"

SQL Server przetrzymuje dane 8kb (8192b) kawałkach, które nazywa "page"

blog
Copyright © Cezary Walenciuk

Architektura indeksu

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk

Operacje indeksowe w Execution Plans

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk

                    

                        SELECT [MonsterAttackId]
                        ,[AttackType]
                        ,[DamageToTheCityInNumbers]
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [AttackType] = 'A' AND 
                        [DamageToTheCityInNumbers] >= 24928

                        -- SCAN or SEEK
                    
                

blog
Copyright © Cezary Walenciuk

                    

                        SELECT 
                        SUM([DamageToTheCityInNumbers])
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        Group By [AttackType]

                        -- SCAN
                    
                

Idealny Index na jakiej kolumnie

Idealny index

Spełnienie tych wszystkich zasad może być trudne

Niezmieniający

Wąski

Rosnący ciągle tak samo

blog
Copyright © Cezary Walenciuk
Zróbmy test

                    

                        CREATE CLUSTERED INDEX idx_MonsterAttacks_ClusteredALL
                        on dbo.MonsterAttacks_ClusteredALL 
                        ([MonsterAttackId],[MonsterId],[CityId],
                        [AttackDate],[AttackType],[DamageToTheCityInNumbers])

                    
                

                    

                        CREATE CLUSTERED INDEX 
                        idx_MonsterAttacks_ClusteredDamagToTheCity
                        on dbo.MonsterAttacks_ClusteredDamagToTheCity 
                        (DamageToTheCityInNumbers)

                    
                

                    

                        CREATE CLUSTERED INDEX 
                        idx_MonsterAttacks_ClusteredAttackDate
                        on dbo.MonsterAttacks_ClusteredAttackDate 
                        (AttackDate)

                    
                

blog
Copyright © Cezary Walenciuk

                    

                        CREATE OR ALTER  PROCEDURE [dbo].[InsertMonsterAttacks_ClusteredAttackDate]
                        AS
                        
                        INSERT INTO [MonsterAttacks_ClusteredAttackDate]
                                   ([MonsterId]
                                   ,[CityId]
                                   ,[AttackDate]
                                   ,[AttackType]
                                   ,[DamageToTheCityInNumbers])
                             VALUES
                                   (1
                                   ,1
                                   ,GETDATE()
                                   ,'T'
                                   ,RAND(CHECKSUM(NEWID()))*5000)
                        
                    
                

                    

                        CREATE OR ALTER  PROCEDURE [dbo].[UpdateRowMonsterAttacks_ClusteredDamagToTheCity]
                        AS
                        
                        DECLARE @i INT = FLOOR(RAND(CHECKSUM(NEWID())) * 18008)
                        
                        UPDATE [dbo].InsertMonsterAttacks_ClusteredDamagToTheCity
                            SET [DamageToTheCityInNumbers] = RAND(CHECKSUM(NEWID()))*500
                            WHERE MonsterAttackId = @i;
                        
                    
                

                    
                        //C#
                        using (var conn = new SqlConnection(connectionString))
                        {
                            watch.Start();
                            conn.Open();
                            for (int i = 0; i < 5000; i++)
                            {
                                using (var command = new SqlCommand(insertProcedureName, conn)
                                {
                                    CommandType = CommandType.StoredProcedure
                                })
                                {
                                    command.ExecuteNonQuery();
                                }
                            }
                        }
                    
                

Sprawdzenie rozmiarów indeksów na Tabelkach

                    
                        SELECT OBJECT_NAME(OBJECT_ID) AS TableName,
                        'Index' = 
                          CASE 
                             WHEN index_id  =  0 THEN 'HEAP'
                             WHEN index_id  = 1  THEN 'Clustered index.'
                             WHEN index_id  > 1 THEN 'Nonclustered index'
                             ELSE 'ERROR'
                        END,
                        used_page_count,
                        reserved_page_count,
                        row_count
                        FROM sys.dm_db_partition_stats
                        WHERE OBJECT_NAME(object_id) IN 
                        (
                            'MonsterAttacks_ClusteredALL',
                            'MonsterAttacks_ClusteredAttackDate',
                            'MonsterAttacks_ClusteredDamagToTheCity',
                            'MonsterAttacks_ClusteredId'
                        )
                        and index_id  = 1
                    
                

blog
Cezary Walenciuk
blog
Cezary Walenciuk

Co trzeba zrobić : Zorganizować tabele

Co trzeba zrobić : Wspieranie jakich zapytań

Kompromis 1

Kompromis 2

Typy indeksów

Indeksy Nonclustered

Indeksy Nonclustered

Predytkaty

Predytkaty Równości

                    

                        WHERE DamageToTheCityInNumbers = 105

                        WHERE DamageToTheCityInNumbers = 105 AND
                        CityId = 2
                    
                

Predytkaty Nie równości

                    

                        WHERE DamageToTheCityInNumbers > 5000

                        WHERE DamageToTheCityInNumbers > 5000 AND
                        CityId = 2

                        WHERE DamageToTheCityInNumbers > 5000 AND
                        CityId > 3
                    
                

Predytkaty połączone z OR

                    

                        WHERE AttackType = 'M' OR AttackType = 'S'

                        WHERE DamageToTheCityInNumbers > 5000 
                        OR AttackType = 'S'

                        WHERE DamageToTheCityInNumbers > 5000 OR
                        CityId > 3
                    
                

Jak tam JOIN-y

                    

                        SELECT 
                            F.[DidDefeatMonster],
                            MA.AttackDate,
                            M.Name,
                            M.DisasterLevel
                        FROM [HeroDataBase].[dbo].[Fight] AS F
                        INNER JOIN [MonsterAttacks] AS MA ON 
                        MA.MonsterAttackId = F.MonsterAttackId
                        INNER JOIN [Monster] AS M ON M.IdMonster = MA.MonsterId
                    
                

blog
Copyright © Cezary Walenciuk
Zasady dla równości

Zasady dla równości

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
A co teraz?
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
A to da radę?
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Zmiana indeksu : co teraz gdy jest inna kolejność kolumn
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Co jeśli mamy wiele zapytań
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Zasady dla NIE równości

Zasady dla NIE równości

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Zmiana kolejności indeksu
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Zmiana kolejności indeksu
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Zobaczmy to w praktyce

Zapytanie dla CityId = 4

                    

                        SELECT  [MonsterAttackId]
                        ,[CityId]
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [CityId] = 4

                    
                

blog
Copyright © Cezary Walenciuk

Indeks NONCLUSTERED na CityId

                    

                        CREATE NONCLUSTERED INDEX 
                        idx_MonsterAttacks_CityId 
                        ON [dbo].[MonsterAttacks](CityId);

                    
                

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk

Równości WHERE [AttackType] = 'S';

                    

                        SELECT  [MonsterAttackId]
                        ,[AttackType]
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [AttackType] = 'S';

                    
                

blog
Copyright © Cezary Walenciuk
Tworze kolejny indeks NonClustered dla [AttackType]
blog
Copyright © Cezary Walenciuk

Równości [AttackType] = 'S' AND [CityId] = 51

                    

                        SELECT  [MonsterAttackId]
                        ,[AttackType]
                        ,[CityId]
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [AttackType] = 'S' AND [CityId] = 51

                    
                

blog
Copyright © Cezary Walenciuk
Tworze kolejny indeks NonClustered dla [AttackType,CityId]
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Predytkaty nierówności

Predytkaty nierówności

                    

                        SELECT  [MonsterAttackId]
                        ,[DamageToTheCityInNumbers]
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [DamageToTheCityInNumbers] > 9000
                  
                        SELECT  [MonsterAttackId]
                                ,[DamageToTheCityInNumbers], [CityId] 
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [DamageToTheCityInNumbers] > 9000 AND [CityId] = 175
                  
                        SELECT  [MonsterAttackId]
                                ,[DamageToTheCityInNumbers], [AttackDate] 
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [DamageToTheCityInNumbers] > 9000 AND [AttackDate] < '2019-10-02'
                  
                    
                

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Jak to jest z operacją OR

Jak kto jest z operacją OR

Predytkaty połączone z OR

                    

                        SELECT  [MonsterAttackId]
                        ,[DamageToTheCityInNumbers], [AttackDate] 
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [DamageToTheCityInNumbers] > 8000 
                        OR [AttackDate] < '2019-10-10'
                  

                    
                

blog
Copyright © Cezary Walenciuk
Załóżmy 1 indeks na ([DamageToTheCityInNumbers]), i drugi na ([AttackDate])

Predytkaty połączone z OR

                    

                        SELECT  [MonsterAttackId]
                        ,[DamageToTheCityInNumbers], [AttackDate] 
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [DamageToTheCityInNumbers] = 8000 
                        OR [AttackDate] = '2019-10-10'
                  

                    
                

blog
Copyright © Cezary Walenciuk
Indeksowanie, a JOIN-y i operacje

Jakiś słownik? Algorytmy scalajace rekordy

Jakiś słownik? Słowa kluczowe Merge Join i Hash Join

Indeksowanie, a JOIN-y i operacje

JOIN

                    

                        SELECT 
                            F.[DidDefeatMonster],
                            MA.AttackDate,
                            M.Name,
                            M.DisasterLevel
                        FROM [HeroDataBase].[dbo].[Fight] AS F
                        INNER JOIN [MonsterAttacks] AS MA 
                        ON MA.MonsterAttackId = F.MonsterAttackId
                        INNER JOIN [Monster] AS M ON M.IdMonster = MA.MonsterId
                        WHERE M.Name = 'Geryuganshoop'
                  

                    
                

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk

                    

                        SELECT 
                            F.[DidDefeatMonster],
                            MA.AttackDate,
                            M.Name,
                            M.DisasterLevel
                        FROM [HeroDataBase].[dbo].[Fight] AS F
                        INNER MERGE JOIN [MonsterAttacks] AS MA 
                        ON MA.MonsterAttackId = F.MonsterAttackId
                        INNER MERGE JOIN [Monster] AS M ON M.IdMonster = MA.MonsterId
                        WHERE M.Name = 'Geryuganshoop'

                    
                

blog
Copyright © Cezary Walenciuk

                    

                        SELECT 
                            F.[DidDefeatMonster],
                            MA.AttackDate,
                            M.Name,
                            M.DisasterLevel
                        FROM [HeroDataBase].[dbo].[Fight] AS F
                        INNER HASH JOIN [MonsterAttacks] AS MA 
                        ON MA.MonsterAttackId = F.MonsterAttackId
                        INNER HASH JOIN [Monster] AS M ON M.IdMonster = MA.MonsterId
                        WHERE M.Name = 'Geryuganshoop'
                  

                    
                

blog
Copyright © Cezary Walenciuk
Indeksy i dodanie do nich kolumn

Indeksy i dodanie do nich kolumn

blog
Copyright © Cezary Walenciuk

Key Lookups

blog
Copyright © Cezary Walenciuk
Przykład

                    

                        SELECT [MonsterAttackId]
                            ,[CityId]
                            ,[AttackDate]
                            ,[AttackType]
                            ,[DamageToTheCityInNumbers]
                            ,[IsDone]
                            ,[IsRepaired]
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [CityId] = 2487 AND [AttackType] = 'J'
                    
                        CREATE NONCLUSTERED INDEX idx_test2 ON 
                        [dbo].[MonsterAttacks]([CityId],[AttackType])

                    
                

blog
Copyright © Cezary Walenciuk

                    

                        SELECT [MonsterAttackId]
                            ,[CityId]
                            ,[AttackDate]
                            ,[AttackType]
                            ,[DamageToTheCityInNumbers]
                            ,[IsDone]
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [CityId] = 2487 AND [AttackType] = 'J'
                    
                        CREATE NONCLUSTERED INDEX idx_test3 ON 
                        [dbo].[MonsterAttacks]([CityId],[AttackType])
                        INCLUDE([DamageToTheCityInNumbers],[AttackDate],
                        [MonsterAttackId],[IsDone])
                                

                    
                

blog
Copyright © Cezary Walenciuk
Filtry na Indeksach

Filtry na Indeksach

                    

                        SELECT [MonsterAttackId]
                        ,[CityId]
                        ,[AttackType]
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [CityId] = 2487 AND [AttackType] = 'J'
                  
                        CREATE NONCLUSTERED INDEX idx_Test ON 
                        [dbo].[MonsterAttacks]([CityId])
                        WHERE [AttackType] = 'J'

                    
                

blog
Copyright © Cezary Walenciuk

Problem z filtrem gdy korzystamy z parametrów

                    

                        DECLARE @CityId INT = 2487, 
                        @AttackType CHAR(1) = 'J'

                        SELECT [MonsterAttackId]
                            ,[CityId]
                            ,[AttackType]
                        FROM [HeroDataBase].[dbo].[MonsterAttacks]
                        WHERE [CityId] = @CityId 
                        AND [AttackType] = @AttackType

                    
                

blog
Copyright © Cezary Walenciuk
Jak wiele indeksów NonClustered?

Jak wiele indeksów NonClustered

Indeksy dla View

Indeksy dla View

Istnieją jednak ograniczenia

Gdzie są użyteczne?

                    

                        CREATE OR ALTER VIEW dbo.MonsterAttacksWithTotals
                        WITH SCHEMABINDING
                        AS
                        
                        SELECT
                        COUNT_BIG(*) AS NumberOfAttacks,
                        ma.CityId,
                        SUM(ma.DamageToTheCityInNumbers) AS TotalDamage,
                        AVG(ma.DamageToTheCityInNumbers) AS AVGDamage
                        FROM [dbo].[MonsterAttacks] AS ma
                        GROUP BY ma.CityId
                        HAVING COUNT_BIG(*)< 100 AND COUNT_BIG(*) > 2
                        
                        GO
                        
                        CREATE UNIQUE CLUSTERED INDEX idx_MonsterAttacksWithTotals
                        ON dbo.MonsterAttacksWithTotals (CityId);

                    
                

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk

                    

                        CREATE OR ALTER VIEW dbo.MonsterAttacksWithTotals
                        WITH SCHEMABINDING
                        AS
                        
                        SELECT
                        COUNT_BIG(*) AS NumberOfAttacks,
                        ma.CityId,
                        SUM(ma.DamageToTheCityInNumbers) AS TotalDamage
                        FROM [dbo].[MonsterAttacks] AS ma
                        GROUP BY ma.CityId
                        
                        GO
                        
                        CREATE UNIQUE CLUSTERED INDEX idx_MonsterAttacksWithTotals
                        ON dbo.MonsterAttacksWithTotals (CityId);

                    
                

blog
Copyright © Cezary Walenciuk

Wady indeksów w widokach

Indeksy ColumnStore

Indeksy ColumnStore

Indeksy ColumnStore

Architektura ColumnStore Index

                    

                        CREATE NONCLUSTERED COLUMNSTORE INDEX 
                        [NonClusteredColumnStoreIndex-20201004] 
                        ON [dbo].[MonsterAttacks_ColumnIndex]
                        (
                            [MonsterAttackId],
                            [AttackDate],
                            [AttackType],
                            [DamageToTheCityInNumbers]
                        )

                    
                

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk

Typy Indeksy ColumnStore

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk

                    

                        SELECT [Id]
                            ,[Class]
                            ,[PowerLevel]
                            ,[NickName]
                            ,[Adress]
                        FROM [HeroDataBase].[dbo].[SuperHeroes_ColumnIndex]
                    
                    
                        SELECT [Id]
                            ,[Class]
                            ,[PowerLevel]
                            ,[NickName]
                            ,[Adress]
                        FROM [HeroDataBase].[dbo].[SuperHeroes]

                    
                

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk

Kiedy ColumnStore

ColumnStore słaby dla tabel gdy często ją modyfikujemy

ColumnStore

ColumnStore

Dane na temat SuperHeroes_ColumnInde : Ile zajmuje PAGE

                    

                        SELECT 
                            OBJECT_ID,
                            index_id,
                            used_page_count,
                            reserved_page_count,
                            row_count,
                            OBJECT_NAME(OBJECT_ID)
                        FROM sys.dm_db_partition_stats 
                        WHERE OBJECT_ID = OBJECT_ID('SuperHeroes_ColumnIndex') 
                        OR OBJECT_ID = OBJECT_ID('MonsterAttacks_ColumnIndex');

                    
                

blog
Copyright © Cezary Walenciuk

Jak jest skompresowane? Columnstore

                    

                        SELECT OBJECT_NAME(OBJECT_ID),* FROM sys.column_store_row_groups;

                    
                

blog
Copyright © Cezary Walenciuk

Zróbmy 18008 Insertów

                    

                        INSERT INTO [dbo].[SuperHeroes_ColumnIndex]
                        ([Class]
                        ,[PowerLevel]
                        ,[NickName]
                        ,[Adress])
                        VALUES
                        ('Y'
                        ,@randompowerlevel
                        ,@randomname
                        ,@randomname2)

                    
                

blog
Copyright © Cezary Walenciuk

Zróbmy 5000 losowych kasowań i 18000 update-ów

                    

                        DECLARE @i INT = FLOOR(RAND(CHECKSUM(NEWID())) * 18008)

                        UPDATE [dbo].[SuperHeroes_ColumnIndex]
                            SET [PowerLevel] = RAND(CHECKSUM(NEWID()))*500
                            WHERE Id = @i;

                        DECLARE @j INT = FLOOR(RAND(CHECKSUM(NEWID())) * 18008)

                        DELETE [dbo].[SuperHeroes_ColumnIndex]
                            WHERE Id = @j;
                            

                    
                

blog
Copyright © Cezary Walenciuk
blog
Copyright © Cezary Walenciuk
Jeszcze 18000 update-ów
blog
Copyright © Cezary Walenciuk
Podsumowanie
Dzięki za obecność