最初にTenantID:これはまったく選択的ではありません。100個のTenantIDしかない場合、100万行に渡ってほとんど変動がない可能性があります。ただし、テナントAのクエリは500,000行をプルバックするが、テナントBの同じクエリは50行のみであることがSQL Serverに認識されるため、これらのクエリの統計はより正確です。これが主な問題点です。この方法は、ストアドプロシージャの最初の実行がテナントAである場合にパラメータースニッフィングの問題が発生する可能性を大幅に高め、クエリオプティマイザーがこれらの統計を確認し、50万行を効率的に取得する必要があることを認識して適切に動作します。しかし、50行しかないテナントBが実行されると、その実行計画は適切ではなくなり、実際、まったく不適切です。AND、データは先行フィールドの順序で挿入されていないため、
ただし、ストアドプロシージャを実行する最初のTenantIDの場合、データは(少なくともインデックスメンテナンスを実行した後)物理的および論理的に編成されるため、他のアプローチよりもパフォーマンスが向上するはずです。クエリ。つまり、物理I / Oが少なくなり、論理読み取りが少なくなり、同じデータページのテナント間の競合が少なくなり、バッファプールで消費される無駄なスペースが少なくなります(したがって、ページの寿命が長くなります)。
このパフォーマンスの向上には、主に2つのコストがあります。1つ目はそれほど難しくありません。断片化の増加に対処するには、定期的なインデックスメンテナンスを行う必要があります。2番目は少し面白くありません。
増加したパラメータスニッフィングの問題に対処するには、実行プランをテナント間で分離する必要があります。単純なアプローチはWITH RECOMPILE
、プロシージャまたはOPTION (RECOMPILE)
クエリヒントで使用することですが、それはパフォーマンスへの打撃であり、TenantID
最初に置くことによって得られるすべての利益を一掃する可能性があります。私が最もうまくいくとわかった方法は、を介してパラメータ化された動的SQLを使用することsp_executesql
です。ダイナミックSQLが必要な理由は、TenantIDをクエリのテキストに連結できるようにするためですが、通常はパラメーターとなる他のすべての述語はまだパラメーターです。たとえば、特定の注文を探している場合は、次のようにします。
DECLARE @GetOrderSQL NVARCHAR(MAX);
SET @GetOrderSQL = N'
SELECT ord.field1, ord.field2, etc.
FROM dbo.Orders ord
WHERE ord.TenantID = ' + CONVERT(NVARCHAR(10), @TenantID) + N'
AND ord.OrderID = @OrderID_dyn;
';
EXEC sp_executesql
@GetOrderSQL,
N'@OrderID_dyn INT',
@OrderID_dyn = @OrderID;
これにより、特定のテナントのデータボリュームに一致する、そのテナントIDのみの再利用可能なクエリプランが作成されます。同じテナントAが別のストアドプロシージャを再度実行すると、@OrderID
キャッシュされたクエリプランが再利用されます。同じストアドプロシージャを実行している別のテナントは、TenantIDの値のみが異なるクエリテキストを生成しますが、クエリテキストの違いは、異なるプランを生成するのに十分です。また、テナントBに対して生成されたプランは、テナントBのデータボリュームと一致するだけでなく、異なる値のテナントBに対しても再利用可能になります@OrderID
(述部はまだパラメーター化されているため)。
このアプローチの欠点は次のとおりです。
- 単純なクエリを入力するだけではありません(ただし、すべてのクエリが動的SQLである必要はなく、パラメータスニッフィングの問題が発生するクエリだけである必要があります)。
- システム上のテナントの数に応じて、各クエリはそれを呼び出しているTenantIDごとに1つのプランを必要とするため、プランキャッシュのサイズが増加します。これは問題ではないかもしれませんが、少なくとも注意する必要があります。
動的SQLは所有権の連鎖を破ります。つまり、テーブルへの読み取り/書き込みアクセスEXECUTE
は、ストアドプロシージャに対する権限を持つことで想定できません。簡単だが安全性の低い修正方法は、ユーザーがテーブルに直接アクセスできるようにすることです。これは確かに理想的ではありませんが、通常は迅速で簡単なトレードオフです。より安全なアプローチは、証明書ベースのセキュリティを使用することです。つまり、証明書を作成し、その証明書からユーザーを作成し、そのユーザーに必要なアクセス許可を付与し(証明書ベースのユーザーまたはログインはそれ自体ではSQL Serverに接続できません)、それで動的SQLを使用するストアドプロシージャに署名しますADD SIGNATUREを介した同じ証明書。
モジュールの署名と証明書の詳細については、ModuleSigning.Infoをご覧ください。