web-dev-qa-db-ja.com

ストアドプロシージャのチューニングにおけるvarcharとnvarchar-このシナリオでパフォーマンスを向上させる方法は?

1日に100万回以上呼び出される次の手順がありますが、リソースをより適切に使用するために調整できると思います。

ALTER PROCEDURE [DenormV2].[udpProductTaxRateGet]
(
    @itemNo varchar ( 20 ),
    @calculateDate datetime,
    @addressLine1 nvarchar( 50 ),
    @addressLine2 nvarchar( 50 ),
    @addressLine3 nvarchar( 50 ),
    @addressLine4 nvarchar( 50 ),
    @addressLine5 nvarchar( 50 ),
    @addressLine6 nvarchar( 50 ),
    @postalCode nvarchar( 20 ),
    @countryCode varchar( 2 ),
    @addressFormatID int
)
WITH EXECUTE AS 'webUserWithRW'
AS
--see Bocss2.dbo.[fnGetProductTax] for equivalent logic and comments in Bocss
DECLARE @Addresses TABLE (TaxRegionId int NOT NULL)

INSERT INTO @Addresses(TaxRegionId)
SELECT  DISTINCT TaxRegionId
FROM    dbo.[ShipTaxAddress]
WHERE   [CountryCode] = @countryCode
AND     [AddressFormatID] = @addressFormatID
AND     ISNULL (CONVERT(nvarchar(50),[MatchAddressLine1]), ISNULL(@addressLine1, '')) = ISNULL(@addressLine1, '')
AND     ISNULL (CONVERT(nvarchar(50),[MatchAddressLine2]), ISNULL(@addressLine2, '')) = ISNULL(@addressLine2, '')
AND     ISNULL (CONVERT(nvarchar(50),[MatchAddressLine3]), ISNULL(@addressLine3, '')) = ISNULL(@addressLine3, '')
AND     ISNULL (CONVERT(nvarchar(50),[MatchAddressLine4]), ISNULL(@addressLine4, '')) = ISNULL(@addressLine4, '')
AND     ISNULL (CONVERT(nvarchar(50),[MatchAddressLine5]), ISNULL(@addressLine5, '')) = ISNULL(@addressLine5, '')
AND     ISNULL (CONVERT(nvarchar(50),[MatchAddressLine6]), ISNULL(@addressLine6, '')) = ISNULL(@addressLine6, '')
AND     @postalcode Like ISNULL ( CONVERT(nvarchar(20),[MatchPostalCode]), @postalcode)




SELECT DISTINCT ISNULL(pst.TaxCode, '') as TaxCode
     , ISNULL(pst.TaxRate, 0) as TaxRate
FROM    dbo.[ProductShipTax] pst
        INNER JOIN
        @Addresses a
            ON pst.TaxRegionId = a.TaxRegionId
WHERE   pst.[ItemNo] = @itemNo
AND     @calculateDate BETWEEN pst.[DateFrom] AND pst.[DateTo]

GO

このプロシージャは、次のテーブルのテーブル変数に値を挿入します。テーブルの列はVARCHARであり、何らかの理由でストアドプロシージャのパラメータとコード内でも、すべてがNVARCHARに変換されることに注意してください。

IF OBJECT_ID('[dbo].[ShipTaxAddress]') IS NOT NULL 
DROP TABLE [dbo].[ShipTaxAddress] 
GO
CREATE TABLE [dbo].[ShipTaxAddress] ( 
[TaxRegionAddressId]  INT                              NOT NULL,
[TaxRegionId]         INT                              NOT NULL,
[CountryCode]         VARCHAR(2)                       NOT NULL,
[AddressFormatId]     INT                              NOT NULL,
[MatchAddressLine1]   VARCHAR(50)                          NULL,
[MatchAddressLine2]   VARCHAR(50)                          NULL,
[MatchAddressLine3]   VARCHAR(50)                          NULL,
[MatchAddressLine4]   VARCHAR(50)                          NULL,
[MatchAddressLine5]   VARCHAR(50)                          NULL,
[MatchAddressLine6]   VARCHAR(50)                          NULL,
[MatchPostalCode]     VARCHAR(20)                          NULL,
CONSTRAINT   [PK_ShipTaxAddress]  PRIMARY KEY CLUSTERED    ([TaxRegionAddressId] asc))

