{"id":460,"date":"2008-03-28T10:38:10","date_gmt":"2008-03-28T09:38:10","guid":{"rendered":"http:\/\/soci.hu\/blog\/index.php\/2008\/03\/28\/sql-server-2008-ujdonsagok-26-forceseek-table-hint\/"},"modified":"2008-03-28T10:38:10","modified_gmt":"2008-03-28T09:38:10","slug":"sql-server-2008-ujdonsagok-26-forceseek-table-hint","status":"publish","type":"post","link":"https:\/\/soci.hu\/blog\/index.php\/2008\/03\/28\/sql-server-2008-ujdonsagok-26-forceseek-table-hint\/","title":{"rendered":"SQL Server 2008 \u00fajdons\u00e1gok 26. &#8211; FORCESEEK table hint"},"content":{"rendered":"<p>Mint minden optimizer hint el\u0151tt figyelmeztet\u00e9s: ne haszn\u00e1ld, csak ha \u00e9rted mit csin\u00e1l, \u00e9s biztosan tudod, hogy az adott helyzetben seg\u00edt, \u00e9s hogy az adatok eloszl\u00e1sa nem v\u00e1ltozik meg jelent\u0151sen az id\u0151k folyam\u00e1n, ami miatt m\u00e1r ront, \u00e9s nem jav\u00edt a hint.<\/p>\n<p>A szerver lek\u00e9rdez\u00e9s optimaliz\u00e1l\u00f3ja \u00e1ltal\u00e1ban \u00fcgyesen kiv\u00e1lasztja a sz\u00fcks\u00e9ges indexet egy sz\u0171r\u00e9s hat\u00e9kony v\u00e9grehajt\u00e1s\u00e1hoz, azonban vannak esetek, amikor<br \/>\n1. nem el\u00e9g bev\u00e1llal\u00f3s<br \/>\n2. felhaszn\u00e1lja a cache-elt tervet, de ezzel rosszul j\u00e1runk.<\/p>\n<p>Ezekben az esetekben hasznos lehet a forceseek. N\u00e9mi h\u00e1tt\u00e9r a meg\u00e9rt\u00e9shez.<\/p>\n<p>A nonclustered (NC) index egy B* fa, amelyben csak az indexhez megadott oszlop(ok) adata szerepel rendezetten, a t\u00e9nyleges adatok az indexf\u00e1t\u00f3l elk\u00fcl\u00f6n\u00fclt adatter\u00fcleten helyezkednek el. Emiatt, ha mondjuk egy where felt\u00e9telhez szeretn\u00e9 haszn\u00e1lni a szerver az indexet, el\u0151sz\u00f6r v\u00e9gigszalad az NC index f\u00e1n, ami nagyon gyors m\u0171velet, hisz rendezett a fa. A lev\u00e9lszinten tal\u00e1lt egy vagy t\u00f6bb sort pedig \u00fagy szedi fel, hogy \u00e1tny\u00fal az indexben tal\u00e1lhat\u00f3 pointer ment\u00e9n az adatlapokhoz, \u00e9s kiolvassa az ott tal\u00e1lhat\u00f3 sorokat. Ezt Bookmark Lookup oper\u00e1tork\u00e9nt l\u00e1thattuk az SQL 2000-ben, 2005-ben JOIN-k\u00e9nt jelenik meg, most 2008-ban pedig Key Lookupk\u00e9nt l\u00e1tom (ez a legtal\u00e1l\u00f3bb eddig).<br \/>\nSok sor eset\u00e9n ez j\u00f3 nagy ugrabugr\u00e1val j\u00e1r, hisz pl. 100 sor eset\u00e9n extr\u00e9m esetben 100 lapon vannak sz\u00e9tsz\u00f3rva az adatok, \u00edgy 100 lapot be kell t\u00f6lteni a mem\u00f3ri\u00e1ba, 1-1 nyomorult sor miatt. Ez\u00e9rt a szerver nem lelkesedik alapban az NC index\u00e9rt, igen konzervat\u00edvan \u00e9l csak vele. Sokszor ink\u00e1bb v\u00e9gigolvassa a teljes t\u00e1bl\u00e1t, read ahead m\u00f3don, n\u00e9h\u00e1ny nagy harap\u00e1ssal, semmint hogy apr\u00f3 8k-s laponk\u00e9nt z\u00f6ty\u00f6gtesse a viny\u00f3k fej\u00e9t. A visszafogotts\u00e1ga sokszor megalapozott, de n\u00e9ha nem.<br \/>\nAz elej\u00e9n felvetett 1. esetben p\u00e9ld\u00e1ul lehet, hogy egy 10e soros t\u00e1bl\u00e1b\u00f3l 200 sort lev\u00e1logatva m\u00e1r nem index seek-el (azaz az el\u0151bb le\u00edrt m\u00f3don haszn\u00e1lja az indexet), hanem table scan-t csin\u00e1l. Ennek lehet oka egy rossz statisztika is persze, ami miatt azt hiszi, hogy sokkal t\u00f6bb sor j\u00f6n vissza, ez\u00e9rt mindig KELL friss\u00edteni a statisztik\u00e1kat, miel\u0151tt hinteln\u00e9nk (sp_updatestats \u00e9s UPDATE STATISTICS). Viszont lehet, hogy csak egyszer\u0171en nem merte bev\u00e1llalni az index seeket, helyette scan-elt.<br \/>\nA 2. eset nem egyenletes eloszl\u00e1s\u00fa adatok vagy nagyon v\u00e1ltoz\u00f3 param\u00e9ter\u0171 sz\u0171r\u00e9sek eset\u00e9n okozhat probl\u00e9m\u00e1t. Tegy\u00fck fel, hogy egy t\u00e1bl\u00e1ban megrendel\u00e9sek vannak \u00e9s ezek d\u00e1tum\u00e1ra nagyon gyakran sz\u0171rnek, de mindig \u00fagy, hogy csak egy adott nap megrendel\u00e9seire k\u00edv\u00e1ncsiak. Mivel ez kis r\u00e9sz\u00e9t hozza vissza a t\u00e1bl\u00e1nak index seeket haszn\u00e1l a szerver, ha van a megrendel\u00e9s d\u00e1tuma oszlopon egy ak\u00e1rmilyen index. C\u00e9l, hogy ezek a napi lek\u00e9rdez\u00e9sek nagyon gyorsak legyenek, a nagyobb tartom\u00e1nyra lek\u00e9rdez\u00e9s sebess\u00e9ge els\u0151 k\u00f6rben nem fontos (\u00e9rtsd, nem baj, ha rosszabb lesz a b\u0171v\u00e9szked\u00e9s\u00fcnk hat\u00e1s\u00e1ra, mint volt).<br \/>\nHa azonban egy koll\u00e9ga szerver \u00fajraindul\u00e1s vagy cache \u00fcr\u00fcl\u00e9s ut\u00e1n valami\u00e9rt mondjuk egy \u00e9vet v\u00e1logat le, akkor teljes t\u00e1blaolvas\u00e1st fog haszn\u00e1lni a szerver. Ez rendben is van, j\u00f3 d\u00f6nt\u00e9s, de a v\u00e9grehajt\u00e1si terv let\u00e1rol\u00f3dhat a cache-ben, \u00edgy ezek ut\u00e1n lehet, hogy az 1 napi sz\u0171r\u00e9sek is scan-elni fognak, azaz piszok lass\u00fak lesznek. Erre persze sokf\u00e9le megold\u00e1s adhat\u00f3, a forcessek csak az egyik ezek k\u00f6z\u00fcl.<\/p>\n<p>L\u00e1ssuk az alapprobl\u00e9m\u00e1t:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\ncreate nonclustered index idx_orderdate on Purchasing.PurchaseOrderHeader(OrderDate)\r\ngo\r\n\r\ncreate proc GetOrdersByOrderDate\r\n  @start datetime,\r\n  @end datetime\r\nas\r\nSELECT * FROM Purchasing.PurchaseOrderHeader\r\nwhere OrderDate &gt;= @start and OrderDate &lt; @end\r\ngo\r\n&amp;#91;\/source&amp;#93;\r\n\r\n&amp;#91;source=&#039;sql&#039;&amp;#93;\r\nexec GetOrdersByOrderDate &#039;20030317&#039;, &#039;20040318&#039;\r\n&amp;#91;\/source&amp;#93;\r\nEz egy \u00e9vi adat, a t\u00e1bla majdnem fele lej\u00f6n, persze, hogy table scan lesz a v\u00e9ge, 44 IO m\u0171velettel, ennyi lapb\u00f3l \u00e1ll a t\u00e1bla:\r\n\r\n&lt;a href=&#039;http:\/\/soci.hu\/blog\/wp-content\/uploads\/2008\/03\/scan1.png&#039; title=&#039;Index scan sok adat miatt&#039;&gt;&lt;img src=&#039;http:\/\/soci.hu\/blog\/wp-content\/uploads\/2008\/03\/scan1.png&#039; alt=&#039;Index scan sok adat miatt&#039; \/&gt;&lt;\/a&gt;\r\n\r\nA baj az, hogy a k\u00f6vetkez\u0151 lek\u00e9rdez\u00e9s is scan-el, nem seekel, mert felhaszn\u00e1lja az el\u0151bbi tervet, pedig csak 8 sort hoz vissza:\r\n&#x5B;source=&#039;sql&#039;]\r\nexec GetOrdersByOrderDate &#039;20030317&#039;, &#039;20030318&#039;\r\n<\/pre>\n<p>Mit lehet tenni? A legegyszer\u0171bb, ha kik\u00e9nyszer\u00edtj\u00fck az \u00fajraford\u00edt\u00e1st minden egyes futtat\u00e1skor vagy az sp l\u00e9trehoz\u00e1sakor (r\u00e9gi f\u00edcs\u00f6r):<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\nexec GetOrdersByOrderDate &#039;20030317&#039;, &#039;20030318&#039; with recompile\r\n<\/pre>\n<p><a href='http:\/\/soci.hu\/blog\/wp-content\/uploads\/2008\/03\/seek1.png' title='Seek a recompile kik\u00e9nyszer\u00edt\u00e9se miatt'><img src='http:\/\/soci.hu\/blog\/wp-content\/uploads\/2008\/03\/seek1.png' alt='Seek a recompile kik\u00e9nyszer\u00edt\u00e9se miatt' \/><\/a><\/p>\n<p>Viszont sokszor pont az\u00e9rt hoztunk l\u00e9tre sp-t, hogy ne legyen recompile. SQL Server 2005-t\u0151l haszn\u00e1lhat\u00f3 az optimize for hint:<\/p>\n<p>alter proc GetOrdersByOrderDate<br \/>\n  @start datetime,<br \/>\n  @end datetime<br \/>\nas<br \/>\nSELECT * FROM Purchasing.PurchaseOrderHeader<br \/>\nwhere OrderDate >= @start and OrderDate < @end\noption(optimize for (@start = '20030317', @end = '20030318'))\n[\/source]\n\nAzt mondtuk a szervernek, t\u00f6k mindegy milyen param\u00e9terrel h\u00edvj\u00e1k meg az sp-t, a k\u00e9t param\u00e9ter \u00e9rt\u00e9k\u00e9t tekintsd annak, amit oda\u00edrtam, \u00e9s ennek megfelel\u0151en optimaliz\u00e1lj. Direkt egy napnyi, p\u00e1r sornyi adatot adtam meg, ami miatt index seek-et haszn\u00e1l a szerver, ami stabilan gyorsan tartja a napi lek\u00e9rdez\u00e9seket, de nagyon lelass\u00edtja a nagy tartom\u00e1nyt, mert arra is seek lesz, ami miatt az el\u0151bbi 44 helyett 3458 IO m\u0171veletet kapunk.\n\nAz el\u0151bbi megold\u00e1s teh\u00e1t akkor j\u00f3, ha nem nagyon vannak nagy tartom\u00e1nybeli lek\u00e9rdez\u00e9sek. Ha abb\u00f3l is sok van, akkor vagy recompile kell mindig, vagy kell k\u00e9t sp, m\u00e1s param\u00e9terekre optimaliz\u00e1lva, \u00e9s a kliens okosan a megfelel\u0151t h\u00edvja meg.\n\nM\u00e1sk\u00e9pp is ki lehet k\u00e9nyszer\u00edteni a seeket:\n\n[source='sql']\nalter proc GetOrdersByOrderDate\n  @start datetime,\n  @end datetime\nas\nSELECT * FROM Purchasing.PurchaseOrderHeader with(index(idx_orderdate))\nwhere OrderDate >= @start and OrderDate < @end\n[\/source]\n\nAzaz r\u00e1k\u00e9nyszer\u00edtj\u00fck, hogy haszn\u00e1lja az NC index\u00fcnket. Az optimize for megold\u00e1s h\u00e1tr\u00e1nya, hogy ha az adatok v\u00e1ltoznak a t\u00e1bl\u00e1ban, lehet, hogy \u00e9rv\u00e9nytelen lesz a mintak\u00e9nt megadott \u00e9rt\u00e9k, \u00edgy rosszul d\u00f6nt a szerver az adatel\u00e9r\u00e9s m\u00f3dj\u00e1r\u00f3l. A with index hint pedig belevarrja az index nev\u00e9t a lek\u00e9rdez\u00e9sbe, \u00edgy ha az megv\u00e1ltozik vagy az index t\u00f6rl\u00e9se ker\u00fcl, a lek\u00e9rdez\u00e9s hib\u00e1zni fog.\n\nNo, l\u00e1ssuk most m\u00e1r, mire is j\u00f3 a FORCESEEK:\n\n[source='sql']\nalter proc GetOrdersByOrderDate\n  @start datetime,\n  @end datetime\nas\nSELECT * FROM Purchasing.PurchaseOrderHeader with(forceseek)\nwhere OrderDate >= @start and OrderDate < @end\n[\/source]\n\nEz ugyan\u00fagy r\u00e1veszi a szervert a kor\u00e1bbi index\u00fcnk haszn\u00e1lat\u00e1ra, seek-elve, de nem mondja meg, hogy milyen indexet haszn\u00e1ljon. T\u00f6rt\u00e9netesen adtunk neki olyat, amit haszn\u00e1lhat, \u00e9l is vele. Azaz ezzel egyfajta noszogat\u00e1st adtunk neki, hogy n\u00e9zz sz\u00e9t j\u00f3l, van ott olyan index, ami j\u00f3 neked seek-elve is.\n\nHa gonosz m\u00f3don ledobjuk az indexet, nagyot n\u00e9z a szerver, \u00e9s ezt mondja:\n\nQuery processor could not produce a query plan because of the hints defined in this query. Resubmit the query without specifying any hints and without using SET FORCEPLAN.\n\nNo, igen, ennek is vannak k\u00f6t\u00f6tts\u00e9gei, ha egyszer nincs megfelel\u0151 index, akkor mi a francot er\u0151ltetj\u00fck, nem?\n\n\u00d6sszegezve, az FORCESEEK egy \u00fajabb eszk\u00f6z az optimaliz\u00e1l\u00e1si reperto\u00e1runkban, semmik\u00e9ppen se csodafegyver, illetve l\u00e1that\u00f3, hogy a hat\u00e1s\u00e1t m\u00e1sk\u00e9pp is el lehet \u00e9rni. Egy \u00fajra tr\u00fckk, amit bevet\u00fcnk, HA INDOKOLT. De csak akkor.\n\n<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mint minden optimizer hint el\u0151tt figyelmeztet\u00e9s: ne haszn\u00e1ld, csak ha \u00e9rted mit csin\u00e1l, \u00e9s biztosan tudod, hogy az adott helyzetben seg\u00edt, \u00e9s&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6,4,58],"tags":[],"class_list":["post-460","post","type-post","status-publish","format-standard","hentry","category-adatbazisok","category-szakmai-elet","category-sql-server-2008"],"_links":{"self":[{"href":"https:\/\/soci.hu\/blog\/index.php\/wp-json\/wp\/v2\/posts\/460","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/soci.hu\/blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/soci.hu\/blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/soci.hu\/blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/soci.hu\/blog\/index.php\/wp-json\/wp\/v2\/comments?post=460"}],"version-history":[{"count":0,"href":"https:\/\/soci.hu\/blog\/index.php\/wp-json\/wp\/v2\/posts\/460\/revisions"}],"wp:attachment":[{"href":"https:\/\/soci.hu\/blog\/index.php\/wp-json\/wp\/v2\/media?parent=460"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/soci.hu\/blog\/index.php\/wp-json\/wp\/v2\/categories?post=460"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/soci.hu\/blog\/index.php\/wp-json\/wp\/v2\/tags?post=460"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}