/*================================================================================== Curso: SQL SERVER, do Básico ao Avançado. Para ter acesso ao curso completo: https://www.udemy.com/course/draft/3957796/?referralCode=FB10D369E786D9FE8A48 Instrutor: Sandro Servino https://www.linkedin.com/in/sandroservino/?originalSubdomain=pt https://filiado.wixsite.com/sandroservino ATIVIDADES ROTINEIRAS BÁSICAS DO DBA PERFORMANCE: LOCKS, BLOCKS e DEADLOCKS ISOLATION LEVEL E LOCK ESCALATION -- LOCKS (Conceito básico), qualquer operação realizada no banco, o SQL SERVER -- para proteger a operação evitando por exemplo que duas operações ao mesmo tempo -- altere o mesmo dado, ele antes de iniciar a operação tenta colocar um lock no dado -- enquanto o estar alterando e neste momento nenhuma outra operação pode alterar ou deletar este dado, -- pois está sendo alterado para um novo valor. O mesmo para inserts e deletes. No caso do select existe -- uma especificidade que iremos verificar no lab. -- Os locks são normais e é mecanismo usado pelos SQL SERVER para protegerem a integridade dos dados durante as transações. -- BLOCKS (Conceito básico), O block ocorre quando dois processos precisam acessar o mesmo fragmento de dados simultaneamente, de modo que um processo faz o lock dos dados e o outro precisa esperar que o outro conclua e libere o lock, ficando blocked. Assim que o primeiro processo for concluído, o processo blocked retoma a operação. A cadeia de blocks é como uma fila: assim que o processo em lock estiver concluído, os processos em block podem continuar. Em um ambiente de servidor normal, blocks são aceitáveis, mas se eles forem muito comuns e gerarem muitos waits, provavelmente existem problemas de design do modelo de dados ou de construção de queries, ou de falta de indices ou de atualização das estatisticas. Ainda, pode ocorrer um grande volume de transações não suportados pelo servidor, necessitando do aumento da capacidade de cpu, memória, velocidade do disco, aumento do numero de files da tempdb, partitions em outros discos. -- DEADLOCKS (Conceito básico) Deadlock acontece quando dois ou mais processos são impedidos de prosseguir pois um estar bloqueando o outro, como exemplo, duas pessoas estão querendo usar o mesmo telefone para ligar para elas no mesmo momento, enquanto uma delas não ceder sua vez, nenhuma irá conseguir ligar. Neste caso, se o SQL SERVER fosse a Operadora, A CADA 5 SEGUNDOS, em média, mas pode levar alguns segundos a mais, ira VERIFICAR ESTE TIPO DE PROBLEMA E VAI DERRUBAR A LIGAÇÃO DE UMA DELAS. -- Maneiras to corrigir problemas de blocking e deadlocks: 1.Ter indices e estatisticas atualziadas para fazer suas queries rodar mais rapidamente, mas não muitas para não deixar suas operações de insert, update e deletes lentas, fazendo o sql server manter locks por muito tempo. 2. Ter um correto isolation level, de acordo com a necessidade da sua aplicação 3. Qebrar grandes scripts (Stored procecedures) em blocos menores e no caso para evitar deadlocks, se existem SP diferentes que atualizam o mesmo conjunto de tabelas, procurar colocar a mesma ordem de updates, deletes e inserts nas procedures diferentes para minimizar risco de deadlocks. -- O SQL Server, diferentemente do Oracle e PostreSQL, usa bloqueio “pessimista” por padrão. Isso pode levar a sérios problemas de blocking, mas isto é fácil de ser resolvido com grande impacto positivo na performance da aplicação. -- O grande problema de performance não é deadlock, porque são raros e o sql server de forma automatica irá derrubar um deles depois de alguns segundos. O grande problema sãos blocks que podem travaer outras operações por horas e até mesmo por dias. ==================================================================================*/ -- VAMOS AO LAB USE StackOverflow2010 GO -- Vamos estudar as colunas desta tabela SELECT * FROM users GO -- Vamos rodar este codigo SELECT * FROM users WHERE Lastaccessdate >= '2012/11/10' and Lastaccessdate <= '2013/05/11' -- Agora vamos rodar este codigo BEGIN TRAN UPDATE users SET Reputation = Reputation - 1000 WHERE Lastaccessdate >= '2012/11/10' and Lastaccessdate <= '2013/05/11' go -- Ao mesmo tempo, abra uma nova sessao e rode o seguinte codigo SELECT * FROM users where id = 2228 -- Irá rodar para sempre ou até o serviço do SQL SERVER PARAR -- Vamos abrir uma nova sessao e rodar SP_WHOISACTIVE -- e verificar a coluna WAIT_INFO SP_WHOISACTIVE -- Agora vamos rodar com 1 parametro para verificar -- o tipo de lock que está ocorrendo SP_WHOISACTIVE @get_locks = 1 -- role a direita e veja agora a coluna locks. Clique no link -- Verifique que a tabela está toda em lock devido ao UPDATE -- RESOURCE_TYPE = OBJECT e REQUEST_MODE = X -- O SELECT está em WAIT esperando o UPDATE TERMINAR, devido a falta do COMMIT. -- NO SQL SERVER, READERS BLOQUEIAM WRITERS E WRITERS BLOQUEIAM READS -- ISTO É CHAMADO LOCK PESSIMISTA -- ESTA É UMA DAS DIFERENÇAS DO ORACLE E POSTGRESQL QUE TRABALHA COM LOCK OTIMISTA -- POR PADRAO MAS ESTA CONFIGURAÇÃO É SIMPLES DE MUDAR E TEM GRANDE IMPACTO NA PERFORMANCE -- DAS APLICACOES QUE USAM UM BANCO DE DADOS COM ESTA CONFIGURACAO QUE VEM POR -- PADRAO PELA MICROSOFT, PELO MENOS ATÉ A VERSAO SQL SERVER 2019. -- NO SQL DATABASE (AZURE) JÁ NAO OCORRE ESTA QUESTÃO POIS JA VEM -- CONFIGURADO COM LOCK OTIMISTA E NAO PODE ALTERAR. -- Como podemos resolver problemas de contenção que certamente ocorrem. -- Muitos DEVS e DBAS erradamente ,no meu ponto de vista, incluem uma cláusula -- with (nolock). SELECT * FROM users with (nolock) where id = 2228 -- Aparentemente o problema foi resolvido, mas voce pode estar lendo -- dados ainda não comitados no banco de dados e isto pode gerar problemas -- serios de reports ou processos baseados em dados ainda não garantidos no banco de dados -- Existem situaçoes como DW, onde os dados são lidos a noite nas bases OLTP e realizado os inserts em tabelas ROLAP, -- muito tempo depois dos dados já terem sidos garantidos. Neste caso o problema pode ser minimizado mas -- ainda assim é um risco. Existe forma melhor de minimizar este risco e na -- grande maioria dos casos resolve-lo. Vamos ver logo a frente. -- Vamos dar um ROLLBACK no Updade para parar o processo -- AQUI ENTRA O RCSI e Snapshot Isolation Levels, PARA RESOLVER O PROBLEMAS DE CONTENCÃO SEM O RISCO DE LER DADOS NAO COMITADOS. -- Especifica que os dados lidos por qualquer instrução em uma transação serão -- uma versão consistente dos dados que existiam no início da transação. -- Modificações de dados feitas por outras transações após o -- o início da transação atual(leitura) não são visíveis para as instruções em execução na transação atual (leitura). -- O efeito é como se as instruções em uma transação(leitura) obtivessem um instantâneo (SNAPSHOT-FOTOGRAFIA) dos dados confirmados conforme existiam no início da transação. -- IS READ_COMMITTED_SNAPSHOT ON -- Configuração geral para todas as transações de um banco de dados. -- Padrão False de fábrica (MS). Mudar para True. -- SNAPSHOT -- Roda em nivel de blocos especificos de transações. -- Mas em nivel de banco, o parametro ALLOW_SNAPSHOT_ISOLATION deve ser confiurada para TRUE -- Para maiores informacoes e exemplo -- https://docs.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql?view=sql-server-ver15 -- https://imasters.com.br/data/entendendo-transaction-isolation-level-no-sql-server -- SET TRANSACTION ISOLATION LEVEL -- { READ UNCOMMITTED -- | READ COMMITTED -- | REPEATABLE READ -- | SNAPSHOT -- | SERIALIZABLE -- VAMOS ALTERAR A PROPRIEDADE GERAL DE UM BANCO ESPECIFICO (RCSI). -- Vamos realizar a mesma simulacao. -- EM UMA MESMA SESSAO RODE ESTES CODIGOS E VEJOS QUE OS VALORES ANTES E DEPOIS DO UPDATE. SELECT * FROM users where id = 2228 -- REPUTACAO = 1357 BEGIN TRAN UPDATE users SET Reputation = Reputation - 1000 WHERE Lastaccessdate >= '2012/11/10' and Lastaccessdate <= '2013/05/11' go SELECT * FROM users where id = 2228 -- REPUTACAO = 357 -- E VAMOS NA OUTRA SESSAO TENTAR ACESSAR NOVAMENTE OS DADOS SELECT * FROM users where id = 2228 GO -- CONSEGUIU LER AGORA, MAS TROUXE O DADO COMITADO ANTERIORMENTE, -- PORQUE O ULTIMO UPDATE NAO FOI COMITADO (GARANTIDO NO BANCO)-- REPUTACAO = 1357 -- AGORA RODE ESTE CODIGO SELECT * FROM users with (nolock) where id = 2228 -- CONSEGUIU LER MAS LEU UM DADO SUJO, QUE AINDA NAO FOI COMITADO (VERDADE) E PODE -- NAO SER SE HOUVER UM ROLLBACK E ASSIM PODE ENITIR UM RELATORIO, OU UM SALDO DE UMA -- CONTA COM DADO FALSO. -- RODE EM OUTRA SESSAO SP_WHOISACTIVE @get_locks = 1 -- Vai verificar que na coluna lock, o SQL SERVER -- continuou com o exclusive lock na tabela ou seja -- qualquer outra operação de insert, delete e update -- vai ser impedida de ser executada, mas -- devido ao novo nivel de isolamento definido, quando -- inicio o update, o SQL SERVER colocou os dados, como estavam -- antes de estar atualizados, mas comitados anteriormente, -- na TEMPDB (snapthot-fotografia) e o SELECT leu estes dados comitados anteriormente -- Para testar abra uma nova sessao e execute um outro update BEGIN TRAN UPDATE users SET Reputation = Reputation - 20 WHERE Lastaccessdate >= '2012/11/10' and Lastaccessdate <= '2013/05/11' go -- vai ficar executando eternamente -- Apenas o select é liberado. Neste tipo de nivel de isolamento QUE ALTERAMOS O PADRAO -- writers nao bloqueiam mais readers e readers não bloqueiam mais writers, então -- problemas com reports em termos de contenção é resolvido sem o risco -- de ler dados não efetivados, como ocorre com with (nolock) -- DE UM ROLLBACK NOS 2 PROCESSOS E RODE OS MESMOS COMANDOS COM E SEM O WITH (NOLOCK) SELECT * FROM users where id = 2228 SELECT * FROM users with (nolock) where id = 2228 ------------------------------------------------------------------------------------- -- VAMOS CONVERSAR UM POUCO AGORA SOBRE LOCK ESCALATION /*===== Para evitar uma situação em que o locks esteja usando muita memória SQL Server introduziu o recurso de escalonamento de bloqueio. Sem escalonamento, os bloqueios podem exigir uma quantidade significativa de recursos de memória. Vejamos um exemplo em que um bloqueio deve ser imposto nas 30.000 linhas de dados para deletar estas linhas com 500 bytes por linhas. Sem escalonamento, um bloqueio compartilhado (S) será imposto no banco de dados, 1 bloqueio exclusivo de intenção (IX) na tabela, 1.875 bloqueios exclusivos de intenção (IX) nas páginas (a página de 8 KB contém 16 linhas de 500 bytes, o que perfaz 1.875 páginas que contêm 30.000 linhas) e 30.000 bloqueios exclusivos (X) nas próprias linhas. Como cada bloqueio tem 96 bytes de tamanho para ambiente de 32bits e 128 para 64bits, 31.877 bloqueios ocuparão entre 3 MB e 6 MB de memória para uma única operação de exclusão. Executar um grande número de operações em paralelo pode exigir alguns recursos significativos apenas para garantir que o gerenciador de bloqueio possa executar a operação sem problemas Para evitar essa situação, o SQL Server usa o escalonamento de bloqueio. Isso significa que, em uma situação em que mais de 5.000 bloqueios são adquiridos em um único nível, o SQL Server escalará esses bloqueios para um único bloqueio de nível de tabela. Por padrão, o SQL Server sempre escalará diretamente para o nível da tabela, o que significa que nunca ocorrerá escalonamento para o nível da página. Em vez de adquirir várias linhas e bloqueio de páginas, o SQL Server escalará para o bloqueio exclusivo (X) em nível de tabela Embora isso reduza a necessidade de recursos, bloqueios exclusivos (X) em uma tabela significam que nenhuma outra transação será capaz de acessar a tabela bloqueada e todas as consultas que tentarem acessar essa tabela serão bloqueadas. Portanto, isso reduzirá a sobrecarga do sistema, mas aumentará a probabilidade de contenção de simultaneidade Cada uma dessas opções é definida para permitir controle específico sobre o processo de escalonamento de bloqueio: Tabel - esta é a opção padrão para qualquer tabela recém-criada, pois por padrão o SQL Server sempre executará o escalonamento de bloqueio para o nível da tabela, o que também inclui tabelas particionadas Auto - esta opção permite o escalonamento de bloqueio para um nível de partição quando uma tabela é particionada. Quando 5.000 bloqueios são adquiridos em uma única partição, o escalonamento de bloqueio irá adquirir um bloqueio exclusivo (X) nessa partição enquanto a tabela irá adquirir bloqueio exclusivo intencional (IX). Caso essa tabela não seja particionada, o escalonamento de bloqueio adquirirá o bloqueio no nível da tabela (igual à opção Tabela). Disable - Esta opção desabilitará completamente o escalonamento de bloqueio para uma tabela. Novamente, esta opção deve ser usada com cuidado para evitar que o gerenciador de bloqueio do SQL Server seja forçado a usar uma quantidade excessiva de memória =====*/ -- Pode ser mudado em nivel de instancia sql server e assim todos os bancos -- desta instancia serão afetados, atraves de um traceflag em uma stored procedure na master -- ou mudando a configuração de tabelas especificas. USE AdventureWorks2014 GO ALTER TABLE Table_name SET (LOCK_ESCALATION = < TABLE | AUTO | DISABLE > –One of those options) GO -- Agora pode alterar a configuracao em nivel de toda a instancia sql server e todas as bases de dados desta instancia -- nao irao mais fazer lock escalation para table lock a nao ser que o sql server esteja sem memoria. Deverá -- criar na master uma proc para mudar lock escalation de toda a base de dados use master go CREATE PROCEDURE [dbo].[up_startup] AS BEGIN -------------------------------------- -- Traceflags -------------------------------------- -- Causes files in a filegroup to grow at the same time. Most often used for tempdb, but affects all databases. DBCC TRACEON (1117,-1); -- Disables lock escalation based on the number of locks. DBCC TRACEON (1224,-1); END GO EXEC sp_procoption N'[dbo].[up_startup]', 'startup', '1' GO -------------------------------------------------------------------------------------------------------------------------------- -- PREVENINDO LOCK ESCALATION -- Agora a melhor forma de evitar lock escalation: -- Divida grandes operações SQL em lote em várias operações menores. -- Reduza a área de bloqueio da consulta tornando a consulta o mais eficiente possível. Varreduras grandes ou um grande número de -- pesquisas de marcador podem aumentar a chance de escalonamento de bloqueio; além disso, aumenta a chance de deadlocks e afeta -- adversamente a simultaneidade e o desempenho. -- Depois de encontrar a consulta que causa o escalonamento de bloqueios, procure oportunidades para criar novos índices ou adicionar -- colunas a um índice existente para remover varreduras de índice ou tabela e maximizar a eficiência das buscas de índice. -- VAMOS VAZER UM LAB BEGIN TRAN UPDATE users SET Reputation = Reputation - 1000 WHERE Lastaccessdate >= '2012/11/10' and Lastaccessdate <= '2015/05/11' go -- E VAMOS NA OUTRA SESSAO E VERIFICAR O NIVEL DE BLOQUEIO SP_WHOISACTIVE @get_locks = 1 -- AGORA ALTERE A TABELA PARA QUE O SQL SERVER NAO FAÇA LOCK ESCALATION PARA TABELA, -- OU SEJA NAO REALIZE MAIS TABEL LOCK (EXCLUSIVE LOCK) -- antes de o Rollback na transacao aberta USE StackOverflow2010 GO ALTER TABLE users SET (LOCK_ESCALATION = DISABLE ) GO -- E RODE NOVAMENTE BEGIN TRAN UPDATE users SET Reputation = Reputation - 1000 WHERE Lastaccessdate >= '2012/11/10' and Lastaccessdate <= '2015/05/11' go -- E VAMOS NA OUTRA SESSAO E VERIFICAR O NIVEL DE BLOQUEIO SP_WHOISACTIVE @get_locks = 1 -- VOLTE PARA O PADRAO E VEJO NOVAMENTE O SQL SERVER FAZENDO LOCK ESCALATION PARA NIVEL DE TABELA -- QUANDO EXISTE LOCK DE MAIS DE 5.000 LINHAS -- mas antes de o rollback no update USE StackOverflow2010 GO ALTER TABLE users SET (LOCK_ESCALATION = TABLE ) GO -- E RODE NOVAMENTE BEGIN TRAN UPDATE users SET Reputation = Reputation - 1000 WHERE Lastaccessdate >= '2012/11/10' and Lastaccessdate <= '2015/05/11' go -- E VAMOS NA OUTRA SESSAO E VERIFICAR O NIVEL DE BLOQUEIO SP_WHOISACTIVE @get_locks = 1 -- VAMOS ALTERAR AGORA NO NIVEL DA INSTANCIA SQL SERVER PARA NAO REALIZAR MAIS LOCK ESCALATION -- EXECUTE ESTA SQL. Irá criar uma SP na master. Pare o servico sql server e de start novamente. -- O nome da procedure a ser criada na MASTER é fundamental, para quando o sql server for iniciado -- ser executado esta proc automaticamente e todos os comandos dentro. Vc pode colocar outros -- codigos sql que serão executados de forma automática quando o sql server for iniciado. use master go CREATE PROCEDURE [dbo].[up_startup] AS BEGIN -------------------------------------- -- Traceflags -------------------------------------- -- Causes files in a filegroup to grow at the same time. Most often used for tempdb, but affects all databases. DBCC TRACEON (1117,-1); -- Disables lock escalation based on the number of locks. DBCC TRACEON (1224,-1); END GO EXEC sp_procoption N'[dbo].[up_startup]', 'startup', '1' GO -- FIM DA PROCEDURE -------------- Pare O SERVICO DO SQL SERVER E INICIE NOVAMENTE -- Rodar novamente o procedimento use StackOverflow2010 go BEGIN TRAN UPDATE users SET Reputation = Reputation - 1000 WHERE Lastaccessdate >= '2012/11/10' and Lastaccessdate <= '2015/05/11' -- e execute SP_WHOISACTIVE @get_locks = 1 -- Vera que o sql server nao está fazendo lock escalation para OBJECT EXCLUSIVE LOCK (X), -- ou seja, não está mais fazendo table lock mas com certeza esta gastando mais memória, porque -- para realizar os 19.946 locks referente a este update, -- no minimo foi consumido 19.946 X 128bytes= 2.553.088 bytes, ou 2,43MBytes de memória do servidor, apenas para -- este simple update. -- VAMOS AGORA DELETAR DA MASTER A PROCEDURE [up_startup] criada e vamos dar um boot -- no servico do SQL SERVER -- E vamos rodar novamente o mesmo processo para verificarmos se o sql server voltou a fazer -- lock escalation table, ou seja table lock. use StackOverflow2010 go BEGIN TRAN UPDATE users SET Reputation = Reputation - 1000 WHERE Lastaccessdate >= '2012/11/10' and Lastaccessdate <= '2015/05/11' -- e execute SP_WHOISACTIVE @get_locks = 1 -- Ira verificar que novamente o SQL SERVER Voltou ao padrao, ou seja -- operacoes SQL que fazem mais de 5.000 locks (linhas), o SQL SERVER para evitar -- gastar mais memória do que o necessário e assim tirar da memória dados cacheados e planos, irá fazer table lock, -- porque neste caso irá gastar apenas 1 lock ao inves de no minimo 5.001, no nosso exemplo iria alocar na memória cerca de 19.946 locks. -- Este ajuste precisa ser feito com muito cuidado, em um servidor que tenha memória suficiente e muito problema de contenção, o que -- de antemão poderia ser resolvido ou muito minimizado com bons códigos sql (evitando leituras desnecessárias, problemas de cardinalidade, -- conversóes de dados), estatisticas atualizadas e bons indices. ----------------- FIM ------------------------------------------------------------------------------------------------------------------ -- VAMOS SIMULAR PROBLEMA DE DEADLOCK ------------------------------------------------------------------------------------------------------------------- -- Imagine outra analogia, uma pessoa no café da manha, quer fazer café com leite e a outra pessoa ao mesmo tempo -- quer fazer o seu café com leite também. O primeiro pega a jarra do café e fica esperando a jarra de leite que -- está com a outra pessoa, o segundo pega a jarra do leite e fica esperando a jarra de café que está com a outra pessoa. -- Poderar monitorar deadlocks que ocorrem: -- Colocando na proc up_startup, o comando: dbcc traceon (1204,-1) dbcc traceon (1222,-1) -- Como identificar todos os traces que estão ativos na instancia SQL DBCC TRACESTATUS(-1) GO -- Agora se houver deadlocks irá aparecer no ERRORLOG -- Deadlocks as vezes são uma grande dor de cabeça para os DBAs por 3 razões: -- Ocorrem intermitentemente -- Envolvem processos que rodam independentemente um do outro -- As mensagens de erro para o usuário não dão informações completas sobre o que aconteceu -- VAMOS AO LAB. USE AuditoriaDBA GO DROP TABLE IF EXISTS TestaTabelaA GO DROP TABLE IF EXISTS TestaTabelaB GO CREATE TABLE TestaTabelaA( Cod integer, Des VARCHAR(100) ,Qt INT ) CREATE TABLE TestaTabelaB( Cod integer, Des VARCHAR(100) ,Qt INT ) -- Vamos popular estas duas tabelas INSERT INTO dbo.TestaTabelaA( Cod, Des, Qt ) VALUES (1,'prod1', 1) ,(2, 'prod2', 2) INSERT INTO dbo.TestaTabelaB( Cod ,Des ,Qt ) VALUES (3,'prod3', 2) ,(4, 'prod4', 3) -- e agora vamos fazer os updates -- NA PRIMEIRA SESSAO DO SSMS, EXECUTE BEGIN TRAN UPDATE dbo.TestaTabelaA SET Qt = 55 WHERE Cod = 1 WAITFOR DELAY '00:00:30' UPDATE dbo.TestaTabelaB SET Qt = 20 WHERE Cod = 3 -- NA SEGUNDA SESSAO DO SSMS, EXECUTE BEGIN TRAN WAITFOR DELAY '00:00:10' UPDATE dbo.TestaTabelaB SET Qt = 30 WHERE Cod = 3 UPDATE dbo.TestaTabelaA SET Qt = 4 WHERE Cod = 1 -- Ai acontece o Deadlock, um processo x esperando um processo y terminar a transação dele, e um processo y -- esperando um processo x, todos necessitando mesmos recursos (linhas, paginas, tabelas) -- que estão em lock por outra operação e tentando colocar um lock na transação do outro que já -- tem um lock para update, neste caso. No sql server -- a cada 5 segundos, em média, verifica esta questão, ou seja, deadlocks, e assim derruba um dos processo com menos custo -- para evitar que os dois processos fiquem se bloqueando eternamente. -- VAMOS VERIFICAR O ERRORLOG ATUAL NO SSMS E NO DISCO, PESQUISE DEADLOCK -- E PARA DESABILITAR DBCC TRACEOFF (1204,-1) GO DBCC TRACEOFF (1222,-1) GO DBCC TRACESTATUS(-1) GO -- FIM