このテーブル[dbo]。[ShipTaxAddress]の行数は200未満であることに注意してください。

これは他のテーブルです:

sp_gettabledef 'dbo.ProductShipTax'-これは、テーブル定義を取得するために使用するものです。もし興味があれば コードは共有のためにここにあります

IF OBJECT_ID('[dbo].[ProductShipTax]') IS NOT NULL 
DROP TABLE [dbo].[ProductShipTax] 
GO
CREATE TABLE [dbo].[ProductShipTax] ( 
[ProductShipTaxID]  INT              IDENTITY(1,1)   NOT NULL,
[DateFrom]          SMALLDATETIME                    NOT NULL,
[DateTo]            SMALLDATETIME                    NOT NULL,
[TaxRate]           DECIMAL(18,4)                    NOT NULL,
[ItemNo]            VARCHAR(20)                      NOT NULL,
[TaxCode]           VARCHAR(20)                          NULL,
[TaxRegionId]       INT                              NOT NULL,
CONSTRAINT   [PK_ProductShipTax]  PRIMARY KEY CLUSTERED    ([ProductShipTaxID] asc))

GO

CREATE NONCLUSTERED INDEX [IX_ProductShipTax_ITemNo_DateFrom_DateTo] 
   ON [dbo].[ProductShipTax] ([ItemNo] asc, [DateFrom] asc, [DateTo] asc)

CREATE NONCLUSTERED INDEX [idx_ProductShipTax__K7_K5_K2_K3_K1_K4_6_INCL] 
   ON [dbo].[ProductShipTax] ([TaxRegionId] asc, [ItemNo] asc, [DateFrom] asc, [DateTo] asc, [ProductShipTaxID] asc, [TaxRate] asc)
   INCLUDE ([TaxCode])

これは このプロシージャのXML実行プラン 以下のステートメントに従って実行されます。

    exec dbo.udpProductTaxRateGet 
@itemNo=N'35638956',
@calculateDate='Aug  8 2016  1:01:46:760PM',
@addressLine1=N'',
@addressLine2=N'',
@addressLine3=N'114 FORGE LN',
@addressLine4=N'',
@addressLine5=N'FEASTERVILLE TREVOSE',
@addressLine6=N'PA',
@postalcode=N'190537838',
@countryCode=N'US',
@addressFormatID=2

どこから始めますか?

これが私がこの手順を改善した方法です:

次のインデックスを作成しました。

    CREATE INDEX IDX_ShipTaxAddress_ShipTaxAddress
    ON dbo.[ShipTaxAddress] (CountryCode,
                             AddressFormatID,
                             MatchPostalCode)
    INCLUDE (TaxRegionId,
             [MatchAddressLine1],
             [MatchAddressLine2],
             [MatchAddressLine3],
             [MatchAddressLine4],
             [MatchAddressLine5],
             [MatchAddressLine6])
    GO

     CREATE NONCLUSTERED INDEX IX_ProductShipTax_ITemNo_DateFrom_DateTo 
     ON [dbo].[ProductShipTax] (  [ItemNo] ASC  
                                , [DateFrom] ASC  
                                , [DateTo] ASC  )   
    INCLUDE (TaxRegionId ,TaxCode,TaxRate)
    WITH (DROP_EXISTING=ON)

変換の必要性をなくすために、テーブルの関連する列をVARCHARからNVARCHARに変更しました。テーブルは次のようになりました:

