SQL Azure:如何跨所有模式执行通用DDL脚本?

时间:2015-09-24 17:41:37

标签: sql-server tsql azure azure-sql-database multi-tenant

是否有内置的方法在所有模式中执行公共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替换为当前循环的模式名称,并在成功时为所有模式提交更改。

这是一个合理的计划,还是我错过了一种更常见的方法?

1 个答案:

答案 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无法真正做任何事情来参数化/重用声明/执行计划 - 但话说再来一次,这不是运行数十亿次,对吗?