sql >> データベース >  >> RDS >> Database

Hekatonのネイティブにコンパイルされたストアドプロシージャを呼び出さない方法

    注:この投稿は元々、eBook、SQL Serverの高性能テクニック、第2巻でのみ公開されていました。eBookについては、こちらをご覧ください。また、SQL Server 2016で計画されているインメモリOLTPの機能強化により、これらの一部が変更される可能性があることにも注意してください。

    Transact-SQLコードに関して、私たちの多くが時間の経過とともに開発するいくつかの習慣とベストプラクティスがあります。特にストアドプロシージャでは、正しいデータ型のパラメータ値を渡すように努めており、序数の位置だけに依存するのではなく、パラメータに明示的に名前を付けています。ただし、これについて怠惰になる場合があります。Unicode文字列の前に Nを付けるのを忘れる場合があります。 、またはパラメータ名を指定する代わりに、定数または変数を順番にリストします。またはその両方。

    SQL Server 2014では、インメモリOLTP( "Hekaton")とネイティブにコンパイルされたプロシージャを使用している場合、これらのことについての考え方を少し調整することをお勧めします。私は、AdventureWorks2012サンプルデータベースを拡張するCodePlex上のSQL Server2014RTMインメモリOLTPサンプルに対するいくつかのコードでデモンストレーションします。 (これを最初から設定してフォローする場合は、前の投稿での私の観察結果をざっと見てください。)

    ストアドプロシージャSales.usp_InsertSpecialOffer_inmemの署名を見てみましょう。 :

    CREATE PROCEDURE [Sales].[usp_InsertSpecialOffer_inmem] 
    	@Description    NVARCHAR(255)  NOT NULL, 
    	@DiscountPct    SMALLMONEY     NOT NULL = 0,
    	@Type           NVARCHAR(50)   NOT NULL,
    	@Category       NVARCHAR(50)   NOT NULL,
    	@StartDate      DATETIME2      NOT NULL,
    	@EndDate        DATETIME2      NOT NULL,
    	@MinQty         INT            NOT NULL = 0,
    	@MaxQty         INT                     = NULL,
    	@SpecialOfferID INT OUTPUT
    WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
    AS
    BEGIN ATOMIC 
    WITH (TRANSACTION ISOLATION LEVEL=SNAPSHOT, LANGUAGE=N'us_english')
     
    	DECLARE @msg nvarchar(256)
     
            -- validation removed for brevity
     
    	INSERT Sales.SpecialOffer_inmem (Description, 
    		DiscountPct,
    		Type,
    		Category,
    		StartDate,
    		EndDate,
    		MinQty,
    		MaxQty) 
    	VALUES (@Description, 
    		@DiscountPct,
    		@Type,
    		@Category,
    		@StartDate,
    		@EndDate,
    		@MinQty,
    		@MaxQty)
     
    	SET @SpecialOfferID = SCOPE_IDENTITY()
    END
    GO

    パラメータに名前が付けられているかどうか、またはネイティブにコンパイルされたプロシージャが、従来のストアドプロシージャよりも優れたストアドプロシージャへの引数として暗黙の変換を処理するかどうかが重要であるかどうかに興味がありました。まず、コピーを作成しました Sales.usp_InsertSpecialOffer_inmem 従来のストアドプロシージャとして–これには ATOMICを削除するだけで済みます NOT NULLをブロックして削除する 入力パラメータからの宣言:

    CREATE PROCEDURE [Sales].[usp_InsertSpecialOffer] 
    	@Description    NVARCHAR(255), 
    	@DiscountPct    SMALLMONEY     = 0,
    	@Type           NVARCHAR(50),
    	@Category       NVARCHAR(50),
    	@StartDate      DATETIME2,
    	@EndDate        DATETIME2,
    	@MinQty         INT            = 0,
    	@MaxQty         INT            = NULL,
    	@SpecialOfferID INT OUTPUT
    AS
    BEGIN
    	DECLARE @msg nvarchar(256)
     
            -- validation removed for brevity
     
    	INSERT Sales.SpecialOffer_inmem (Description, 
    		DiscountPct,
    		Type,
    		Category,
    		StartDate,
    		EndDate,
    		MinQty,
    		MaxQty) 
    	VALUES (@Description, 
    		@DiscountPct,
    		@Type,
    		@Category,
    		@StartDate,
    		@EndDate,
    		@MinQty,
    		@MaxQty)
     
    	SET @SpecialOfferID = SCOPE_IDENTITY()
    END
    GO

    シフト基準を最小限に抑えるために、プロシージャは引き続きインメモリバージョンのテーブルSales.SpecialOffer_inmemに挿入します。

    次に、次の基準を使用して、ストアドプロシージャの両方のコピーに対して100,000回の呼び出しの時間を計りたいと思いました。

    明示的に名前が付けられたパラメータ 名前のないパラメータ
    正しいデータ型のすべてのパラメータ x x
    間違ったデータ型の一部のパラメータ x x


    次のバッチを使用して、従来のバージョンのストアドプロシージャ用にコピーします( _inmem を削除するだけです)。 4つのEXECから 呼び出し):

    SET NOCOUNT ON;
     
    CREATE TABLE #x
    (
      i INT IDENTITY(1,1),
      d VARCHAR(32), 
      s DATETIME2(7) NOT NULL DEFAULT SYSDATETIME(), 
      e DATETIME2(7)
    );
    GO
     
    INSERT #x(d) VALUES('Named, proper types');
    GO
     
    /* this uses named parameters, and uses correct data types */
     
    DECLARE 
    	@p1 NVARCHAR(255) = N'Product 1',
    	@p2 SMALLMONEY    = 10,
    	@p3 NVARCHAR(50)  = N'Volume Discount',
    	@p4 NVARCHAR(50)  = N'Reseller',
    	@p5 DATETIME2     = '20140615',
    	@p6 DATETIME2     = '20140620',
    	@p7 INT           = 10, 
    	@p8 INT           = 20, 
    	@p9 INT;
     
    EXEC Sales.usp_InsertSpecialOffer_inmem 
    	@Description    = @p1,
    	@DiscountPct    = @p2,
    	@Type           = @p3,
    	@Category       = @p4,
    	@StartDate      = @p5,
    	@EndDate        = @p6,
    	@MinQty         = @p7,
    	@MaxQty         = @p8,
    	@SpecialOfferID = @p9 OUTPUT;
     
    GO 100000
     
    UPDATE #x SET e = SYSDATETIME() WHERE i = 1;
    GO
     
    DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1';
    GO
     
    INSERT #x(d) VALUES('Not named, proper types');
    GO
     
    /* this does not use named parameters, but uses correct data types */
     
    DECLARE 
    	@p1 NVARCHAR(255) = N'Product 1',
    	@p2 SMALLMONEY    = 10,
    	@p3 NVARCHAR(50)  = N'Volume Discount',
    	@p4 NVARCHAR(50)  = N'Reseller',
    	@p5 DATETIME2     = '20140615',
    	@p6 DATETIME2     = '20140620',
    	@p7 INT           = 10, 
    	@p8 INT           = 20, 
    	@p9 INT;
     
    EXEC Sales.usp_InsertSpecialOffer_inmem 
    	@p1, @p2, @p3, @p4, @p5, 
    	@p6, @p7, @p8, @p9 OUTPUT;
     
    GO 100000
     
    UPDATE #x SET e = SYSDATETIME() WHERE i = 2;
    GO
     
    DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1';
    GO
     
    INSERT #x(d) VALUES('Named, improper types');
    GO
     
    /* this uses named parameters, but incorrect data types */
     
    DECLARE 
    	@p1 VARCHAR(255)  = 'Product 1',
    	@p2 DECIMAL(10,2) = 10,
    	@p3 VARCHAR(255)  = 'Volume Discount',
    	@p4 VARCHAR(32)   = 'Reseller',
    	@p5 DATETIME      = '20140615',
    	@p6 CHAR(8)       = '20140620',
    	@p7 TINYINT       = 10, 
    	@p8 DECIMAL(10,2) = 20, 
    	@p9 BIGINT;
     
    EXEC Sales.usp_InsertSpecialOffer_inmem 
    	@Description    = @p1,
    	@DiscountPct    = @p2,
    	@Type           = @p3,
    	@Category       = @p4,
    	@StartDate      = @p5,
    	@EndDate        = @p6,
    	@MinQty         = '10',
    	@MaxQty         = @p8,
    	@SpecialOfferID = @p9 OUTPUT;
     
    GO 100000
     
    UPDATE #x SET e = SYSDATETIME() WHERE i = 3;
    GO
     
    DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1';
    GO
     
    INSERT #x(d) VALUES('Not named, improper types');
    GO
     
    /* this does not use named parameters, and uses incorrect data types */
     
    DECLARE 
    	@p1 VARCHAR(255)  = 'Product 1',
    	@p2 DECIMAL(10,2) = 10,
    	@p3 VARCHAR(255)  = 'Volume Discount',
    	@p4 VARCHAR(32)   = 'Reseller',
    	@p5 DATETIME      = '20140615',
    	@p6 CHAR(8)       = '20140620',
    	@p7 TINYINT       = 10, 
    	@p8 DECIMAL(10,2) = 20, 
    	@p9 BIGINT;
     
    EXEC Sales.usp_InsertSpecialOffer_inmem 
    	@p1, @p2, @p3, @p4, @p5, 
    	@p6, '10', @p8, @p9 OUTPUT;
     
    GO 100000
     
    UPDATE #x SET e = SYSDATETIME() WHERE i = 4;
    GO
    DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1';
    GO
     
    SELECT d, duration_ms = DATEDIFF(MILLISECOND, s, e) FROM #x;
    GO
    DROP TABLE #x;
    GO

    各テストを10回実行しました。これは、ミリ秒単位の平均期間です。

    従来のストアドプロシージャ
    パラメータ 平均継続時間
    (ミリ秒)
    名前付き、適切なタイプ 72,132
    名前なし、適切なタイプ 72,846
    名前付き、不適切なタイプ 76,154
    名前がない、不適切なタイプ 76,902
    ネイティブにコンパイルされたストアドプロシージャ
    パラメータ 平均継続時間
    (ミリ秒)
    名前付き、適切なタイプ 63,202
    名前なし、適切なタイプ 61,297
    名前付き、不適切なタイプ 64,560
    名前がない、不適切なタイプ 64,288

    さまざまな呼び出しメソッドの平均継続時間(ミリ秒単位)

    従来のストアドプロシージャでは、間違ったデータ型を使用するとパフォーマンスに大きな影響があり(約4秒の違い)、パラメータに名前を付けないとそれほど劇的な影響はありません(約700ミリ秒追加)。私は常にベストプラクティスに従い、適切なデータ型を使用し、すべてのパラメータに名前を付けるように努めてきました。この小さなテストでは、そうすることが有益であることが確認されているようです。

    ネイティブにコンパイルされたストアドプロシージャでは、間違ったデータ型を使用すると、従来のストアドプロシージャと同様のパフォーマンスの低下が発生しました。ただし、今回は、パラメーターに名前を付けることはあまり役に立ちませんでした。実際、それはマイナスの影響を及ぼし、全体の持続時間にほぼ2秒を追加しました。公平を期すために、これはかなり短時間での大量の呼び出しですが、この機能から可能な限り最先端のパフォーマンスを絞り込もうとしている場合は、ナノ秒ごとにカウントされます。

    問題の発見

    ネイティブにコンパイルされたストアドプロシージャがこれらの「遅い」メソッドのいずれかで呼び出されているかどうかをどのように知ることができますか?そのためのXEventがあります!このイベントはnatively_compiled_proc_slow_parameter_passingと呼ばれます 、現時点ではBooksOnlineに記載されていないようです。次の拡張イベントセッションを作成して、このイベントを監視できます。

    CREATE EVENT SESSION [XTP_Parameter_Events] ON SERVER 
    ADD EVENT sqlserver.natively_compiled_proc_slow_parameter_passing
    (
        ACTION(sqlserver.sql_text)
    ) 
    ADD TARGET package0.event_file(SET filename=N'C:\temp\XTPParams.xel');
    GO
    ALTER EVENT SESSION [XTP_Parameter_Events] ON SERVER STATE = START;

    セッションが実行されたら、上記の4つの呼び出しのいずれかを個別に試すことができ、次のクエリを実行できます。

    ;WITH x([timestamp], db, [object_id], reason, batch)
    AS
    (
      SELECT 
        xe.d.value(N'(event/@timestamp)[1]',N'datetime2(0)'),
        DB_NAME(xe.d.value(N'(event/data[@name="database_id"]/value)[1]',N'int')),
        xe.d.value(N'(event/data[@name="object_id"]/value)[1]',N'int'),
        xe.d.value(N'(event/data[@name="reason"]/text)[1]',N'sysname'),
        xe.d.value(N'(event/action[@name="sql_text"]/value)[1]',N'nvarchar(max)')
      FROM 
        sys.fn_xe_file_target_read_file(N'C:\temp\XTPParams*.xel',NULL,NULL,NULL) AS ft
        CROSS APPLY (SELECT CONVERT(XML, ft.event_data)) AS xe(d)
    )
    SELECT [timestamp], db, [object_id], reason, batch FROM x;

    実行した内容に応じて、次のような結果が表示されます。

    タイムスタンプ db object_id 理由 バッチ
    2014-07-01 16:23:14 AdventureWorks2012 2087678485 named_pa​​rameters
    DECLARE 
    	@p1 NVARCHAR(255) = N'Product 1',
    	@p2 SMALLMONEY    = 10,
    	@p3 NVARCHAR(50)  = N'Volume Discount',
    	@p4 NVARCHAR(50)  = N'Reseller',
    	@p5 DATETIME2     = '20140615',
    	@p6 DATETIME2     = '20140620',
    	@p7 INT           = 10, 
    	@p8 INT           = 20, 
    	@p9 INT;
    
    EXEC Sales.usp_InsertSpecialOffer_inmem 
    	@Description    = @p1,
    	@DiscountPct    = @p2,
    	@Type           = @p3,
    	@Category       = @p4,
    	@StartDate      = @p5,
    	@EndDate        = @p6,
    	@MinQty         = @p7,
    	@MaxQty         = @p8,
    	@SpecialOfferID = @p9 OUTPUT;
    2014-07-01 16:23:22 AdventureWorks2012 2087678485 parameter_conversion
    DECLARE 
    	@p1 VARCHAR(255)  = 'Product 1',
    	@p2 DECIMAL(10,2) = 10,
    	@p3 VARCHAR(255)  = 'Volume Discount',
    	@p4 VARCHAR(32)   = 'Reseller',
    	@p5 DATETIME      = '20140615',
    	@p6 CHAR(8)       = '20140620',
    	@p7 TINYINT       = 10, 
    	@p8 DECIMAL(10,2) = 20, 
    	@p9 BIGINT;
    
    EXEC Sales.usp_InsertSpecialOffer_inmem 
    	@p1, @p2, @p3, @p4, @p5, 
    	@p6, '10', @p8, @p9 OUTPUT;

    拡張イベントのサンプル結果

    うまくいけば、 batch 列は原因を特定するのに十分ですが、ネイティブにコンパイルされたプロシージャへの複数の呼び出しを含む大きなバッチがあり、この問題を具体的に引き起こしているオブジェクトを追跡する必要がある場合は、object_id それぞれのデータベースで。

    さて、セッションがアクティブなときにテキストで40万回の呼び出しをすべて実行したり、同時性の高い本番環境でこのセッションをオンにしたりすることはお勧めしません。これを頻繁に行うと、かなりのオーバーヘッドが発生する可能性があります。ビジネスサイクル全体をカバーする適切なワークロードを実行できる限り、開発環境またはステージング環境でこの種のアクティビティをチェックすることをお勧めします。

    結論

    長い間ベストプラクティスと見なされていた名前付けパラメーターが、ネイティブにコンパイルされたストアドプロシージャでは、最悪のプラクティスに変わったという事実に、私は間違いなく驚きました。また、Microsoftは、それを追跡するために特別に設計された拡張イベントを作成したことで、潜在的な問題が十分にあることを知っています。インメモリOLTPを使用している場合、これは、サポートするストアドプロシージャを開発するときにレーダーで維持する必要があることの1つです。名前付きパラメーターを使用しないように、筋肉の記憶を訓練しないようにする必要があることは間違いありません。


    1. Unicodeを完全にサポートするSQLiteで大文字と小文字を区別しない検索を実装する5つの方法

    2. T-SQLを使用してSQLServerのリンクサーバーを削除する

    3. SQL ServerからSqlDataReaderへのデータのフェッチはどのように機能しますか?

    4. InnoDBを使用した全文検索