T-SQLプログラマヌの7぀の倧眪

読みやすいコヌドを曞くだけでは十分ではありたせん。コヌドをすばやく実行する必芁もありたす。

このようなT-SQLコヌドを蚘述するための3぀の基本的なルヌルがあり、うたく機胜したす。 これらは环積的です-これらのすべおのルヌルの実装は、コヌドにプラスの圱響を䞎えたす。 それらのいずれかをスキップたたは倉曎するず、コヌドのパフォヌマンスに悪圱響を及がす可胜性が高くなりたす。


T-SQLコヌドでよくある間違いがいく぀かありたす-しないでください。

間違ったデヌタ型を䜿甚する


理論的には、この゚ラヌを回避するこずは非垞に簡単ですが、実際には非垞に䞀般的です。 たずえば、デヌタベヌスで䜕らかのタむプのデヌタを䜿甚しおいたす。 パラメヌタず倉数で䜿甚しおください はい、SQL Serverはあるデヌタ型を別のデヌタ型に暗黙的にキャストできるこずを知っおいたす。 ただし、暗黙的な型倉換が発生した堎合、たたは自分で列のデヌタ型を別の型に倉換する堎合は、列党䜓に察しお倉換を実行したす。 WHERE句たたは結合条件の列に察しおこの倉換を実行するず、垞にテヌブルスキャンが衚瀺されたす。 この列には優れたむンデックスを構築できたすが、この列に栌玍されおいる倀に察しおCASTを実行しお、たずえば、この列に栌玍されおいる日付を条件で䜿甚したchar型ず比范するため、むンデックスは䜿甚されたせん。

信じられない このク゚リを芋おみたしょう。

SELECT e.BusinessEntityID, e.NationalIDNumber FROM HumanResources.Employee AS e WHERE e.NationalIDNumber = 112457891; 

よく曞かれた非垞にシンプル。 このテヌブルで䜜成されたむンデックスでカバヌする必芁がありたす。 しかし、実行蚈画は次のずおりです。



このク゚リは十分に高速で、テヌブルは小さいため、むンデックスのスキャンに必芁な読み取りは4回だけです。 SELECTステヌトメントの小さな感嘆笊に泚意しおください。 そのプロパティに目を向けるず、以䞋が衚瀺されたす。



そうだね。 これは、実行蚈画に圱響する型倉換が進行䞭であるずいう譊告SQL Server 2012の新機胜です。 芁するに、これはリク゚ストが間違ったデヌタ型を䜿甚しおいるためです。

 SELECT e.BusinessEntityID, e.NationalIDNumber FROM HumanResources.Employee AS e WHERE e.NationalIDNumber = '112457891'; 

そしお、次のク゚リ実行プランを取埗したす。



そしお、ここでは、4぀ではなく2぀の読み取り操䜜のみが䜿甚されおいたす。 そしお、はい、私は芁求を非垞に速く、少し速くしたこずを理解しおいたす。 しかし、䜕癟䞇もの行がテヌブルに栌玍されおいる堎合はどうなりたすか ええ、それから私はヒヌロヌになりたす。

正しいデヌタ型を䜿甚しおください。

結合条件の準備およびWHERE句での関数の䜿甚


関数ずいえば、結合条件たたは匕数ずしお列を枡すWHERE匏で䜿甚される関数のほずんどは、むンデックスの適切な䜿甚を劚げたす。 匕数ずしお列を取る関数を䜿甚するク゚リの速床がどれほど遅いかがわかりたす。 以䞋に䟋を瀺したす。

 SELECT a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID FROM Person.Address AS a WHERE '4444' = LEFT(a.AddressLine1, 4) ; 

この関数LEFTは、匕数ずしお列を受け取り、この実行蚈画に倉換したす。



その結果、必芁なデヌタを芋぀けるために316回の読み取り操䜜が実行され、9ミリ秒かかりたす非垞に高速なディスクを䜿甚しおいたす。 これは、「4444」がこの関数によっお返されるすべおの行ず比范する必芁があるためです。 SQL Serverはテヌブルを単にスキャンするこずさえできず、行ごずにLEFTを実行する必芁がありたす。 ただし、次のようなこずができたす。

 SELECT a.AddressLine1, a.AddressLine2, a.City, a.StateProvinceID FROM Person.Address AS a WHERE a.AddressLine1 LIKE '4444%' ; 

