SQL Server的嵌套存储过程中使用同名的临时表怪像浅析
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的话,就会报错:“Invalid column name 'name'.”
此时只要先我执行一次存储过程dbo.PRC_SUB_TEST,然后再去执行存储过程dbo.PRC_TEST就不会报错了。而且只要执行过一次这个存储过程,然后在当前会话或其它任何会话执行dbo.PRC_TEST都不会报错了。是否非常让人迷惑或不解。
EXEC dbo.PRC_SUB_TEST;
EXEC PRC_TEST;
如果你要再次重现这个现象的话,只能通过下面SQL或者删除/重建存储过程的方式,才能重现这个现象。似乎有点幽灵现象的感觉。
DBCC FREEPROCCACHE
关于这个现象,官方文档(详见参考资料的链接地址)有这么一段描述:
A local temporary table created within a stored procedure or trigger can have the same name as a temporary table that was created before the stored procedure or trigger is called. However, if a query references a temporary table and two temporary tables with the same name exist at that time, it is not defined which table the query is resolved against. Nested stored procedures can also create temporary tables with the same name as a temporary table that was created by the stored procedure that called it. However, for modifications to resolve to the table that was created in the nested procedure, the table must have the same structure, with the same column names, as the table created in the calling procedure. This is shown in the following example.
在存储过程或触发器中创建的本地临时表的名称可以与在调用存储过程或触发器之前创建的临时表名称相同。 但是,如果查询引用临时表,而同时有两个同名的临时表,则不定义针对哪个表解析该查询。 嵌套存储过程同样可以创建与调用它的存储过程所创建的临时表同名的临时表。但是,为了对其进行修改以解析为在嵌套过程中创建的表,此表必须与调用过程创建的表具有相同的结构和列名。下面的示例说明了这一点。
CREATE PROCEDURE dbo.Test2
AS
CREATE TABLE #t(x INT PRIMARY KEY);
INSERT INTO #t VALUES (2);
SELECT Test2Col = x FROM #t;
GO
CREATE PROCEDURE dbo.Test1
AS
CREATE TABLE #t(x INT PRIMARY KEY);
INSERT INTO #t VALUES (1);
SELECT Test1Col = x FROM #t;
EXEC Test2;
GO
CREATE TABLE #t(x INT PRIMARY KEY);
INSERT INTO #t VALUES (99);
GO
EXEC Test1;
GO
官方文档中“同时有两个同名的临时表,则不定义针对哪个表解析该查询”这种阐述感觉还是让人有点迷糊。这里简单解释一下,在存储过程的嵌套调用中,允许外层过程和内层存储过程中存在相同名字的本地临时表,但是在内存过程中,如果要对其进行修改或解析(修改很好理解,例如新增索引,增加字段等这类DDL操作;关于解析,查询临时表,SQL中指定字段名,就需要解析resolve),那么此时这个临时表必须表结构一致,否则就会报错。官方文档,就是这么一句话,告诉你不行,但是具体原因没有说。那么我们不妨做一些推测,在存储过程的嵌套调用中,是否创建了两个本地临时表呢?有没有可能实际只创建了一个本地临时表呢?出现本地临时表重用的情况呢? 那么我们简单验证一下,如下所示,这里可以判断实际上创建了两个本地临时表。并没有出现临时表重用的情况。
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
然后我们来看看临时表的“作用域”,抱歉我用这么一个概念,官方文档是没有这个概念,这个只是我们思考的一个方面,细节方面没有必要抬杠。如下所示,我们修改一下存储过程
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
通过实验验证,我们发现外层存储过程的临时表在内层存储过程中有效,它的“作用域”是在内层存储过程的同名临时表创建之前,这个跟高级语言中的全局变量和局部变量作用域有点类似。
既然创建了两个本地临时表,那么为什么修改或解析的时候就会报错呢? 个人的一个猜测是,优化器解析过后,在执行过程中,解析或修改的时候,数据库引擎无法判断或者代码里面没有这种逻辑去控制检索哪一个临时表。有可能是代码里面的一个缺陷亦或是某种逻辑原因导致。上述仅仅是个人的一个猜测、推理。如有不足或不对的地方,敬请指正。
参考资料:
https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2012/ms174979(v=sql.110)?redirectedfrom=MSDN
SQL Server的嵌套存储过程中使用同名的临时表怪像浅析的更多相关文章
- SQL Server数据库的存储过程中定义的临时表,真的有必要显式删除临时表(drop table #tableName)吗?
本文出处:http://www.cnblogs.com/wy123/p/6704619.html 问题背景 在写SQL Server存储过程中,如果存储过程中定义了临时表,有些人习惯在存储过程结束的时 ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(2)——SQL Server如何编译存储过程
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(1)--简介 本文介绍SQL Server如何编译存储过程并使用计划缓存 ...
- 如何在SQL Server查询语句(Select)中检索存储过程(Store Procedure)的结果集?
如何在SQL Server查询语句(Select)中检索存储过程(Store Procedure)的结果集?(2006-12-14 09:25:36) 与这个问题具有相同性质的其他描述还包括:如何 ...
- 【SQL Server】SQL Server基础之存储过程
SQL Server基础之存储过程 阅读目录 一:存储过程概述 二:存储过程分类 三:创建存储过程 1.创建无参存储过程 2.修改存储过程 3.删除存储过程 4.重命名存储过程 5.创建带参数的存储 ...
- Sql Server 常用系统存储过程大全
-- 来源于网络 -- 更详细的介结参考联机帮助文档 xp_cmdshell --*执行DOS各种命令,结果以文本行返回. xp_fixeddrives --*查询各磁盘/分区可用空间 xp_logi ...
- SQL实现递归及存储过程中In()参数传递解决方案[转]
SQL实现递归及存储过程中In()参数传递解决方案 1.SQL递归 在SQL Server中,我们可以利用表表达式来实现递归算法,一般用于阻止机构的加载及相关性处理. -->实现: 假设 ...
- SQL点滴12—SQL Server备份还原数据库中的小把戏
原文:SQL点滴12-SQL Server备份还原数据库中的小把戏 备份数据库时出现一个不太了解的错误 ,错误信息“is formatted to support 1 media families, ...
- SQL Server 禁用扩展存储过程
概述 扩展存储过程是 SQL Server 实例可以动态加载和运行的 DLL.扩展存储过程是使用 SQL Server 扩展存储过程 API 编写的,可直接在 SQL Server 实例的地址空间中运 ...
- sql server内置存储过程、查看系统信息
1.检索关键字:sql server内置存储过程,sql server查看系统信息 2.查看磁盘空间:EXEC master.dbo.xp_fixeddrives , --查看各个数据库所在磁盘情况S ...
随机推荐
- 用percona monitoring plugins 监控mysql
下载:http://www.percona.com/redir/downloads/percona-monitoring-plugins/1.1.1/percona-zabbix-templates- ...
- 如何构建一个多人(.io) Web 游戏,第 2 部分
原文:How to Build a Multiplayer (.io) Web Game, Part 2 探索 .io 游戏背后的后端服务器. 上篇:如何构建一个多人(.io) Web 游戏,第 1 ...
- 浏览器performance工具介绍及内存问题表现与监控内存的几种方式
一.GC的目的 为了实现内存空间的良性循环,performance提供多种监控方式监控内存 分析内存相关信息 当代码出现问题的时候及时定位到出现问题的代码块, 提高执行效率. preforcemanc ...
- WTM5.0发布,全面支持.net5
WTM5.0是WTM框架开源2年以来最大的一次升级,全面支持.net5,大幅重构了底层代码,针对广大用户提出的封装过度,不够灵活,性能不高等问题进行了彻底的修改. 这次升级使WTM继续保持开箱即用,高 ...
- CSS奇思妙想 -- 使用 CSS 创造艺术
本文属于 CSS 绘图技巧其中一篇.之前有过一篇:在 CSS 中使用三角函数绘制曲线图形及展示动画 想写一篇关于 CSS 创造艺术的文章已久,本文主要介绍如何借助 CSS-doodle ,利用 CSS ...
- 华为三层交换机限制vlan段的指定端口
屏蔽vlan 120这个段的ip的所有2333端口 [NTT_3L]int Vlanif 120 [NTT_3L-Vlanif120]dis this # interface Vlanif120 ip ...
- Bitter ORM NETCORE ORM 全网最粗暴简单易用高性能的 NETCore ORM 开源了
开源的来了,懒人程序员的福音,.NET 生态闭环太缺开源精神了, 拥抱开源! 前言: 本人不是不喜欢现有ORM的轮子,而是发现现有的ORM 的都不太符合开发人员的一些习惯.现有的ORM 要么功能太冗余 ...
- DevOps运动的缘起 将DevOps想象为一种编程语言里面的一个接口,而SRE类实现了这个接口
SRE vs DevOps:是敌是友? - DockOne.io http://www.dockone.io/article/5935 RE vs DevOps:是敌是友? [编者的话]网站可靠 ...
- windows IOCP 实践
关于 windows IOCP 有人说 windows IOCP 是 windows 上最好的东西. IOCP 是真正的异步 IO,意味着每次发起一个 IO 请求,该调用本身则立即返回, 而包括 IO ...
- Redis下载及安装(windows版)
下载地址1.Github下载地址:https://github.com/MicrosoftArchive/redis/releases2.百度网盘下载地址 https://pan.baidu.com/ ...