IF OBJECT_ID('[dbo].[ShipTaxAddress]') IS NOT NULL 
DROP TABLE [dbo].[ShipTaxAddress] 
GO
CREATE TABLE [dbo].[ShipTaxAddress] ( 
[TaxRegionAddressId]  INT                              NOT NULL,
[TaxRegionId]         INT                              NOT NULL,
[CountryCode]         VARCHAR(2)                       NOT NULL,
[AddressFormatId]     INT                              NOT NULL,
[MatchAddressLine1]   NVARCHAR(50)                         NULL,
[MatchAddressLine2]   NVARCHAR(50)                         NULL,
[MatchAddressLine3]   NVARCHAR(50)                         NULL,
[MatchAddressLine4]   NVARCHAR(50)                         NULL,
[MatchAddressLine5]   NVARCHAR(50)                         NULL,
[MatchAddressLine6]   NVARCHAR(50)                         NULL,
[MatchPostalCode]     NVARCHAR(20)                         NULL,
CONSTRAINT   [PK_ShipTaxAddress]  
PRIMARY KEY NONCLUSTERED ([TaxRegionAddressId] asc))
GO

手順を変更しました:

ALTER PROCEDURE [DenormV2].[udpProductTaxRateGet]
(
    @itemNo varchar ( 20 ),
    @calculateDate datetime,
    @addressLine1 nvarchar( 50 ),
    @addressLine2 nvarchar( 50 ),
    @addressLine3 nvarchar( 50 ),
    @addressLine4 nvarchar( 50 ),
    @addressLine5 nvarchar( 50 ),
    @addressLine6 nvarchar( 50 ),
    @postalCode nvarchar( 20 ),
    @countryCode varchar( 2 ),
    @addressFormatID int
)
WITH EXECUTE AS 'webUserWithRW'
AS
--see Bocss2.dbo.[fnGetProductTax] for equivalent logic and comments in Bocss


SELECT @postalcode    = CASE WHEN @postalcode   = N'' THEN NULL ELSE @postalcode   END
SELECT @addressLine1  = CASE WHEN @addressLine1 = N'' THEN NULL ELSE @addressLine1 END
SELECT @addressLine2  = CASE WHEN @addressLine2 = N'' THEN NULL ELSE @addressLine2 END
SELECT @addressLine3  = CASE WHEN @addressLine3 = N'' THEN NULL ELSE @addressLine3 END
SELECT @addressLine4  = CASE WHEN @addressLine4 = N'' THEN NULL ELSE @addressLine4 END
SELECT @addressLine5  = CASE WHEN @addressLine5 = N'' THEN NULL ELSE @addressLine5 END
SELECT @addressLine6  = CASE WHEN @addressLine6 = N'' THEN NULL ELSE @addressLine6 END



SELECT TOP 1  ISNULL(pst.TaxCode, '') as TaxCode
            , ISNULL(pst.TaxRate, 0) as TaxRate
FROM    dbo.[ProductShipTax] pst
WHERE EXISTS (

    SELECT  TaxRegionId
    FROM    dbo.[ShipTaxAddress]
    WHERE   [CountryCode] = @countryCode
    AND     [AddressFormatID] = @addressFormatID

    AND ([MatchAddressLine1] = @AddressLine1 OR ([MatchAddressLine1] IS NULL AND @AddressLine1 IS NULL) )
    AND ([MatchAddressLine2] = @AddressLine2 OR ([MatchAddressLine2] IS NULL AND @AddressLine2 IS NULL) )
    AND ([MatchAddressLine3] = @AddressLine3 OR ([MatchAddressLine3] IS NULL AND @AddressLine3 IS NULL) )
    AND ([MatchAddressLine4] = @AddressLine4 OR ([MatchAddressLine4] IS NULL AND @AddressLine4 IS NULL) )
    AND ([MatchAddressLine5] = @AddressLine5 OR ([MatchAddressLine5] IS NULL AND @AddressLine5 IS NULL) )
    AND ([MatchAddressLine6] = @AddressLine6 OR ([MatchAddressLine6] IS NULL AND @AddressLine6 IS NULL) )

    AND (@postalcode = [MatchPostalCode]     OR ([MatchPostalCode]   IS NULL AND @postalcode   IS NULL) )
    AND  TaxRegionId = pst.TaxRegionId     

)
AND pst.[ItemNo] = @itemNo
AND @calculateDate BETWEEN pst.[DateFrom] AND pst.[DateTo]

GO

以下を比較する場合:

