我知道这个问题已经被问过好几次了,我知道解决方案,但我试图了解问题的根本原因:
我有以下代码来执行数据库备份。
DECLARE @Filename VARCHAR(256)
DECLARE @FileDate VARCHAR(15)
DECLARE @Path VARCHAR(50)
DECLARE @Name VARCHAR(50)
-- specify database backup directory
SET @Path = '\MyPath'
-- specify filename date
SELECT @FileDate = CONVERT(VARCHAR(20), GETDATE(), 112) + '_' + REPLACE(CONVERT(VARCHAR(20), GETDATE(), 108),':','')
DECLARE db_cursor CURSOR FOR
SELECT [name]
FROM master.sys.databases
WHERE [name] NOT IN ('master', 'msdb', 'model', 'tempdb')
AND [state_desc] = 'ONLINE'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO @Name
WHILE @@FETCH_STATUS = 0
BEGIN
SET @Filename = @Path + @Name + '_Full_Backup_' + @FileDate + '.bak'
BACKUP DATABASE @Name
TO DISK = @Filename
WITH CHECKSUM,
COMPRESSION
FETCH NEXT FROM db_cursor INTO @Name
END
CLOSE db_cursor
DEALLOCATE db_cursor
有时,只备份了一些数据库,这表明游标没有遍历所有返回的行,或者查询本身没有返回它应该返回的所有数据库的名称。
我试图理解为什么会发生这种情况。我知道解决方法是使用STATIC
游标,这表明问题出在基础查询中的结果上
SELECT [name]
FROM master.sys.databases
WHERE [name] NOT IN ('master', 'msdb', 'model', 'tempdb')
AND [state_desc] = 'ONLINE'
正在发生变化,但我看不到会发生什么变化(没有数据库名称会发生变化,并且错误日志并不表明数据库状态已发生变化)
sys.databases
是一个复杂的视图。在 SQL Server 2016 上是:通过不指定特定的游标选项,您隐式地请求
local, dynamic, updatable, optimistic, forward-only
此视图上的游标。SQL Server 无法生成动态游标计划,因此它将游标转换为本地、键集、可更新、乐观、只进游标。
键集意味着在打开游标时,在底层系统表中定位行所需的最少键存储在tempdb中。如果底层系统表中的这些键值中的任何一个发生更改,则不会返回一行并将
@@FETCH_STATUS
返回 -2。您的循环将因失败而过早退出@@FETCH_STATUS = 0
,并且不会处理游标中的其他数据库。可能导致密钥更改的一个示例是更改ALLOW_SNAPSHOT_ISOLATION
数据库的状态。当可能发生并发键更改时,键集游标是不明智的。我们不控制基础表,因此使用键集游标
sys.databases
是自找麻烦。检查失败也很重要@@FETCH_STATUS
,而不是在到达终点之前假设成功。乐观游标使用校验和来检测自游标打开以来对行的更改,因此不会丢失更新。这在这里并不直接重要,因为您不是通过游标更新行,但 SQL Server 不知道这一点。它必须制定一个收集、存储和比较校验和的计划。
旁注:在 open 和 fetch 执行计划中完成的大部分工作都是多余的,因为您只对数据库名称和
state_desc
列感兴趣。大多数冗余系统表访问无法删除,因为优化器没有足够的键关系信息来允许删除外连接。您可以通过添加来解决此问题DISTINCT name
(因此外部连接不能重复行),但这也会强制游标降级为静态(快照)。不过,您可能有兴趣查看添加DISTINCT
.这里的教训是明确指定您想要的游标类型,而不是依赖默认值。游标的这种使用要求静态(快照)类型: