Archive for the ‘SQL Server 2016’ Category

Nagy táblák join-olása eredmények

Tuesday, January 17th, 2017

Korábbi bejegyzésemben írtam, hogy demó környezetben a columnstore indexek nagyon jelentős gyorsulást okoznak.

Élő adatbázisban azt tapasztaltuk, hogy integer kulcsokon végzett join-okon 5-10-szeres gyorsulást okozott a normál indexekhez képest.

Azonban GUID-os oszlopokkal nem okozott értékelhető gyorsulást, annak ellenére, hogy batch módúak voltak a műveletek. Erre úgy látszik nem gyúrtak még rá, vagy a guid randomsága miatt nem tud érdelmes teljesítményt elérni.

Nagy táblák joinolása

Tuesday, November 22nd, 2016

Egyik folyó munkámban több tízmilló soros táblákon végzett joinokat kellett optimalizálni. Általában ez nem kihívás, mert szinte mindig vannak szűrési feltételek, amelyeket kellő közelségbe víve a táblákhoz és rendes indexekat alápakolva már csak pár ezer joint kell végrehajtani, ami gyors lesz.
De most tényleg össze kellett joinolni sok millió sort, szűrés nélkül.
Mit lehet ezzel kezdeni? Sajnos itt már eléggé behatárolt területen mozgunk. A normál indexelős megoldások nem segítenek, mivel minden táblát teljes egészében be kell járni (nincs where).
Ráadásul ha *-os a select, akkor a cover NC index se játszik, hogy legalább az IO csökkenne.
Merge joinra lehet játszani clu indexekkel, de azért ez korlátos terület sok tábla esetén, illetve párhuzamos tervek esetén magától nem fog merge joint használni (itt írnak egy trace flagről, amivel mégis rá lehet venni).
Mit lehet tenni. Egyik lehetőség előre elkészíteni a join indexelt view-ban. Erre ügyesen ráharap az optimizer, ha van olyan join amit aztán többször futtatunk, akkor megéri ez a denormalizálás.
Ha viszont van újabb szerverünk (2016), akkor van sokkal durvább lehetőség: Columnstore index.
Az a baj ugye a nagy joinnal, hogy akárhogy is trükközünk, ez nagy meló a prociknak és az IO alrendszernek (vinkóknak). Az indexed view ezt úgy oldja meg, hogy egyszer kell megcsinálni, aztán sokszor élvezni az előre összepakolt adatokat.
A columnstore viszont (dióhéjban) azért piszok gyors mert:
1. 5-10-szeresen tömörítve tárolja az adatokat, kevesebb IO, illetve a memóriában a buffer cache-t is jobban ki tudja használni (mintha több RAM-unk lenne)
2. Képes az adatok csak egy részét felolvasni, ha csak kevés oszlop kell (select *-on ez nem segít persze)
3. Képes batch módban belülről párhuzamosan végrehajtani a műveletek egy részét (ez nagyon durván megdobja)
4. Képes a sorok egy részét felolvasni where feltétel alapján, mivel minden 1m sorhoz (szegmens) nyilván tarja az adott oszlop min és max értékét
5. Le tud nyomni operátorokat (pl. sum) a storage engine-be, így nem kell adatokat passzolgatni a rétegek között.

No, lássuk a medvét. Létrehoztam két másolatot egy 100 millió soros táblából. A tesztgép egy két éves laptop 2 core-ral és 8G RAM-mal, SSD-vel. Nem egy szerver.
A két táblát a kulcsai mentés join-olom, így mind a 100 millió sort végig kell néznie, és ennyi találat is lesz.

Először sima Clu index:
create clustered index IX_Clu1 on B1(Id)
create clustered index IX_Clu2 on B2(Id)

select count(*) from B1 join B2 on B1.Id = B2.Id

SQL Server parse and compile time:
CPU time = 15 ms, elapsed time = 18 ms.

(1 row(s) affected)
Table ‘B1’. Scan count 5, logical reads 1141262, physical reads 6,
read-ahead reads 1138814, lob logical reads 0, lob physical reads 0,
lob read-ahead reads 0.
Table ‘B2’. Scan count 5, logical reads 1140956, physical reads 4,
read-ahead reads 1138821, lob logical reads 0, lob physical reads 0,
lob read-ahead reads 0.
Table ‘Workfile’. Scan count 896, logical reads 480256, physical reads
2688, read-ahead reads 477568, lob logical reads 0, lob physical reads
0, lob read-ahead reads 0.
Table ‘Worktable’. Scan count 0, logical reads 0, physical reads 0,
read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob
read-ahead reads 0.

SQL Server Execution Times:
CPU time = 477262 ms, elapsed time = 377318 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 1 ms.

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.