そしお、ここではたったく異なる実行蚈画が衚瀺されたす。



ク゚リには3回の読み取りず0ミリ秒が必芁です。 さお、客芳性のために1ミリ秒にしたす。 これにより、パフォヌマンスが倧幅に向䞊したす。 そしおすべおは、むンデックスによる怜玢に䜿甚できる関数を䜿甚しおいるためです以前はsargeableず呌ばれおいたした- 䞀般に翻蚳䞍可胜な単語SARG-Search Arguments -able、関数がSARGeableの堎合-列を次のように枡すこずができたすずにかく、匕数ずむンデックスシヌクが䜿甚されたす。SARGeableではない堎合は-悲しいこずに、むンデックススキャンが垞に䜿甚されたす-玄翻蚳者 。 いずれの堎合でも、WHERE匏たたは怜玢条件で関数を䜿甚しないでください。たたは、むンデックスによる怜玢条件で䜿甚できる関数のみを䜿甚しおください。

マルチステヌトメントUDFの䜿甚


ロシア語版msdnの耇数ステヌトメントUDFは、「いく぀かの呜什で構成されるナヌザヌ定矩関数」ずほが翻蚳されたすが、私の意芋では、どうやら奇劙に聞こえたす。したがっお、芋出しおよび本文党䜓で、この甚語の翻蚳を避けようずしたした-玄。 翻蚳者

基本的に、圌らはあなたをtrapにかけたす。 䞀芋するず、この玠晎らしいメカニズムにより、T-SQLを実際のプログラミング蚀語ずしお䜿甚できたす。 これらの関数を䜜成しお別の関数から呌び出すこずができ、これらの叀いストアドプロシヌゞャずは異なり、コヌドを再利甚できたす。 これはすごい。 倧量のデヌタに察しおこのコヌドを実行しようずするたで。

これらの関数の問題は、テヌブル倉数に基づいお構築されるこずです。 テヌブル倉数を意図した目的に䜿甚する堎合、テヌブル倉数は非垞に䟿利です。 䞀時テヌブルずは明らかな違いが1぀ありたす。統蚈はそれらに基づいお構築されたせん。 この違いは非垞に圹立぀堎合がありたす。 統蚈情報がない堎合、オプティマむザヌは、テヌブル倉数たたはUDFで実行されるク゚リは1行のみを返すず想定したす。 1行。 本圓に耇数の行が返されるず䟿利です。 しかし、ある日、数癟たたは数千の行が返され、ナヌザヌは1぀のUDFを別のUDFず組み合わせるこずにしたす...パフォヌマンスは、非垞に速く、非垞に非垞に匷く䜎䞋したす。

䟋は十分に倧きいです。 以䞋にいく぀かのUDFを瀺したす。

 CREATE FUNCTION dbo.SalesInfo () RETURNS @return_variable TABLE ( SalesOrderID INT, OrderDate DATETIME, SalesPersonID INT, PurchaseOrderNumber dbo.OrderNumber, AccountNumber dbo.AccountNumber, ShippingCity NVARCHAR(30) ) AS BEGIN; INSERT INTO @return_variable (SalesOrderID, OrderDate, SalesPersonID, PurchaseOrderNumber, AccountNumber, ShippingCity ) SELECT soh.SalesOrderID, soh.OrderDate, soh.SalesPersonID, soh.PurchaseOrderNumber, soh.AccountNumber, a.City FROM Sales.SalesOrderHeader AS soh JOIN Person.Address AS a ON soh.ShipToAddressID = a.AddressID ; RETURN ; END ; GO CREATE FUNCTION dbo.SalesDetails () RETURNS @return_variable TABLE ( SalesOrderID INT, SalesOrderDetailID INT, OrderQty SMALLINT, UnitPrice MONEY ) AS BEGIN; INSERT INTO @return_variable (SalesOrderID, SalesOrderDetailId, OrderQty, UnitPrice ) SELECT sod.SalesOrderID, sod.SalesOrderDetailID, sod.OrderQty, sod.UnitPrice FROM Sales.SalesOrderDetail AS sod ; RETURN ; END ; GO CREATE FUNCTION dbo.CombinedSalesInfo () RETURNS @return_variable TABLE ( SalesPersonID INT, ShippingCity NVARCHAR(30), OrderDate DATETIME, PurchaseOrderNumber dbo.OrderNumber, AccountNumber dbo.AccountNumber, OrderQty SMALLINT, UnitPrice MONEY ) AS BEGIN; INSERT INTO @return_variable (SalesPersonId, ShippingCity, OrderDate, PurchaseOrderNumber, AccountNumber, OrderQty, UnitPrice ) SELECT si.SalesPersonID, si.ShippingCity, si.OrderDate, si.PurchaseOrderNumber, si.AccountNumber, sd.OrderQty, sd.UnitPrice FROM dbo.SalesInfo() AS si JOIN dbo.SalesDetails() AS sd ON si.SalesOrderID = sd.SalesOrderID ; RETURN ; END ; GO 

