TTYF ~earlgrey の雑記~

主に自分用メモとか

sp_executesql におけるオブジェクト名の記述方法

SqlClient でパラメータを使用した SQL を実行すると、実際は sp_executesql に変換されます。
プロファイラを使えばその様子を簡単に観察することができます。

using System;
using System.Data;
using System.Data.SqlClient;

static void Main(string[] args)
{
    using (var conn = new SqlConnection(@"Data Source=localhost\SQLEXPRESS;Initial Catalog=AdventureWorksDW2014;Integrated Security=True"))
    using (var paramCmd = new SqlCommand("SELECT [ProductAlternateKey] FROM [DimProduct] WHERE [ProductKey] = @ProductKey", conn))
    using (var adhocCmd = new SqlCommand("SELECT [ProductAlternateKey] FROM [DimProduct] WHERE [ProductKey] = 1", conn))
    {
        conn.Open();

        // パラメータクエリの実行
        var param = new SqlParameter("ProductKey", SqlDbType.Int);
        paramCmd.Parameters.Add(param);
        param.Value = 1;
        var productAlternateKey = paramCmd.ExecuteScalar() as string;
        Console.WriteLine(productAlternateKey);

        // アドホッククエリの実行
        productAlternateKey = adhocCmd.ExecuteScalar() as string;
        Console.WriteLine(productAlternateKey);
    }

    Console.ReadKey();
}

上記のコードを実行したときのプロファイラの出力結果がこちらです。

f:id:s_earlgrey:20160505192832p:plain

色の付いた行がパラメータクエリで、確かに sp_executesql が実行されていることがわかります。一方で、後続のアドホッククエリは SQL がそのまま実行されています。

ちなみにデータベースにはデータウェアハウス用の Adventureworks を使用しています。(Adventure Works DW 2014 Full Database Backup.zip)

さてその sp_executesql ですが、リファレンスにこのような記述があります。

パフォーマンスを向上させるには、ステートメント文字列に完全修飾オブジェクト名を使用します。

完全修飾オブジェクト名というのは、サーバ名.データベース名.スキーマ名.オブジェクト名で表されます。つまりそのまま解釈すると、先のコードの場合だと、[APOLLO\SQLEXPRESS].[AdventureWorksDW2014].[dbo].[DimProduct] と書く必要があるということです(APOLLO というのはコンピュータ名です)。
これはめんどくさい。特にサーバ名は localhost じゃダメらしいので、開発環境と本番環境のサーバ名の違いを吸収するのが大変です。

一体なぜこんなことを?という感じですが、別の説明ページ にはこういう風に書かれています。

SQL Server によって実行プランが再利用されるようにするには、ステートメント文字列内のオブジェクト名を完全修飾名にする必要があります。

えええ本当ですか...、という感じです。正直信じがたいので、完全修飾名じゃない場合に実行プランが再利用されるかどうかを確かめてみます。

完全修飾でないどころか、データベース名やスキーマ名も省略したオブジェクト名を使って、同じ sp_executesql をパラメータ値を変えて 2 回実行します。

-- キャッシュ済実行プランをクリア
DBCC FREEPROCCACHE
GO

EXEC sp_executesql
    N'SELECT [ProductAlternateKey] FROM [DimProduct] WHERE [ProductKey] = @ProductKey',N'@ProductKey int',
    @ProductKey = 1
GO

EXEC sp_executesql
    N'SELECT [ProductAlternateKey] FROM [DimProduct] WHERE [ProductKey] = @ProductKey',N'@ProductKey int',
    @ProductKey = 2
GO

これを実行した後で、キャッシュされた実行プランを確認してみます。

SELECT usecounts, cacheobjtype, objtype, text 
FROM sys.dm_exec_cached_plans 
    CROSS APPLY sys.dm_exec_sql_text(plan_handle) 
GO

f:id:s_earlgrey:20160505205315p:plain

sys.dm_exec_cached_plans
sys.dm_exec_sql_text

これを見る限り、2 回とも同じ実行プランが利用されています。少なくとも実行プラン再利用の観点からすると、sp_executesql で完全修飾オブジェクト名を使う必要はなさそうです。