SQL Server嵌套游标分配变更跟踪权限时内部游标未随USE切库问题
问题核心原因
- 动态SQL执行的上下文是独立的:你通过
EXEC sp_sqlexec执行的USE语句仅在该次动态SQL的执行周期内生效,执行完成后会立即回到脚本初始连接的数据库上下文,后续所有针对sys系统视图的查询仍然是在初始库执行,自然读不到其他库的变更跟踪配置。 - 静态游标在编译阶段就已绑定上下文:你定义的内部
TblCursor游标属于静态定义,在整个脚本开始编译时就已经绑定到初始当前数据库的系统视图,哪怕后续真的切换了当前数据库,已经编译完成的游标也不会切换查询的目标库。
修正方案
需要把每个目标库下要执行的所有操作(创建角色、查询变更跟踪表、授权)全部封装到同一段动态SQL中,在同一个上下文内完成所有库内操作,同时补充特殊字符转义逻辑避免异常,修正后的代码如下:
DECLARE @Debug BIT = 1 DECLARE @newgrp VARCHAR(100) = 'ChangeTrakingViewableRole' -- 注意此处拼写少了一个c,标准拼写为ChangeTracking,可按需调整 DECLARE @DBName sysname DECLARE @tsql NVARCHAR(MAX) DECLARE @msg VARCHAR(900) IF @Debug = 1 PRINT 'Debuging ON' IF COALESCE(@newgrp,'') = '' BEGIN PRINT 'There was no DatabaseRole, User or Group Specified to take the place of the Public Role' SET NOEXEC ON END ELSE BEGIN DECLARE DbCursor CURSOR FOR SELECT d.name FROM sys.databases d JOIN sys.change_tracking_databases c ON d.database_id = c.database_id ORDER BY d.name OPEN DbCursor FETCH NEXT FROM DbCursor INTO @DBName WHILE @@Fetch_Status = 0 BEGIN RAISERROR ('当前处理数据库:%s', 0, 1, @DBName) WITH NOWAIT -- 将单库所有操作封装到同一段动态SQL SET @tsql = N' USE '+QUOTENAME(@DBName)+'; DECLARE @SchName VARCHAR(55), @TblName sysname, @InnerTSQL NVARCHAR(MAX) -- 创建角色 IF NOT EXISTS (SELECT name FROM sys.database_principals where name = N''' + @newgrp + N''') BEGIN CREATE ROLE '+QUOTENAME(@newgrp)+N' AUTHORIZATION [dbo] ' + CASE WHEN @Debug=1 THEN N'PRINT ''创建角色:' + @newgrp + N'''' ELSE N'' END + N' END -- 遍历当前库开启变更跟踪的表授权 DECLARE TblCursor CURSOR FOR SELECT sch.name, tbl.name FROM sys.change_tracking_tables chg JOIN sys.tables tbl ON chg.object_id=tbl.object_id JOIN sys.schemas sch ON tbl.schema_id=sch.schema_id ORDER BY sch.name, tbl.name OPEN TblCursor FETCH NEXT FROM TblCursor INTO @SchName,@TblName WHILE @@FETCH_STATUS = 0 BEGIN SET @InnerTSQL = N''GRANT VIEW CHANGE TRACKING ON ''+QUOTENAME(@SchName)+''.''+QUOTENAME(@TblName)+'' TO '+QUOTENAME(@newgrp)+N''' ' + CASE WHEN @Debug=1 THEN N'PRINT @InnerTSQL' ELSE N'EXEC sp_executesql @InnerTSQL' END + N' FETCH NEXT FROM TblCursor INTO @SchName,@TblName END CLOSE TblCursor DEALLOCATE TblCursor ' IF @Debug = 0 BEGIN EXEC sp_executesql @tsql END ELSE BEGIN PRINT @tsql END FETCH NEXT FROM DbCursor INTO @DBName END CLOSE DbCursor DEALLOCATE DbCursor END
内容的提问来源于stack exchange,提问作者user43591
相关产品推荐
相关产品推荐

