SQLServerのネストされたストアドプロシージャ内の同じ名前の一時テーブルの奇妙なイメージの分析

SQL Serverのネストされたストアドプロシージャで、外部ストアドプロシージャと内部ストアドプロシージャ(ネストによって呼び出されるストアドプロシージャ)に同じ名前のローカル一時テーブルが存在する可能性はありますか?もしそうなら、何か問題や制限はありますか?ネストされたストアドプロシージャでは、外部ストアドプロシージャの一時テーブルですか、それとも自分で定義した一時テーブルですか。高水準言語の変数と同様に、ローカル一時テーブルの「スコープ」スコープはありますか?

注:これは、親ストアード・プロシージャーおよび子ストアード・プロシージャー、外部ストアード・プロシージャーおよび内部ストアード・プロシージャーと呼ぶこともできます。これらは単に異なるタイトルまたは名前です。ここでは、外部ストアドプロシージャと内部ストアドプロシージャを一律に使用します。フォローアップ記事には入りません。

最初に例を見てみましょう。以下に示すように、簡単な例を作成します。

IF EXISTS(SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.PRC_TEST ')AND OBJECTPROPERTY(object_id、' IsProcedure ')= 1)
BEGIN
DROP PROCEDURE dbo.PRC_TEST
END
GO
CREATE PROC dbo.PRC_TEST
AS
BEGIN

CREATE TABLE #tmp_test(id INT);

INSERT INTO #tmp_test
SELECT 1;

SELECT * FROM #tmp_test;

EXEC PRC_SUB_TEST

SELECT * FROM #tmp_test


END
GO

IF EXISTS(SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.PRC_SUB_TEST ')AND OBJECTPROPERTY(object_id、' IsProcedure ')= 1)
BEGIN
DROP PROCEDURE dbo.PRC_SUB_TEST;
END
GO

CREATE PROCEDURE dbo.PRC_SUB_TEST
AS
BEGIN

CREATE TABLE #tmp_test(name VARCHAR(128));

INSERT INTO #tmp_test
SELECT name FROM sys.objects

SELECT * FROM #tmp_test;
END
GO

EXEC PRC_TEST;

簡単なテストは正常のようで、問題は見つかりませんでした。この時点で結論を出すと、時期尚早です!たとえば、白鳥が白いのを見て、「すべての白鳥は白い」という結論に達した場合、実際、この世界には黒い白鳥がいますが、あなたはそれを見たことがありません!以下に示すように、ストアドプロシージャdbo.PRC_SUB_TESTを変更し、以下に示すように*をフィールド名nameに置き換えます。

IF EXISTS(SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.PRC_SUB_TEST ')AND OBJECTPROPERTY(object_id、' IsProcedure ')= 1)
BEGIN
DROP PROCEDURE dbo.PRC_SUB_TEST;
END
GO

CREATE PROCEDURE dbo.PRC_SUB_TEST
AS
BEGIN

CREATE TABLE #tmp_test(name VARCHAR(128));

INSERT INTO #tmp_test
SELECT name FROM sys.objects

SELECT name FROM #tmp_test;
END
GO

次に、以下に示すように、上記のテストを繰り返します。この時点でストアドプロシージャdbo.PRC_TESTを実行すると、「無効な列名 'name'」というエラーが報告されます。

この時点で、ストアドプロシージャdbo.PRC_SUB_TESTを1回実行してから、ストアドプロシージャdbo.PRC_TESTを実行する限り、エラーは報告されません。また、このストアドプロシージャを一度実行してから、現在のセッションまたは他のセッションでdbo.PRC_TESTを実行する限り、エラーは報告されません。それは非常に紛らわしいですか、それとも紛らわしいですか。

EXEC dbo.PRC_SUB_TEST;

EXEC PRC_TEST;

この現象を再度再現したい場合は、以下のSQLまたはストアドプロシージャの削除/再構築によってのみこの現象を再現できます。ちょっとゴースト現象のようです。

DBCC FREEPROCCACHE

この現象に関して、公式文書(詳細は参考資料のリンクアドレスを参照)には次のような説明があります。

ストアード・プロシージャーまたはトリガー内で作成されたローカル一時テーブルは、ストアード・プロシージャーまたはトリガーが呼び出される前に作成された一時テーブルと同じ名前を持つことができます。ただし、クエリが一時テーブルを参照していて、その時点で同じ名前の2つの一時テーブルが存在する場合、クエリがどのテーブルに対して解決されるかは定義されていません。ネストされたストアドプロシージャは、それを呼び出したストアドプロシージャによって作成された一時テーブルと同じ名前の一時テーブルを作成することもできます。ただし、ネストされたプロシージャで作成されたテーブルに変更を解決するには、テーブルは、呼び出し元のプロシージャで作成されたテーブルと同じ構造、同じ列名である必要があります。これを次の例に示します。

ストアード・プロシージャーまたはトリガーで作成されたローカル一時テーブルの名前は、ストアード・プロシージャーまたはトリガーを呼び出す前に作成された一時テーブルの名前と同じにすることができます。ただし、クエリが一時テーブルを参照していて、同時に同じ名前の2つの一時テーブルがある場合、クエリが解析されるテーブルは定義されません。ネストされたストアドプロシージャは、それを呼び出すストアドプロシージャによって作成された一時テーブルと同じ名前の一時テーブルを作成することもできます。ただし、ネストされたプロシージャで作成されたテーブルに解決するように変更するには、このテーブルは、呼び出し元のプロシージャで作成されたテーブルと同じ構造と列名を持っている必要があります。次の例は、この点を示しています。

CREATE PROCEDURE dbo.Test2
AS
CREATE TABLE #t(x INT PRIMARY KEY);
#t値に挿入(2);
SELECT Test2Col = x FROM #t;
GO

CREATE PROCEDURE dbo.Test1
AS
CREATE TABLE #t(x INT PRIMARY KEY);
#t値に挿入(1);
SELECT Test1Col = x FROM #t;
EXEC Test2;
GO

CREATE TABLE #t(x INT PRIMARY KEY);
#t値に挿入(99);
GO

EXEC Test1;
GO

公式文書では、「同じ名前の一時テーブルが同時に2つあるため、どちらのテーブルに対してクエリを解析するかが定義されていません」という説明は少し混乱を招きます。簡単な説明を次に示します。ストアドプロシージャのネストされた呼び出しでは、外部プロシージャと内部ストアドプロシージャに同じ名前のローカル一時テーブルを含めることができますが、変更する場合はメモリプロセスに、またはそれを解析します(インデックスの追加、フィールドの追加、その他のDDL操作などの変更は適切です。解析、一時テーブルのクエリ、SQLでのフィールド名の指定については、解決を解決する必要があります)、この一時テーブルは同じテーブル構造である必要がありますこのとき、それ以外の場合はエラーが報告されます。公式文書にはそれは不可能であると書かれていますが、具体的な理由は述べられていません。次に、ストアドプロシージャのネストされた呼び出しで2つのローカル一時テーブルが作成されるかどうか、いくつかの推測を行うこともできますか?実際に1つのローカル一時テーブルのみが作成される可能性はありますか?ローカル一時テーブルの再利用についてはどうですか?次に、以下に示すように、2つのローカル一時テーブルが実際に作成されていることを確認できます。一時テーブルの再利用はありません。

SELECT *
FROM sys.dm_os_performance_counters
WHERE counter_name LIKE'Temp Tables Creation Rate% ';

EXEC PRC_TEST;

SELECT *
FROM sys.dm_os_performance_counters
WHERE counter_name LIKE'Temp Tables Creation Rate% ';

 

もちろん、次のSQLを使用して検証できます。これは、上記の検証の結果と一致しています。

IF EXISTS(SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.PRC_SUB_TEST ')AND OBJECTPROPERTY(object_id、' IsProcedure ')= 1)
BEGIN
DROP PROCEDURE dbo.PRC_SUB_TEST;
END
GO


CREATE PROCEDURE dbo.PRC_SUB_TEST
AS
BEGIN

SELECT * FROM #tmp_test;

SELECT * FROM tempdb.dbo.sysobjects WHERE name LIKE '#tmp_test%'
CREATE TABLE #tmp_test(name VARCHAR(128));

INSERT INTO #tmp_test
SELECT name FROM sys.objects
SELECT * FROM tempdb.dbo.sysobjects WHERE name LIKE '#tmp_test%'
SELECT * FROM #tmp_test;
END
GO

それでは、仮設テーブルの「スコープ」を見てみましょう。申し訳ありませんが、このような概念を使用しました。公式文書にはこの概念がありません。これは私たちの考え方の1つの側面に過ぎず、持ち上げる必要はありません。詳細をアップします。以下に示すように、ストアドプロシージャを変更します

IF EXISTS(SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.PRC_SUB_TEST ')AND OBJECTPROPERTY(object_id、' IsProcedure ')= 1)
BEGIN
DROP PROCEDURE dbo.PRC_SUB_TEST;
END
GO
CREATE PROCEDURE dbo.PRC_SUB_TEST
AS
BEGIN

SELECT * FROM #tmp_test;
CREATE TABLE #tmp_test(name VARCHAR(128));

INSERT INTO #tmp_test
SELECT name FROM sys.objects

SELECT * FROM #tmp_test;
END
GO

実験的な検証の結果、外部ストアドプロシージャの一時テーブルが内部ストアドプロシージャで有効であることがわかりました。その「スコープ」は、内部ストアドプロシージャで同じ名前の一時テーブルが作成される前です。これは、グローバル変数と高レベル言語のローカル変数スコープは少し似ています。

2つのローカル一時テーブルが作成されているのに、変更または解析時にエラーが報告されるのはなぜですか?個人的な推測では、オプティマイザが解析した後、実行プロセス中にデータベースエンジンがそれを判断できないか、分析または変更中に取得される一時テーブルを制御するロジックがコードにありません。コードの欠陥または何らかの論理的な理由である可能性があります。上記は個人的な推測と推論にすぎません。欠陥やエラーがある場合は、私を訂正してください。

おすすめ

転載: blog.csdn.net/hbznd/article/details/115247701