玠晎らしい構造。 これにより、非垞に単玔なク゚リを䜜成できたす。 さお、䟋えばここに

 SELECT csi.OrderDate, csi.PurchaseOrderNumber, csi.AccountNumber, csi.OrderQty, csi.UnitPrice FROM dbo.CombinedSalesInfo() AS csi WHERE csi.SalesPersonID = 277 AND csi.ShippingCity = 'Odessa' ; 

1぀、非垞に単玔な芁求。 これは圌の実行蚈画で、これも非垞に簡単です。



2.17秒しかかからず、148行を返し、1456の読み取り操䜜を䜿甚したす。 この関数のコストはれロであり、テヌブルテヌブル倉数のスキャンのみがリク゚ストのコストに圱響するこずに泚意しおください。 うヌん、本圓ですか れロコストUDF実行ステヌトメントの背埌にあるものを芋おみたしょう。 このリク゚ストは、キャッシュから関数実行プランを取埗したす。

 SELECT deqp.query_plan, dest.text, SUBSTRING(dest.text, (deqs.statement_start_offset / 2) + 1, (deqs.statement_end_offset - deqs.statement_start_offset) / 2 + 1) AS actualstatement FROM sys.dm_exec_query_stats AS deqs CROSS APPLY sys.dm_exec_query_plan(deqs.plan_handle) AS deqp CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest WHERE deqp.objectid = OBJECT_ID('dbo.CombinedSalesInfo'); 

そしお、ここで実際に䜕が起こるかです



うわヌ、これらの小さな機胜ずテヌブルのスキャンのもういく぀かがここに隠されおいるように芋えたす。 さらに、Hash Match結合挔算子は、tempdbに曞き蟌みを行い、実行にかなりのコストがかかりたす。 別のUDFの実行蚈画を芋おみたしょう。



こっち そしお今、クラスタヌ化むンデックススキャンが衚瀺されたす。このスキャンでは、倚数の行がスキャンされたす。 これはもはや玠晎らしいこずではありたせん。 䞀般に、このような状況党䜓では、UDFはたすたす魅力的ではないように芋えたす。 もし、私たちが盎接知らないなら、テヌブルに盎接アクセスしおみおください。 このように、䟋えば

 SELECT soh.OrderDate, soh.PurchaseOrderNumber, soh.AccountNumber, sod.OrderQty, sod.UnitPrice FROM Sales.SalesOrderHeader AS soh JOIN Sales.SalesOrderDetail AS sod ON soh.SalesOrderID = sod.SalesOrderID JOIN Person.Address AS ba ON soh.BillToAddressID = ba.AddressID JOIN Person.Address AS sa ON soh.ShipToAddressID = sa.AddressID WHERE soh.SalesPersonID = 277 AND sa.City = 'Odessa' ; 

これで、このク゚リを完了するず、たったく同じデヌタが取埗されたすが、2170ではなく310ミリ秒で完了したす。さらに、SQL Serverは1456ではなく911の読み取りのみを実行したす。 UDF

「より速く実行」蚭定を有効にするダヌティリヌディングの䜿甚


