Verificou-se que esse erro de código causou um loop infinito na produção:
DECLARE @BatchID INT
DECLARE MyCursor CURSOR FOR
SELECT BatchID = ...
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO @BatchID
WHILE @@FETCH_STATUS = 0
BEGIN
...
IF [condition]
BEGIN
...
FETCH NEXT FROM MyCursor INTO @BatchID
END
END
CLOSE MyCursor
DEALLOCATE MyCursor
Supondo que a necessidade de um cursor seja garantida , existe alguma maneira de se proteger contra esse erro (além de mais testes/revisão de código)?
Em outras linguagens, temos FOREACH
loops que gerenciam o progresso da iteração para nós, ou FOR
loops que têm uma reflexão tardia , então existe algum equivalente no SQL Server que evite erros desleixados como extraviar (ou esquecer!)?
Eu nunca vi loops personalizados no SQL precisarem fazer algo sofisticado com o cursor, e WHILE
os loops têm o mesmo risco (junto com casos como DELETE #WorkData WHERE ID = @BatchID
@BatchID isNULL
), então como esse risco pode ser mitigado programaticamente/limpamente para o caso de uso típico?
Uma abordagem como esta é muito desagradável:
DECLARE @BatchID INT
DECLARE MyCursor CURSOR FOR
SELECT BatchID = -1--dummy entry to always be skipped
UNION ALL SELECT BatchID = ...
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO @BatchID
WHILE @@FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM MyCursor INTO @BatchID
IF @@FETCH_STATUS = 0
BEGIN
...
END
END
CLOSE MyCursor
DEALLOCATE MyCursor
Parece-me que verificar @@FETCH_STATUS
duas vezes seguidas sem nenhuma instrução de posição/controle do cursor no meio pode ser uma maneira eficaz para o SQL Server adivinhar se houve esse erro, pois minha experiência com cursores nunca viu duas verificações seguidas intencionalmente (sem cursores aninhados pelo menos, mas aqueles ainda têm instruções de controle como OPEN
e CLOSE
entre verificações do @@FETCH_STATUS
cursor externo).
PS O [condition]
desta vez foi equivalente a "não é o dia de transição do horário de verão" (que foi neste domingo). Mais erros de fuso horário!
Estou mais interessado em recursos de linguagem para obter a mesma garantia fornecida automaticamente de como outras linguagens lidam com a cláusula de reflexão tardia de um loop for (por exemplo, o i++
infor (int i = 0; i < myArray.Length; i++)
). Quanto menos coisas o indivíduo é responsável, menos lugares existem para cometer um erro, e seria uma tarefa monumental auditar literalmente milhares de procedimentos armazenados em nosso sistema, especialmente porque muitos deles estão iterando dados de forma inadequada em vez de usar lógica baseada em conjunto já.
Para evitar problemas como esse, estou usando um padrão diferente para buscar:
Nesse padrão, você tem
FETCH
exatamente uma vez e precisa verificar o status de busca uma vez também.