1行のみをフェッチする必要があるにもかかわらず、SQLサーバーがテーブル内のすべての値に対してユーザー定義関数を呼び出すことを決定する理由を理解するのに問題があります。実際のSQLはもっと複雑ですが、問題をこれまで減らすことができました。
select
S.GROUPCODE,
H.ORDERCATEGORY
from
ORDERLINE L
join ORDERHDR H on H.ORDERID = L.ORDERID
join PRODUCT P on P.PRODUCT = L.PRODUCT
cross apply dbo.GetGroupCode (P.FACTORY) S
where
L.ORDERNUMBER = 'XXX/YYY-123456' and
L.RMPHASE = '0' and
L.ORDERLINE = '01'
このクエリでは、SQL Serverは、ORDERLINEから返される推定行数と実際の行数が1(主キー)であっても、PRODUCTテーブルに存在するすべての値に対してGetGroupCode関数を呼び出すことを決定します。
行カウントを示すプランエクスプローラーの同じプラン:
テーブル:
ORDERLINE: 1.5M rows, primary key: ORDERNUMBER + ORDERLINE + RMPHASE (clustered)
ORDERHDR: 900k rows, primary key: ORDERID (clustered)
PRODUCT: 6655 rows, primary key: PRODUCT (clustered)
スキャンに使用されているインデックスは次のとおりです。
create unique nonclustered index PRODUCT_FACTORY on PRODUCT (PRODUCT, FACTORY)
この関数は実際には少し複雑ですが、次のようなダミーのマルチステートメント関数でも同じことが起こります。
create function GetGroupCode (@FACTORY varchar(4))
returns @t table(
TYPE varchar(8),
GROUPCODE varchar(30)
)
as begin
insert into @t (TYPE, GROUPCODE) values ('XX', 'YY')
return
end
SQLサーバーにトップ1の製品を強制的にフェッチさせることにより、パフォーマンスを「修正」することができました。
select
S.GROUPCODE,
H.ORDERCAT
from
ORDERLINE L
join ORDERHDR H
on H.ORDERID = M.ORDERID
cross apply (select top 1 P.FACTORY from PRODUCT P where P.PRODUCT = L.PRODUCT) P
cross apply dbo.GetGroupCode (P.FACTORY) S
where
L.ORDERNUMBER = 'XXX/YYY-123456' and
L.RMPHASE = '0' and
L.ORDERLINE = '01'
それから、計画の形状も、元々予想されていたものに変わります。
また、インデックスPRODUCT_FACTORYがクラスター化インデックスPRODUCT_PKよりも小さいと影響がありますが、クエリでPRODUCT_PKを使用するように強制しても、計画は元のプランと同じで、関数を6655呼び出します。
ORDERHDRを完全に省くと、プランはまずORDERLINEとPRODUCTの間のネストされたループから始まり、関数は1回だけ呼び出されます。
すべての操作は主キーを使用して行われるため、これが原因である理由と、これを簡単に解決できないより複雑なクエリで発生した場合の修正方法を理解したいと思います。
編集:テーブルステートメントを作成します。
CREATE TABLE dbo.ORDERHDR(
ORDERID varchar(8) NOT NULL,
ORDERCATEGORY varchar(2) NULL,
CONSTRAINT ORDERHDR_PK PRIMARY KEY CLUSTERED (ORDERID)
)
CREATE TABLE dbo.ORDERLINE(
ORDERNUMBER varchar(16) NOT NULL,
RMPHASE char(1) NOT NULL,
ORDERLINE char(2) NOT NULL,
ORDERID varchar(8) NOT NULL,
PRODUCT varchar(8) NOT NULL,
CONSTRAINT ORDERLINE_PK PRIMARY KEY CLUSTERED (ORDERNUMBER,ORDERLINE,RMPHASE)
)
CREATE TABLE dbo.PRODUCT(
PRODUCT varchar(8) NOT NULL,
FACTORY varchar(4) NULL,
CONSTRAINT PRODUCT_PK PRIMARY KEY CLUSTERED (PRODUCT)
)