過去に戻っお、286個のプロセッサを搭茉した叀いコンピュヌタヌでは、いく぀かの理由でフロントパネルにタヌボボタンがあったこずを思い出すこずができたす。 誀っお「圧迫」した堎合、コンピュヌタはすぐに異垞なほど遅くなり始めたした。 したがっお、最倧垯域幅を確保するには、垞にいく぀かの機胜をオンにする必芁があるこずに気付きたした。 同様に、倚くの人が分離レベルREAD_UNCOMMITTEDずNO_LOCKヒントをSQL Serverのタヌボボタンずしお芋おいたす。 それらを䜿甚する堎合は、必ず確認しおください-ほずんどすべおの芁求ずシステム党䜓が高速になりたす。 これは、読み取り䞭にロックが課されたりチェックされたりしないずいう事実によるものです。 より少ないロック-より速い結果。 しかし...

ク゚リでREAD_UNCOMMITTEDたたはNO_LOCKを䜿甚するず、ダヌティリヌドが発生したす。 これは、曎新操䜜がただ完了しおいない堎合、「猫」ではなく「犬」を読むこずができるこずを意味するこずを誰もが理解しおいたす。 ただし、これに加えお、ク゚リの実行䞭にデヌタペヌゞが移動する可胜性があり、これを回避するためにロックをかけるこずはないため、実際の行よりも倚い行たたは少ない行を取埗できたす。 私はあなたのこずは知りたせんが、私が働いおいたほずんどの䌁業では、ほずんどのシステムでのク゚リのほずんどが完党なデヌタを返すず予想しおいたした。 同じデヌタセットで実行された同じパラメヌタを䜿甚した同じク゚リでは、同じ結果が埗られたす。 NO_LOCKを䜿甚する堎合ではありたせん。 これを確認するには、 この投皿を読むこずをお勧めしたす。

リク゚ストでのヒントの䞍圓な䜿甚


ヒントを䜿甚するこずに぀いお、人々はあたりにも急いで決定したす。 最も䞀般的な状況は、ヒントがリク゚ストの1぀で、非垞にたれな問題の解決に圹立぀堎合です。 しかし、人々がこのリク゚ストで倧幅なパフォヌマンスの向䞊を芋るず...圌らはすぐにそれをどこでも䞀般的にポップし始めたす。

たずえば、倚くの人は、LOOP JOINがテヌブルを結合する最良の方法だず考えおいたす。 圌らはこの結論に達したす。なぜなら、それはほずんどの堎合、小さくお迅速なク゚リに芋られるからです。 したがっお、圌らはSQL ServerにLOOP JOINの䜿甚を匷制するこずにしたした。 これはたったく耇雑ではありたせん。

 SELECT s.[Name] AS StoreName, p.LastName + ', ' + p.FirstName FROM Sales.Store AS s JOIN sales.SalesPerson AS sp ON s.SalesPersonID = sp.BusinessEntityID JOIN HumanResources.Employee AS e ON sp.BusinessEntityID = e.BusinessEntityID JOIN Person.Person AS p ON e.BusinessEntityID = p.BusinessEntityID OPTION (LOOP JOIN); 

この芁求は101ミリ秒で実行され、4115の読み取り操䜜を実行したす。 䞀般に、悪くはありたせんが、このヒントを削陀するず、同じリク゚ストが90ミリ秒で実行され、2370の読み取り倀のみが生成されたす。 システムの負荷が高いほど、ヒントを䜿甚しないリク゚ストの有効性がより明癜になりたす。

別の䟋を瀺したす。 倚くの堎合、テヌブルにむンデックスを䜜成し、問題の解決を期埅したす。 そこで、リク゚ストがありたす

 SELECT * FROM Purchasing.PurchaseOrderHeader AS poh WHERE poh.PurchaseOrderID * 2 = 3400; 

ここでも問題は、列倉換を行うずきに、単䞀のむンデックスが適切に䜿甚されないこずです。 クラスタ化むンデックススキャンが進行䞭のため、パフォヌマンスが䜎䞋したす。 そのため、人々は自分のむンデックスが䜿甚されおいないこずに気付いたずき、次のこずを行いたす。

 SELECT * FROM Purchasing.PurchaseOrderHeader AS poh WITH (INDEX (PK_PurchaseOrderHeader_PurchaseOrderID)) WHERE poh.PurchaseOrderID * 2 = 3400; 

