我目前正在使用的应用程序的表有不同的模式名称,例如Table1可以有多个存在,例如A.Table1和B.Table1。我的所有存储过程都存储在dbo下。我正在使用动态SQL编写以下存储过程。我目前正在使用SQL Server 2008 R2,很快它将迁移到SQL Server 2012。
create procedure dbo.usp_GetDataFromTable1
@schemaname varchar(100),
@userid bigint
as
begin
declare @sql nvarchar(4000)
set @sql='select a.EmailID from '+@schemaname+'.Table1 a where a.ID=@user_id';
exec sp_executesql @sql, N'@user_id bigint', @user_id=@userid
end
现在我的问题是, 1.这种方法是否会影响我的存储过程的性能? 2.如果性能受到影响,那么如何为这种情况编写程序?
答案 0 :(得分:1)
如果可能的话,解决这个问题的最佳方法就是重新设计。
您甚至可以通过添加新列来替换架构来追溯实现此功能,例如:Profile
,然后将每个架构中的所有表合并为一个架构中的一个(例如dbo
)。
然后您的程序将显示如下:
create procedure dbo.usp_GetDataFromTable1
@profile int,
@userid bigint
as
begin
select a.EmailID from dbo.Table1 a
where a.ID = @user_id
and a.Profile = @profile
end
我在配置文件列中使用int
,但如果使用varchar
,您甚至可以保留配置文件值的架构名称,如果这有助于使事情更清晰。
答案 1 :(得分:1)
我会看一下配置方法,在这种方法中,您可以动态创建表和存储过程,作为一些前期过程的一部分。我不是100%肯定你的场景,但也许这可能是你添加一个新用户。然后,您可以在应用程序中按惯例调用这些SP。
例如,新用户创建调用SP创建c.Table和c.GetDetails SP。
然后在应用程序中,您可以根据用户定义的属性“c”调用c.GetDetails。
这可以解决使用动态SQL带来的任何安全问题。它仍然是动态的,但是它是预先构建的。
答案 2 :(得分:1)
动态模式和相同的表结构非常不寻常,但您仍然可以使用以下内容获得所需内容:
declare @sql nvarchar(4000)
declare @schemaName VARCHAR(20) = 'schema'
declare @tableName VARCHAR(20) = 'Table'
-- this will fail, as the whole string will be 'quoted' within [..]
-- declare @tableName VARCHAR(20) = 'Category; DELETE FROM TABLE x;'
set @sql='select * from ' + QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName)
PRINT @sql
-- @user_id is not used here, but it can if the query needs it
exec sp_executesql @sql, N'@user_id bigint', @user_id=0
因此,对于SQL注入,QUOTENAME
应该保持安全。
<强> 1。性能 - 动态SQL无法从某些性能改进中受益(我认为程序相关的统计信息或类似的东西),因此存在性能风险。
然而,对于运行在相当少量数据(最多数千万)上的简单事物以及没有大量变化(插入和删除)的数据,我不认为你会有明显的问题。
<强> 2。替代 - bukko
已提出解决方案。由于所有表都具有相同的结构,因此可以合并它们。如果它变得很大,那么良好的索引和分区应该能够减少查询执行时间。
答案 3 :(得分:0)
动态Sql通常会影响性能和安全性,大部分时间都会影响最差。但是,由于您无法对标识符进行参数化,因此除非您愿意为每个模式复制存储过程,否则这可能是您的唯一方法:
create procedure dbo.usp_GetDataFromTable1
@schemaname varchar(100),
@userid bigint
as
begin
if @schemaname = 'a'
begin
select EmailID from a.Table1 where ID = @user_id
end
else if schemaname = 'b'
begin
select EmailID from b.Table1 where ID = @user_id
end
end
答案 4 :(得分:0)
我能想到的唯一理由是满足多个租户。你关闭了,但你所采取的方法是错误的。
我发现有3种多租户解决方案:每个租户的数据库,每个租户的单个数据库架构,或单个数据库单个架构(也就是逐行租户)。
其他用户已在此处提及其中两个。没有详细说明的是每个租户的架构,这就是你看起来像是什么样的。对于此方法,您需要更改查看数据库的方式。此时数据库只是模式的容器。每个模式都可以有自己的设计,存储过程,触发器,队列,函数等。主要目标是数据隔离。你不想要租户A看到租户Bs的东西。每个租户方法的架构的优点是您可以更灵活地更改租户特定的数据库。它还允许您比每个租户方法的数据库更容易扩展。
答案:您应该为每个架构创建相同的存储过程,而不是编写动态SQL来考虑使用DBO用户的架构(创建过程示例:schema_name.stored_proc_name)。为了运行模式的存储过程,您需要模拟与相关模式绑定的用户。它看起来像这样:
execute as user = 'tenantA'
exec sp_testing
revert --revert will take us back to the original user, most likely DBO in your case.
所有租户的数据整理有点困难。我所知道的唯一解决方案是使用DBO用户和&#34; union all&#34;所有模式的结果分开,如果你有大量的模式,那就太乏味了。
答案 5 :(得分:0)
如果您知道将要使用的架构,则可以解决此问题。您在此处表示架构名称是在注册时创建的,我们在登录时使用此方法。我有一个视图,可以在会话启动/处置时添加或删除联合。下面的示例。
CREATE VIEW [engine].[vw_Preferences]
AS
SELECT TOP (0) CAST (NULL AS NVARCHAR (255)) AS SessionID,
CAST (NULL AS UNIQUEIDENTIFIER) AS [PreferenceGUID],
CAST (NULL AS NVARCHAR (MAX)) AS [Value]
UNION ALL SELECT 'ZZZ_7756404F411B46138371B45FB3EA6ADB', * FROM ZZZ_7756404F411B46138371B45FB3EA6ADB.Preferences
UNION ALL SELECT 'ZZZ_CE67D221C4634DC39664975494DB53B2', * FROM ZZZ_CE67D221C4634DC39664975494DB53B2.Preferences
UNION ALL SELECT 'ZZZ_5D6FB09228D941AC9ECD6C7AC47F6779', * FROM ZZZ_5D6FB09228D941AC9ECD6C7AC47F6779.Preferences
UNION ALL SELECT 'ZZZ_5F76B619894243EB919B87A1E4408D0C', * FROM ZZZ_5F76B619894243EB919B87A1E4408D0C.Preferences
UNION ALL SELECT 'ZZZ_A7C5ED1CFBC843E9AD72281702FCC2B4', * FROM ZZZ_A7C5ED1CFBC843E9AD72281702FCC2B4.Preferences
首先选择的前0行是回退,因此我始终具有默认定义和静态表定义。您可以从视图中进行选择,并使用
按会话ID进行过滤SELECT PreferenceGUID, Value
FROM engine.vw_Preferences
WHERE SessionID = 'ZZZ_5D6FB09228D941AC9ECD6C7AC47F6779';
这里有趣的部分是当视图中有静态值时如何生成执行计划。代码不会评估不会产生结果的并集,从而使基本执行计划没有任何联接或并集...
您可以对其进行测试,它的效率与直接从表中读取一样有效(误差范围之内,所以很少有人会在意)。甚至可以通过使用“代替”触发器来替换回写过程,然后在后台构建动态sql。动态sql在写入时效率较低,但这意味着您可以通过视图更新任何表,通常只能在单个表视图中进行。