如何在 SQL 中刷新 PRINT 缓冲区?

Posted

技术标签:

【中文标题】如何在 SQL 中刷新 PRINT 缓冲区?【英文标题】:How do I flush the PRINT buffer in TSQL? 【发布时间】:2010-09-23 08:17:07 【问题描述】:

我正在尝试调试 SQL Server 2005 中一个运行时间很长的存储过程,并且我正在使用“打印”命令来执行此操作。问题是,我只是在存储过程结束时从 SQL Server 获取消息 - 我希望能够在存储过程运行时刷新消息缓冲区并立即查看这些消息,而不是在结束。

【问题讨论】:

对于那些(像我一样)认为答案对他们不起作用的人来说只是一个简短的通知:请确保在查询运行时切换到“消息”选项卡。默认情况下,您会看到“结果”选项卡。 我在 Messages 上仍然一无所获。 【参考方案1】:

使用RAISERROR函数:

RAISERROR( 'This message will show up right away...',0,1) WITH NOWAIT

您不应该用 raiserror 完全替换所有打印件。如果您在某处有一个循环或大光标,则每次迭代执行一次或两次,甚至每隔几次迭代执行一次。

另外:我第一次了解 RAISERROR 是在这个链接上,我现在认为它是关于 SQL Server 错误处理的权威来源,绝对值得一读:http://www.sommarskog.se/error-handling-I.html

【讨论】:

请注意,SQL 中的 TRY/CATCH 只会捕获严重性 > 10 的错误,因此以这种方式使用 RAISERROR 不会跳转到您的 CATCH 语句中。这很好,因为这意味着您仍然可以像这样使用 RAISERROR 和 TRY/CATCH。参考:msdn.microsoft.com/en-us/library/ms175976.aspx 请注意,这在前 500 条消息之后不起作用;一旦您打印的数量超过了它,它就会突然开始缓冲! @MahmoudMoravej 不,我仍在使用 RAISEERROR 运行长时间运行的进程,并且只是处理一段时间后消息开始缓冲的事实。似乎唯一的解决方案是使用 SSMS 以外的其他工具。 我认为这在最近的 SS 版本中有所改变。早在我第一次写这篇文章时,我们使用 RAISERROR 来广泛记录包含 500 多条消息的夜间批处理过程,这不是问题。但 7 年内会有很多变化。 在@GendoIkari 的通知下。我已经用这个脚本从 2016SP1 开始使用 ssms 进行了尝试。在 500 时切换到缓冲 50 行,在 1k 时切换到每行 100 行。这至少持续到 2k,但后来我停止了脚本。声明 @i int set @i = 0 声明 @t varchar(100) while 1=1 begin set @i = @i + 1 set @t = 'print ' + convert(varchar, @i) RAISERROR (@t, 10 , 1) WITH NOWAIT waitfor delay '00:00:00.010' end【参考方案2】:

基于@JoelCoehoorn 的回答,我的方法是保留我所有的 PRINT 语句,然后简单地在它们后面加上 RAISERROR 语句以导致刷新。

例如:

PRINT 'MyVariableName: ' + @MyVariableName
RAISERROR(N'', 0, 1) WITH NOWAIT

这种方法的优点是 PRINT 语句可以连接字符串,而 RAISERROR 不能。 (因此,无论哪种方式,您的代码行数都相同,因为您必须声明并设置要在 RAISERROR 中使用的变量)。

如果您像我一样使用 AutoHotKey 或 SSMSBoost 或等效工具,您可以轻松设置快捷方式,例如“]flush”为您输入 RAISERROR 行。如果每次都是同一行代码,即不需要定制来保存特定的文本或变量,这可以节省您的时间。

【讨论】:

请注意,RAISERROR() 确实支持printf() 样式的字符串插值。例如,如果@MyVariableName 是字符串类型(例如,VARCHAR(MAX)NVARCHAR(MAX) 等),您可以使用 RAISERROR() 一行:RAISERROR(N'MyVariableName: %s', 0, 1, @MyVariableName) 这太方便了!我知道 RAISERROR 可以做一些简单的替换,但请尝试替换 [日期] 时间,或从 RAISERROR 语句中调用函数!这个答案以引发空错误的形式为您提供了一个简单的 FLUSH(以换行为代价)。【参考方案3】:

是... RAISERROR 函数的第一个参数需要一个 NVARCHAR 变量。所以尝试以下方法;

-- Replace PRINT function
DECLARE @strMsg NVARCHAR(100)
SELECT @strMsg = 'Here''s your message...'
RAISERROR (@strMsg, 0, 1) WITH NOWAIT

RAISERROR (n'Here''s your message...', 0, 1) WITH NOWAIT

【讨论】:

查看底部的消息选项卡,在结果选项卡旁边或切换到结果转文本模式。 要切换到结果到文本模式,在 SSMS 中,菜单工具 -> 选项 -> 查询结果 -> SQL Server -> 常规 -> 结果的默认目标,然后选择“结果到文本”的“结果到网格”,重新打开查询窗口,然后当 RAISERROR 输出转到“消息”选项卡时,您不会像假人一样坐在那里看着空白的“结果”选项卡。【参考方案4】:

