是否有内置的方法在所有模式中执行公共DDL脚本?
我正在处理多租户应用程序,该应用程序为每个租户创建数据库架构。每个模式包含每个租户的相同表定义。例如:
Schema named "tenant1" contains tables: tenant1.Users, tenant1.HistoryRecords, etc.
Schema named "tenant2" contains tables: tenant2.Users, tenant2.HistoryRecords, etc.
当我添加字段时,我希望将其添加到tenant1架构,tenant2架构等中。
初步想法: 我有一个表,其中包含租户的架构名称和相关信息。我正在考虑向此表添加数据库版本字段以跟踪架构更改。然后,我将创建一个存储过程,该过程接受DDL脚本和模式版本作为参数。
CREATE PROCEDURE UpdateSchema(DDLScript, InitialSchemaName, DbVersion)
@DDLScript nvarchar(5000),
@InitialSchemaName nvarchar(10),
@DbVersion nvarchar(5)...
脚本将循环遍历模式集,为每个模式运行DDL脚本,将InitialSchemaName替换为当前循环的模式名称,并在成功时为所有模式提交更改。
这是一个合理的计划,还是我错过了一种更常见的方法?
答案 0 :(得分:2)
您必须对多租户环境非常小心。有很多简单的方法可以解决问题。正如您所指出的,您的DDL脚本必须按模式进行编辑,这意味着更改脚本中的对象名称。这很可怕,但我理解这是必要的。它引入了一个用于SQL注入的向量。
我希望你有一个或多个"安全"用于测试的模式。哎呀,我希望你有一个完整的测试世界进行测试。但是 - 除了所有的担忧之外,这里是我过去用来将更改应用于多模式环境的脚本的脚手架:
create type dbo.ObjectNamesType as table ( Name sysname )
go
create procedure RunDDL( @scriptTemplate nvarchar( max ), @objName sysname, @objType nvarchar( 10 ), @schemas dbo.ObjectNamesType readonly )
as
begin
set nocount on
declare @script nvarchar( max )
declare @objectName nvarchar( 256 )
declare c cursor for
select N'[' + s.name + N'].[' + o.name + N']'
from
sys.objects o
inner join
@schemas s
on
o.schema_id = schema_id( s.Name )
where
o.name = @objName
and
o.type = @objType
open c
while ( 1=1 )
begin
fetch next from c into @objectName
if ( @@fetch_status != 0 ) break;
select @script = replace( @scriptTemplate, N'@objectName', @objectName )
exec sys.sp_executesql @script
end
close c
deallocate c
end
go
......并测试它......
declare @script nvarchar( max )
declare @objName sysname
declare @objType sysname
declare @schemas dbo.ObjectNamesType
insert @schemas values( 'dbo' )
select @objName = 'someTable'
select @objType = 'u'
select @script = 'select * from @objectName' --> not really ddl, eh?
exec dbo.RunDDL @script, @objName, @objType, @schemas
我实际使用的那个更复杂 - 所以我只是留下了多汁的部分。几个笔记:
输入的设置方式使得脚本可以针对一组模式运行。这允许您首先在测试模式上运行它并查看它是否正常 - 并且假设您喜欢它,然后您可以针对其余模式运行它 en masse 。
在我的世界中,@templateScript
,@objName
和@objType
位于我加入的表格中 - 并且未被传入。我不建议运行此类来自外部世界的投入的程序,因为这是对灾难的邀请......但是为了说明/测试,它有助于达到目的。此外,在我的世界中,输入表具有版本ID和序列。对于版本为 x 的任何模式,我们按顺序运行所有脚本并假设成功,将该模式的版本提升为 y 。每个脚本都适用于单个对象。
这里的要点是,您希望通过更新数据库来制定例行工作流程 - 此过程是该工作流程的核心。
请注意,它从sys.objects中选择,而不仅仅是相信输入。这只是另一个小的保护措施,可以防止脚本在名称中使用拼写错误。如果我们编辑的对象数量与指定的对象数量不匹配,我们会为自己记录一个警告。
此类型的过程还应该尝试/捕获脚本的实际执行,并记录它在此过程中尝试的所有内容。它应该在错误时回滚。确保您有足够的事务日志空间,因为即使很小的DDL也会导致巨大的变化。
它可以在指定的对象中运行,将@templateScript
编辑为@script
变量,然后使用@script
运行sys.sp_executesql
变量。这样,它永远不会更改源变量,因此替换目标保持不变。
不幸的是,您无法将变量用于tsql对象名称,或者您可以减少注入攻击的表面。因此,建议输入表而不是参数。这也意味着SQL无法真正做任何事情来参数化/重用声明/执行计划 - 但话说再来一次,这不是运行数十亿次,对吗?