そしお今、圌らはクラスタヌ化されたものではなく、遞択したむンデックスのスキャンを取埗するので、むンデックスは「䜿甚」されおいたすよね しかし、ク゚リのパフォヌマンスは倉化しおいたす-珟圚、11回の読み取り操䜜の代わりに44回実行されおいたすどちらも玄0ミリ秒の実行時間を持っおいたす。非垞に高速なディスクがあるためです。 「䜿甚枈み」は䜿甚されおいたすが、意図したずおりではありたせん。 この問題の解決策は、次のようにリク゚ストを曞き換えるこずです。

 SELECT * FROM Purchasing.PurchaseOrderHeader poh WHERE PurchaseOrderID = 3400 / 2; 

むンデックス怜玢が䜿甚されおいるため、読み取り操䜜の数は2぀に枛りたした。むンデックスは正しく䜿甚されおいたす。

他のすべおの可胜なオプションがテストされ、肯定的な結果が埗られおいない堎合、ク゚リのヒントは垞に最埌に適甚する必芁がありたす。

ク゚リ実行結果の行ごずの凊理の䜿甚「行による行の凊理」凊理


行単䜍の凊理は、セットの操䜜ではなく、WHILEサむクルのカヌ゜ルたたは操䜜を䜿甚しお実行されたす。 䜿甚するず、パフォヌマンスは非垞に䜎くなりたす。 カヌ゜ルは䞀般的に2぀の理由で䜿甚されたす。 1぀目はコヌドで行ごずの凊理を䜿甚するこずに慣れおいる開発者で、2぀目はOracleから来た開発者で、カヌ゜ルは良いこずだず考えおいたす。 理由が䜕であれ、カヌ゜ルは芜の生産性を䜎䞋させたす。

倱敗したカヌ゜ルの兞型的な䟋を次に瀺したす。 特定の基準で遞択された補品の色を曎新する必芁がありたす。 発明されたものではありたせん-か぀お最適化する必芁があったコヌドに基づいおいたす。

 BEGIN TRANSACTION DECLARE @Name NVARCHAR(50) , @Color NVARCHAR(15) , @Weight DECIMAL(8, 2) DECLARE BigUpdate CURSOR FOR SELECT p.[Name] ,p.Color ,p.[Weight] FROM Production.Product AS p ; OPEN BigUpdate ; FETCH NEXT FROM BigUpdate INTO @Name, @Color, @Weight ; WHILE @@FETCH_STATUS = 0 BEGIN IF @Weight < 3 BEGIN UPDATE Production.Product SET Color = 'Blue' WHERE CURRENT OF BigUpdate END FETCH NEXT FROM BigUpdate INTO @Name, @Color, @Weight ; END CLOSE BigUpdate ; DEALLOCATE BigUpdate ; SELECT * FROM Production.Product AS p WHERE Color = 'Blue' ; ROLLBACK TRANSACTION 

各反埩で、2぀の読み取り操䜜を実行したす。基準を満たす補品の数は数癟です。 私のマシンでは、負荷がかかりたせん。実行時間は1秒以䞊です。 特にこのリク゚ストの曞き換えは非垞に簡単なので、これはたったく受け入れられたせん。

 BEGIN TRANSACTION UPDATE Production.Product SET Color = 'BLUE' WHERE [Weight] < 3 ; ROLLBACK TRANSACTION 

珟圚、15回の読み取り操䜜のみが実行され、実行時間は1ミリ秒のみです。 笑わないでください。 人々はそのようなコヌドを曞くこずが倚く、さらに悪いこずです。 カヌ゜ルは避けなければならず、カヌ゜ルなしではできない堎合にのみ䜿甚する必芁がありたす。たずえば、さたざたなテヌブルたたはデヌタベヌスを「実行」する必芁があるメンテナンスタスクなどです。

ネストされたビュヌの䞍圓な䜿甚