另一个更好的选择是不依赖 PRINT 或 RAISERROR,只需将“打印”语句加载到 TempDB 中的 ##Temp 表或数据库中的永久表中,这将使您可以通过 SELECT 语句立即查看数据从另一个窗口。这对我来说是最好的。使用永久表也可以作为过去发生的事情的日志。打印语句对于错误很方便,但使用日志表,您还可以根据该特定执行的最后记录值确定确切的故障点(假设您在日志表中跟踪总体执行开始时间。)

【讨论】:

如果您正在编写具有提交和回滚功能的真正事务性脚本,这可能是个问题。我不相信您将能够实时查询您的临时表 - 如果您的事务失败,它将消失。 @SteveJ 您可以在监控会话中使用SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 实时查询它 @TheConstructor ;这是一个有用的提示 - 我会利用它,谢谢。但是,我们不是还剩下临时表在回滚时消失吗?如果做故障分析,那似乎是一个很大的缺点。 @SteveJ 是的,当然有这个。您当然可以将READ UNCOMMITTED 事务中的数据复制到另一个表,但您可能会错过ROLLBACK 之前的那一刻。所以它可能解决了“多远?”不是“为什么要回滚?” 对于大多数错误,如果您 SET XACT_ABORT OFF; 并在 catch 块中手动回滚或通过其他错误检测方式,您可以通过表变量保存日志以防止回滚(请务必使用表变量因为它们不受事务回滚的影响,但临时表受): ` -- 在开头 DECLARE @maxLogId INT = (SELECT MAX(ID) FROM LogTable); -- 做事 -- 错误处理 DECLARE @tmpLog TABLE (/* log table cols */); INSERT INTO @tmpLog SELECT * FROM LogTable WHERE ID > @maxLogId;回滚转; -- 设置身份插入并重新插入 tmpLog 的内容 `【参考方案5】:

仅供参考,如果您在脚本(批处理)中工作,而不是在存储过程中,刷新输出由 GO 命令触发,例如

print 'test'
print 'test'
go

总的来说,我的结论如下:mssql 脚本执行的输出,在 SMS GUI 中或使用 sqlcmd.exe 执行,在第一个 GO 语句或直到脚本结束时刷新到文件、stdoutput、gui 窗口。

在存储过程函数内部刷新不同,因为你不能将 GO 放在里面。

参考:tsql Go statement

【讨论】:

go 不仅会刷新输出,还会根据您提供的链接结束批处理。您declared 的任何内容都将被丢弃,因此对调试不太有用。 declare @test int print "I want to read this!" go set @test=5 会告诉你一个错误,声称 @test 是未定义的,因为它在一个新批次中。 我同意,这不是对这个问题的正确答案,但我给出了答案(请参阅开头的免责声明),因为它可能对其他人有用 - 例如运行批处理 sql 的人。【参考方案6】:

要扩展Eric Isaac's answer,下面是正确使用表格方法的方法:

首先,如果您的 sp 使用事务,您将无法实时监控表的内容,除非您使用 READ UNCOMMITTED 选项:

SELECT *
FROM table_log WITH (READUNCOMMITTED);

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT *
FROM table_log;

为了解决回滚问题,在日志表上放一个递增的ID,并使用以下代码:

SET XACT_ABORT OFF;
BEGIN TRY
  BEGIN TRANSACTION mytran;
  -- already committed logs are not affected by a potential rollback
  -- so only save logs created in this transaction
  DECLARE @max_log_id = (SELECT MAX(ID) FROM table_log);
  /*
   * do stuff, log the stuff
   */

  COMMIT TRANSACTION mytran;
END TRY
BEGIN CATCH
  DECLARE @log_table_saverollback TABLE
  (
    ID INT,
    Msg NVARCHAR(1024),
    LogTime DATETIME
  );
  
  INSERT INTO @log_table_saverollback(ID, Msg, LogTime)
  SELECT ID, Msg, LogTime
  FROM table_log
  WHERE ID > @max_log_id;

  ROLLBACK TRANSACTION mytran; -- this deletes new log entries from the log table

  SET IDENTITY_INSERT table_log ON;

  INSERT INTO table_log(ID, Msg, LogTime)
  SELECT ID, Msg, LogTime
  FROM @log_table_saverollback;

  SET IDENTITY_INSERT table_log OFF;
END CATCH

注意这些重要细节:

    SET XACT_ABORT OFF; 防止 SQL Server 只是关闭整个事务而不是运行您的 catch 块,如果您使用此技术,请始终包含它。 使用@table_variable,而不是#temp_table。临时表也会受到回滚的影响。

【讨论】:

以上是关于如何在 SQL 中刷新 PRINT 缓冲区?的主要内容,如果未能解决你的问题,请参考以下文章

Django Channels:如何刷新发送缓冲区

使用 boost::serialization 时如何刷新文件缓冲区?

SQL Server 了解日志缓存刷新

SQL Server 了解日志缓存刷新

当磁盘上的文件发生更改时,如何让 Emacs 自动刷新所有缓冲区?

如何缓冲multiprocessing.Process内部和外部函数的所有print()?