USE US16HSMMProduct_ORIGINAL
GO
exec dbo.udpProductTaxRateGet 
        @itemNo=N'31997299',
        @calculateDate='Aug  8 2016  1:01:46:760PM',
        @addressLine1=N'',
        @addressLine2=N'',
        @addressLine3=N'',
        @addressLine4=N'',
        @addressLine5=N'',
        @addressLine6=N'FL',
        @postalcode=N'',
        @countryCode=N'US',
        @addressFormatID=2
go
USE US16HSMMProduct_AFTER_CHANGES
GO
exec DenormV2.udpProductTaxRateGet
        @itemNo=N'31997299',
        @calculateDate='Aug  8 2016  1:01:46:760PM',
        @addressLine1=N'',
        @addressLine2=N'',
        @addressLine3=N'',
        @addressLine4=N'',
        @addressLine5=N'',
        @addressLine6=N'FL',
        @postalcode=N'',
        @countryCode=N'US',
        @addressFormatID=2
go

これを取得します: enter image description here

これは古いプロシージャの実行計画です

これは新しいプロシージャの実行計画です

5
  1. nvarcharへの変換を取り除きます。テーブルはvarcharを使用しています。パラメーターをvarcharに変更してください。
  2. このようなロジックを取り除く:

    AND     ISNULL (CONVERT(nvarchar(50),[MatchAddressLine1]), ISNULL(@addressLine1, '')) = ISNULL(@addressLine1, '')
    

    Where句の列で関数を使用すると、SQLも同様に機能しません。代わりにこれを実行します(nvarcharパラメータを削除していることを忘れないでください)。

    AND ([MatchAddressLine1] = @AddressLine1
         OR ([MatchAddressLine1] IS NULL and @AddressLine1 IS NULL) )
    

    現在のロジックは、一方がNULLでもう一方が ''である行を返すことに注意してください。それでもそのロジックが必要な場合は、さらに2つのORオプションを追加する必要がありますが、それでも機能します。オプティマイザは、このタイプのロジックではるかにうまく機能します。

  3. また、テーブル変数から一時テーブルに変更する場合もあります。あなたが見ることができる重要な違いがあります ここ

  4. 最後のオプションは、一時テーブル/変数を完全に削除してCTEを使用することです。

    WITH Addresses AS (
        SELECT  DISTINCT TaxRegionId
        FROM    dbo.[ShipTaxAddress]
        WHERE   [CountryCode] = @countryCode
        AND     [AddressFormatID] = @addressFormatID
        AND     ([MatchAddressLine1] = @addressLine1
            OR ([MatchAddressLine1] IS NULL and @AddressLine1 IS NULL) )
        AND     ([MatchAddressLine2] = @addressLine2
            OR ([MatchAddressLine2] IS NULL and @AddressLine2 IS NULL) )
        AND     ([MatchAddressLine3] = @addressLine3
            OR ([MatchAddressLine3] IS NULL and @AddressLine3 IS NULL) )
        AND     ([MatchAddressLine4] = @addressLine4
            OR ([MatchAddressLine4] IS NULL and @AddressLine4 IS NULL) )
        AND     ([MatchAddressLine5] = @addressLine5
            OR ([MatchAddressLine5] IS NULL and @AddressLine5 IS NULL) )
        AND     ([MatchAddressLine6] = @addressLine6
            OR ([MatchAddressLine6] IS NULL and @AddressLine6 IS NULL) )
        AND    (@postalcode IS NULL OR [MatchPostalCode] = @postalcode)
    )
    SELECT DISTINCT ISNULL(pst.TaxCode, '') as TaxCode
         , ISNULL(pst.TaxRate, 0) as TaxRate
    FROM    dbo.[ProductShipTax] pst
            INNER JOIN Addresses a
                ON pst.TaxRegionId = a.TaxRegionId
    WHERE   pst.[ItemNo] = @itemNo
    AND     @calculateDate BETWEEN pst.[DateFrom] AND pst.[DateTo]
    

私のコードをチェックして、ロジックが正しいことを確認する必要がありますが、私はそれが正しいと信じています。また、SET STATISTICS IO ONのようなものを使用して、前後に実行される時間の長さ(ミリ秒単位)を取得します。あなたのコードが私のものよりも速くなるよりも、見知らぬものが起こっています。

5
Kenneth Fisher