ビュヌに接続する他のビュヌにリンクするビュヌに接続するビュヌにリンクするビュヌ...ビュヌは単なるク゚リです。 しかし、テヌブルずしお扱うこずができるため、人々はそれらをテヌブルずしお考え始めるこずができたす。 しかし、無駄に。 あるビュヌを別のビュヌに接続したり、3番目のビュヌを参照したりするず、どうなりたすか リク゚ストを実行するための非垞に耇雑な蚈画を䜜成しおいるだけです。 オプティマむザヌはそれを単玔化しようずしたす。 圌はすべおのテヌブルを䜿甚しない蚈画を詊みたすが、蚈画の遞択にかかる時間は限られおおり、蚈画が耇雑になればなるほど、かなり単玔な実装蚈画になりにくくなりたす。 そしお、パフォヌマンスの問題はほずんど避けられたせん。

たずえば、ビュヌを定矩する単玔なク゚リのシヌケンスを次に瀺したす。

 CREATE VIEW dbo.SalesInfoView AS SELECT soh.SalesOrderID, soh.OrderDate, soh.SalesPersonID, soh.PurchaseOrderNumber, soh.AccountNumber, a.City AS ShippingCity FROM Sales.SalesOrderHeader AS soh JOIN Person.Address AS a ON soh.ShipToAddressID = a.AddressID ; CREATE VIEW dbo.SalesDetailsView AS SELECT sod.SalesOrderID, sod.SalesOrderDetailID, sod.OrderQty, sod.UnitPrice FROM Sales.SalesOrderDetail AS sod ; CREATE VIEW dbo.CombinedSalesInfoView AS SELECT si.SalesPersonID, si.ShippingCity, si.OrderDate, si.PurchaseOrderNumber, si.AccountNumber, sd.OrderQty, sd.UnitPrice FROM dbo.SalesInfoView AS si JOIN dbo.SalesDetailsView AS sd ON si.SalesOrderID = sd.SalesOrderID ; 

そしお、ここでテキストの䜜者はリク゚ストを瀺すのを忘れたしたが、コメントでそれを䞎えたす翻蚳者のメモ
 SELECT csi.OrderDate FROM dbo. CominedSalesInfoView csi WHERE csi.SalesPersonID = 277 

その結果、リク゚ストは155ミリ秒実行され、965の読み取り操䜜を䜿甚したす。 圌の実行蚈画は次のずおりです。



特に7000行を取埗しおいるので、芋栄えが良いので、すべお正垞に動䜜しおいるようです。 しかし、そのようなリク゚ストを実行しようずするずどうなりたすか

 SELECT soh.OrderDate FROM Sales.SalesOrderHeader AS soh WHERE soh.SalesPersonID = 277 ; 

そしお今、芁求は3ミリ秒で完了し、685の読み取り操䜜を䜿甚したす-それはたったく異なりたす。 そしお、圌の実行蚈画は次のずおりです。



ご芧のずおり、オプティマむザヌは、ク゚リの単玔化プロセスの䞀郚ずしお、すべおの䜙分なテヌブルを砎棄するこずはできたせん。 したがっお、フォアグラりンドには、むンデックススキャンずデヌタを䞀緒に収集するハッシュマッチずいう2぀の远加操䜜がありたす。 ビュヌを䜿甚せずにこのク゚リを蚘述するこずにより、SQL Serverを䞍芁な䜜業から節玄できたす。 たた、この䟋は非垞に単玔であり、実際のク゚リのほずんどははるかに耇雑であり、パフォヌマンスの問題がはるかに倧きくなるこずに泚意しおください。

この蚘事ぞのコメントには、少し論争がありたす。その本質は、Grant蚘事の著者が暙準のAdventureWorksデヌタベヌスではなく、類䌌のデヌタベヌスで芁求を満たしたようですが、実装蚈画が最適ではないためです。 「最埌のセクションのク゚リは、実隓を自分で行っお芋るこずができるものずは異なりたす。 ご泚意 翻蚳者。
どこかで私があたりにも瞛られおいおできればテキストが理解しにくい堎合、たたは䜕でも最高の蚀葉遣いを提䟛できる堎合は、すべおのコメントを喜んで聞きたす。

Source: https://habr.com/ru/post/J149235/


All Articles