web-dev-qa-db-ja.com

sys.schemasおよびsys.synonymsに対するクエリの実行が1人のユーザーに対して非常に遅い

シナリオ:SQL Server 2014(v12.0.4100.1)

.NETサービスは次のクエリを実行します。

SELECT name, base_object_name 
FROM sys.synonyms 
WHERE schema_id IN (SELECT schema_id 
                    FROM sys.schemas 
                    WHERE name = N'XXXX')
ORDER BY name

...約6500行を返しますが、3分以上経過するとタイムアウトすることがよくあります。上記のXXXXnot 'dbo'です。

このクエリをSSMSでUserAとして実行すると、クエリは1秒未満で返されます。

UserB(.NETサービスの接続方法)として実行すると、クエリは3-6かかり、CPU%は25%( 4コアの)全体の時間。

UserAは、sysadminロールのドメインログインです。

UserBはSQLログインです。

EXEC sp_addrolemember N'db_datareader', N'UserB'
EXEC sp_addrolemember N'db_datawriter', N'UserB'
EXEC sp_addrolemember N'db_ddladmin', N'UserB'
GRANT EXECUTE TO [UserB]
GRANT CREATE SCHEMA TO [UserB]
GRANT VIEW DEFINITION TO [UserB]

上記のSQLをExecute as...Revertブロックでラップすることで、SSMSでこれを複製できます。そのため、.NETコードは見えなくなります。

実行計画は同じに見えます。私はXMLを比較しましたが、わずかな違い(CompileTime、CompileCPU、CompileMemory)しかありません。

IO Statsはすべて物理的な読み取りを示していません。

テーブル 'sysobjvalues'。スキャンカウント0、論理読み取り19970、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
テーブル 'Workfile'。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
テーブル 'Worktable'。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。
テーブル 'sysschobjs'。スキャンカウント1、論理読み取り9122、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。
テーブル 'sysclsobjs'。スキャンカウント0、論理読み取り2、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

XEventの待機ステータス(〜3分のクエリの場合)は次のとおりです。

 + --------------------- + ------------ + --------- ------------- + ------------------------------ + ----- ------------------------ + 
 |待機タイプ|待機数|合計待機時間(ミリ秒)|総リソース待機時間(ミリ秒)|合計信号待機時間(ミリ秒)| 
 + --------------------- + ------------ +- --------------------- + ---------------------------- --- + ----------------------------- + 
 | SOS_SCHEDULER_YIELD | 37300 | 427 | 20 | 407 | 
 | NETWORK_IO | 5 | 26 | 26 | 0 | 
 | IO_COMPLETION | 3 | 1 | 1 | 0 | 
 + --------------------- + ------------ + ------- --------------- + ------------------------------- +- --------------------------- + 

クエリを書き換えると(SSMSでは、アプリコードにアクセスできません)、次のようになります。

declare @id int 
SELECT @id=schema_id FROM sys.schemas WHERE name = N'XXXX'
SELECT a.name, base_object_name FROM sys.synonyms a
WHERE schema_id = @id
ORDER BY name

次に、UserBはUserAと同じ(高速)速度で実行されます。

UserBにdb_ownerを追加すると、クエリは1秒未満で実行されます。

このテンプレートを介して作成されたスキーマ:

DECLARE @TranName VARCHAR(20)
SELECT @TranName = 'MyTransaction'

BEGIN TRANSACTION @TranName
GO

IF NOT EXISTS (SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
        WHERE SCHEMA_NAME = '{1}')
BEGIN
    EXEC('CREATE SCHEMA [{1}]')
    EXEC sp_addextendedproperty @name='User', @value='{0}', @level0type=N'Schema', @level0name=N'{1}'
END
GO

{2}

COMMIT TRANSACTION MyTransaction;
GO

そして、{2}は、そのスキーマで作成されたシノニムのリストだと思います。

クエリへの2つのポイントでのクエリプロファイル:

enter image description here

enter image description here

マイクロソフトでチケットをオープンしました。

また、UserBをdb_ownerに追加してから、db_ownerに関連付けられている既知のすべての権限をDENYingしました。結果は高速なクエリです。何かを逃した(完全に可能)、またはdb_ownerロールの特別なチェックがあります。

8
James

クエリを次のように書き直すこともできます(テストデータベースでいくつかの同義語を見つけるために、dboではなくXXXXを使用しています)。これは、より効率的であることがわかった書き換えに似ていますが、変数を宣言して2つのクエリを使用する必要がありません。

_SELECT name, base_object_name 
FROM sys.synonyms 
WHERE schema_id = SCHEMA_ID(N'dbo')
ORDER BY name
_

これにより、次のような計画が生成されます。

enter image description here

この計画のFilter演算子について非常に興味深いことの1つは、内部has_access()チェックを実行する述語があることです。このフィルターは、現在のアカウントに表示するための十分な権限がないオブジェクトを削除します。ただし、_db_owner_ロールのメンバーである場合、このチェックは短絡されます(つまり、はるかに迅速に完了します)。これにより、パフォーマンスの違いが説明される場合があります。

enter image description here

これが元のクエリのクエリプランです。データベース上のすべての同義語(私の場合は_1,126_ですが、場合によってはもっと多い可能性があります)は、スキーマに一致する_2_の同義語のみであっても、非常に高価なhas_access()フィルターを通過することに注意してください。上記の簡略化されたクエリを使用すると、has_access()がデータベース内のすべてのシノニムではなく、クエリに一致するシノニムに対してのみ呼び出されるようにすることができます。

enter image description here


sys.dm_exec_query_profilesを使用してさらに探索する

Martinが示唆しているように、SQL Server 2014以降で_sys.dm_exec_query_profiles_を使用することにより、has_access()チェックが重大なボトルネックであることを確認できます。 〜700Kオブジェクトのデータベースで_db_owner_アカウントを使用して次のクエリを実行すると、クエリは_~500ms_を取得します。

_SELECT COUNT(*)
FROM sys.objects
_

_db_owner_ではないアカウントで実行すると、この同じクエリは約8分かかります。実際の計画を実行し、_sys.dm_exec_query_profiles_の出力をより簡単に解析するために記述した p_queryProgress プロシージャを使用すると、ほぼすべての処理時間がFilterhas_access()チェックを実行している演算子:

enter image description here

5
Geoff Patterson

これがまだライブである場合-同じ問題が発生しています-あなたがdboまたはsysadminのいずれかである場合、sys.objects(またはそのようなもの)へのアクセスはすべて、個々のオブジェクトに対するチェックなしで瞬時に行われるようです。

低いdb_datareaderの場合、各オブジェクトを順番にチェックする必要があります...これらはビュー/テーブルではなく関数のように動作するため、クエリプランでは非表示になっています。

計画は同じに見えますが、フードの背後で別のことをしています

0
mike