Comando de suspensão no T-SQL?

364

Existe uma maneira de escrever um comando T-SQL para fazê-lo dormir por um período de tempo? Estou escrevendo um serviço da Web de forma assíncrona e quero poder executar alguns testes para verificar se o padrão assíncrono realmente o tornará mais escalável. Para "zombar" de um serviço externo lento, desejo poder chamar um servidor SQL com um script que seja executado lentamente, mas na verdade não esteja processando muitas coisas.

skb
fonte
19
Pergunta justa! Eu posso querer usar isso algum dia. Como um completo lado, esta é a primeira vez que eu já ouvi de querer o DB a ser mais lento;)
p.campbell
2
Estou impressionado chamando um serviço assíncrono do T-SQL.
jmucchiello

Respostas:

616

Veja o comando WAITFOR .

Por exemplo

-- wait for 1 minute
WAITFOR DELAY '00:01'

-- wait for 1 second
WAITFOR DELAY '00:00:01'

Esse comando permite um alto grau de precisão, mas é preciso apenas entre 10 e 16 ms em uma máquina típica, pois depende do GetTickCount . Assim, por exemplo, WAITFOR DELAY '00:00:00:001'é provável que a chamada resulte em nenhuma espera.

Sam Saffron
fonte
4
Alguém sabe como fazer isso funcionar com uma função? Eu recebo (corretamente provavelmente), mas por uma questão de testar eu gostaria de override) 'Uso inválido de um operador de efetuar lado 'WAITFOR' dentro de uma função ....
monojohnny
2
@monojohnny para obter um SVF para esperar, tentei a resposta de Josh abaixo, mas não funcionou. Em vez disso, eu apenas crio um loop WHILE como este:CREATE FUNCTION [dbo].[ForcedTimeout](@seconds int) returns int as BEGIN DECLARE @endTime datetime2(0) = DATEADD(SECOND, @seconds, GETDATE()); WHILE (GETDATE() < @endTime ) BEGIN SET @endTime = @endTime; -- do nothing, but SQL requires a statement. END
GilesDMiddleton
3
Certifique-se de usar 3 dígitos para que ms - '00: 00: 00: 01 'não seja igual a '00: 00: 00: 010', use o segundo. (testado no MSSQL 2016)
Nick
você também pode tentar BEGIN TRANSACTION e END TRANSACTION se precisar bloquear uma tabela
Richárd Baldauf
9
WAITFOR DELAY 'HH:MM:SS'

Acredito que o tempo máximo que isso pode esperar é de 23 horas, 59 minutos e 59 segundos.

Aqui está uma função com valor escalar para mostrar seu uso; a função abaixo terá um parâmetro inteiro de segundos, que depois é convertido em HH: MM: SS e o executa usando o EXEC sp_executesql @sqlcodecomando para consultar. A função abaixo é apenas para demonstração, eu sei que ela não se encaixa realmente como uma função com valor escalar! :-)

    CREATE FUNCTION [dbo].[ufn_DelayFor_MaxTimeIs24Hours]
    (
    @sec int
    )
    RETURNS
    nvarchar(4)
    AS
    BEGIN


    declare @hours int = @sec / 60 / 60
    declare @mins int = (@sec / 60) - (@hours * 60)
    declare @secs int = (@sec - ((@hours * 60) * 60)) - (@mins * 60)


    IF @hours > 23 
    BEGIN
    select @hours = 23
    select @mins = 59
    select @secs = 59
    -- 'maximum wait time is 23 hours, 59 minutes and 59 seconds.'
    END


    declare @sql nvarchar(24) = 'WAITFOR DELAY '+char(39)+cast(@hours as nvarchar(2))+':'+CAST(@mins as nvarchar(2))+':'+CAST(@secs as nvarchar(2))+char(39)


    exec sp_executesql @sql

    return ''
    END

Se você deseja atrasar mais de 24 horas, sugiro que você use um parâmetro @Days para passar vários dias e agrupar a função executável dentro de um loop ... por exemplo.

    Declare @Days int = 5
    Declare @CurrentDay int = 1

    WHILE @CurrentDay <= @Days
    BEGIN

    --24 hours, function will run for 23 hours, 59 minutes, 59 seconds per run.
    [ufn_DelayFor_MaxTimeIs24Hours] 86400

    SELECT @CurrentDay = @CurrentDay + 1
    END
Josh Harris
fonte
O SQL Azure não gosta disso. O Only functions and some extended stored procedures can be executed from within a function. MS Docs fornece um exemplo usando os procedimentos armazenados - parece que essa abordagem é inválida
SliverNinja - MSFT
5

Você também pode "WAITFOR" a "TIME":

    RAISERROR('Im about to wait for a certain time...', 0, 1) WITH NOWAIT
    WAITFOR TIME '16:43:30.000'
    RAISERROR('I waited!', 0, 1) WITH NOWAIT
Jeremy Giaco
fonte
2
Por que não usar em PRINTvez de RAISERROR?
slartidan
4
Porque, caso contrário, você não veria nada até que toda a espera terminasse. RAISERROR fornece a opção NOWAIT, para que você mostre as instruções (essencialmente) em tempo real, ao contrário de quando o buffer estiver cheio ou após a conclusão do lote.
Jeremy Giaco
0

Aqui está uma parte muito simples do código C # para testar o CommandTimeout. Ele cria um novo comando que aguarda 2 segundos. Defina o CommandTimeout como 1 segundo e você verá uma exceção ao executá-lo. Definir o CommandTimeout como 0 ou algo maior que 2 funcionará bem. A propósito, o CommandTimeout padrão é 30 segundos.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Data.SqlClient;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      var builder = new SqlConnectionStringBuilder();
      builder.DataSource = "localhost";
      builder.IntegratedSecurity = true;
      builder.InitialCatalog = "master";

      var connectionString = builder.ConnectionString;

      using (var connection = new SqlConnection(connectionString))
      {
        connection.Open();

        using (var command = connection.CreateCommand())
        {
          command.CommandText = "WAITFOR DELAY '00:00:02'";
          command.CommandTimeout = 1;

          command.ExecuteNonQuery();
        }
      }
    }
  }
}
user2192239
fonte
2
Se você estiver em c #, provavelmente deverá usar Thread.currentThread.sleep (60000) OU Thread.sleep (60000), que faz a mesma coisa. Dessa forma, seu atraso é isolado no seu aplicativo. Em seguida, chame sua lógica de banco de dados subsequente posteriormente.
Ação Dan
2
@ActionDan Usar Thread.Sleep não vai ajudar a exercitar o CommandTimeout, é isso. Como um exemplo artificial, ele faz o que está escrito na caixa.
Richard Hauer