377 másodperc.

Jöhet a columnstore index:
create clustered columnstore index IX_CStore1 on B1
create clustered columnstore index IX_CStore2 on B2

select count(*) from B1 join B2 on B1.Id = B2.Id

SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 6 ms.

(1 row(s) affected)
Table ‘B2’. Scan count 4, logical reads 0, physical reads 0,
read-ahead reads 0, lob logical reads 105018, lob physical reads 0,
lob read-ahead reads 0.
Table ‘B2’. Segment reads 103, segment skipped 0.
Table ‘B1’. Scan count 4, logical reads 0, physical reads 0,
read-ahead reads 0, lob logical reads 104998, lob physical reads 0,
lob read-ahead reads 0.
Table ‘B1’. Segment reads 102, segment skipped 0.
Table ‘Worktable’. Scan count 0, logical reads 0, physical reads 0,
read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob
read-ahead reads 0.

SQL Server Execution Times:
CPU time = 79920 ms, elapsed time = 27834 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.

377 sec vs. 28 sec. Azért ez masszív különbség. :)

Érdekességképpen megnéztem NC Columnstore index-szel is, úgy 60 sec jön ki. Ez se rossz.

A jövő héten lehet ki tudjuk próbálni egy nagyobb géppel is, kíváncsi vagyok, ott mit tudunk vele kihozni.

Ha esetleg valakinek vannak már gyakorlati sikerei, érdekelnek a számok.

SQL Server memória hiány miatti lassú lekérdezés

Sunday, June 19th, 2016

Érdekes hibába futottam bele mostanában. A lapotopomban csak 8G RAM van, így az azon futó SQL servernek nem sok marad, amikor a Chrome, a Visual Studio 2015 és más memóriazabáló alkalmazások elkezdenek terjeszkedni.
A probléma az volt, hogy egy olyan lekérdezés, amely Sortot tartalmazott a szokásos 2-3 mp helyett fél-egy percig futott. A végrehajtási tervben volt egy figyelmeztetés, hogy a sort operátor kénytelen kipakolni a rendezést a tempdb-be (spill). De ettől még nem kellett volna ilyen lassúnak lennie. Csak 3500 sorról volt szó, ez azért belefért volna memóriába.

A select * from sys.dm_exec_query_memory_grants lekérdezésből kiderült, hogy a lekérdezés sortjának kellett volna 10M memória, de nem tudott annyit kapni, ezért várakozott, és aztán 20 másodperc múlva egyszer csak timeout lett, és kényszeredetten csak nekifogott végrehajtani a sortot a tempdbben.

Azaz egy egy nem szokványos lassú lekérdezés volt, nem sok lapolvasással járt, mint a tipikus rosszul optimalizált lekérdezések, hanem a memory grantre várt.
A megoldás az lett, hogy beállítottam 1G min memory-t az SQL Servernek, így már kiszámíthatóan jól érzi magát.

Egy rendes szerveren valószínűleg ritkább az ilyen memória kényszer, de azért jó tudni róla.

2 karakter, és máris más az execution plan

Wednesday, June 24th, 2015

Az eredeti lekérdezésben az ORDER BY így nézett ki: order by BDT.
Ez a számított oszlopra vonatkozott, azért a szervernek meg kellett oldania a rendezést egy külön lépésben. A b.BDT után viszont már tudja használni az alatta levő index rendezettségét, így nem kell rendeznie. 4x teljesítménynövekedést okozott ez a 2 karakter.

select
dateadd(second, @dtModifier, BDT) BDT,
cast(O as real) O, 
cast(H as real) H, 
cast(L as real) L,
cast(C as real) C,
V
from dbo.Bar b with (nolock)
where b.TickerID = @TickerID
and b.BDT >= @StartDate
and I = @I
order by b.BDT

Intra query deadlock

Tuesday, June 16th, 2015

Ma láttam egy újfajta deadlockot, amiben az SQL Server által párhuzamosan végrehajtott lekérdezés szálai akadtak össze. Azaz ugyanaz a PID akadt össze magával.

Először arra gondoltam, lefogom 1 szálra a lekérdezést maxdoppal, de szerencsére nem volt jól optimalizálva, így jól fel lehetett gyorsítani. 240000 lapolvasásról 4-re. :) Így már magától soros lett a plan, volt deadlock, nincs deadlock.

Bob is írt már erről.

SQL Server 2016 In-Memory OLTP

Friday, May 15th, 2015

Úgy tűnik 2016-ra felnő, 1.0-ssá az In-Memory OLTP az SQL Serverben. Gondolkodik valaki a bevezetésén (úgy értem 14-ben) ?