Se eu entendi a solicitação corretamente, o objetivo é excluir lotes de linhas, enquanto, ao mesmo tempo, operações DML estão ocorrendo nas linhas da tabela. O objetivo é excluir um lote; no entanto, se alguma linha subjacente contida no intervalo definido pelo referido lote estiver bloqueada, devemos pular esse lote e passar para o próximo lote. Devemos então retornar a qualquer lote que não tenha sido excluído anteriormente e tentar novamente nossa lógica de exclusão original. Devemos repetir esse ciclo até que todos os lotes necessários de linhas sejam excluídos.
Como foi mencionado, é razoável usar uma dica READPAST e o nível de isolamento READ COMMITTED (padrão), para ignorar os intervalos anteriores que podem conter linhas bloqueadas. Vou dar um passo adiante e recomendo o uso do nível de isolamento SERIALIZABLE e exclusões de mordiscar.
O SQL Server usa bloqueios de intervalo de chave para proteger um intervalo de linhas implicitamente incluídas em um conjunto de registros que está sendo lido por uma instrução Transact-SQL ao usar o nível de isolamento de transação serializável ... saiba mais aqui:
https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
Com exclusões de mordiscar, nosso objetivo é isolar um intervalo de linhas e garantir que nenhuma alteração ocorra nessas linhas enquanto as excluirmos, ou seja, não queremos leituras ou inserções fantasmas. O nível de isolamento serializável visa solucionar esse problema.
Antes de demonstrar minha solução, gostaria de acrescentar que não estou recomendando mudar o nível de isolamento padrão do seu banco de dados para SERIALIZABLE nem estou recomendando que minha solução seja a melhor. Eu apenas desejo apresentá-lo e ver para onde podemos ir daqui.
Algumas notas de manutenção:
- A versão do SQL Server que estou usando é o Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- Meu banco de dados de teste está usando o modelo de recuperação COMPLETO
Para iniciar meu experimento, configurarei um banco de dados de teste, uma tabela de amostra e preencherei a tabela com 2.000.000 de linhas.
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
ALTER DATABASE [test] SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
c1 BIGINT IDENTITY (1,1) NOT NULL
, c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows
INSERT INTO dbo.tbl
SELECT TOP 2000
number
FROM
master..spt_values
ORDER BY
number
GO 1000
Nesse ponto, precisaremos de um ou mais índices nos quais os mecanismos de bloqueio do nível de isolamento SERIALIZABLE possam atuar.
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2
ON dbo.tbl (c2);
GO
Agora, vamos verificar se nossas 2.000.000 de linhas foram criadas
SELECT
COUNT(*)
FROM
tbl;
Portanto, temos nosso banco de dados, tabela, índices e linhas. Então, vamos configurar o experimento para excluir petiscos. Primeiro, devemos decidir qual a melhor maneira de criar um mecanismo típico de exclusão.
DECLARE
@BatchSize INT = 100
, @LowestValue BIGINT = 20000
, @HighestValue BIGINT = 20010
, @DeletedRowsCount BIGINT = 0
, @RowCount BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE @DeletedRowsCount < ( @HighestValue - @LowestValue )
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
DELETE
FROM
dbo.tbl
WHERE
c1 IN (
SELECT TOP (@BatchSize)
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN @LowestValue AND @HighestValue
ORDER BY
c1
);
SET @RowCount = ROWCOUNT_BIG();
COMMIT TRANSACTION;
SET @DeletedRowsCount += @RowCount;
WAITFOR DELAY '000:00:00.025';
CHECKPOINT;
END;
Como você pode ver, coloquei a transação explícita dentro do loop while. Se você deseja limitar as liberações de log, sinta-se à vontade para colocá-lo fora do loop. Além disso, como estamos no modelo de recuperação COMPLETO, convém criar backups de log de transações com mais frequência ao executar suas operações de exclusão, a fim de garantir que seu log de transações possa ser impedido de crescer escandalosamente.
Então, eu tenho alguns objetivos com essa configuração. Primeiro, quero meus bloqueios no intervalo de teclas; então, tento manter os lotes o menor possível. Eu também não quero impactar negativamente a concorrência na minha mesa "gigantesca"; então, quero pegar meus bloqueios e deixá-los o mais rápido possível. Portanto, recomendo que você diminua o tamanho do lote.
Agora, quero fornecer um exemplo muito curto dessa rotina de exclusão em ação. Devemos abrir uma nova janela no SSMS e excluir uma linha da nossa tabela. Farei isso dentro de uma transação implícita usando o nível de isolamento READ COMMITTED padrão.
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
Esta linha foi realmente excluída?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
Sim, foi excluído.
Agora, para ver nossos bloqueios, vamos abrir uma nova janela no SSMS e adicionar um trecho de código ou dois. Estou usando o sp_whoisactive do Adam Mechanic, que pode ser encontrado aqui: sp_whoisactive
SELECT
DB_NAME(resource_database_id) AS DatabaseName
, resource_type
, request_mode
FROM
sys.dm_tran_locks
WHERE
DB_NAME(resource_database_id) = 'test'
AND resource_type = 'KEY'
ORDER BY
request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
Agora, estamos prontos para começar. Em uma nova janela do SSMS, vamos começar uma transação explícita que tentará reinserir a linha que excluímos. Ao mesmo tempo, iniciaremos nossa operação de exclusão mordisada.
O código de inserção:
BEGIN TRANSACTION
SET IDENTITY_INSERT dbo.tbl ON;
INSERT INTO dbo.tbl
( c1 , c2 )
VALUES
( 20005 , 1 );
SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
Vamos iniciar as duas operações começando com a inserção e seguidas pelas nossas exclusões. Podemos ver os bloqueios de faixa de chave e bloqueios exclusivos.
A inserção gerou esses bloqueios:
A exclusão / seleção mordisca está mantendo estes bloqueios:
Nossa inserção está bloqueando nossa exclusão conforme o esperado:
Agora, vamos confirmar a transação de inserção e ver o que está acontecendo.
E, como esperado, todas as transações são concluídas. Agora, devemos verificar se a inserção era um fantasma ou se a operação de exclusão também a removeu.
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
De fato, a inserção foi excluída; portanto, nenhuma inserção fantasma foi permitida.
Portanto, concluindo, acho que a verdadeira intenção deste exercício não é tentar rastrear todas as linhas, páginas ou bloqueios em nível de tabela e tentar determinar se um elemento de um lote está bloqueado e, portanto, exigiria que nossa operação de exclusão fosse executada. esperar. Essa pode ter sido a intenção dos questionadores; no entanto, essa tarefa é hercúlea e basicamente impraticável, se não impossível. O objetivo real é garantir que nenhum fenômeno indesejado ocorra depois que isolarmos o intervalo do nosso lote com bloqueios próprios e, em seguida, precedermos a exclusão do lote. O nível de isolamento SERIALIZABLE atinge esse objetivo. A chave é manter pequenos petiscos, controlar o log de transações e eliminar fenômenos indesejados.
Se você deseja velocidade, não crie tabelas gigantes profundas que não possam ser particionadas e, portanto, não consiga usar a alternância de partições para obter os resultados mais rápidos. A chave da velocidade é particionamento e paralelismo; a chave do sofrimento é mordiscar e travar a vida.
Por favor, deixe-me saber o que você pensa.
Criei mais alguns exemplos do nível de isolamento SERIALIZABLE em ação. Eles devem estar disponíveis nos links abaixo.
Excluir operação
Inserir operação
Operações de igualdade - bloqueios de intervalo de chave nos próximos valores-chave
Operações de Igualdade - Busca Singleton de Dados Existentes
Operações de igualdade - busca singleton de dados inexistentes
Operações de desigualdade - bloqueios de intervalo de chave no intervalo e nos próximos valores-chave