如何在TSQL中清空PRINT缓冲区?

260

我有一个在 SQL Server 2005 中非常耗时的存储过程需要进行调试,我正在使用 'print' 命令来实现。问题是,我只能在 sproc 最后获取来自 SQL Server 的消息 - 我想要刷新消息缓冲区并在 sproc 运行期间立即查看这些消息,而不是在最后。


8
如果你和我一样认为答案对你不起作用,注意切换到“Messages”选项卡以便查看正在运行的查询。默认情况下,你会看到“Results”选项卡。 - Tomasz Gandor
我在信息上,却仍然什么也没收到。 - Maury Markowitz
6个回答

354

使用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


48
请注意,在SQL中,TRY/CATCH只会捕获严重程度大于10的错误,因此使用RAISERROR以这种方式不会跳转到CATCH语句块。这很好,因为这意味着您仍然可以在TRY/CATCH中使用RAISERROR。参考:http://msdn.microsoft.com/en-us/library/ms175976.aspx - Rory
18
请注意,这种方法在发送了500条消息后将不再起作用;一旦超过500条,它就会开始缓冲! - GendoIkari
1
@MahmoudMoravej 不,我仍在使用RAISEERROR运行长时间运行的进程,并且只是处理一段时间后消息开始被缓冲的事实。似乎唯一的解决方案是使用除SSMS之外的其他工具。 - GendoIkari
1
我认为这是最近版本的SS中发生的变化。很久以前,当我第一次编写此代码时,我们使用RAISERROR对过夜批处理进行广泛记录,超过500条消息,并没有问题。但是在7年内很多事情都可能发生改变。 - Joel Coehoorn
在这个问题中有一些更多的细节:https://dev59.com/mHrZa4cB1Zd3GeqP00wR,为什么在最近版本的sqlcmd中这样做不起作用。 - Lanorkin
2
根据@GendoIkari的通知,我已经使用2016SP1的ssms尝试了这个脚本。在500行时它切换到每50行缓冲一次,并且在1k时切换到每100行缓冲一次。这至少持续到2k,但是然后我停止了脚本。 声明 @i int 设置 @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 - Zartag

59

在 @JoelCoehoorn 的回答基础上,我的做法是保留所有的 PRINT 语句,并在它们后面添加 RAISERROR 语句来进行刷新。

例如:

PRINT 'MyVariableName: ' + @MyVariableName
RAISERROR(N'', 0, 1) WITH NOWAIT
这种方法的优点在于,PRINT语句可以连接字符串,而RAISERROR则不行。(因此无论哪种方式,您都需要相同行数的代码,因为您必须声明和设置一个变量以在RAISERROR中使用)。如果您像我一样使用AutoHotKey或SSMSBoost或等效工具,则可以轻松设置快捷方式,例如“]flush”来为您输入RAISERROR行。 如果每次都是相同的代码行,即不需要自定义以包含特定文本或变量,则这将节省您的时间。

11
请注意,RAISERROR() 支持 printf() 风格的字符串插值。例如,如果 @MyVariableName 是字符串类型(例如 VARCHAR(MAX)NVARCHAR(MAX) 等),您可以使用一行代码的 RAISERROR()RAISERROR(N'MyVariableName: %s', 0, 1, @MyVariableName) - binki
这太方便了!我知道RAISERROR可以进行一些简单的替换,但是尝试在RAISERROR语句内部替换[date]time或调用函数!这个答案以引发空错误的形式给出了一个简单的FLUSH(代价是一个换行符)。 - Tomasz Gandor

21

是的... 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

11
请查看底部的“消息”选项卡,位于“结果”选项卡旁边,或切换到“文本结果”模式。 - Mehmet Ergut
要切换到文本模式的结果,在SSMS中,菜单工具->选项->查询结果->SQL Server->常规->结果的默认目标,选择“结果为文本”而不是“结果为网格”,重新打开查询窗口,然后你就不会像个傻瓜一样坐在空白的结果选项卡前看着RAISERROR输出到消息选项卡了。 - Adam

14

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


3
如果您正在编写带有提交和回滚的真正事务脚本,这可能会成为一个问题。我认为您将无法实时查询您的临时表 - 如果事务失败,它将消失。 - SteveJ
@SteveJ 你可以通过在监控会话中使用 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 实时查询它。 - TheConstructor
2
@构造函数; 这是一个有用的提示-我会利用它,谢谢。然而,如果进行故障分析,我们是否仍然面临回滚时临时表消失的问题?这似乎是一个很大的缺点。 - SteveJ
1
@SteveJ 是的,当然有这个方法。你可以在一个READ UNCOMMITTED事务中将数据复制到另一个表中,但你可能会错过ROLLBACK之前的那一刻。所以它可能只解决了“到哪里?”而不是“为什么回滚?”的问题。 - TheConstructor
1
对于大多数错误,如果您将 SET XACT_ABORT OFF; 并在 catch 块或其他错误检测手段中手动回滚,则可以通过表变量保存日志以避免回滚(请确保使用表变量,因为它们不受事务回滚的影响,但临时表会受到影响):` -- 在开头 DECLARE @maxLogId INT = (SELECT MAX(ID) FROM LogTable);-- 执行操作-- 错误处理 DECLARE @tmpLog TABLE (/* 日志表列 */); INSERT INTO @tmpLog SELECT * FROM LogTable WHERE ID > @maxLogId;ROLLBACK TRAN;-- 设置标识插入并重新插入 tmpLog 的内容 ` - sisisisi

5

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

print 'test'
print 'test'
go

总的来说,我的结论如下:在SMS GUI中执行或使用sqlcmd.exe执行的mssql脚本输出会在第一个GO语句或脚本结束时刷新到文件、标准输出和GUI窗口。
由于无法在存储过程中放置GO语句,因此存储过程内部的刷新方式不同。
参考:tsql Go statement

6
根据你提供的链接,go 不仅会刷新输出,还会结束当前批处理。任何你声明的变量都会被丢弃,因此不适用于调试。例如,declare @test int print "I want to read this!" go set @test=5 会抛出一个错误,表示 @test 未定义,因为它在一个新的批处理中。 - asontu
2
我同意,这不是这个问题的恰当答案,但我还是提供了答案(请注意开头的免责声明),因为它可能对其他人有用——例如运行批处理 SQL 的人。 - Robert Lujo

2

为了扩展Eric Isaac的回答,以下是正确使用表格方法的步骤:

首先,如果您的存储过程使用事务,则无法实时监视表格内容,除非您使用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

请注意以下重要细节:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接